@@ -29,6 +29,7 @@ export default class GleapSession {
2929 realtimeHost = undefined ;
3030 sdkKey = null ;
3131 updatingSession = false ;
32+ fileRefreshTimeout = null ;
3233 useCookies = true ;
3334 localStorageDisabled = false ;
3435 session = {
@@ -155,6 +156,16 @@ export default class GleapSession {
155156 } ;
156157
157158 clearSession = ( attemp = 0 , retry = true ) => {
159+ clearTimeout ( this . fileRefreshTimeout ) ;
160+ this . openingProtectedFile = null ;
161+ if ( this . session ?. fileAccessToken ) {
162+ try {
163+ const revoke = new XMLHttpRequest ( ) ;
164+ revoke . open ( 'POST' , this . apiUrl + '/files/session/revoke' ) ;
165+ revoke . setRequestHeader ( 'X-File-Session' , this . session . fileAccessToken ) ;
166+ revoke . send ( ) ;
167+ } catch ( _ ) { /* Local logout still clears credentials if offline. */ }
168+ }
158169 if ( this . session && this . session . gleapHash ) {
159170 GleapEventManager . notifyEvent ( 'unregister-pushmessage-group' , `gleapuser-${ this . session . gleapHash } ` ) ;
160171 }
@@ -212,11 +223,40 @@ export default class GleapSession {
212223 } , newTimeout * 1000 ) ;
213224 } ;
214225
226+ openRequestedProtectedFile = ( ) => {
227+ const fileId = new URLSearchParams ( window . location ?. search || '' ) . get ( 'gleapFile' ) ;
228+ if ( ! / ^ [ a - f 0 - 9 ] { 24 } $ / . test ( fileId || '' ) || this . openingProtectedFile === fileId ) return ;
229+ this . openingProtectedFile = fileId ;
230+ const token = this . session . fileAccessToken ;
231+ const request = new XMLHttpRequest ( ) ;
232+ request . open ( 'GET' , `${ this . apiUrl } /files/${ fileId } /location` ) ;
233+ request . setRequestHeader ( 'X-File-Session' , this . session . fileAccessToken ) ;
234+ request . onload = ( ) => {
235+ if ( this . session ?. fileAccessToken !== token ) return ;
236+ if ( request . status !== 200 ) { this . openingProtectedFile = null ; return ; }
237+ try {
238+ const { shareToken } = JSON . parse ( request . responseText ) ;
239+ const frame = GleapFrameManager . getInstance ( ) ;
240+ frame . setAppMode ( 'widget' ) ;
241+ frame . sendMessage ( { name : 'open-conversation' , data : { shareToken } } , true ) ;
242+ frame . showWidget ( ) ;
243+ } catch { this . openingProtectedFile = null ; }
244+ } ;
245+ request . onerror = ( ) => { this . openingProtectedFile = null ; } ;
246+ request . send ( ) ;
247+ } ;
248+
215249 validateSession = ( session ) => {
216250 if ( ! session || ! session . gleapId ) {
217251 return ;
218252 }
219253
254+ // Ordinary session refreshes must not discard the independently verified,
255+ // short-lived file session. Never carry it across an identity change.
256+ if ( ! session . fileAccessToken && session . gleapId === this . session ?. gleapId &&
257+ session . userId === this . session ?. userId && Date . parse ( this . session . fileAccessExpiresAt ) > Date . now ( ) ) {
258+ session = { ...session , fileAccessToken : this . session . fileAccessToken , fileAccessExpiresAt : this . session . fileAccessExpiresAt } ;
259+ }
220260 let sessionChanged = false ;
221261 if ( this . session ?. gleapId !== session ?. gleapId ) {
222262 sessionChanged = true ;
@@ -236,6 +276,16 @@ export default class GleapSession {
236276
237277 this . session = session ;
238278 this . ready = true ;
279+ if ( session . fileAccessToken ) this . openRequestedProtectedFile ( ) ;
280+ clearTimeout ( this . fileRefreshTimeout ) ;
281+ if ( session . fileAccessToken && this . lastIdentify ?. userHash ) {
282+ this . fileRefreshTimeout = setTimeout ( ( ) => {
283+ if ( this . lastIdentify ) {
284+ const { userId, userData, userHash } = this . lastIdentify ;
285+ this . identifySession ( userId , userData , userHash , true ) ?. catch ?. ( ( ) => { } ) ;
286+ }
287+ } , Math . max ( 0 , Date . parse ( session . fileAccessExpiresAt ) - Date . now ( ) - 5 * 60 * 1000 ) ) ;
288+ }
239289
240290 // Register new push group.
241291 if ( this . session && this . session . gleapHash ) {
@@ -272,6 +322,10 @@ export default class GleapSession {
272322 if ( sessionChanged ) {
273323 this . replayIdentifyIfNeeded ( ) ;
274324 }
325+ if ( session . authenticatedFilesRequired && ! session . fileAccessToken && this . lastIdentify ?. userHash && ! this . identifyInFlight ) {
326+ const { userId, userData, userHash } = this . lastIdentify ;
327+ this . identifySession ( userId , userData , userHash , true ) ?. catch ?. ( ( ) => { } ) ;
328+ }
275329 } ;
276330
277331 /**
@@ -475,13 +529,14 @@ export default class GleapSession {
475529 } ) ;
476530 } ;
477531
478- identifySession = ( userId , userData , userHash ) => {
532+ identifySession = ( userId , userData , userHash , refreshFileSession = false ) => {
479533 // Remember the args before any early-out: a no-op identify against a
480534 // session that later gets recreated must still be replayable.
481535 this . lastIdentify = { userId, userData, userHash } ;
482536
483537 const sessionNeedsUpdate = this . checkIfSessionNeedsUpdate ( userId , userData ) ;
484- if ( ! sessionNeedsUpdate ) {
538+ const needsFileIdentity = ! ! userHash && this . session ?. authenticatedFilesRequired && ( ! this . session ?. fileAccessToken || Date . parse ( this . session . fileAccessExpiresAt ) <= Date . now ( ) ) ;
539+ if ( ! sessionNeedsUpdate && ! refreshFileSession && ! needsFileIdentity ) {
485540 return ;
486541 }
487542
0 commit comments