275 lines
8.2 KiB
TypeScript
275 lines
8.2 KiB
TypeScript
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<string, (response: AppBridgeResponse) => 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<T>(type: BridgeMessageType, data?: unknown): Promise<T> {
|
|
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<DeviceInfo> {
|
|
return this.sendMessage(BridgeMessageType.GET_DEVICE_INFO);
|
|
}
|
|
|
|
// 네비게이션 관련
|
|
async navigateBack(): Promise<void> {
|
|
return this.sendMessage(BridgeMessageType.NAVIGATE_BACK);
|
|
}
|
|
|
|
async navigateTo(path: string): Promise<void> {
|
|
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);
|
|
}
|
|
|
|
// 알림 관련
|
|
async showToast(message: string, duration: number = 3000): Promise<void> {
|
|
return this.sendMessage(BridgeMessageType.SHOW_TOAST, { message, duration });
|
|
}
|
|
|
|
async showAlert(title: string, message: string): Promise<void> {
|
|
return this.sendMessage(BridgeMessageType.SHOW_ALERT, { title, message });
|
|
}
|
|
|
|
async showConfirm(title: string, message: string): Promise<boolean> {
|
|
return this.sendMessage(BridgeMessageType.SHOW_CONFIRM, { title, message });
|
|
}
|
|
|
|
// 저장소 관련
|
|
async setStorage(key: string, value: unknown): Promise<void> {
|
|
return this.sendMessage(BridgeMessageType.SET_STORAGE, { key, value: JSON.stringify(value) });
|
|
}
|
|
|
|
async getStorage<T>(key: string): Promise<T | null> {
|
|
try {
|
|
const result = await this.sendMessage<string>(BridgeMessageType.GET_STORAGE, { key });
|
|
return result ? JSON.parse(result) : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async removeStorage(key: string): Promise<void> {
|
|
return this.sendMessage(BridgeMessageType.REMOVE_STORAGE, { key });
|
|
}
|
|
|
|
async requestToken(): Promise<any> {
|
|
return this.sendMessage(BridgeMessageType.REQUEST_TOKEN);
|
|
}
|
|
|
|
// 공유 관련
|
|
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);
|
|
}
|
|
|
|
// 간편 인증 등록 요청
|
|
async registerBiometric(): Promise<void> {
|
|
return this.sendMessage(BridgeMessageType.REGISTER_BIOMETRIC);
|
|
}
|
|
|
|
// 간편 인증 등록 팝업 열기
|
|
async openBiometricRegistrationPopup(): Promise<boolean> {
|
|
return this.sendMessage(BridgeMessageType.OPEN_BIOMETRIC_REGISTRATION_POPUP);
|
|
}
|
|
|
|
// 간편 인증 등록 팝업 닫기
|
|
async closeBiometricRegistrationPopup(): Promise<void> {
|
|
return this.sendMessage(BridgeMessageType.CLOSE_BIOMETRIC_REGISTRATION_POPUP);
|
|
}
|
|
|
|
// 언어 설정
|
|
async setLanguage(language: string): Promise<void> {
|
|
return this.sendMessage(BridgeMessageType.SET_LANGUAGE, { language });
|
|
}
|
|
|
|
async getLanguage(): Promise<string> {
|
|
return this.sendMessage(BridgeMessageType.GET_LANGUAGE);
|
|
}
|
|
|
|
// 메시지 카운트 업데이트
|
|
async updateMessageCount(count: number): Promise<void> {
|
|
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<T>(
|
|
bridgeMethod: () => Promise<T>,
|
|
fallback?: T,
|
|
onError?: (error: Error) => void
|
|
): Promise<T | undefined> {
|
|
try {
|
|
return await bridgeMethod();
|
|
} catch (error) {
|
|
console.error('Bridge call failed:', error);
|
|
if (onError) {
|
|
onError(error as Error);
|
|
}
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
// 타임아웃을 가진 브리지 호출
|
|
async callWithTimeout<T>(
|
|
bridgeMethod: () => Promise<T>,
|
|
timeout: number = 5000
|
|
): Promise<T> {
|
|
return Promise.race([
|
|
bridgeMethod(),
|
|
new Promise<never>((_, 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; |