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
29 changes: 15 additions & 14 deletions src/api/refresh.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,34 @@
import type { refreshDto } from "../types/user";
import { refreshAxiosInstance } from "./axios";
import type { refreshDto } from '../types/user';
import { refreshAxiosInstance } from './axios';

let timeout: number | null = null;

function getExp(accessToken: string) {
export function getExp(accessToken: string) {
try {
const [, p] = accessToken.split(".");
const [, p] = accessToken.split('.');
if (!p) return null;
const b64 = p.replace(/-/g, "+").replace(/_/g, "/");
const padded = b64.padEnd(b64.length + ((4 - (b64.length % 4)) % 4), "=");
const b64 = p.replace(/-/g, '+').replace(/_/g, '/');
const padded = b64.padEnd(b64.length + ((4 - (b64.length % 4)) % 4), '=');
const { exp } = JSON.parse(atob(padded));
return typeof exp === "number" ? exp : null;
return typeof exp === 'number' ? exp : null;
} catch {
return null;
}
}

const patchSignUp = async (): Promise<refreshDto> => {
const { data } = await refreshAxiosInstance.post<refreshDto>("/auth/reissue");
localStorage.setItem("accessToken", data.result.accessToken);
export const patchSignUp = async (): Promise<refreshDto> => {
const { data } = await refreshAxiosInstance.post<refreshDto>('/auth/reissue');
localStorage.setItem('accessToken', data.result.accessToken);
return data;
};

export async function refreshTimer(bufferSec = 60): Promise<void> {

if (timeout) {
window.clearTimeout(timeout);
timeout = null;
}

const accessToken = localStorage.getItem("accessToken");
const accessToken = localStorage.getItem('accessToken');
if (!accessToken) return;

const exp = getExp(accessToken);
Expand All @@ -42,10 +41,12 @@ export async function refreshTimer(bufferSec = 60): Promise<void> {
try {
await patchSignUp();
} catch (error) {
console.error("재발급 실패", error);
console.error('재발급 실패', error);
} finally {
// 1초 후 새 토큰 기준으로 다시 '한 번'만 예약
timeout = window.setTimeout(() => { void refreshTimer(bufferSec); }, 1000);
timeout = window.setTimeout(() => {
void refreshTimer(bufferSec);
}, 1000);
}
};

Expand Down
40 changes: 39 additions & 1 deletion src/hooks/chat/useWebSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Client, type IFrame, type IMessage } from '@stomp/stompjs';
import { useEffect, useRef, useState } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import type { ChatMessage } from '../../types/chat';
import { getExp, patchSignUp } from '../../api/refresh';

export const webSocketStatus = {
CONNECTING: 'CONNECTING',
Expand Down Expand Up @@ -30,12 +31,49 @@ export const useWebSocket = (roomId: number) => {

setStatus('CONNECTING');

let token = localStorage.getItem('accessToken');

const refreshAccessToken = async () => {
try {
const { result } = await patchSignUp(); // 토큰 갱신 API 호출
if (!result?.accessToken) {
throw new Error('No access token returned');
}
return result.accessToken;
} catch (err) {
console.error('Token refresh failed:', err);
return null;
}
};

if (token) {
const exp = getExp(token);
// 토큰이 만료되었거나 1분 이내 만료 시 재발급
if (!exp || exp * 1000 - 60000 < Date.now()) {
console.log('Access token is expired or expiring soon, refreshing for WebSocket...');
const newAccessToken = refreshAccessToken();
if (newAccessToken) {
localStorage.setItem('accessToken', String(newAccessToken)); // 갱신된 토큰 저장
token = localStorage.getItem('accessToken');
} else {
console.error('Failed to refresh token for WebSocket. Connection aborted.');
setStatus('CLOSED');
return; // 토큰 갱신 실패 시 연결 중단
}
}
}

if (!token) {
console.error('No valid access token for WebSocket connection.');
return;
}

// stomp 클라이언트 객체 생성
const client = new Client({
webSocketFactory: () => new WebSocket(import.meta.env.VITE_WS_URL),
// 헤더에 토큰 전달
connectHeaders: {
Authorization: `Bearer ${localStorage.getItem('accessToken')}`,
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
reconnectDelay: 5000, // 자동 재연결 시도 간격
Expand Down