1- import { AuthenticationInfo , fetchAuthenticationInfo , logout } from "./api"
2- import { currentTimeSeconds , getLocalStorageNumber , hasLocalStorage , hasWindow } from "./helpers"
1+ import { AuthenticationInfo , fetchAuthenticationInfo , logout } from "./api"
2+ import { currentTimeSeconds , getLocalStorageNumber , hasLocalStorage , hasWindow } from "./helpers"
33
44const LOGGED_IN_AT_KEY = "__PROPEL_AUTH_LOGGED_IN_AT"
55const LOGGED_OUT_AT_KEY = "__PROPEL_AUTH_LOGGED_OUT_AT"
6- const AUTH_TOKEN_REFRESH_BEFORE_EXPIRATION_SECONDS = 4 * 60
7- const DEBOUNCE_DURATION_FOR_REFOCUS_SECONDS = 4 * 60
6+ const AUTH_TOKEN_REFRESH_BEFORE_EXPIRATION_SECONDS = 10 * 60
7+ const DEBOUNCE_DURATION_FOR_REFOCUS_SECONDS = 60
88
99export interface RedirectToSignupOptions {
1010 postSignupRedirectUrl : string
@@ -95,7 +95,6 @@ export interface IAuthClient {
9595 */
9696 redirectToSetupSAMLPage ( orgId : string ) : void
9797
98-
9998 /**
10099 * Adds an observer which is called whenever the users logs in or logs out.
101100 */
@@ -106,6 +105,16 @@ export interface IAuthClient {
106105 */
107106 removeLoggedInChangeObserver ( observer : ( isLoggedIn : boolean ) => void ) : void
108107
108+ /**
109+ * Adds an observer which is called whenever the access token changes.
110+ */
111+ addAccessTokenChangeObserver ( observer : ( accessToken : string | undefined ) => void ) : void
112+
113+ /**
114+ * Removes the observer
115+ */
116+ removeAccessTokenChangeObserver ( observer : ( accessToken : string | undefined ) => void ) : void
117+
109118 /**
110119 * Cleanup the auth client if you no longer need it.
111120 */
@@ -131,6 +140,7 @@ interface ClientState {
131140 initialLoadFinished : boolean
132141 authenticationInfo : AuthenticationInfo | null
133142 observers : ( ( isLoggedIn : boolean ) => void ) [ ]
143+ accessTokenObservers : ( ( accessToken : string | undefined ) => void ) [ ]
134144 lastLoggedInAtMessage : number | null
135145 lastLoggedOutAtMessage : number | null
136146 refreshInterval : number | null
@@ -161,6 +171,7 @@ export function createClient(authOptions: IAuthOptions): IAuthClient {
161171 initialLoadFinished : false ,
162172 authenticationInfo : null ,
163173 observers : [ ] ,
174+ accessTokenObservers : [ ] ,
164175 lastLoggedInAtMessage : getLocalStorageNumber ( LOGGED_IN_AT_KEY ) ,
165176 lastLoggedOutAtMessage : getLocalStorageNumber ( LOGGED_OUT_AT_KEY ) ,
166177 authUrl : authOptions . authUrl ,
@@ -178,6 +189,15 @@ export function createClient(authOptions: IAuthOptions): IAuthClient {
178189 }
179190 }
180191
192+ function notifyObserversOfAccessTokenChange ( accessToken : string | undefined ) {
193+ for ( let i = 0 ; i < clientState . accessTokenObservers . length ; i ++ ) {
194+ const observer = clientState . accessTokenObservers [ i ]
195+ if ( observer ) {
196+ observer ( accessToken )
197+ }
198+ }
199+ }
200+
181201 function userJustLoggedOut ( accessToken : string | undefined , previousAccessToken : string | undefined ) {
182202 // Edge case: the first time we go to the page, if we can't load the
183203 // auth token we should treat it as a logout event
@@ -217,6 +237,10 @@ export function createClient(authOptions: IAuthOptions): IAuthClient {
217237 updateLastLoggedInAt ( )
218238 }
219239
240+ if ( previousAccessToken !== accessToken ) {
241+ notifyObserversOfAccessTokenChange ( accessToken )
242+ }
243+
220244 clientState . lastRefresh = currentTimeSeconds ( )
221245 clientState . initialLoadFinished = true
222246 }
@@ -242,17 +266,17 @@ export function createClient(authOptions: IAuthOptions): IAuthClient {
242266 const getSignupPageUrl = ( options ?: RedirectToSignupOptions ) => {
243267 let qs = ""
244268 if ( options && options . postSignupRedirectUrl ) {
245- const encode = window ? window . btoa : btoa ;
246- qs = new URLSearchParams ( { "rt" : encode ( options . postSignupRedirectUrl ) } ) . toString ( )
269+ const encode = window ? window . btoa : btoa
270+ qs = new URLSearchParams ( { rt : encode ( options . postSignupRedirectUrl ) } ) . toString ( )
247271 }
248272 return `${ clientState . authUrl } /signup?${ qs } `
249273 }
250274
251275 const getLoginPageUrl = ( options ?: RedirectToLoginOptions ) => {
252276 let qs = ""
253277 if ( options && options . postLoginRedirectUrl ) {
254- const encode = window ? window . btoa : btoa ;
255- qs = new URLSearchParams ( { "rt" : encode ( options . postLoginRedirectUrl ) } ) . toString ( )
278+ const encode = window ? window . btoa : btoa
279+ qs = new URLSearchParams ( { rt : encode ( options . postLoginRedirectUrl ) } ) . toString ( )
256280 }
257281 return `${ clientState . authUrl } /login?${ qs } `
258282 }
@@ -298,6 +322,26 @@ export function createClient(authOptions: IAuthOptions): IAuthClient {
298322 }
299323 } ,
300324
325+ addAccessTokenChangeObserver ( observer : ( accessToken : string | undefined ) => void ) {
326+ const hasObserver = clientState . accessTokenObservers . includes ( observer )
327+ if ( hasObserver ) {
328+ console . error ( "Observer has been attached already." )
329+ } else if ( ! observer ) {
330+ console . error ( "Cannot add a null observer" )
331+ } else {
332+ clientState . accessTokenObservers . push ( observer )
333+ }
334+ } ,
335+
336+ removeAccessTokenChangeObserver ( observer : ( accessToken : string | undefined ) => void ) {
337+ const observerIndex = clientState . accessTokenObservers . indexOf ( observer )
338+ if ( observerIndex === - 1 ) {
339+ console . error ( "Cannot find observer to remove" )
340+ } else {
341+ clientState . accessTokenObservers . splice ( observerIndex , 1 )
342+ }
343+ } ,
344+
301345 async getAuthenticationInfoOrNull ( forceRefresh ?: boolean ) : Promise < AuthenticationInfo | null > {
302346 const currentTimeSecs = currentTimeSeconds ( )
303347 if ( forceRefresh ) {
@@ -375,6 +419,7 @@ export function createClient(authOptions: IAuthOptions): IAuthClient {
375419
376420 destroy ( ) {
377421 clientState . observers = [ ]
422+ clientState . accessTokenObservers = [ ]
378423 window . removeEventListener ( "storage" , onStorageChange )
379424 if ( clientState . refreshInterval ) {
380425 clearInterval ( clientState . refreshInterval )
@@ -412,7 +457,10 @@ export function createClient(authOptions: IAuthOptions): IAuthClient {
412457 // If we were offline or on a different tab, when we return, refetch auth info
413458 // Some browsers trigger focus more often than we'd like, so we'll debounce a little here as well
414459 const onOnlineOrFocus = async function ( ) {
415- if ( clientState . lastRefresh && currentTimeSeconds ( ) > clientState . lastRefresh + DEBOUNCE_DURATION_FOR_REFOCUS_SECONDS ) {
460+ if (
461+ clientState . lastRefresh &&
462+ currentTimeSeconds ( ) > clientState . lastRefresh + DEBOUNCE_DURATION_FOR_REFOCUS_SECONDS
463+ ) {
416464 await forceRefreshToken ( true )
417465 } else {
418466 await client . getAuthenticationInfoOrNull ( )
0 commit comments