@@ -51,15 +51,16 @@ type AuthContextType = {
5151 logout : ( ) => Promise < void > ;
5252 validateLocalToken : ( ) => void ;
5353 checkToken : ( ) => Promise < void > ;
54- refreshToken : ( ) => Promise < boolean > ;
54+ refreshToken : ( signal ?: AbortSignal ) => Promise < boolean > ;
5555} ;
5656
5757const AuthContext = createContext < AuthContextType | undefined > ( undefined ) ;
5858
5959export type AuthProviderProps = {
6060 cookieNames ?: CookieNames ;
6161 logoutHandler ?: ( ) => void ;
62- refreshHandler ?: ( ) => Promise < boolean > ;
62+ refreshHandler ?: ( signal ?: AbortSignal ) => Promise < boolean > ;
63+ refreshTimeoutMs ?: number ;
6364
6465 // Deprecated
6566 refreshTokenEndpoint ?: string ;
@@ -300,46 +301,60 @@ export function AuthProvider({
300301 return undefined ;
301302 } ;
302303
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 ) ;
306309 }
307310
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+ }
311315
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+ }
322319
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+ } ) ;
326331
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+ }
331340
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 ;
335352 }
336- return data ;
337353 } ;
338354
339355 const clearTokens = async ( ) => {
340356 if ( options . logoutHandler ) {
341- await options . logoutHandler ( ) ;
342- return ;
357+ return options . logoutHandler ( ) ;
343358 }
344359
345360 if ( ! options . logoutEndpoint || ! options . logoutMutation ) {
0 commit comments