@@ -51,15 +51,16 @@ type AuthContextType = {
51
51
logout : ( ) => Promise < void > ;
52
52
validateLocalToken : ( ) => void ;
53
53
checkToken : ( ) => Promise < void > ;
54
- refreshToken : ( ) => Promise < boolean > ;
54
+ refreshToken : ( signal ?: AbortSignal ) => Promise < boolean > ;
55
55
} ;
56
56
57
57
const AuthContext = createContext < AuthContextType | undefined > ( undefined ) ;
58
58
59
59
export type AuthProviderProps = {
60
60
cookieNames ?: CookieNames ;
61
61
logoutHandler ?: ( ) => void ;
62
- refreshHandler ?: ( ) => Promise < boolean > ;
62
+ refreshHandler ?: ( signal ?: AbortSignal ) => Promise < boolean > ;
63
+ refreshTimeoutMs ?: number ;
63
64
64
65
// Deprecated
65
66
refreshTokenEndpoint ?: string ;
@@ -300,46 +301,60 @@ export function AuthProvider({
300
301
return undefined ;
301
302
} ;
302
303
303
- const refreshAccessToken = async ( ) : Promise < boolean > => {
304
- if ( options . refreshHandler ) {
305
- return await options . refreshHandler ( ) ;
304
+ const refreshAccessToken = async ( signal ?: AbortSignal ) : Promise < boolean > => {
305
+ // If no signal is provided, create one with default timeout using AbortSignal.timeout
306
+ if ( ! signal ) {
307
+ const timeout = options . refreshTimeoutMs ?? 10000 ; // Default 10 seconds
308
+ signal = AbortSignal . timeout ( timeout ) ;
306
309
}
307
310
308
- if ( ! options . refreshTokenEndpoint || ! options . refreshTokenMutation ) {
309
- throw new Error ( "No refresh token endpoint or mutation provided" ) ;
310
- }
311
+ try {
312
+ if ( options . refreshHandler ) {
313
+ return await options . refreshHandler ( signal ) ;
314
+ }
311
315
312
- // Since we are storing the refresh token in a cookie this will be sent
313
- // automatically by the browser.
314
- const response = await fetch ( options . refreshTokenEndpoint , {
315
- method : "POST" ,
316
- body : options . refreshTokenMutation ,
317
- headers : {
318
- "Content-Type" : "application/json" ,
319
- } ,
320
- credentials : "include" ,
321
- } ) ;
316
+ if ( ! options . refreshTokenEndpoint || ! options . refreshTokenMutation ) {
317
+ throw new Error ( "No refresh token endpoint or mutation provided" ) ;
318
+ }
322
319
323
- if ( ! response . ok ) {
324
- throw new Error ( "Failed to refresh token" ) ;
325
- }
320
+ // Since we are storing the refresh token in a cookie this will be sent
321
+ // automatically by the browser.
322
+ const response = await fetch ( options . refreshTokenEndpoint , {
323
+ method : "POST" ,
324
+ body : options . refreshTokenMutation ,
325
+ headers : {
326
+ "Content-Type" : "application/json" ,
327
+ } ,
328
+ credentials : "include" ,
329
+ signal : signal ,
330
+ } ) ;
326
331
327
- const data = await response . json ( ) ;
328
- if ( ! data ) {
329
- throw new Error ( "Failed to refresh token" ) ;
330
- }
332
+ if ( ! response . ok ) {
333
+ throw new Error ( "Failed to refresh token" ) ;
334
+ }
335
+
336
+ const data = await response . json ( ) ;
337
+ if ( ! data ) {
338
+ throw new Error ( "Failed to refresh token" ) ;
339
+ }
331
340
332
- // Check if there is a GraphQL error
333
- if ( data . errors && data . errors . length > 0 ) {
334
- throw new Error ( "Failed to refresh token" ) ;
341
+ // Check if there is a GraphQL error
342
+ if ( data . errors && data . errors . length > 0 ) {
343
+ throw new Error ( "Failed to refresh token" ) ;
344
+ }
345
+ return data ;
346
+ } catch ( error ) {
347
+ if ( error instanceof Error && error . name === "AbortError" ) {
348
+ const timeout = options . refreshTimeoutMs ?? 10000 ;
349
+ throw new Error ( `Token refresh timed out after ${ timeout } ms` ) ;
350
+ }
351
+ throw error ;
335
352
}
336
- return data ;
337
353
} ;
338
354
339
355
const clearTokens = async ( ) => {
340
356
if ( options . logoutHandler ) {
341
- await options . logoutHandler ( ) ;
342
- return ;
357
+ return options . logoutHandler ( ) ;
343
358
}
344
359
345
360
if ( ! options . logoutEndpoint || ! options . logoutMutation ) {
0 commit comments