Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
"@pinback/contracts": "workspace:*",
"@tanstack/react-query": "^5.85.3",
"axios": "^1.11.0",
"class-variance-authority": "^0.7.1",
Expand Down
3 changes: 2 additions & 1 deletion apps/client/src/layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ROUTES_CONFIG } from '@routes/routesConfig';
import { useGetHasJob } from '@shared/apis/queries';
import JobSelectionFunnel from '@shared/components/jobSelectionFunnel/JobSelectionFunnel';
import { Sidebar } from '@shared/components/sidebar/Sidebar';
import { authStorage } from '@shared/utils/authStorage';
import { useQueryClient } from '@tanstack/react-query';
import { Outlet, useLocation } from 'react-router-dom';

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

const isSidebarHidden = isAuthPage || isPolicyPage;
const isLoggedIn = !!localStorage.getItem('token');
const isLoggedIn = authStorage.hasAccessToken();

const { data: hasJobData, isLoading: isHasJobLoading } = useGetHasJob(
isLoggedIn && !isAuthPage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ const MyBookmarkContent = ({
queryClient.invalidateQueries({
queryKey: ['categoryBookmarkArticles'],
});
queryClient.invalidateQueries({ queryKey: ['acorns'] });
},
onError: (error: any) => {
console.error(error);
Expand Down
23 changes: 7 additions & 16 deletions apps/client/src/pages/onBoarding/GoogleCallback.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
import apiRequest from '@shared/apis/setting/axiosInstance';
import LoadingChippi from '@shared/components/loadingChippi/LoadingChippi';
import { authStorage } from '@shared/utils/authStorage';
import { extensionBridge } from '@shared/utils/extensionBridge';
import { useEffect } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';

const sendTokenToExtension = (token: string) => {
window.postMessage(
{
type: 'SET_TOKEN',
token,
},
window.location.origin
);
};

const GoogleCallback = () => {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
Expand All @@ -37,16 +29,16 @@ const GoogleCallback = () => {
) => {
if (isUser) {
if (accessToken) {
localStorage.setItem('token', accessToken);
sendTokenToExtension(accessToken);
authStorage.setAccessToken(accessToken);
extensionBridge.syncToken(accessToken);
}

if (refreshToken) {
localStorage.setItem('refreshToken', refreshToken);
authStorage.setRefreshToken(refreshToken);
}

if (typeof hasJob === 'boolean') {
localStorage.setItem('hasJob', String(hasJob));
authStorage.setHasJob(hasJob);
}
navigate('/');
} else {
Expand Down Expand Up @@ -74,8 +66,7 @@ const GoogleCallback = () => {
const { isUser, userId, email, accessToken, refreshToken, hasJob } =
res.data.data;

localStorage.setItem('email', email);
localStorage.setItem('userId', userId);
authStorage.setUserIdentity(email, userId);

handleUserLogin(isUser, accessToken, refreshToken, hasJob);
} catch (error) {
Expand Down
17 changes: 6 additions & 11 deletions apps/client/src/shared/apis/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import {
JobsResponse,
} from '@shared/types/api';
import { fetchOGData } from '@shared/utils/fetchOgData';
import { authStorage } from '@shared/utils/authStorage';
import { extensionBridge } from '@shared/utils/extensionBridge';
import {
useMutation,
UseMutationResult,
Expand Down Expand Up @@ -84,6 +86,7 @@ export const useGetAcorns = (): UseQueryResult<AcornsResponse, AxiosError> => {
return useQuery({
queryKey: ['acorns'],
queryFn: () => getAcorns(),
refetchOnWindowFocus: true,
});
};

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

if (newToken) {
localStorage.setItem('token', newToken);
const sendTokenToExtension = (token: string) => {
window.postMessage(
{
type: 'SET_TOKEN',
token,
},
window.location.origin
);
};
sendTokenToExtension(newToken);
authStorage.setAccessToken(newToken);
extensionBridge.syncToken(newToken);
}
},
onError: (error) => {
Expand All @@ -129,6 +123,7 @@ export const usePutArticleReadStatus = (): UseMutationResult<
});
queryClient.invalidateQueries({
queryKey: ['acorns'],
refetchType: 'none',
});
queryClient.invalidateQueries({
queryKey: ['bookmarkReadArticles'],
Expand Down
66 changes: 40 additions & 26 deletions apps/client/src/shared/apis/setting/axiosInstance.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,34 @@
import axios from 'axios';
import { authStorage } from '@shared/utils/authStorage';
import { extensionBridge } from '@shared/utils/extensionBridge';

const noAuthNeeded = [
'/api/v1/auth/token',
'/api/v3/auth/signup',
'/api/v3/auth/google',
'/api/v3/auth/reissue',
];

const reissueToken = async () => {
return await axios.post(
`${import.meta.env.VITE_BASE_URL}/api/v3/auth/reissue`,
{},
{
withCredentials: true,
}
);
};

const syncAccessToken = (token: string) => {
authStorage.setAccessToken(token);
extensionBridge.syncToken(token);
};

const clearAuthSessionAndRedirect = () => {
authStorage.clearSession();
extensionBridge.logout();
window.location.href = '/onboarding?step=SOCIAL_LOGIN';
};

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

// 요청 인터셉터
apiRequest.interceptors.request.use(async (config) => {
const token = localStorage.getItem('token');
const token = authStorage.getAccessToken();

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

const noAuthNeeded = [
'/api/v1/auth/token',
'/api/v3/auth/signup',
'/api/v3/auth/google',
'/api/v3/auth/reissue',
];

const isNoAuth = noAuthNeeded.some((url) =>
originalRequest.url?.includes(url)
);
Expand All @@ -48,30 +71,21 @@ apiRequest.interceptors.response.use(
originalRequest._retry = true;

try {
const res = await axios.post(
`${import.meta.env.VITE_BASE_URL}/api/v3/auth/reissue`,
{},
{
withCredentials: true,
}
);

const newAccessToken = res.data.data.token;
localStorage.setItem('token', newAccessToken);

window.postMessage(
{ type: 'SET_TOKEN', token: newAccessToken },
window.location.origin
);
const res = await reissueToken();
const newAccessToken = res.data?.data?.token;

if (!newAccessToken) {
throw new Error('토큰 재발급 응답에 access token이 없습니다.');
}

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

localStorage.removeItem('token');
localStorage.removeItem('refreshToken');
window.location.href = '/onboarding?step=SOCIAL_LOGIN';
clearAuthSessionAndRedirect();

return Promise.reject(reissueError);
}
Expand Down
16 changes: 4 additions & 12 deletions apps/client/src/shared/components/profilePopup/ProfilePopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Icon } from '@pinback/design-system/icons';
import { Button } from '@pinback/design-system/ui';
import { useQueryClient } from '@tanstack/react-query';
import formatRemindTime from '@shared/utils/formatRemindTime';
import { authStorage } from '@shared/utils/authStorage';
import { extensionBridge } from '@shared/utils/extensionBridge';
import { useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';

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

const handleLogout = () => {
localStorage.removeItem('token');
localStorage.removeItem('email');
localStorage.removeItem('userId');
authStorage.clearSession();
queryClient.clear();
const sendExtensionLogout = () => {
window.postMessage(
{
type: 'Extension-Logout',
},
window.location.origin
);
};
sendExtensionLogout();
extensionBridge.logout();
navigate('/login');
};

Expand Down
27 changes: 27 additions & 0 deletions apps/client/src/shared/utils/authStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const AUTH_STORAGE_KEYS = {
token: 'token',
refreshToken: 'refreshToken',
email: 'email',
userId: 'userId',
hasJob: 'hasJob',
} as const;

export const authStorage = {
getAccessToken: () => localStorage.getItem(AUTH_STORAGE_KEYS.token),
hasAccessToken: () => !!localStorage.getItem(AUTH_STORAGE_KEYS.token),
setAccessToken: (token: string) =>
localStorage.setItem(AUTH_STORAGE_KEYS.token, token),
setRefreshToken: (refreshToken: string) =>
localStorage.setItem(AUTH_STORAGE_KEYS.refreshToken, refreshToken),
setHasJob: (hasJob: boolean) =>
localStorage.setItem(AUTH_STORAGE_KEYS.hasJob, String(hasJob)),
setUserIdentity: (email: string, userId: string) => {
localStorage.setItem(AUTH_STORAGE_KEYS.email, email);
localStorage.setItem(AUTH_STORAGE_KEYS.userId, userId);
},
clearSession: () => {
Object.values(AUTH_STORAGE_KEYS).forEach((key) => {
localStorage.removeItem(key);
});
},
};
21 changes: 21 additions & 0 deletions apps/client/src/shared/utils/extensionBridge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { EXTENSION_MESSAGE_TYPE } from '@pinback/contracts/extension-messages';

export const extensionBridge = {
syncToken: (token: string) => {
window.postMessage(
{
type: EXTENSION_MESSAGE_TYPE.setToken,
token,
},
window.location.origin
);
},
logout: () => {
window.postMessage(
{
type: EXTENSION_MESSAGE_TYPE.logout,
},
window.location.origin
);
},
};
1 change: 1 addition & 0 deletions apps/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"zip": "vite build && node scripts/zip.mjs"
},
"dependencies": {
"@pinback/contracts": "workspace:*",
"@tanstack/react-query": "^5.85.5",
"axios": "^1.11.0",
"react": "^19.1.1",
Expand Down
6 changes: 4 additions & 2 deletions apps/extension/src/background.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { EXTENSION_MESSAGE_TYPE } from '@pinback/contracts/extension-messages';

chrome.runtime.onInstalled.addListener((details) => {
if (details.reason === 'install') {
chrome.identity.getProfileUserInfo(function (info) {
chrome.storage.local.set({ email: info.email }, () => {
console.log('User email saved:');

Check warning on line 7 in apps/extension/src/background.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement

Check warning on line 7 in apps/extension/src/background.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
});
setTimeout(() => {
chrome.tabs.create({
Expand All @@ -14,17 +16,17 @@
});

chrome.runtime.onMessage.addListener((message) => {
if (message.type === 'SET_TOKEN') {
if (message.type === EXTENSION_MESSAGE_TYPE.setToken) {
chrome.storage.local.set({ token: message.token }, () => {
console.log('Token saved!');

Check warning on line 21 in apps/extension/src/background.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement

Check warning on line 21 in apps/extension/src/background.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
});
}
});

chrome.runtime.onMessage.addListener((message) => {
if (message.type === 'Extension-Logout') {
if (message.type === EXTENSION_MESSAGE_TYPE.logout) {
chrome.storage.local.remove('token', () => {
console.log('Token removed!');

Check warning on line 29 in apps/extension/src/background.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement

Check warning on line 29 in apps/extension/src/background.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
});
}
});
8 changes: 5 additions & 3 deletions apps/extension/src/content.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import { EXTENSION_MESSAGE_TYPE } from '@pinback/contracts/extension-messages';

window.addEventListener('message', (event) => {
if (event.source !== window) return;
if (event.data.type === 'SET_TOKEN') {
if (event.data.type === EXTENSION_MESSAGE_TYPE.setToken) {
chrome.runtime.sendMessage({
type: 'SET_TOKEN',
type: EXTENSION_MESSAGE_TYPE.setToken,
token: event.data.token,
});
chrome.storage.local.set({ token: event.data.token }, () => {
console.log('Token saved!');

Check warning on line 11 in apps/extension/src/content.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement

Check warning on line 11 in apps/extension/src/content.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
});
}
});

window.addEventListener('message', (event) => {
if (event.source !== window) return;
if (event.data.type === 'Extension-Logout') {
if (event.data.type === EXTENSION_MESSAGE_TYPE.logout) {
chrome.storage.local.remove('token', () => {
console.log('Token removed!');

Check warning on line 20 in apps/extension/src/content.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement

Check warning on line 20 in apps/extension/src/content.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
});
}
});
16 changes: 16 additions & 0 deletions packages/contracts/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "@pinback/contracts",
"version": "0.0.0",
"private": true,
"type": "module",
"exports": {
"./extension-messages": "./src/extension-messages.ts"
},
"scripts": {
"check-types": "tsc --noEmit"
},
"devDependencies": {
"@pinback/typescript-config": "workspace:*",
"typescript": "5.9.2"
}
}
16 changes: 16 additions & 0 deletions packages/contracts/src/extension-messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const EXTENSION_MESSAGE_TYPE = {
setToken: 'SET_TOKEN',
logout: 'Extension-Logout',
} as const;

export type ExtensionMessageType =
(typeof EXTENSION_MESSAGE_TYPE)[keyof typeof EXTENSION_MESSAGE_TYPE];

export type ExtensionMessage =
| {
type: typeof EXTENSION_MESSAGE_TYPE.setToken;
token: string;
}
| {
type: typeof EXTENSION_MESSAGE_TYPE.logout;
};
Loading
Loading