불필요 파일 제거

This commit is contained in:
focp212@naver.com
2025-10-15 10:35:01 +09:00
parent 66524b36bc
commit 8446ad91e3
21 changed files with 3 additions and 1768 deletions

View File

@@ -1,260 +0,0 @@
import axios, { AxiosInstance, AxiosResponse, AxiosError, AxiosRequestConfig } from 'axios';
import { ApiResponse, ApiErrorResponse, FileUploadProgress, FileUploadResponse } from '@/types';
import { tokenManager } from './tokenManager';
import config from '@/config';
import { useAppBridge } from '@/hooks';
class ApiClient {
private instance: AxiosInstance;
private isRefreshing = false;
private failedQueue: Array<{
resolve: (value?: unknown) => void;
reject: (error?: unknown) => void;
}> = [];
private isNativeEnvironment: any;
private requestRefreshToken: any;
constructor() {
this.instance = axios.create({
baseURL: config.api.baseURL,
timeout: config.api.timeout,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
});
this.setupInterceptors();
}
private setupInterceptors(): void {
// 요청 인터셉터
this.instance.interceptors.request.use(
(config) => {
const token = tokenManager.getAccessToken();
console.log('setupInterceptors request ==> ', token)
if(token){
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// 응답 인터셉터
this.instance.interceptors.response.use(
(response: AxiosResponse) => response,
async (error: AxiosError) => {
console.log(' this.instance.interceptors.response' );
const originalRequest = error.config as AxiosRequestConfig & { _retry?: boolean };
console.log('originalRequest ==> ', JSON.stringify(originalRequest));
if (error.response?.status === 401 && !originalRequest._retry) {
if (this.isRefreshing) {
return new Promise((resolve, reject) => {
this.failedQueue.push({ resolve, reject });
}).then(token => {
originalRequest.headers!.Authorization = `Bearer ${token}`;
return this.instance(originalRequest);
}).catch(err => {
return Promise.reject(err);
});
}
originalRequest._retry = true;
this.isRefreshing = true;
console.log('refreshToken!!');
/*
this.requestRefreshToken1().then((token) => {
console.log('requestRefreshToken +[' + JSON.stringify(token) + ']' );
});
try {
const newToken = await this.refreshAccessToken();
this.processQueue(null, newToken);
originalRequest.headers!.Authorization = `Bearer ${newToken}`;
return this.instance(originalRequest);
} catch (refreshError) {
this.processQueue(refreshError, null);
tokenManager.clearTokens();
// 로그인 페이지로 리디렉션 또는 로그아웃 처리
window.location.href = '/login';
return Promise.reject(refreshError);
} finally {
this.isRefreshing = false;
}
*/
}
return Promise.reject(this.handleError(error));
}
);
}
private processQueue(error: unknown, token: string | null): void {
this.failedQueue.forEach(({ resolve, reject }) => {
if (error) {
reject(error);
} else {
resolve(token);
}
});
this.failedQueue = [];
}
/*
private async requestRefreshAccessToken(): Promise<any> {
const {
isNativeEnvironment,
requestRefreshToken
} = useAppBridge();
}
*/
private async refreshAccessToken(): Promise<string> {
const refreshToken = tokenManager.getRefreshToken();
if (!refreshToken) {
throw new Error('No refresh token available');
}
try {
const response = await axios.post(`${config.api.baseURL}/auth/refresh`, {
refresh_token: refreshToken
});
const { access_token, refresh_token: newRefreshToken } = response.data.data;
tokenManager.setTokens(access_token, newRefreshToken);
return access_token;
} catch {
throw new Error('Token refresh failed');
}
}
private handleError(error: AxiosError): ApiErrorResponse {
const errorResponse: ApiErrorResponse = {
success: false,
error: {
code: 'UNKNOWN_ERROR',
message: '알 수 없는 오류가 발생했습니다.',
},
timestamp: new Date().toISOString(),
};
if (error.response) {
// 서버 응답이 있는 경우
const { status, data } = error.response;
errorResponse.error.code = `HTTP_${status}`;
if (data && typeof data === 'object' && 'message' in data) {
errorResponse.error.message = (data as { message: string }).message;
} else {
errorResponse.error.message = this.getErrorMessage(status);
}
errorResponse.error.details = data;
} else if (error.request) {
// 네트워크 오류
errorResponse.error.code = 'NETWORK_ERROR';
errorResponse.error.message = '네트워크 연결을 확인해주세요.';
} else {
// 요청 설정 오류
errorResponse.error.code = 'REQUEST_ERROR';
errorResponse.error.message = '요청 처리 중 오류가 발생했습니다.';
}
return errorResponse;
}
private getErrorMessage(status: number): string {
const messages: Record<number, string> = {
400: '잘못된 요청입니다.',
401: '인증이 필요합니다.',
403: '접근 권한이 없습니다.',
404: '요청한 리소스를 찾을 수 없습니다.',
409: '데이터 충돌이 발생했습니다.',
422: '입력 데이터를 확인해주세요.',
500: '서버 오류가 발생했습니다.',
502: '서버 연결에 문제가 있습니다.',
503: '서비스를 일시적으로 사용할 수 없습니다.',
};
return messages[status] || '알 수 없는 오류가 발생했습니다.';
}
async get<T>(url: string, config?: AxiosRequestConfig): Promise<ApiResponse<T>> {
const response = await this.instance.get(url, config);
return response.data;
}
async post<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<ApiResponse<T>> {
const response = await this.instance.post(url, data, config);
return response.data;
}
async put<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<ApiResponse<T>> {
const response = await this.instance.put(url, data, config);
return response.data;
}
async patch<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<ApiResponse<T>> {
const response = await this.instance.patch(url, data, config);
return response.data;
}
async delete<T>(url: string, config?: AxiosRequestConfig): Promise<ApiResponse<T>> {
const response = await this.instance.delete(url, config);
return response.data;
}
async uploadFile(
url: string,
file: File,
onProgress?: (progress: FileUploadProgress) => void
): Promise<ApiResponse<FileUploadResponse>> {
const formData = new FormData();
formData.append('file', file);
const config: AxiosRequestConfig = {
headers: {
'Content-Type': 'multipart/form-data',
},
onUploadProgress: (progressEvent) => {
if (onProgress && progressEvent.total) {
const progress: FileUploadProgress = {
loaded: progressEvent.loaded,
total: progressEvent.total,
percentage: Math.round((progressEvent.loaded * 100) / progressEvent.total),
};
onProgress(progress);
}
},
};
const response = await this.instance.post(url, formData, config);
return response.data;
}
async downloadFile(url: string, filename: string): Promise<void> {
const response = await this.instance.get(url, {
responseType: 'blob',
});
const blob = new Blob([response.data]);
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(downloadUrl);
}
}
export const apiClient = new ApiClient();
export default apiClient;

View File

@@ -5,7 +5,6 @@ import {
DeviceInfo,
ShareContent
} from '@/types';
import { LoginCredentials, UserInfo } from '@/types/auth';
class AppBridge {
private static instance: AppBridge;
@@ -114,10 +113,6 @@ class AppBridge {
return this.sendMessage(BridgeMessageType.NAVIGATE_TO, { path });
}
async navigateToLogin(): Promise<void> {
return this.sendMessage(BridgeMessageType.NAVIGATE_TO_LOGIN);
}
async closeWebView(): Promise<void> {
return this.sendMessage(BridgeMessageType.CLOSE_WEBVIEW);
}
@@ -165,12 +160,7 @@ class AppBridge {
async shareContent(content: ShareContent): Promise<void> {
return this.sendMessage(BridgeMessageType.SHARE_CONTENT, content);
}
// 로그인 요청
async login(credentials?: LoginCredentials): Promise<UserInfo> {
return this.sendMessage(BridgeMessageType.LOGIN, credentials);
}
// 로그아웃 요청
async logout(): Promise<void> {
return this.sendMessage(BridgeMessageType.LOGOUT);

View File

@@ -1,188 +0,0 @@
import { LoginCredentials, RegisterData, UserInfo, UserRole } from '@/types';
import { apiClient } from './api';
import { tokenManager } from './tokenManager';
export class AuthService {
private static instance: AuthService;
private constructor() {}
static getInstance(): AuthService {
if (!AuthService.instance) {
AuthService.instance = new AuthService();
}
return AuthService.instance;
}
async login(credentials: LoginCredentials): Promise<{
user: UserInfo;
accessToken: string;
refreshToken: string;
}> {
const response = await apiClient.post<{
user: UserInfo;
access_token: string;
refresh_token: string;
}>('/auth/login', credentials);
const { user, access_token, refresh_token } = response.data;
tokenManager.setTokens(access_token, refresh_token);
return {
user,
accessToken: access_token,
refreshToken: refresh_token,
};
}
async register(data: RegisterData): Promise<{
user: UserInfo;
accessToken: string;
refreshToken: string;
}> {
const response = await apiClient.post<{
user: UserInfo;
access_token: string;
refresh_token: string;
}>('/auth/register', data);
const { user, access_token, refresh_token } = response.data;
tokenManager.setTokens(access_token, refresh_token);
return {
user,
accessToken: access_token,
refreshToken: refresh_token,
};
}
async logout(): Promise<void> {
try {
await apiClient.post('/auth/logout');
} catch (error) {
// 로그아웃 API 호출 실패해도 로컬 토큰은 삭제
console.error('Logout API call failed:', error);
} finally {
tokenManager.clearTokens();
}
}
async refreshToken(): Promise<string> {
const refreshToken = tokenManager.getRefreshToken();
if (!refreshToken) {
throw new Error('No refresh token available');
}
const response = await apiClient.post<{
access_token: string;
refresh_token: string;
}>('/auth/refresh', {
refresh_token: refreshToken,
});
const { access_token, refresh_token: newRefreshToken } = response.data;
tokenManager.setTokens(access_token, newRefreshToken);
return access_token;
}
async getCurrentUser(): Promise<UserInfo> {
const response = await apiClient.get<UserInfo>('/auth/me');
return response.data;
}
async updateProfile(data: Partial<UserInfo>): Promise<UserInfo> {
const response = await apiClient.put<UserInfo>('/auth/profile', data);
return response.data;
}
async changePassword(currentPassword: string, newPassword: string): Promise<void> {
await apiClient.post('/auth/change-password', {
current_password: currentPassword,
new_password: newPassword,
});
}
async forgotPassword(email: string): Promise<void> {
await apiClient.post('/auth/forgot-password', { email });
}
async resetPassword(token: string, password: string): Promise<void> {
await apiClient.post('/auth/reset-password', {
token,
password,
});
}
isAuthenticated(): boolean {
return tokenManager.hasValidTokens();
}
getCurrentUserFromToken(): UserInfo | null {
return tokenManager.getUserInfoFromToken();
}
hasRole(role: UserRole): boolean {
const user = this.getCurrentUserFromToken();
return user?.role === role;
}
hasPermission(permission: string): boolean {
const user = this.getCurrentUserFromToken();
if (!user) return false;
// 권한 체크 로직 구현
const permissions = this.getRolePermissions(user.role);
return permissions.includes(permission);
}
private getRolePermissions(role: UserRole): string[] {
const permissionMap: Record<UserRole, string[]> = {
[UserRole.ADMIN]: [
'user:read',
'user:write',
'user:delete',
'payment:read',
'payment:write',
'payment:refund',
'system:config',
],
[UserRole.MERCHANT]: [
'payment:read',
'payment:write',
'payment:refund',
'profile:read',
'profile:write',
],
[UserRole.USER]: [
'payment:read',
'profile:read',
'profile:write',
],
};
return permissionMap[role] || [];
}
async verifyEmail(token: string): Promise<void> {
await apiClient.post('/auth/verify-email', { token });
}
async resendVerificationEmail(): Promise<void> {
await apiClient.post('/auth/resend-verification');
}
shouldRefreshToken(): boolean {
return tokenManager.shouldRefreshToken();
}
getAccessToken(): string | null {
return tokenManager.getAccessToken();
}
getRefreshToken(): string | null {
return tokenManager.getRefreshToken();
}
}
export const authService = AuthService.getInstance();

View File

@@ -1,4 +1 @@
export { apiClient } from './api';
export { authService } from './auth';
export { tokenManager } from './tokenManager';
export { appBridge } from './appBridge';

View File

@@ -1,121 +0,0 @@
import { JWTPayload } from '@/types';
/**
* Browser-compatible JWT decoder
* Only supports decoding (reading) JWT tokens, not signing/verifying
*/
export class JWTDecoder {
/**
* Decode a JWT token to extract the payload
* @param token - The JWT token string
* @returns The decoded payload or null if invalid
*/
static decode(token: string): JWTPayload | null {
try {
if (!token || typeof token !== 'string') {
return null;
}
// JWT tokens have 3 parts separated by dots: header.payload.signature
const parts = token.split('.');
if (parts.length !== 3) {
return null;
}
// Get the payload (second part)
const payload = parts[1];
if (!payload) {
return null;
}
// Decode base64url
const decoded = this.base64UrlDecode(payload);
// Parse JSON
return JSON.parse(decoded) as JWTPayload;
} catch (error) {
console.error('JWT decode error:', error);
return null;
}
}
/**
* Check if a JWT token is expired
* @param token - The JWT token string
* @returns true if expired, false if valid
*/
static isExpired(token: string): boolean {
try {
const payload = this.decode(token);
if (!payload || !payload.exp) {
return true;
}
const currentTime = Date.now() / 1000;
return payload.exp < currentTime;
} catch {
return true;
}
}
/**
* Get the expiration timestamp from a JWT token
* @param token - The JWT token string
* @returns The expiration timestamp or null if not found
*/
static getExpiration(token: string): number | null {
try {
const payload = this.decode(token);
return payload?.exp || null;
} catch {
return null;
}
}
/**
* Get time until token expiration in seconds
* @param token - The JWT token string
* @returns Seconds until expiration, 0 if expired or invalid
*/
static getTimeUntilExpiration(token: string): number {
try {
const expirationTime = this.getExpiration(token);
if (!expirationTime) return 0;
const currentTime = Date.now() / 1000;
const timeUntilExpiration = expirationTime - currentTime;
return Math.max(0, timeUntilExpiration);
} catch {
return 0;
}
}
/**
* Decode base64url string to regular string
* @param str - base64url encoded string
* @returns decoded string
*/
private static base64UrlDecode(str: string): string {
// Convert base64url to base64
let base64 = str.replace(/-/g, '+').replace(/_/g, '/');
// Add padding if needed
while (base64.length % 4) {
base64 += '=';
}
// Decode base64
const decoded = atob(base64);
// Convert to UTF-8
return decodeURIComponent(
decoded
.split('')
.map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
.join('')
);
}
}
// Export a default instance for convenience
export const jwtDecoder = JWTDecoder;

View File

@@ -1,17 +0,0 @@
// safeArea.ts
export function setSafeAreaInsets(top: number, right: number, bottom: number, left: number) {
const root = document.documentElement;
root.style.setProperty("--safe-area-inset-top", `${top}px`);
root.style.setProperty("--safe-area-inset-right", `${right}px`);
root.style.setProperty("--safe-area-inset-bottom", `${bottom}px`);
root.style.setProperty("--safe-area-inset-left", `${left}px`);
}
// 전역에서 호출할 수 있게 window에 바인딩 (Android WebView 네이티브에서 실행할 수 있도록)
declare global {
interface Window {
setSafeAreaInsets: typeof setSafeAreaInsets;
}
}
window.setSafeAreaInsets = setSafeAreaInsets;

View File

@@ -1,189 +0,0 @@
import { JWTPayload, UserInfo } from '@/types';
import { appBridge } from './appBridge';
import { jwtDecoder } from './jwtDecoder';
const ACCESS_TOKEN_KEY = 'nice_access_token';
const REFRESH_TOKEN_KEY = 'nice_refresh_token';
export class TokenManager {
private static instance: TokenManager;
private constructor() {}
static getInstance(): TokenManager {
if (!TokenManager.instance) {
TokenManager.instance = new TokenManager();
}
return TokenManager.instance;
}
setTokens(accessToken: string, refreshToken: string): void {
localStorage.setItem(ACCESS_TOKEN_KEY, accessToken);
localStorage.setItem(REFRESH_TOKEN_KEY, refreshToken);
this.saveTokensToAppBridge(accessToken, refreshToken);
}
getAccessToken(): string | null {
console.log('getAccessToken')
return localStorage.getItem(ACCESS_TOKEN_KEY);
}
getRefreshToken(): string | null {
return localStorage.getItem(REFRESH_TOKEN_KEY);
}
clearTokens(): void {
localStorage.removeItem(ACCESS_TOKEN_KEY);
localStorage.removeItem(REFRESH_TOKEN_KEY);
}
isTokenExpired(token: string): boolean {
return jwtDecoder.isExpired(token);
}
isAccessTokenExpired(): boolean {
const token = this.getAccessToken();
if (!token) return true;
return this.isTokenExpired(token);
}
isRefreshTokenExpired(): boolean {
const token = this.getRefreshToken();
if (!token) return true;
return this.isTokenExpired(token);
}
decodeToken(token: string): JWTPayload | null {
return jwtDecoder.decode(token);
}
getUserInfoFromToken(token?: string): UserInfo | null {
const accessToken = token || this.getAccessToken();
if (!accessToken) return null;
const decoded = this.decodeToken(accessToken);
if (!decoded) return null;
return {
id: decoded.userId,
email: decoded.email,
name: '', // 토큰에서 이름을 가져오지 않으므로 빈 문자열
role: decoded.role,
createdAt: '',
updatedAt: ''
};
}
getTokenExpirationTime(token: string): number | null {
return jwtDecoder.getExpiration(token);
}
getTimeUntilExpiration(token: string): number {
return jwtDecoder.getTimeUntilExpiration(token);
}
shouldRefreshToken(): boolean {
const accessToken = this.getAccessToken();
if (!accessToken) return false;
const timeUntilExpiration = this.getTimeUntilExpiration(accessToken);
// 토큰이 5분 이내에 만료되면 갱신
const REFRESH_BUFFER_SECONDS = 5 * 60; // 5 minutes
return timeUntilExpiration < REFRESH_BUFFER_SECONDS;
}
hasValidTokens(): boolean {
const accessToken = this.getAccessToken();
const refreshToken = this.getRefreshToken();
if (!accessToken || !refreshToken) return false;
// 리프레시 토큰이 유효하면 액세스 토큰은 갱신 가능
return !this.isRefreshTokenExpired();
}
async refreshTokens(): Promise<{accessToken: string, refreshToken: string}> {
const refreshToken = this.getRefreshToken();
if (!refreshToken) {
throw new Error('No refresh token available');
}
const response = await fetch('http://3.35.79.250:8090/auth/v1/refresh', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ refreshToken }),
});
if (!response.ok) {
throw new Error(`Token refresh failed: ${response.status}`);
}
const data = await response.json();
const newAccessToken = data.accessToken;
const newRefreshToken = data.refreshToken;
this.setTokens(newAccessToken, newRefreshToken);
return { accessToken: newAccessToken, refreshToken: newRefreshToken };
}
async makeAuthenticatedRequest(url: string, options: RequestInit = {}): Promise<Response> {
let accessToken = this.getAccessToken();
if (!accessToken || this.isAccessTokenExpired()) {
try {
const refreshed = await this.refreshTokens();
accessToken = refreshed.accessToken;
} catch (error) {
this.clearTokens();
throw new Error('Authentication failed - please log in again');
}
}
const requestOptions = {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${accessToken}`,
},
};
const response = await fetch(url, requestOptions);
if (response.status === 401) {
try {
const refreshed = await this.refreshTokens();
const retryOptions = {
...requestOptions,
headers: {
...requestOptions.headers,
'Authorization': `Bearer ${refreshed.accessToken}`,
},
};
return await fetch(url, retryOptions);
} catch (error) {
this.clearTokens();
throw new Error('Authentication failed - please log in again');
}
}
return response;
}
private async saveTokensToAppBridge(accessToken: string, refreshToken: string): Promise<void> {
try {
if (appBridge.isNativeEnvironment()) {
await Promise.all([
appBridge.setStorage(ACCESS_TOKEN_KEY, accessToken),
appBridge.setStorage(REFRESH_TOKEN_KEY, refreshToken)
]);
}
} catch (error) {
console.error('Failed to save tokens to AppBridge:', error);
}
}
}
export const tokenManager = TokenManager.getInstance();