// Check if running in WebView2 const isWebView = typeof window !== 'undefined' && !!window.chrome?.webview; // 비동기 프록시 캐싱 (한 번만 초기화) - 매번 접근 시 오버헤드 제거 const machine = isWebView ? window.chrome!.webview!.hostObjects.machine : null; type MessageCallback = (data: any) => void; class CommunicationLayer { private listeners: MessageCallback[] = []; private ws: WebSocket | null = null; private isConnected = false; constructor() { if (isWebView) { console.log("[COMM] Running in WebView2 Mode"); this.isConnected = true; // WebView2 is always connected window.chrome!.webview!.addEventListener('message', (event: any) => { this.notifyListeners(event.data); }); } else { console.log("[COMM] Running in Browser Mode (WebSocket)"); this.connectWebSocket(); } } private connectWebSocket() { this.ws = new WebSocket('ws://localhost:8081'); this.ws.onopen = () => { console.log("[COMM] WebSocket Connected"); this.isConnected = true; this.notifyListeners({ type: 'CONNECTION_STATE', connected: true }); }; this.ws.onmessage = (event) => { try { const data = JSON.parse(event.data); this.notifyListeners(data); } catch (e) { console.error("[COMM] JSON Parse Error", e); } }; this.ws.onclose = () => { console.log("[COMM] WebSocket Closed. Reconnecting..."); this.isConnected = false; this.notifyListeners({ type: 'CONNECTION_STATE', connected: false }); setTimeout(() => this.connectWebSocket(), 2000); }; this.ws.onerror = (err) => { console.error("[COMM] WebSocket Error", err); }; } private notifyListeners(data: any) { this.listeners.forEach(cb => cb(data)); } public subscribe(callback: MessageCallback) { this.listeners.push(callback); return () => { this.listeners = this.listeners.filter(cb => cb !== callback); }; } public getConnectionState(): boolean { return this.isConnected; } // --- API Methods --- public async getConfig(): Promise { if (isWebView && machine) { return await machine.GetConfig(); } else { // WebSocket Request/Response Pattern return new Promise((resolve, reject) => { // 1. Wait for Connection (max 2s) if (!this.isConnected) { // Simple wait logic (could be improved) setTimeout(() => { if (!this.isConnected) reject("WebSocket connection timeout"); }, 2000); } // 2. Send Request with Timeout (max 10s) const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject("Config fetch timeout"); }, 10000); const handler = (data: any) => { if (data.type === 'CONFIG_DATA') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(JSON.stringify(data.data)); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'GET_CONFIG' })); }); } } public async getIOList(): Promise { if (isWebView && machine) { return await machine.GetIOList(); } else { return new Promise((resolve, reject) => { if (!this.isConnected) { setTimeout(() => { if (!this.isConnected) reject("WebSocket connection timeout"); }, 2000); } const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject("IO fetch timeout"); }, 10000); const handler = (data: any) => { if (data.type === 'IO_LIST_DATA') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(JSON.stringify(data.data)); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'GET_IO_LIST' })); }); } } public async getRecipeList(): Promise { if (isWebView && machine) { return await machine.GetRecipeList(); } else { return new Promise((resolve, reject) => { if (!this.isConnected) { setTimeout(() => { if (!this.isConnected) reject("WebSocket connection timeout"); }, 2000); } const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject("Recipe fetch timeout"); }, 10000); const handler = (data: any) => { if (data.type === 'RECIPE_LIST_DATA') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(JSON.stringify(data.data)); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'GET_RECIPE_LIST' })); }); } } public async saveConfig(configJson: string): Promise { if (isWebView && machine) { await machine.SaveConfig(configJson); } else { this.ws?.send(JSON.stringify({ type: 'SAVE_CONFIG', data: JSON.parse(configJson) })); } } public async sendControl(command: string) { if (isWebView && machine) { await machine.SystemControl(command); } else { this.ws?.send(JSON.stringify({ type: 'CONTROL', command })); } } public async moveAxis(axis: string, value: number) { if (isWebView && machine) { await machine.MoveAxis(axis, value); } else { this.ws?.send(JSON.stringify({ type: 'MOVE', axis, value })); } } public async setIO(id: number, state: boolean) { if (isWebView && machine) { await machine.SetIO(id, false, state); } else { this.ws?.send(JSON.stringify({ type: 'SET_IO', id, state })); } } public async selectRecipe(recipeId: string): Promise<{ success: boolean; message: string; recipeId?: string }> { if (isWebView && machine) { const resultJson = await machine.SelectRecipe(recipeId); return JSON.parse(resultJson); } else { return new Promise((resolve, reject) => { if (!this.isConnected) { setTimeout(() => { if (!this.isConnected) reject({ success: false, message: "WebSocket connection timeout" }); }, 2000); } const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject({ success: false, message: "Recipe selection timeout" }); }, 10000); const handler = (data: any) => { if (data.type === 'RECIPE_SELECTED') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(data.data); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'SELECT_RECIPE', recipeId })); }); } } public async copyRecipe(recipeId: string, newName: string): Promise<{ success: boolean; message: string; newRecipe?: any }> { if (isWebView && machine) { const resultJson = await machine.CopyRecipe(recipeId, newName); return JSON.parse(resultJson); } else { return new Promise((resolve, reject) => { if (!this.isConnected) { setTimeout(() => { if (!this.isConnected) reject({ success: false, message: "WebSocket connection timeout" }); }, 2000); } const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject({ success: false, message: "Recipe copy timeout" }); }, 10000); const handler = (data: any) => { if (data.type === 'RECIPE_COPIED') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(data.data); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'COPY_RECIPE', recipeId, newName })); }); } } public async deleteRecipe(recipeId: string): Promise<{ success: boolean; message: string; recipeId?: string }> { if (isWebView && machine) { const resultJson = await machine.DeleteRecipe(recipeId); return JSON.parse(resultJson); } else { return new Promise((resolve, reject) => { if (!this.isConnected) { setTimeout(() => { if (!this.isConnected) reject({ success: false, message: "WebSocket connection timeout" }); }, 2000); } const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject({ success: false, message: "Recipe delete timeout" }); }, 10000); const handler = (data: any) => { if (data.type === 'RECIPE_DELETED') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(data.data); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'DELETE_RECIPE', recipeId })); }); } } public async getRecipe(recipeTitle: string): Promise { if (isWebView && machine) { return await machine.GetRecipe(recipeTitle); } else { return new Promise((resolve, reject) => { if (!this.isConnected) { setTimeout(() => { if (!this.isConnected) reject("WebSocket connection timeout"); }, 2000); } const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject("Recipe fetch timeout"); }, 10000); const handler = (data: any) => { if (data.type === 'RECIPE_DATA') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(JSON.stringify(data.data)); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'GET_RECIPE', recipeTitle })); }); } } public async saveRecipe(recipeTitle: string, recipeData: string): Promise<{ success: boolean; message: string; recipeId?: string }> { if (isWebView && machine) { const resultJson = await machine.SaveRecipe(recipeTitle, recipeData); return JSON.parse(resultJson); } else { return new Promise((resolve, reject) => { if (!this.isConnected) { setTimeout(() => { if (!this.isConnected) reject({ success: false, message: "WebSocket connection timeout" }); }, 2000); } const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject({ success: false, message: "Recipe save timeout" }); }, 10000); const handler = (data: any) => { if (data.type === 'RECIPE_SAVED') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(data.data); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'SAVE_RECIPE', recipeTitle, recipeData })); }); } } public async initializeDevice(): Promise<{ success: boolean; message: string }> { if (isWebView && machine) { const resultJson = await machine.InitializeDevice(); return JSON.parse(resultJson); } else { return new Promise((resolve, reject) => { if (!this.isConnected) { setTimeout(() => { if (!this.isConnected) reject({ success: false, message: "WebSocket connection timeout" }); }, 2000); } const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject({ success: false, message: "Initialize device timeout" }); }, 10000); const handler = (data: any) => { if (data.type === 'DEVICE_INITIALIZED') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(data.data); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'INITIALIZE_DEVICE' })); }); } } public async getInitializeStatus(): Promise { if (isWebView && machine) { return await machine.GetInitializeStatus(); } else { return new Promise((resolve, reject) => { if (!this.isConnected) { setTimeout(() => { if (!this.isConnected) reject("WebSocket connection timeout"); }, 2000); } const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject("Initialize status fetch timeout"); }, 10000); const handler = (data: any) => { if (data.type === 'INITIALIZE_STATUS_DATA') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(JSON.stringify(data.data)); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'GET_INITIALIZE_STATUS' })); }); } } public async getProcessedData(): Promise { if (isWebView && machine) { return await machine.GetProcessedData(); } else { return new Promise((resolve, reject) => { if (!this.isConnected) { setTimeout(() => { if (!this.isConnected) reject("WebSocket connection timeout"); }, 2000); } const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject("Processed data fetch timeout"); }, 10000); const handler = (data: any) => { if (data.type === 'PROCESSED_DATA') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(JSON.stringify(data.data)); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'GET_PROCESSED_DATA' })); }); } } // ===== VISION CONTROL METHODS ===== private async sendVisionCommand(command: string, responseType: string, methodName: string): Promise<{ success: boolean; message: string }> { if (isWebView && machine) { const result = await (machine as any)[methodName](); return JSON.parse(result); } else { return new Promise((resolve, reject) => { if (!this.isConnected) { setTimeout(() => { if (!this.isConnected) reject({ success: false, message: "WebSocket connection timeout" }); }, 2000); } const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject({ success: false, message: "Vision command timeout" }); }, 10000); const handler = (data: any) => { if (data.type === responseType) { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(data.data); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: command })); }); } } public async cameraConnect(): Promise<{ success: boolean; message: string }> { return this.sendVisionCommand('CAMERA_CONNECT', 'CAMERA_RESULT', 'CameraConnect'); } public async cameraDisconnect(): Promise<{ success: boolean; message: string }> { return this.sendVisionCommand('CAMERA_DISCONNECT', 'CAMERA_RESULT', 'CameraDisconnect'); } public async cameraGetImage(): Promise<{ success: boolean; message: string }> { return this.sendVisionCommand('CAMERA_GET_IMAGE', 'CAMERA_RESULT', 'CameraGetImage'); } public async cameraLiveView(): Promise<{ success: boolean; message: string }> { return this.sendVisionCommand('CAMERA_LIVE_VIEW', 'CAMERA_RESULT', 'CameraLiveView'); } public async cameraReadTest(): Promise<{ success: boolean; message: string }> { return this.sendVisionCommand('CAMERA_READ_TEST', 'CAMERA_RESULT', 'CameraReadTest'); } public async keyenceTriggerOn(): Promise<{ success: boolean; message: string }> { return this.sendVisionCommand('KEYENCE_TRIGGER_ON', 'KEYENCE_RESULT', 'KeyenceTriggerOn'); } public async keyenceTriggerOff(): Promise<{ success: boolean; message: string }> { return this.sendVisionCommand('KEYENCE_TRIGGER_OFF', 'KEYENCE_RESULT', 'KeyenceTriggerOff'); } public async keyenceGetImage(): Promise<{ success: boolean; message: string }> { return this.sendVisionCommand('KEYENCE_GET_IMAGE', 'KEYENCE_RESULT', 'KeyenceGetImage'); } public async keyenceSaveImage(): Promise<{ success: boolean; message: string }> { return this.sendVisionCommand('KEYENCE_SAVE_IMAGE', 'KEYENCE_RESULT', 'KeyenceSaveImage'); } // Light, Manual Print, Cancel Job commands public async toggleLight(): Promise<{ success: boolean; message: string }> { if (isWebView && machine) { const result = await machine.ToggleLight(); return JSON.parse(result); } else { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject(new Error('Light toggle timeout')); }, 5000); const handler = (data: any) => { if (data.type === 'LIGHT_RESULT') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(data.data); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'TOGGLE_LIGHT' })); }); } } public async executeManualPrint(printData: { sid: string; venderLot: string; qty: string; mfg: string; rid: string; spy: string; partNo: string; printer: string; count: number; }): Promise<{ success: boolean; message: string }> { if (isWebView && machine) { const result = await machine.ExecuteManualPrint( printData.sid, printData.venderLot, printData.qty, printData.mfg, printData.rid, printData.spy, printData.partNo, printData.printer, printData.count ); return JSON.parse(result); } else { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject(new Error('Manual print timeout')); }, 10000); const handler = (data: any) => { if (data.type === 'MANUAL_PRINT_RESULT') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(data.data); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'EXECUTE_MANUAL_PRINT', ...printData })); }); } } public async cancelJob(): Promise<{ success: boolean; message: string }> { if (isWebView && machine) { const result = await machine.CancelJob(); return JSON.parse(result); } else { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject(new Error('Cancel job timeout')); }, 5000); const handler = (data: any) => { if (data.type === 'CANCEL_JOB_RESULT') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(data.data); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'CANCEL_JOB' })); }); } } public async openManage(): Promise<{ success: boolean; message: string }> { if (isWebView && machine) { const result = await machine.OpenManage(); return JSON.parse(result); } else { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject(new Error('Open manage timeout')); }, 5000); const handler = (data: any) => { if (data.type === 'MANAGE_RESULT') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(data.data); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'OPEN_MANAGE' })); }); } } public async openManual(): Promise<{ success: boolean; message: string }> { if (isWebView && machine) { const result = await machine.OpenManual(); return JSON.parse(result); } else { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject(new Error('Open manual timeout')); }, 5000); const handler = (data: any) => { if (data.type === 'MANUAL_RESULT') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(data.data); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'OPEN_MANUAL' })); }); } } public async openLogViewer(): Promise<{ success: boolean; message: string }> { if (isWebView && machine) { const result = await machine.OpenLogViewer(); return JSON.parse(result); } else { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject(new Error('Open log viewer timeout')); }, 5000); const handler = (data: any) => { if (data.type === 'LOG_VIEWER_RESULT') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(data.data); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'OPEN_LOG_VIEWER' })); }); } } private async openFolder(command: string, methodName: string): Promise<{ success: boolean; message: string }> { if (isWebView && machine) { const result = await (machine as any)[methodName](); return JSON.parse(result); } else { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject(new Error('Open folder timeout')); }, 5000); const handler = (data: any) => { if (data.type === 'FOLDER_RESULT') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(data.data); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: command })); }); } } public async openProgramFolder(): Promise<{ success: boolean; message: string }> { return this.openFolder('OPEN_PROGRAM_FOLDER', 'OpenProgramFolder'); } public async openLogFolder(): Promise<{ success: boolean; message: string }> { return this.openFolder('OPEN_LOG_FOLDER', 'OpenLogFolder'); } public async openScreenshotFolder(): Promise<{ success: boolean; message: string }> { return this.openFolder('OPEN_SCREENSHOT_FOLDER', 'OpenScreenshotFolder'); } public async openSavedDataFolder(): Promise<{ success: boolean; message: string }> { return this.openFolder('OPEN_SAVED_DATA_FOLDER', 'OpenSavedDataFolder'); } // ===== PICKER MOVE METHODS ===== public async getPickerStatus(): Promise { if (isWebView && machine) { const result = await machine.GetPickerStatus(); this.notifyListeners({ type: 'PICKER_STATUS', data: JSON.parse(result) }); } else { this.ws?.send(JSON.stringify({ type: 'GET_PICKER_STATUS' })); } } private async sendPickerCommand(command: string, responseType: string, methodName: string): Promise<{ success: boolean; message: string }> { if (isWebView && machine) { const result = await (machine as any)[methodName](); return JSON.parse(result); } else { return new Promise((resolve, reject) => { if (!this.isConnected) { setTimeout(() => { if (!this.isConnected) reject({ success: false, message: "WebSocket connection timeout" }); }, 2000); } const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject({ success: false, message: "Picker command timeout" }); }, 10000); const handler = (data: any) => { if (data.type === responseType) { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(data.data); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: command })); }); } } public async pickerMoveLeft(): Promise<{ success: boolean; message: string }> { return this.sendPickerCommand('PICKER_MOVE_LEFT', 'PICKER_RESULT', 'PickerMoveLeft'); } public async pickerMoveLeftWait(): Promise<{ success: boolean; message: string }> { return this.sendPickerCommand('PICKER_MOVE_LEFT_WAIT', 'PICKER_RESULT', 'PickerMoveLeftWait'); } public async pickerMoveCenter(): Promise<{ success: boolean; message: string }> { return this.sendPickerCommand('PICKER_MOVE_CENTER', 'PICKER_RESULT', 'PickerMoveCenter'); } public async pickerMoveRightWait(): Promise<{ success: boolean; message: string }> { return this.sendPickerCommand('PICKER_MOVE_RIGHT_WAIT', 'PICKER_RESULT', 'PickerMoveRightWait'); } public async pickerMoveRight(): Promise<{ success: boolean; message: string }> { return this.sendPickerCommand('PICKER_MOVE_RIGHT', 'PICKER_RESULT', 'PickerMoveRight'); } public async pickerJogStart(direction: 'up' | 'down' | 'left' | 'right'): Promise<{ success: boolean; message: string }> { if (isWebView && machine) { const result = await machine.PickerJogStart(direction); return JSON.parse(result); } else { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject({ success: false, message: "Jog command timeout" }); }, 5000); const handler = (data: any) => { if (data.type === 'PICKER_JOG_RESULT') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(data.data); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'PICKER_JOG_START', direction })); }); } } public async pickerJogStop(): Promise<{ success: boolean; message: string }> { return this.sendPickerCommand('PICKER_JOG_STOP', 'PICKER_JOG_RESULT', 'PickerJogStop'); } public async pickerStop(): Promise<{ success: boolean; message: string }> { return this.sendPickerCommand('PICKER_STOP', 'PICKER_RESULT', 'PickerStop'); } public async cancelVisionValidation(side: 'left' | 'right'): Promise<{ success: boolean; message: string }> { if (isWebView && machine) { const result = await machine.CancelVisionValidation(side); return JSON.parse(result); } else { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject({ success: false, message: "Vision cancel timeout" }); }, 5000); const handler = (data: any) => { if (data.type === 'VISION_CANCEL_RESULT') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(data.data); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'CANCEL_VISION_VALIDATION', side })); }); } } public async pickerManagePosition(side: 'left' | 'right'): Promise<{ success: boolean; message: string }> { if (isWebView && machine) { const result = await machine.PickerManagePosition(side); return JSON.parse(result); } else { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject({ success: false, message: "Manage position timeout" }); }, 30000); // Longer timeout for motion const handler = (data: any) => { if (data.type === 'PICKER_MANAGE_RESULT') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(data.data); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'PICKER_MANAGE_POSITION', side })); }); } } public async pickerManageReturn(): Promise<{ success: boolean; message: string }> { return this.sendPickerCommand('PICKER_MANAGE_RETURN', 'PICKER_MANAGE_RESULT', 'PickerManageReturn'); } public async pickerZHome(): Promise<{ success: boolean; message: string }> { return this.sendPickerCommand('PICKER_Z_HOME', 'PICKER_RESULT', 'PickerZHome'); } public async pickerZZero(): Promise<{ success: boolean; message: string }> { return this.sendPickerCommand('PICKER_Z_ZERO', 'PICKER_RESULT', 'PickerZZero'); } public async pickerTestPrint(side: 'left' | 'right'): Promise<{ success: boolean; message: string }> { if (isWebView && machine) { const result = await machine.PickerTestPrint(side); return JSON.parse(result); } else { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject({ success: false, message: "Test print timeout" }); }, 10000); const handler = (data: any) => { if (data.type === 'PICKER_PRINT_RESULT') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(data.data); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'PICKER_TEST_PRINT', side })); }); } } public async canCloseManage(): Promise<{ canClose: boolean; message: string }> { if (isWebView && machine) { const result = await machine.CanCloseManage(); return JSON.parse(result); } else { return new Promise((resolve) => { const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); resolve({ canClose: true, message: '' }); }, 5000); const handler = (data: any) => { if (data.type === 'CAN_CLOSE_MANAGE_RESULT') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(data.data); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'CAN_CLOSE_MANAGE' })); }); } } // Close manage dialog - backend handles flag clear and auto-init check public async closeManage(): Promise<{ shouldAutoInit: boolean }> { if (isWebView && machine) { const result = await machine.CloseManage(); return JSON.parse(result); } else { return new Promise((resolve) => { const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); resolve({ shouldAutoInit: false }); }, 5000); const handler = (data: any) => { if (data.type === 'CLOSE_MANAGE_RESULT') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(data.data); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'CLOSE_MANAGE' })); }); } } // ===== HISTORY DATA ===== // Get history data from database (fHistory.cs의 refreshList와 동일) public async getHistoryData(startDate: string, endDate: string, search: string): Promise<{ success: boolean; data: any[]; mcName?: string; message?: string }> { if (isWebView && machine) { const result = await machine.GetHistoryData(startDate, endDate, search); return JSON.parse(result); } else { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject({ success: false, message: 'History data fetch timeout', data: [] }); }, 30000); // 30 second timeout for potentially large data const handler = (data: any) => { if (data.type === 'HISTORY_DATA_RESULT') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(data.data); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'GET_HISTORY_DATA', startDate, endDate, search })); }); } } // ===== INTERLOCK METHODS ===== // Toggle interlock (DIOMonitor.cs의 gvILXF_ItemClick과 동일) public async toggleInterlock(axisIndex: number, lockIndex: number): Promise<{ success: boolean; newState?: boolean; message?: string }> { if (isWebView && machine) { const result = await machine.ToggleInterlock(axisIndex, lockIndex); return JSON.parse(result); } else { return new Promise((resolve) => { const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); resolve({ success: false, message: 'Timeout' }); }, 5000); const handler = (data: any) => { if (data.type === 'TOGGLE_INTERLOCK_RESULT') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(data.data); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'TOGGLE_INTERLOCK', axisIndex, lockIndex })); }); } } // Get interlock list public async getInterlockList(): Promise { if (isWebView && machine) { return await machine.GetInterlockList(); } else { return new Promise((resolve) => { const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); resolve('[]'); }, 5000); const handler = (data: any) => { if (data.type === 'INTERLOCK_LIST_RESULT') { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(JSON.stringify(data.data)); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ type: 'GET_INTERLOCK_LIST' })); }); } } } export const comms = new CommunicationLayer();