Skip to content

Commit 6198d4c

Browse files
committed
add renew token
1 parent a10ea02 commit 6198d4c

File tree

2 files changed

+64
-100
lines changed

2 files changed

+64
-100
lines changed

src/lib/helpers/http.js

Lines changed: 63 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import axios from 'axios';
22
import { getUserStore, globalErrorStore, loaderStore } from '$lib/helpers/store.js';
33
import { renewToken } from '$lib/services/auth-service';
4+
import { delay } from './utils/common';
45

5-
// Refresh handling state
6-
let isRefreshing = false;
76

8-
/** @type {{config: import('axios').InternalAxiosRequestConfig, resolve: (value:any)=>void, reject: (reason?: any)=>void}[]} */
9-
const failedQueue = [];
7+
/** @type {{config: import('axios').InternalAxiosRequestConfig, resolve: (value: any) => void, reject: (reason?: any) => void}[]} */
8+
const retryQueue = [];
109

11-
/** @type {{config: import('axios').InternalAxiosRequestConfig, resolve: (value:any)=>void, reject: (reason?: any)=>void}[]} */
12-
const pendingRequestQueue = [];
10+
// Refresh handling state
11+
let isRefreshingToken = false;
1312

1413
/**
1514
* Wrap renewToken into a Promise that resolves with the new access token string
@@ -22,76 +21,17 @@ function refreshAccessToken(token) {
2221
});
2322
}
2423

25-
/**
26-
* Retry queued requests sequentially with the provided token
27-
* @param {string} newToken
28-
* @returns {Promise<void>}
29-
*/
30-
function processQueueSequentially(newToken) {
31-
let chain = Promise.resolve();
32-
while (failedQueue.length) {
33-
const item = failedQueue.shift();
34-
if (!item) continue;
35-
const { config, resolve, reject } = item;
36-
// Ensure headers exists; Axios may use AxiosHeaders type
37-
// @ts-ignore
38-
config.headers = config.headers || {};
39-
// @ts-ignore
40-
config.headers.Authorization = `Bearer ${newToken}`;
41-
chain = chain.then(() => axios(config).then(resolve).catch(reject));
42-
}
43-
return chain;
44-
}
4524

