Skip to content

Commit c028044

Browse files
Feat(client, extension): Axios interceptor 설정 (#75)
* feat: client axios interceptor 설정 * feat: extension axios interceptor 설정 * chore: jwtToken에서 token으로 rename * chore: client userEmail을extension과 동일하게 email로 naming 통일 * refactor: extension response interceptor에 401도 추가
1 parent 6dca89e commit c028044

File tree

2 files changed

+151
-0
lines changed

2 files changed

+151
-0
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,97 @@
11
import axios from 'axios';
22

3+
// Axios 인스턴스
34
const apiRequest = axios.create({
45
baseURL: import.meta.env.VITE_BASE_URL,
56
headers: {
67
'Content-Type': 'application/json',
78
},
89
});
910

11+
const refreshToken = async (email: string) => {
12+
try {
13+
const response = await axios.get(
14+
`${import.meta.env.VITE_BASE_URL}/api/v1/auth/token`,
15+
{
16+
params: { email },
17+
}
18+
);
19+
20+
const newToken = response.data.data?.token || response.data.token;
21+
22+
if (newToken) {
23+
localStorage.setItem('token', newToken);
24+
return newToken;
25+
}
26+
27+
throw new Error('토큰 재발급 실패');
28+
} catch (error) {
29+
console.error('토큰 재발급 실패:', error);
30+
throw error;
31+
}
32+
};
33+
34+
// 요청 인터셉터
35+
apiRequest.interceptors.request.use(async (config) => {
36+
const noAuthNeeded = ['/api/v1/auth/token', '/api/v1/auth/signup'];
37+
const isNoAuth = noAuthNeeded.some((url) => config.url?.includes(url));
38+
39+
if (!isNoAuth) {
40+
const token = localStorage.getItem('token');
41+
42+
if (!token || token === 'undefined' || token === 'null' || token === '') {
43+
console.error('토큰이 없습니다. 온보딩을 먼저 완료해주세요.');
44+
throw new Error('토큰이 없습니다. 온보딩을 먼저 완료해주세요.');
45+
}
46+
47+
config.headers.Authorization = `Bearer ${token}`;
48+
}
49+
50+
return config;
51+
});
52+
53+
// 응답 인터셉터
54+
apiRequest.interceptors.response.use(
55+
(response) => response,
56+
async (error) => {
57+
const originalRequest = error.config;
58+
const noAuthNeeded = ['/api/v1/auth/token', '/api/v1/auth/signup'];
59+
const isNoAuth = noAuthNeeded.some((url) =>
60+
originalRequest.url?.includes(url)
61+
);
62+
63+
if (
64+
error.response &&
65+
(error.response.status === 401 || error.response.status === 403) &&
66+
!originalRequest._retry &&
67+
!isNoAuth
68+
) {
69+
originalRequest._retry = true;
70+
71+
try {
72+
const email = localStorage.getItem('email');
73+
74+
if (email) {
75+
const newToken = await refreshToken(email);
76+
originalRequest.headers.Authorization = `Bearer ${newToken}`;
77+
return apiRequest(originalRequest);
78+
} else {
79+
console.error(
80+
'사용자 이메일이 없습니다. 온보딩을 다시 완료해주세요.'
81+
);
82+
localStorage.removeItem('token');
83+
window.location.href = '/onboarding';
84+
}
85+
} catch (refreshError) {
86+
console.error('토큰 재발급 실패:', refreshError);
87+
localStorage.removeItem('token');
88+
localStorage.removeItem('email');
89+
window.location.href = '/onboarding';
90+
}
91+
}
92+
93+
return Promise.reject(error);
94+
}
95+
);
96+
1097
export default apiRequest;

apps/extension/src/apis/axiosInstance.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,68 @@ const apiRequest = axios.create({
77
},
88
});
99

10+
const fetchToken = async (email?: string) => {
11+
const response = await axios.get(
12+
`${import.meta.env.VITE_BASE_URL}/api/v1/auth/token`,
13+
{
14+
params: { email },
15+
}
16+
);
17+
const newToken = response.data.data.token;
18+
chrome.storage.local.set({ token: newToken }, () => {
19+
console.log('Token re-saved to chrome storage');
20+
});
21+
return newToken;
22+
};
23+
24+
apiRequest.interceptors.request.use(async (config) => {
25+
const noAuthNeeded = ['/api/v1/auth/token', '/api/v1/auth/signup'];
26+
const isNoAuth = noAuthNeeded.some((url) => config.url?.includes(url));
27+
28+
if (isNoAuth) return config;
29+
30+
const email = await new Promise<string | undefined>((resolve) => {
31+
chrome.storage.local.get('email', (result) => {
32+
resolve(result.email);
33+
});
34+
});
35+
36+
let token = await new Promise<string | undefined>((resolve) => {
37+
chrome.storage.local.get('token', (result) => {
38+
resolve(result.token);
39+
});
40+
});
41+
42+
// 토큰 없으면 fetchToken 호출
43+
if (!token || token === 'undefined') {
44+
token = await fetchToken(email);
45+
}
46+
47+
config.headers.Authorization = `Bearer ${token}`;
48+
return config;
49+
});
50+
51+
apiRequest.interceptors.response.use(
52+
(response) => response,
53+
async (error) => {
54+
const originalRequest = error.config;
55+
const noAuthNeeded = ['/api/v1/auth/token', '/api/v1/auth/signup'];
56+
const isNoAuth = noAuthNeeded.some((url) =>
57+
originalRequest.url?.includes(url)
58+
);
59+
if (
60+
error.response &&
61+
(error.response.status === 401 || error.response.status === 403) &&
62+
!originalRequest._retry &&
63+
!isNoAuth
64+
) {
65+
originalRequest._retry = true;
66+
const newToken = await fetchToken('test@gmail.com');
67+
originalRequest.headers.Authorization = `Bearer ${newToken}`;
68+
return apiRequest(originalRequest);
69+
}
70+
return Promise.reject(error);
71+
}
72+
);
73+
1074
export default apiRequest;

0 commit comments

Comments
 (0)