@@ -4,23 +4,92 @@ import { renewToken } from '$lib/services/auth-service';
44import { delay } from './utils/common' ;
55
66
7- /** @type {{config: import('axios').InternalAxiosRequestConfig, resolve: (value: any) => void, reject: (reason?: any) => void}[] } */
8- const retryQueue = [ ] ;
9-
10- // Refresh handling state
11- let isRefreshingToken = false ;
7+ const retryQueue = {
8+ /** @type {{config: import('axios').InternalAxiosRequestConfig, resolve: (value: any) => void, reject: (reason?: any) => void}[] } */
9+ queue : [ ] ,
10+
11+ /** @type {boolean } */
12+ isRefreshingToken : false ,
13+
14+ /** @type {number } */
15+ timeout : 20 ,
16+
17+ /**
18+ * refresh access token
19+ * @param {string } token
20+ * @returns {Promise<string> }
21+ */
22+ refreshAccessToken ( token ) {
23+ return new Promise ( ( resolve , reject ) => {
24+ renewToken ( token , ( newToken ) => resolve ( newToken ) , ( ) => reject ( new Error ( 'Failed to refresh token' ) ) ) ;
25+ } ) ;
26+ } ,
1227
13- /**
14- * Wrap renewToken into a Promise that resolves with the new access token string
15- * @param {string } token
16- * @returns {Promise<string> }
17- */
18- function refreshAccessToken ( token ) {
19- return new Promise ( ( resolve , reject ) => {
20- renewToken ( token , ( newToken ) => resolve ( newToken ) , ( ) => reject ( new Error ( 'Failed to refresh token' ) ) ) ;
21- } ) ;
22- }
28+ /** @param {{config: import('axios').InternalAxiosRequestConfig, resolve: (value: any) => void, reject: (reason?: any) => void} } item */
29+ enqueue ( item ) {
30+ this . queue . push ( item ) ;
31+
32+ if ( ! this . isRefreshingToken ) {
33+ const user = getUserStore ( ) ;
34+ if ( ! isTokenExired ( user . expires ) ) {
35+ this . dequeue ( user . token ) ;
36+ } else {
37+ this . isRefreshingToken = true ;
38+ this . refreshAccessToken ( user ?. token || '' )
39+ . then ( ( newToken ) => {
40+ this . isRefreshingToken = false ;
41+ const promise = this . dequeue ( newToken ) ;
42+ return promise ;
43+ } )
44+ . catch ( ( err ) => {
45+ this . isRefreshingToken = false ;
46+ // Reject all queued requests
47+ while ( this . queue . length > 0 ) {
48+ const item = this . queue . shift ( ) ;
49+ if ( item ) {
50+ item . reject ( err ) ;
51+ }
52+ }
53+ redirectToLogin ( ) ;
54+ } ) ;
55+ }
56+ }
57+ } ,
2358
59+ /**
60+ * @param {string } newToken
61+ * @returns {Promise<void> }
62+ */
63+ dequeue ( newToken ) {
64+ let chain = Promise . resolve ( ) ;
65+ while ( this . queue . length > 0 ) {
66+ const item = this . queue . shift ( ) ;
67+ if ( ! item ?. config ) {
68+ continue ;
69+ }
70+
71+ const { config } = item ;
72+ // @ts -ignore
73+ config . headers = config . headers || { } ;
74+ // @ts -ignore
75+ config . headers . Authorization = `Bearer ${ newToken } ` ;
76+
77+ chain = chain . then ( ( ) => delay ( this . timeout ) )
78+ . then ( ( ) => {
79+ return new Promise ( ( resolve ) => {
80+ axios ( config ) . then ( ( response ) => {
81+ resolve ( ) ;
82+ item . resolve ( response ) ;
83+ } ) . catch ( ( err ) => {
84+ resolve ( ) ;
85+ item . reject ( err ) ;
86+ } ) ;
87+ } ) ;
88+ } ) ;
89+ }
90+ return chain ;
91+ }
92+ } ;
2493
2594// Add a request interceptor to attach authentication tokens or headers
2695axios . interceptors . request . use (
@@ -54,10 +123,13 @@ axios.interceptors.response.use(
54123 const user = getUserStore ( ) ;
55124
56125 // If token expired or 401 returned, attempt a single token refresh and retry requests in queue.
57- if ( ( error ?. response ?. status === 401 || isTokenExired ( user . expires ) ) && originalRequest && ! originalRequest . _retried ) {
126+ if ( ( error ?. response ?. status === 401 || isTokenExired ( user . expires ) )
127+ && originalRequest
128+ && ! originalRequest . _retried
129+ && ! originalRequest . url . includes ( 'renew-token' ) ) {
58130 originalRequest . _retried = true ;
59131 return new Promise ( ( resolve , reject ) => {
60- enqueue ( { config : originalRequest , resolve, reject } ) ;
132+ retryQueue . enqueue ( { config : originalRequest , resolve, reject } ) ;
61133 } ) ;
62134 } else if ( ! skipGlobalError ( originalRequest ) ) {
63135 globalErrorStore . set ( true ) ;
@@ -71,62 +143,6 @@ axios.interceptors.response.use(
71143 }
72144) ;
73145
74- /**
75- * @param {{config: import('axios').InternalAxiosRequestConfig, resolve: (value: any) => void, reject: (reason?: any) => void} } retryItem
76- */
77- function enqueue ( retryItem ) {
78- // Push the current request into the queue
79- retryQueue . push ( { ...retryItem } ) ;
80-
81- // Start refresh token
82- if ( ! isRefreshingToken ) {
83- const user = getUserStore ( ) ;
84- if ( ! isTokenExired ( user . expires ) ) {
85- dequeue ( user . token ) ;
86- } else {
87- isRefreshingToken = true ;
88- refreshAccessToken ( user ?. token || '' )
89- . then ( ( newToken ) => {
90- isRefreshingToken = false ;
91- const promise = dequeue ( newToken ) ;
92- return promise ;
93- } )
94- . catch ( ( err ) => {
95- isRefreshingToken = false ;
96- // Reject all queued requests
97- while ( retryQueue . length > 0 ) {
98- const item = retryQueue . shift ( ) ;
99- if ( item ) {
100- item . reject ( err ) ;
101- }
102- }
103- redirectToLogin ( ) ;
104- } ) ;
105- }
106- }
107- }
108-
109- /**
110- * Retry queued requests sequentially
111- * @param {string } newToken
112- * @returns {Promise<void> }
113- */
114- function dequeue ( newToken ) {
115- let chain = Promise . resolve ( ) ;
116- while ( retryQueue . length > 0 ) {
117- const item = retryQueue . shift ( ) ;
118- if ( ! item ) continue ;
119- const { config, resolve, reject } = item ;
120- // @ts -ignore
121- config . headers = config . headers || { } ;
122- // @ts -ignore
123- config . headers . Authorization = `Bearer ${ newToken } ` ;
124- chain = chain . then ( ( ) => delay ( 20 ) )
125- . then ( ( ) => { axios ( config ) . then ( resolve ) . catch ( reject ) ; } ) ;
126- }
127- return chain ;
128- }
129-
130146/**
131147 * @param {number } expires
132148 */
0 commit comments