Skip to content

Commit de6130d

Browse files
Merge pull request #316 from Pinback-Team/develop
main << develop
2 parents ba2fb6b + 15bc0b9 commit de6130d

File tree

17 files changed

+176
-73
lines changed

17 files changed

+176
-73
lines changed

apps/client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"preview": "vite preview"
1111
},
1212
"dependencies": {
13+
"@pinback/contracts": "workspace:*",
1314
"@tanstack/react-query": "^5.85.3",
1415
"axios": "^1.11.0",
1516
"class-variance-authority": "^0.7.1",

apps/client/src/layout/Layout.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ROUTES_CONFIG } from '@routes/routesConfig';
22
import { useGetHasJob } from '@shared/apis/queries';
33
import JobSelectionFunnel from '@shared/components/jobSelectionFunnel/JobSelectionFunnel';
44
import { Sidebar } from '@shared/components/sidebar/Sidebar';
5+
import { authStorage } from '@shared/utils/authStorage';
56
import { useQueryClient } from '@tanstack/react-query';
67
import { Outlet, useLocation } from 'react-router-dom';
78

@@ -19,7 +20,7 @@ const Layout = () => {
1920
location.pathname.startsWith(ROUTES_CONFIG.onboardingCallback.path);
2021

2122
const isSidebarHidden = isAuthPage || isPolicyPage;
22-
const isLoggedIn = !!localStorage.getItem('token');
23+
const isLoggedIn = authStorage.hasAccessToken();
2324

2425
const { data: hasJobData, isLoading: isHasJobLoading } = useGetHasJob(
2526
isLoggedIn && !isAuthPage

apps/client/src/pages/myBookmark/components/myBookmarkContent/MyBookmarkContent.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ const MyBookmarkContent = ({
9898
queryClient.invalidateQueries({
9999
queryKey: ['categoryBookmarkArticles'],
100100
});
101-
queryClient.invalidateQueries({ queryKey: ['acorns'] });
102101
},
103102
onError: (error: any) => {
104103
console.error(error);

apps/client/src/pages/onBoarding/GoogleCallback.tsx

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
11
import apiRequest from '@shared/apis/setting/axiosInstance';
22
import LoadingChippi from '@shared/components/loadingChippi/LoadingChippi';
3+
import { authStorage } from '@shared/utils/authStorage';
4+
import { extensionBridge } from '@shared/utils/extensionBridge';
35
import { useEffect } from 'react';
46
import { useNavigate, useSearchParams } from 'react-router-dom';
57

6-
const sendTokenToExtension = (token: string) => {
7-
window.postMessage(
8-
{
9-
type: 'SET_TOKEN',
10-
token,
11-
},
12-
window.location.origin
13-
);
14-
};
15-
168
const GoogleCallback = () => {
179
const navigate = useNavigate();
1810
const [searchParams] = useSearchParams();
@@ -37,16 +29,16 @@ const GoogleCallback = () => {
3729
) => {
3830
if (isUser) {
3931
if (accessToken) {
40-
localStorage.setItem('token', accessToken);
41-
sendTokenToExtension(accessToken);
32+
authStorage.setAccessToken(accessToken);
33+
extensionBridge.syncToken(accessToken);
4234
}
4335

4436
if (refreshToken) {
45-
localStorage.setItem('refreshToken', refreshToken);
37+
authStorage.setRefreshToken(refreshToken);
4638
}
4739

4840
if (typeof hasJob === 'boolean') {
49-
localStorage.setItem('hasJob', String(hasJob));
41+
authStorage.setHasJob(hasJob);
5042
}
5143
navigate('/');
5244
} else {
@@ -74,8 +66,7 @@ const GoogleCallback = () => {
7466
const { isUser, userId, email, accessToken, refreshToken, hasJob } =
7567
res.data.data;
7668

77-
localStorage.setItem('email', email);
78-
localStorage.setItem('userId', userId);
69+
authStorage.setUserIdentity(email, userId);
7970

8071
handleUserLogin(isUser, accessToken, refreshToken, hasJob);
8172
} catch (error) {

apps/client/src/shared/apis/queries.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import {
2929
JobsResponse,
3030
} from '@shared/types/api';
3131
import { fetchOGData } from '@shared/utils/fetchOgData';
32+
import { authStorage } from '@shared/utils/authStorage';
33+
import { extensionBridge } from '@shared/utils/extensionBridge';
3234
import {
3335
useMutation,
3436
UseMutationResult,
@@ -84,6 +86,7 @@ export const useGetAcorns = (): UseQueryResult<AcornsResponse, AxiosError> => {
8486
return useQuery({
8587
queryKey: ['acorns'],
8688
queryFn: () => getAcorns(),
89+
refetchOnWindowFocus: true,
8790
});
8891
};
8992

@@ -94,17 +97,8 @@ export const usePostSignUp = () => {
9497
const newToken = data?.data?.token || data?.token;
9598

9699
if (newToken) {
97-
localStorage.setItem('token', newToken);
98-
const sendTokenToExtension = (token: string) => {
99-
window.postMessage(
100-
{
101-
type: 'SET_TOKEN',
102-
token,
103-
},
104-
window.location.origin
105-
);
106-
};
107-
sendTokenToExtension(newToken);
100+
authStorage.setAccessToken(newToken);
101+
extensionBridge.syncToken(newToken);
108102
}
109103
},
110104
onError: (error) => {
@@ -129,6 +123,7 @@ export const usePutArticleReadStatus = (): UseMutationResult<
129123
});
130124
queryClient.invalidateQueries({
131125
queryKey: ['acorns'],
126+
refetchType: 'none',
132127
});
133128
queryClient.invalidateQueries({
134129
queryKey: ['bookmarkReadArticles'],

apps/client/src/shared/apis/setting/axiosInstance.ts

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,34 @@
11
import axios from 'axios';
2+
import { authStorage } from '@shared/utils/authStorage';
3+
import { extensionBridge } from '@shared/utils/extensionBridge';
4+
5+
const noAuthNeeded = [
6+
'/api/v1/auth/token',
7+
'/api/v3/auth/signup',
8+
'/api/v3/auth/google',
9+
'/api/v3/auth/reissue',
10+
];
11+
12+
const reissueToken = async () => {
13+
return await axios.post(
14+
`${import.meta.env.VITE_BASE_URL}/api/v3/auth/reissue`,
15+
{},
16+
{
17+
withCredentials: true,
18+
}
19+
);
20+
};
21+
22+
const syncAccessToken = (token: string) => {
23+
authStorage.setAccessToken(token);
24+
extensionBridge.syncToken(token);
25+
};
26+
27+
const clearAuthSessionAndRedirect = () => {
28+
authStorage.clearSession();
29+
extensionBridge.logout();
30+
window.location.href = '/onboarding?step=SOCIAL_LOGIN';
31+
};
232

333
// Axios 인스턴스
434
const apiRequest = axios.create({
@@ -10,7 +40,7 @@ const apiRequest = axios.create({
1040

1141
// 요청 인터셉터
1242
apiRequest.interceptors.request.use(async (config) => {
13-
const token = localStorage.getItem('token');
43+
const token = authStorage.getAccessToken();
1444

1545
if (token) {
1646
config.headers.Authorization = `Bearer ${token}`;
@@ -25,13 +55,6 @@ apiRequest.interceptors.response.use(
2555
async (error) => {
2656
const originalRequest = error.config;
2757

28-
const noAuthNeeded = [
29-
'/api/v1/auth/token',
30-
'/api/v3/auth/signup',
31-
'/api/v3/auth/google',
32-
'/api/v3/auth/reissue',
33-
];
34-
3558
const isNoAuth = noAuthNeeded.some((url) =>
3659
originalRequest.url?.includes(url)
3760
);
@@ -48,30 +71,21 @@ apiRequest.interceptors.response.use(
4871
originalRequest._retry = true;
4972

5073
try {
51-
const res = await axios.post(
52-
`${import.meta.env.VITE_BASE_URL}/api/v3/auth/reissue`,
53-
{},
54-
{
55-
withCredentials: true,
56-
}
57-
);
58-
59-
const newAccessToken = res.data.data.token;
60-
localStorage.setItem('token', newAccessToken);
61-
62-
window.postMessage(
63-
{ type: 'SET_TOKEN', token: newAccessToken },
64-
window.location.origin
65-
);
74+
const res = await reissueToken();
75+
const newAccessToken = res.data?.data?.token;
76+
77+
if (!newAccessToken) {
78+
throw new Error('토큰 재발급 응답에 access token이 없습니다.');
79+
}
6680

81+
syncAccessToken(newAccessToken);
82+
originalRequest.headers = originalRequest.headers ?? {};
6783
originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
6884
return apiRequest(originalRequest);
6985
} catch (reissueError) {
7086
console.error('토큰 재발급 실패. 다시 로그인해주세요.', reissueError);
7187

72-
localStorage.removeItem('token');
73-
localStorage.removeItem('refreshToken');
74-
window.location.href = '/onboarding?step=SOCIAL_LOGIN';
88+
clearAuthSessionAndRedirect();
7589

7690
return Promise.reject(reissueError);
7791
}

apps/client/src/shared/components/profilePopup/ProfilePopup.tsx

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { Icon } from '@pinback/design-system/icons';
22
import { Button } from '@pinback/design-system/ui';
33
import { useQueryClient } from '@tanstack/react-query';
44
import formatRemindTime from '@shared/utils/formatRemindTime';
5+
import { authStorage } from '@shared/utils/authStorage';
6+
import { extensionBridge } from '@shared/utils/extensionBridge';
57
import { useEffect, useRef } from 'react';
68
import { useNavigate } from 'react-router-dom';
79

@@ -42,19 +44,9 @@ export default function ProfilePopup({
4244
if (!open) return null;
4345

4446
const handleLogout = () => {
45-
localStorage.removeItem('token');
46-
localStorage.removeItem('email');
47-
localStorage.removeItem('userId');
47+
authStorage.clearSession();
4848
queryClient.clear();
49-
const sendExtensionLogout = () => {
50-
window.postMessage(
51-
{
52-
type: 'Extension-Logout',
53-
},
54-
window.location.origin
55-
);
56-
};
57-
sendExtensionLogout();
49+
extensionBridge.logout();
5850
navigate('/login');
5951
};
6052

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
const AUTH_STORAGE_KEYS = {
2+
token: 'token',
3+
refreshToken: 'refreshToken',
4+
email: 'email',
5+
userId: 'userId',
6+
hasJob: 'hasJob',
7+
} as const;
8+
9+
export const authStorage = {
10+
getAccessToken: () => localStorage.getItem(AUTH_STORAGE_KEYS.token),
11+
hasAccessToken: () => !!localStorage.getItem(AUTH_STORAGE_KEYS.token),
12+
setAccessToken: (token: string) =>
13+
localStorage.setItem(AUTH_STORAGE_KEYS.token, token),
14+
setRefreshToken: (refreshToken: string) =>
15+
localStorage.setItem(AUTH_STORAGE_KEYS.refreshToken, refreshToken),
16+
setHasJob: (hasJob: boolean) =>
17+
localStorage.setItem(AUTH_STORAGE_KEYS.hasJob, String(hasJob)),
18+
setUserIdentity: (email: string, userId: string) => {
19+
localStorage.setItem(AUTH_STORAGE_KEYS.email, email);
20+
localStorage.setItem(AUTH_STORAGE_KEYS.userId, userId);
21+
},
22+
clearSession: () => {
23+
Object.values(AUTH_STORAGE_KEYS).forEach((key) => {
24+
localStorage.removeItem(key);
25+
});
26+
},
27+
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { EXTENSION_MESSAGE_TYPE } from '@pinback/contracts/extension-messages';
2+
3+
export const extensionBridge = {
4+
syncToken: (token: string) => {
5+
window.postMessage(
6+
{
7+
type: EXTENSION_MESSAGE_TYPE.setToken,
8+
token,
9+
},
10+
window.location.origin
11+
);
12+
},
13+
logout: () => {
14+
window.postMessage(
15+
{
16+
type: EXTENSION_MESSAGE_TYPE.logout,
17+
},
18+
window.location.origin
19+
);
20+
},
21+
};

apps/extension/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"zip": "vite build && node scripts/zip.mjs"
1212
},
1313
"dependencies": {
14+
"@pinback/contracts": "workspace:*",
1415
"@tanstack/react-query": "^5.85.5",
1516
"axios": "^1.11.0",
1617
"react": "^19.1.1",

0 commit comments

Comments
 (0)