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
8 changes: 8 additions & 0 deletions src/apis/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,14 @@ async function handleUnauthorized<T = unknown, P extends object = Record<string,
const newAccessToken = await refreshPromise;
useAuthStore.getState().setAccessToken(newAccessToken);

try {
if (window.ReactNativeWebView) {
window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'TOKEN_REFRESH', accessToken: newAccessToken }));
}
} catch {
// 브릿지 전달 실패가 인증 흐름을 중단시키지 않도록 무시
}

return await sendRequestWithoutRetry<T, P>(endPoint, options, timeout);
} catch {
useAuthStore.getState().clearAuth();
Expand Down
7 changes: 7 additions & 0 deletions src/apis/notification/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { apiClient } from '../client';

export const registerPushToken = async (token: string) => {
if (window.ReactNativeWebView) {
if (import.meta.env.DEV) {
console.log('RN WebView 환경: 웹에서 푸시 토큰 등록 생략 (네이티브가 처리)');
}
return;
}

const response = await apiClient.post('notifications/tokens', {
body: { token },
requiresAuth: true,
Expand Down
5 changes: 5 additions & 0 deletions src/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
interface Window {
ReactNativeWebView?: {
postMessage: (message: string) => void;
};
}
7 changes: 7 additions & 0 deletions src/pages/User/MyPage/hooks/useLogout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ export const useLogoutMutation = () => {
return useMutation({
mutationFn: logout,
onSuccess: () => {
try {
if (window.ReactNativeWebView) {
window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'LOGOUT' }));
}
} catch {
// 브릿지 전달 실패가 로그아웃 흐름을 중단시키지 않도록 무시
}
clearAuth();
navigate('/');
},
Expand Down
58 changes: 9 additions & 49 deletions src/stores/authStore.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,6 @@
import { create } from 'zustand';
import { getMyInfo, refreshAccessToken } from '@/apis/auth';
import type { MyInfoResponse } from '@/apis/auth/entity';
import { registerPushToken } from '@/apis/notification';

const PUSH_TOKEN_STORAGE_KEY = 'REGISTERED_PUSH_TOKEN';
const PENDING_PUSH_TOKEN_KEY = 'PENDING_PUSH_TOKEN';

async function registerPushTokenIfNeeded() {
const token = localStorage.getItem(PENDING_PUSH_TOKEN_KEY);
if (!token) return;

const lastRegisteredToken = localStorage.getItem(PUSH_TOKEN_STORAGE_KEY);
if (lastRegisteredToken === token) {
localStorage.removeItem(PENDING_PUSH_TOKEN_KEY);
return;
}

try {
await registerPushToken(token);
localStorage.setItem(PUSH_TOKEN_STORAGE_KEY, token);
localStorage.removeItem(PENDING_PUSH_TOKEN_KEY);
} catch (error) {
console.error('푸시 토큰 등록 실패:', error);
}
}

interface AuthState {
user: MyInfoResponse | null;
Expand All @@ -50,14 +27,20 @@ export const useAuthStore = create<AuthState>((set, get) => ({
}

try {
// 1. 토큰 갱신 (필수 - 이후 API 호출에 필요)
const accessToken = await refreshAccessToken();
set({ accessToken });

// 2. 사용자 정보와 푸시 토큰 등록을 병렬로 실행
const [user] = await Promise.all([getMyInfo(), registerPushTokenIfNeeded()]);
const user = await getMyInfo();

set({ user, isAuthenticated: true, isLoading: false });

try {
if (window.ReactNativeWebView) {
window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'LOGIN_COMPLETE', accessToken }));
}
} catch {
// 브릿지 전달 실패가 인증 성공 상태를 롤백시키지 않도록 무시
}
} catch {
set({ user: null, accessToken: null, isAuthenticated: false, isLoading: false });
}
Expand All @@ -71,26 +54,3 @@ export const useAuthStore = create<AuthState>((set, get) => ({

clearAuth: () => set({ user: null, accessToken: null, isAuthenticated: false, isLoading: false }),
}));

window.addEventListener('message', (event: MessageEvent) => {
try {
const data = JSON.parse(event.data);
if (data.type !== 'PUSH_TOKEN' || !data.token) return;

const lastToken = localStorage.getItem(PUSH_TOKEN_STORAGE_KEY);
if (lastToken === data.token) return;

const { accessToken } = useAuthStore.getState();
if (!accessToken) {
// initialize() 완료 전 도착한 경우 — pending으로 저장 후 initialize()에서 처리
localStorage.setItem(PENDING_PUSH_TOKEN_KEY, data.token);
return;
}

registerPushToken(data.token)
.then(() => localStorage.setItem(PUSH_TOKEN_STORAGE_KEY, data.token))
.catch((error) => console.error('푸시 토큰 등록 실패:', error));
} catch {
// JSON 파싱 실패 등 무시
}
});