4625
// Add a request interceptor to attach authentication tokens or headers
4726
axios.interceptors.request.use(
4827
(config) => {
28+
// Add your authentication logic here
4929
const user = getUserStore();
5030
if (!skipLoader(config)) {
5131
loaderStore.set(true);
5232
}
53-
54-
// Proactive token refresh: if expired or a refresh is in progress,
55-
// queue this request until a new token is available
56-
if (isTokenExired() || isRefreshing) {
57-
return new Promise((resolve, reject) => {
58-
pendingRequestQueue.push({ config, resolve, reject });
59-
60-
if (!isRefreshing) {
61-
isRefreshing = true;
62-
refreshAccessToken(user?.token || '')
63-
.then((newToken) => {
64-
isRefreshing = false;
65-
// Release queued requests with the new token
66-
while (pendingRequestQueue.length) {
67-
const item = pendingRequestQueue.shift();
68-
if (!item) continue;
69-
const { config: cfg, resolve: res } = item;
70-
// @ts-ignore
71-
cfg.headers = cfg.headers || {};
72-
// @ts-ignore
73-
cfg.headers.Authorization = `Bearer ${newToken}`;
74-
res(cfg);
75-
}
76-
})
77-
.catch((err) => {
78-
isRefreshing = false;
79-
// Reject queued requests and redirect to login
80-
while (pendingRequestQueue.length) {
81-
const item = pendingRequestQueue.shift();
82-
if (item) item.reject(err);
83-
}
84-
redirectToLogin();
85-
});
86-
}
87-
});
88-
}
89-
90-
// Attach current token if present
33+
// Attach an authentication token to the request headers
9134
if (user.token) {
92-
// @ts-ignore
93-
config.headers = config.headers || {};
94-
// @ts-ignore
9535
config.headers.Authorization = `Bearer ${user.token}`;
9636
}
9737
return config;
@@ -105,46 +45,17 @@ axios.interceptors.request.use(
10545
// Add a response interceptor to handle 401 errors globally
10646
axios.interceptors.response.use(
10747
(response) => {
108-
// If the request was successful, return the response
10948
loaderStore.set(false);
11049
return response;
11150
},
11251
(error) => {
11352
loaderStore.set(false);
11453
const originalRequest = error?.config;
11554

116-
// If token expired or 401 returned, attempt a single token refresh and
117-
// retry failed requests in sequence.
118-
if ((error?.response?.status === 401 || isTokenExired()) && originalRequest && !originalRequest._retry) {
119-
originalRequest._retry = true;
120-
55+
// If token expired or 401 returned, attempt a single token refresh and retry requests in queue.
56+
if ((error?.response?.status === 401 || isTokenExired()) && originalRequest) {
12157
return new Promise((resolve, reject) => {
122-
// Push the current request into the queue
123-
failedQueue.push({ config: originalRequest, resolve, reject });
124-
125-
// Start refresh if not already in progress
126-
if (!isRefreshing) {
127-
isRefreshing = true;
128-
const user = getUserStore();
129-
130-
refreshAccessToken(user?.token || '')
131-
.then((newToken) => {
132-
isRefreshing = false;
133-
return processQueueSequentially(newToken);
134-
})
135-
.catch((err) => {
136-
isRefreshing = false;
137-
138-
// Reject all queued requests
139-
while (failedQueue.length) {
140-
const item = failedQueue.shift();
141-
if (item) item.reject(err);
142-
}
143-
144-
redirectToLogin();
145-
throw err;
146-
});
147-
}
58+
enqueue({ config: originalRequest, resolve, reject });
14859
});
14960
} else if (!skipGlobalError(originalRequest)) {
15061
globalErrorStore.set(true);
@@ -158,6 +69,59 @@ axios.interceptors.response.use(
15869
}
15970
);
16071

72+
/**
73+
* @param {{config: import('axios').InternalAxiosRequestConfig, resolve: (value: any) => void, reject: (reason?: any) => void}} retryItem
74+
*/
75+
function enqueue(retryItem) {
76+
// Push the current request into the queue
77+
retryQueue.push({ ...retryItem });
78+
79+
// Start refresh token
80+
if (!isRefreshingToken) {
81+
isRefreshingToken = true;
82+
const user = getUserStore();
83+
84+
refreshAccessToken(user?.token || '')
85+
.then((newToken) => {
86+
const promise = dequeue(newToken);
87+
isRefreshingToken = false;
88+
return promise;
89+
})
90+
.catch((err) => {
91+
// Reject all queued requests
92+
while (retryQueue.length > 0) {
93+
const item = retryQueue.shift();
94+
if (item) {
95+
item.reject(err);
96+
}
97+
}
98+
isRefreshingToken = false;
99+
redirectToLogin();
100+
});
101+
}
102+
}
103+
104+
/**
105+
* Retry queued requests sequentially
106+
* @param {string} newToken
107+
* @returns {Promise<void>}
108+
*/
109+
function dequeue(newToken) {
110+
let chain = Promise.resolve();
111+
while (retryQueue.length > 0) {
112+
const item = retryQueue.shift();
113+
if (!item) continue;
114+
const { config, resolve, reject } = item;
115+
// @ts-ignore
116+
config.headers = config.headers || {};
117+
// @ts-ignore
118+
config.headers.Authorization = `Bearer ${newToken}`;
119+
chain = chain.then(() => delay(20))
120+
.then(() => { axios(config).then(resolve).catch(reject); });
121+
}
122+
return chain;
123+
}
124+
161125
function isTokenExired() {
162126
const user = getUserStore();
163127
return Date.now() / 1000 > user.expires;

src/lib/services/signalr-service.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export const signalr = {
6363
connection = new HubConnectionBuilder()
6464
.withUrl(endpoints.chatHubUrl + `?conversation-id=${conversationId}&access_token=${user.token}`) // the hub URL, change it according to your server
6565
.withAutomaticReconnect() // enable automatic reconnection
66-
.configureLogging(LogLevel.Information) // configure the logging level
66+
.configureLogging(LogLevel.Warning) // configure the logging level
6767
.build();
6868

6969
// start the connection

0 commit comments

Comments
 (0)