import { AppBridgeMessage, AppBridgeResponse, BridgeMessageType, DeviceInfo, ShareContent } from '@/types'; import { LoginCredentials, UserInfo } from '@/types/auth'; class AppBridge { private static instance: AppBridge; private messageId = 0; private pendingCallbacks = new Map void>(); private responseListeners: Set<(response: AppBridgeResponse) => void> = new Set(); private constructor() { this.setupMessageListener(); } // 외부에서 네이티브 응답을 구독할 수 있도록 리스너 등록/해제 메서드 추가 public addResponseListener(listener: (response: AppBridgeResponse) => void) { this.responseListeners.add(listener); } public removeResponseListener(listener: (response: AppBridgeResponse) => void) { this.responseListeners.delete(listener); } static getInstance(): AppBridge { if (!AppBridge.instance) { AppBridge.instance = new AppBridge(); } return AppBridge.instance; } private setupMessageListener(): void { window.addEventListener('message', (event) => { try { const response: AppBridgeResponse & { callbackId?: string } = typeof event.data === 'string' ? JSON.parse(event.data) : event.data; console.log('response', response); if (response.callbackId && this.pendingCallbacks.has(response.callbackId)) { const callback = this.pendingCallbacks.get(response.callbackId); if (callback) { callback(response); this.pendingCallbacks.delete(response.callbackId); } } // 등록된 리스너들에게 모든 응답 전달 this.responseListeners.forEach(listener => listener(response)); } catch (error) { console.error('Failed to parse bridge message:', error); } }); } private generateMessageId(): string { return `bridge_${++this.messageId}_${Date.now()}`; } private sendMessage(type: BridgeMessageType, data?: unknown): Promise { return new Promise((resolve, reject) => { const callbackId = this.generateMessageId(); const message: AppBridgeMessage & { callbackId: string } = { type, data, callbackId }; this.pendingCallbacks.set(callbackId, (response: AppBridgeResponse) => { if (response.success) { resolve(response.data); } else { reject(new Error(response.error || 'Bridge call failed')); } }); // Android WebView 인터페이스 if (window.AndroidBridge && window.AndroidBridge.postMessage) { console.log('Android postMessage', message); window.AndroidBridge.postMessage(JSON.stringify(message)); return; } // iOS WKWebView 인터페이스 if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.bridge) { console.log('iOS postMessage', message); window.webkit.messageHandlers.bridge.postMessage(message); return; } // 네이티브 환경이 아닌 경우 에러 발생 setTimeout(() => { this.pendingCallbacks.delete(callbackId); reject(new Error('Native bridge not available')); }, 100); }); } // 앱 정보 관련 async getAppInfo(): Promise<{ version: string; buildNumber: string }> { return this.sendMessage(BridgeMessageType.GET_APP_INFO); } async getDeviceInfo(): Promise { return this.sendMessage(BridgeMessageType.GET_DEVICE_INFO); } // 네비게이션 관련 async navigateBack(): Promise { return this.sendMessage(BridgeMessageType.NAVIGATE_BACK); } async navigateTo(path: string): Promise { return this.sendMessage(BridgeMessageType.NAVIGATE_TO, { path }); } async navigateToLogin(): Promise { return this.sendMessage(BridgeMessageType.NAVIGATE_TO_LOGIN); } async closeWebView(): Promise { return this.sendMessage(BridgeMessageType.CLOSE_WEBVIEW); } // 알림 관련 async showToast(message: string, duration: number = 3000): Promise { return this.sendMessage(BridgeMessageType.SHOW_TOAST, { message, duration }); } async showAlert(title: string, message: string): Promise { return this.sendMessage(BridgeMessageType.SHOW_ALERT, { title, message }); } async showConfirm(title: string, message: string): Promise { return this.sendMessage(BridgeMessageType.SHOW_CONFIRM, { title, message }); } // 저장소 관련 async setStorage(key: string, value: unknown): Promise { return this.sendMessage(BridgeMessageType.SET_STORAGE, { key, value: JSON.stringify(value) }); } async getStorage(key: string): Promise { try { const result = await this.sendMessage(BridgeMessageType.GET_STORAGE, { key }); return result ? JSON.parse(result) : null; } catch { return null; } } async removeStorage(key: string): Promise { return this.sendMessage(BridgeMessageType.REMOVE_STORAGE, { key }); } async requestToken(): Promise { return this.sendMessage(BridgeMessageType.REQUEST_TOKEN); } // 공유 관련 async shareContent(content: ShareContent): Promise { return this.sendMessage(BridgeMessageType.SHARE_CONTENT, content); } // 로그인 요청 async login(credentials?: LoginCredentials): Promise { return this.sendMessage(BridgeMessageType.LOGIN, credentials); } // 로그아웃 요청 async logout(): Promise { return this.sendMessage(BridgeMessageType.LOGOUT); } // 간편 인증 등록 요청 async registerBiometric(): Promise { return this.sendMessage(BridgeMessageType.REGISTER_BIOMETRIC); } // 간편 인증 등록 팝업 열기 async openBiometricRegistrationPopup(): Promise { return this.sendMessage(BridgeMessageType.OPEN_BIOMETRIC_REGISTRATION_POPUP); } // 간편 인증 등록 팝업 닫기 async closeBiometricRegistrationPopup(): Promise { return this.sendMessage(BridgeMessageType.CLOSE_BIOMETRIC_REGISTRATION_POPUP); } // 언어 설정 async setLanguage(language: string): Promise { return this.sendMessage(BridgeMessageType.SET_LANGUAGE, { language }); } async getLanguage(): Promise { return this.sendMessage(BridgeMessageType.GET_LANGUAGE); } // 메시지 카운트 업데이트 async updateMessageCount(count: number): Promise { return this.sendMessage(BridgeMessageType.UPDATE_MESSAGE_COUNT, { count }); } // 네이티브 환경 체크 isNativeEnvironment(): boolean { return !!( (window.AndroidBridge && window.AndroidBridge.postMessage) || (window.Android && window.Android.processMessage) || (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.bridge) ); } isAndroid(): boolean { return !!( (window.AndroidBridge && window.AndroidBridge.postMessage) || (window.Android && window.Android.processMessage) ); } isIOS(): boolean { return !!(window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.bridge); } // 에러 핸들링을 위한 래퍼 메소드들 async safeCall( bridgeMethod: () => Promise, fallback?: T, onError?: (error: Error) => void ): Promise { try { return await bridgeMethod(); } catch (error) { console.error('Bridge call failed:', error); if (onError) { onError(error as Error); } return fallback; } } // 타임아웃을 가진 브리지 호출 async callWithTimeout( bridgeMethod: () => Promise, timeout: number = 5000 ): Promise { return Promise.race([ bridgeMethod(), new Promise((_, reject) => setTimeout(() => reject(new Error('Bridge call timeout')), timeout) ) ]); } } // 글로벌 타입 선언 확장 declare global { interface Window { AndroidBridge?: { postMessage: (message: string) => void; }; Android?: { processMessage: (message: string) => void; }; webkit?: { messageHandlers?: { bridge?: { postMessage: (message: unknown) => void; }; }; }; } } export const appBridge = AppBridge.getInstance(); export default appBridge;