From 7604fb464121ef557150ea7ac88e61ac3228ed3c Mon Sep 17 00:00:00 2001 From: jllee000 Date: Mon, 22 Dec 2025 11:38:27 +0900 Subject: [PATCH 1/4] =?UTF-8?q?fix:=20chrome-storage=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/package.json | 1 + apps/client/src/pages/onBoarding/GoogleCallback.tsx | 7 +++++++ pnpm-lock.yaml | 3 +++ 3 files changed, 11 insertions(+) diff --git a/apps/client/package.json b/apps/client/package.json index 63d2bdf..2365856 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -29,6 +29,7 @@ "@pivanov/vite-plugin-svg-sprite": "^3.1.3", "@tailwindcss/vite": "^4.1.12", "@tanstack/react-query-devtools": "^5.87.4", + "@types/chrome": "^0.0.273", "@vitejs/plugin-react-swc": "^4.0.0", "eslint": "^9.33.0", "eslint-plugin-react-hooks": "^5.2.0", diff --git a/apps/client/src/pages/onBoarding/GoogleCallback.tsx b/apps/client/src/pages/onBoarding/GoogleCallback.tsx index 8229bb2..e87643c 100644 --- a/apps/client/src/pages/onBoarding/GoogleCallback.tsx +++ b/apps/client/src/pages/onBoarding/GoogleCallback.tsx @@ -26,7 +26,14 @@ const GoogleCallback = () => { if (isUser) { if (accessToken) { localStorage.setItem('token', accessToken); + + if (typeof chrome !== 'undefined' && chrome.storage?.local) { + chrome.storage.local.set({ token: accessToken }, () => { + console.log('Token saved to chrome storage'); + }); + } } + navigate('/'); } else { navigate('/onboarding?step=ALARM'); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c22b67d..1b9a5f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -139,6 +139,9 @@ importers: '@tanstack/react-query-devtools': specifier: ^5.87.4 version: 5.87.4(@tanstack/react-query@5.85.5(react@19.1.1))(react@19.1.1) + '@types/chrome': + specifier: ^0.0.273 + version: 0.0.273 '@vitejs/plugin-react-swc': specifier: ^4.0.0 version: 4.0.0(@swc/helpers@0.5.17)(vite@7.1.2(@types/node@22.15.3)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.4)(yaml@2.8.1)) From e7af0c2a9be7948d199aa5a5b023065de1302c66 Mon Sep 17 00:00:00 2001 From: constantly-dev Date: Mon, 22 Dec 2025 11:54:25 +0900 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20interceptor=EC=97=90=EC=84=9C=20refr?= =?UTF-8?q?esh=20=EB=A1=9C=EC=A7=81=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/shared/apis/setting/axiosInstance.ts | 71 ++----------------- apps/extension/src/apis/axiosInstance.ts | 69 +++++++----------- 2 files changed, 32 insertions(+), 108 deletions(-) diff --git a/apps/client/src/shared/apis/setting/axiosInstance.ts b/apps/client/src/shared/apis/setting/axiosInstance.ts index 4f71e25..91cf031 100644 --- a/apps/client/src/shared/apis/setting/axiosInstance.ts +++ b/apps/client/src/shared/apis/setting/axiosInstance.ts @@ -8,55 +8,11 @@ const apiRequest = axios.create({ }, }); -const refreshToken = async (email: string) => { - try { - const response = await axios.get( - `${import.meta.env.VITE_BASE_URL}/api/v1/auth/token`, - { - params: { email }, - } - ); - - const newToken = response.data.data?.token || response.data.token; - - if (newToken) { - localStorage.setItem('token', newToken); - return newToken; - } - - throw new Error('토큰 재발급 실패'); - } catch (error) { - console.error('토큰 재발급 실패:', error); - throw error; - } -}; - // 요청 인터셉터 apiRequest.interceptors.request.use(async (config) => { - const noAuthNeeded = [ - '/api/v1/auth/token', - '/api/v2/auth/signup', - '/api/v2/auth/google', - ]; - const isNoAuth = noAuthNeeded.some((url) => config.url?.includes(url)); - - if (!isNoAuth) { - let token = localStorage.getItem('token'); - const email = localStorage.getItem('email'); - if (email) { - try { - token = await refreshToken(email); - } catch (err) { - console.error('요청 인터셉터에서 토큰 재발급 실패:', err); - localStorage.removeItem('token'); - window.location.href = '/onboarding'; - throw err; - } - } else { - console.error('토큰이 없습니다. 온보딩을 먼저 완료해주세요.'); - throw new Error('토큰이 없습니다. 온보딩을 먼저 완료해주세요.'); - } + const token = localStorage.getItem('token'); + if (token) { config.headers.Authorization = `Bearer ${token}`; } @@ -68,11 +24,13 @@ apiRequest.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config; + const noAuthNeeded = [ '/api/v1/auth/token', '/api/v2/auth/signup', '/api/v2/auth/google', ]; + const isNoAuth = noAuthNeeded.some((url) => originalRequest.url?.includes(url) ); @@ -85,25 +43,10 @@ apiRequest.interceptors.response.use( ) { originalRequest._retry = true; - try { - const email = localStorage.getItem('email'); + localStorage.removeItem('token'); + window.location.href = '/onboarding?step=SOCIAL_LOGIN'; - if (email) { - const newToken = await refreshToken(email); - originalRequest.headers.Authorization = `Bearer ${newToken}`; - return apiRequest(originalRequest); - } else { - console.error( - '사용자 이메일이 없습니다. 온보딩을 다시 완료해주세요.' - ); - localStorage.removeItem('token'); - window.location.href = '/onboarding'; - } - } catch (refreshError) { - console.error('토큰 재발급 실패:', refreshError); - localStorage.removeItem('token'); - window.location.href = '/onboarding'; - } + return Promise.reject(error); } return Promise.reject(error); diff --git a/apps/extension/src/apis/axiosInstance.ts b/apps/extension/src/apis/axiosInstance.ts index 7dc2c37..a7187d6 100644 --- a/apps/extension/src/apis/axiosInstance.ts +++ b/apps/extension/src/apis/axiosInstance.ts @@ -7,75 +7,56 @@ const apiRequest = axios.create({ }, }); -const fetchToken = async (email?: string) => { - const response = await axios.get( - `${import.meta.env.VITE_BASE_URL}/api/v1/auth/token`, - { - params: { email }, - } - ); - const newToken = response.data.data.token; - chrome.storage.local.set({ token: newToken }, () => { - console.log('Token re-saved to chrome storage'); - }); - return newToken; -}; - -apiRequest.interceptors.request.use(async (config) => { - const noAuthNeeded = ['/api/v1/auth/token', '/api/v1/auth/signup']; - const isNoAuth = noAuthNeeded.some((url) => config.url?.includes(url)); - - if (isNoAuth) return config; - - const email = await new Promise((resolve) => { - chrome.storage.local.get('email', (result) => resolve(result.email)); - }); - - let token = await new Promise((resolve) => { +const getTokenFromStorage = () => { + return new Promise((resolve) => { chrome.storage.local.get('token', (result) => { resolve(result.token); }); }); +}; - if (!isNoAuth) { - if (email) { - try { - token = await fetchToken(email); - } catch (err) { - console.error('요청 인터셉터에서 토큰 재발급 실패:', err); - localStorage.removeItem('token'); - window.location.href = '/onboarding'; - throw err; - } - } else { - throw new Error('토큰이 없습니다. 온보딩을 먼저 완료해주세요.'); - } +apiRequest.interceptors.request.use(async (config) => { + const token = await getTokenFromStorage(); + if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); +// TODO: 환경변수로 분리 +// eslint-disable-next-line turbo/no-undeclared-env-vars +const onboardingUrl = import.meta.env.DEV + ? 'http://localhost:5173/onboarding?step=SOCIAL_LOGIN' + : 'https://pinback.today/onboarding?step=SOCIAL_LOGIN'; + apiRequest.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config; - const noAuthNeeded = ['/api/v1/auth/token', '/api/v1/auth/signup']; + + const noAuthNeeded = [ + '/api/v1/auth/token', + '/api/v1/auth/signup', + '/api/v2/auth/google', + ]; const isNoAuth = noAuthNeeded.some((url) => originalRequest.url?.includes(url) ); + if ( error.response && (error.response.status === 401 || error.response.status === 403) && - !originalRequest._retry && !isNoAuth ) { - originalRequest._retry = true; - const newToken = await fetchToken('test@gmail.com'); - originalRequest.headers.Authorization = `Bearer ${newToken}`; - return apiRequest(originalRequest); + chrome.storage.local.remove(['token', 'email'], () => {}); + + chrome.tabs.create({ url: onboardingUrl }); + + return Promise.reject(error); } + return Promise.reject(error); } ); From bb5130a0dc374030a244c276ecee9907ecf446f1 Mon Sep 17 00:00:00 2001 From: jllee000 Date: Mon, 22 Dec 2025 11:54:56 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20=ED=86=A0=ED=81=B0=20=ED=81=AC?= =?UTF-8?q?=EB=A1=AC=EC=8A=A4=ED=86=A0=EB=A6=AC=EC=A7=80=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EC=9D=B8=EC=8A=A4=ED=84=B4=EC=8A=A4=EC=97=90=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/src/shared/apis/queries.ts | 5 +++++ apps/client/src/shared/apis/setting/axiosInstance.ts | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/apps/client/src/shared/apis/queries.ts b/apps/client/src/shared/apis/queries.ts index dd35a74..5d7246b 100644 --- a/apps/client/src/shared/apis/queries.ts +++ b/apps/client/src/shared/apis/queries.ts @@ -80,6 +80,11 @@ export const usePostSignUp = () => { }; if (newToken) { localStorage.setItem('token', newToken); + if (typeof chrome !== 'undefined' && chrome.storage?.local) { + chrome.storage.local.set({ token: newToken }, () => { + console.log('Token saved to chrome storage'); + }); + } sendTokenToExtension(newToken); } diff --git a/apps/client/src/shared/apis/setting/axiosInstance.ts b/apps/client/src/shared/apis/setting/axiosInstance.ts index 4f71e25..fceb243 100644 --- a/apps/client/src/shared/apis/setting/axiosInstance.ts +++ b/apps/client/src/shared/apis/setting/axiosInstance.ts @@ -21,6 +21,11 @@ const refreshToken = async (email: string) => { if (newToken) { localStorage.setItem('token', newToken); + if (typeof chrome !== 'undefined' && chrome.storage?.local) { + chrome.storage.local.set({ token: newToken }, () => { + console.log('Token saved to chrome storage'); + }); + } return newToken; } From ae7f5e84f7c5dc04c6f4fba75a4f95d7405cd3cb Mon Sep 17 00:00:00 2001 From: constantly-dev Date: Mon, 22 Dec 2025 11:56:54 +0900 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20tabs=20=EC=97=AC=EB=9F=AC=EA=B0=9C?= =?UTF-8?q?=20=EC=BC=9C=EC=A7=80=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=EB=B0=A9?= =?UTF-8?q?=EC=A7=80=EC=9A=A9=20isRedirecting=20flag=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/extension/src/apis/axiosInstance.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/extension/src/apis/axiosInstance.ts b/apps/extension/src/apis/axiosInstance.ts index a7187d6..b44dffe 100644 --- a/apps/extension/src/apis/axiosInstance.ts +++ b/apps/extension/src/apis/axiosInstance.ts @@ -31,6 +31,8 @@ const onboardingUrl = import.meta.env.DEV ? 'http://localhost:5173/onboarding?step=SOCIAL_LOGIN' : 'https://pinback.today/onboarding?step=SOCIAL_LOGIN'; +let isRedirecting = false; + apiRequest.interceptors.response.use( (response) => response, async (error) => { @@ -50,11 +52,17 @@ apiRequest.interceptors.response.use( (error.response.status === 401 || error.response.status === 403) && !isNoAuth ) { - chrome.storage.local.remove(['token', 'email'], () => {}); + if (!isRedirecting) { + isRedirecting = true; - chrome.tabs.create({ url: onboardingUrl }); + chrome.storage.local.remove(['token', 'email'], () => {}); - return Promise.reject(error); + chrome.tabs.create({ url: onboardingUrl }, () => { + setTimeout(() => { + isRedirecting = false; + }, 2000); + }); + } } return Promise.reject(error);