import { MachineBridgeInterface, ApiResponse, TodoModel, PurchaseCount, HolyUser, HolyRequestUser, PurchaseItem, KuntaeModel, LoginStatusResponse, LoginResult, UserGroup, PreviousLoginInfo, UserInfoDetail, GroupUser, UserLevelInfo, UserFullData, JobReportItem, JobReportUser, CommonCodeGroup, CommonCode, ItemInfo, JobReportPermission, AppVersionInfo, JobTypeItem, HolidayItem, MailFormItem, UserGroupItem, PermissionInfo } from './types'; // WebView2 환경인지 체크 const isWebView = typeof window !== 'undefined' && !!window.chrome?.webview; // 비동기 프록시 캐싱 (한 번만 초기화) const machine: MachineBridgeInterface | null = isWebView ? window.chrome!.webview!.hostObjects.machine : null; type MessageCallback = (data: unknown) => void; class CommunicationLayer { private listeners: MessageCallback[] = []; private ws: WebSocket | null = null; private isConnected = false; private wsUrl = 'ws://localhost:8082'; // GroupWare WebSocket 포트 constructor() { if (isWebView) { console.log("[COMM] Running in WebView2 Mode (HostObject)"); this.isConnected = true; window.chrome!.webview!.addEventListener('message', (event: MessageEvent) => { this.notifyListeners(event.data); }); // WebView2 환경에서도 연결 상태 알림 setTimeout(() => { this.notifyListeners({ type: 'CONNECTION_STATE', connected: true }); }, 0); } else { console.log("[COMM] Running in Browser Mode (WebSocket)"); this.connectWebSocket(); } } public isWebViewMode(): boolean { return isWebView; } private connectWebSocket() { this.ws = new WebSocket(this.wsUrl); 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: unknown) { 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; } // WebSocket 요청-응답 헬퍼 private wsRequest(requestType: string, responseType: string, params?: Record): Promise { return new Promise((resolve, reject) => { if (!this.isConnected) { setTimeout(() => { if (!this.isConnected) reject(new Error("WebSocket connection timeout")); }, 2000); } const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject(new Error(`${requestType} timeout`)); }, 10000); const handler = (data: unknown) => { const msg = data as { type: string; data?: T; Success?: boolean; Message?: string }; if (msg.type === responseType) { clearTimeout(timeoutId); this.listeners = this.listeners.filter(cb => cb !== handler); resolve(msg.data as T); } }; this.listeners.push(handler); this.ws?.send(JSON.stringify({ ...params, type: requestType })); }); } // ===== Todo API ===== public async getTodos(): Promise> { if (isWebView && machine) { const result = await machine.Todo_GetTodos(); return JSON.parse(result); } else { return this.wsRequest>('GET_TODOS', 'TODOS_DATA'); } } public async getTodo(id: number): Promise> { if (isWebView && machine) { const result = await machine.Todo_GetTodo(id); return JSON.parse(result); } else { return this.wsRequest>('GET_TODO', 'TODO_DATA', { id }); } } public async createTodo( title: string, remark: string, expire: string | null, seqno: number, flag: boolean, request: string | null, status: string ): Promise> { if (isWebView && machine) { const result = await machine.CreateTodo(title, remark, expire, seqno, flag, request, status); return JSON.parse(result); } else { return this.wsRequest>('CREATE_TODO', 'TODO_CREATED', { title, remark, expire, seqno, flag, request, status }); } } public async updateTodo( idx: number, title: string, remark: string, expire: string | null, seqno: number, flag: boolean, request: string | null, status: string ): Promise { if (isWebView && machine) { const result = await machine.Todo_UpdateTodo(idx, title, remark, expire, seqno, flag, request, status); return JSON.parse(result); } else { return this.wsRequest('UPDATE_TODO', 'TODO_UPDATED', { idx, title, remark, expire, seqno, flag, request, status }); } } public async deleteTodo(id: number): Promise { if (isWebView && machine) { const result = await machine.Todo_DeleteTodo(id); return JSON.parse(result); } else { return this.wsRequest('DELETE_TODO', 'TODO_DELETED', { id }); } } public async getUrgentTodos(): Promise> { if (isWebView && machine) { const result = await machine.GetUrgentTodos(); return JSON.parse(result); } else { return this.wsRequest>('GET_URGENT_TODOS', 'URGENT_TODOS_DATA'); } } // ===== Dashboard API ===== public async getPurchaseWaitCount(): Promise { if (isWebView && machine) { const result = await machine.GetPurchaseWaitCount(); return JSON.parse(result); } else { return this.wsRequest('GET_PURCHASE_WAIT_COUNT', 'PURCHASE_WAIT_COUNT_DATA'); } } public async getTodayCountH(): Promise { if (isWebView && machine) { const result = await machine.TodayCountH(); return parseInt(result, 10); } else { const response = await this.wsRequest<{ count: number }>('GET_TODAY_COUNT_H', 'TODAY_COUNT_H_DATA'); return response.count; } } public async getHolyUser(): Promise { if (isWebView && machine) { const result = await machine.GetHolyUser(); return JSON.parse(result); } else { return this.wsRequest('GET_HOLY_USER', 'HOLY_USER_DATA'); } } public async getHolyRequestUser(): Promise { if (isWebView && machine) { const result = await machine.GetHolyRequestUser(); return JSON.parse(result); } else { return this.wsRequest('GET_HOLY_REQUEST_USER', 'HOLY_REQUEST_USER_DATA'); } } public async getPurchaseNRList(): Promise { if (isWebView && machine) { const result = await machine.GetPurchaseNRList(); return JSON.parse(result); } else { return this.wsRequest('GET_PURCHASE_NR_LIST', 'PURCHASE_NR_LIST_DATA'); } } public async getPurchaseCRList(): Promise { if (isWebView && machine) { const result = await machine.GetPurchaseCRList(); return JSON.parse(result); } else { return this.wsRequest('GET_PURCHASE_CR_LIST', 'PURCHASE_CR_LIST_DATA'); } } public async getHolydayRequestCount(): Promise<{ HOLY: number; Message?: string }> { if (isWebView && machine) { const result = await machine.GetHolydayRequestCount(); return JSON.parse(result); } else { return this.wsRequest<{ HOLY: number; Message?: string }>('GET_HOLYDAY_REQUEST_COUNT', 'HOLYDAY_REQUEST_COUNT_DATA'); } } public async getCurrentUserCount(): Promise<{ Count: number; Message?: string }> { if (isWebView && machine) { const result = await machine.GetCurrentUserCount(); return JSON.parse(result); } else { return this.wsRequest<{ Count: number; Message?: string }>('GET_CURRENT_USER_COUNT', 'CURRENT_USER_COUNT_DATA'); } } // ===== Kuntae API ===== public async getKuntaeList(sd: string, ed: string): Promise> { if (isWebView && machine) { const result = await machine.Kuntae_GetList(sd, ed); return JSON.parse(result); } else { return this.wsRequest>('GET_KUNTAE_LIST', 'KUNTAE_LIST_DATA', { sd, ed }); } } public async deleteKuntae(id: number): Promise { if (isWebView && machine) { const result = await machine.Kuntae_Delete(id); return JSON.parse(result); } else { return this.wsRequest('DELETE_KUNTAE', 'KUNTAE_DELETED', { id }); } } // ===== Login API ===== public async checkLoginStatus(): Promise { if (isWebView && machine) { const result = await machine.CheckLoginStatus(); return JSON.parse(result); } else { return this.wsRequest('CHECK_LOGIN_STATUS', 'LOGIN_STATUS_DATA'); } } public async login(gcode: string, id: string, password: string, rememberMe: boolean): Promise { if (isWebView && machine) { const result = await machine.Login(gcode, id, password, rememberMe); return JSON.parse(result); } else { return this.wsRequest('LOGIN', 'LOGIN_RESULT', { gcode, id, password, rememberMe }); } } public async logout(): Promise { if (isWebView && machine) { const result = await machine.Logout(); return JSON.parse(result); } else { return this.wsRequest('LOGOUT', 'LOGOUT_RESULT'); } } public async getUserGroups(): Promise { if (isWebView && machine) { const result = await machine.GetUserGroups(); return JSON.parse(result); } else { return this.wsRequest('GET_USER_GROUPS', 'USER_GROUPS_DATA'); } } public async getPreviousLoginInfo(): Promise { if (isWebView && machine) { const result = await machine.GetPreviousLoginInfo(); return JSON.parse(result); } else { return this.wsRequest('GET_PREVIOUS_LOGIN_INFO', 'PREVIOUS_LOGIN_INFO_DATA'); } } // ===== User API ===== public async getCurrentUserInfo(): Promise> { if (isWebView && machine) { const result = await machine.GetCurrentUserInfo(); return JSON.parse(result); } else { return this.wsRequest>('GET_CURRENT_USER_INFO', 'CURRENT_USER_INFO_DATA'); } } public async getUserInfoById(userId: string): Promise> { if (isWebView && machine) { const result = await machine.GetUserInfoById(userId); return JSON.parse(result); } else { return this.wsRequest>('GET_USER_INFO_BY_ID', 'USER_INFO_DATA', { userId }); } } public async saveUserInfo(userData: UserInfoDetail): Promise { if (isWebView && machine) { const result = await machine.SaveUserInfo(JSON.stringify(userData)); return JSON.parse(result); } else { return this.wsRequest('SAVE_USER_INFO', 'USER_INFO_SAVED', { userData }); } } public async changePassword(oldPassword: string, newPassword: string): Promise { if (isWebView && machine) { const result = await machine.ChangePassword(oldPassword, newPassword); return JSON.parse(result); } else { return this.wsRequest('CHANGE_PASSWORD', 'PASSWORD_CHANGED', { oldPassword, newPassword }); } } // ===== Common Code API ===== public async getCommonGroups(): Promise { if (isWebView && machine) { const result = await machine.Common_GetGroups(); return JSON.parse(result); } else { return this.wsRequest('COMMON_GET_GROUPS', 'COMMON_GROUPS_DATA'); } } public async getCommonList(grp: string): Promise { if (isWebView && machine) { const result = await machine.Common_GetList(grp); return JSON.parse(result); } else { return this.wsRequest('COMMON_GET_LIST', 'COMMON_LIST_DATA', { grp }); } } public async saveCommon(data: CommonCode): Promise { if (isWebView && machine) { const result = await machine.Common_Save( data.idx, data.grp, data.code, data.svalue, data.ivalue, data.fvalue, data.svalue2 || '', data.memo ); return JSON.parse(result); } else { return this.wsRequest('COMMON_SAVE', 'COMMON_SAVED', data as unknown as Record); } } public async deleteCommon(idx: number): Promise { if (isWebView && machine) { const result = await machine.Common_Delete(idx); return JSON.parse(result); } else { return this.wsRequest('COMMON_DELETE', 'COMMON_DELETED', { idx }); } } // ===== Items API ===== public async getItemCategories(): Promise> { if (isWebView && machine) { const result = await machine.Items_GetCategories(); return JSON.parse(result); } else { return this.wsRequest>('ITEMS_GET_CATEGORIES', 'ITEMS_CATEGORIES_DATA'); } } public async getItemList(category: string, searchKey: string): Promise { if (isWebView && machine) { const result = await machine.Items_GetList(category, searchKey); return JSON.parse(result); } else { return this.wsRequest('ITEMS_GET_LIST', 'ITEMS_LIST_DATA', { category, searchKey }); } } public async saveItem(data: ItemInfo): Promise { if (isWebView && machine) { const result = await machine.Items_Save( data.idx, data.sid, data.cate, data.name, data.model, data.scale, data.unit, data.price, data.supply, data.manu, data.storage, data.disable, data.memo ); return JSON.parse(result); } else { return this.wsRequest('ITEMS_SAVE', 'ITEMS_SAVED', data as unknown as Record); } } public async deleteItem(idx: number): Promise { if (isWebView && machine) { const result = await machine.Items_Delete(idx); return JSON.parse(result); } else { return this.wsRequest('ITEMS_DELETE', 'ITEMS_DELETED', { idx }); } } // ===== UserList API ===== public async getCurrentUserLevel(): Promise> { if (isWebView && machine) { const result = await machine.UserList_GetCurrentLevel(); return JSON.parse(result); } else { return this.wsRequest>('USERLIST_GET_CURRENT_LEVEL', 'USERLIST_CURRENT_LEVEL_DATA'); } } public async getUserList(process: string): Promise { if (isWebView && machine) { const result = await machine.UserList_GetList(process); return JSON.parse(result); } else { return this.wsRequest('USERLIST_GET_LIST', 'USERLIST_LIST_DATA', { process }); } } public async getUserListUser(userId: string): Promise> { if (isWebView && machine) { const result = await machine.UserList_GetUser(userId); return JSON.parse(result); } else { return this.wsRequest>('USERLIST_GET_USER', 'USERLIST_USER_DATA', { userId }); } } public async saveGroupUser( userId: string, dept: string, level: number, useUserState: boolean, useJobReport: boolean, exceptHoly: boolean ): Promise { if (isWebView && machine) { const result = await machine.UserList_SaveGroupUser(userId, dept, level, useUserState, useJobReport, exceptHoly); return JSON.parse(result); } else { return this.wsRequest('USERLIST_SAVE_GROUP_USER', 'USERLIST_SAVED', { userId, dept, level, useUserState, useJobReport, exceptHoly }); } } public async saveUserFull(userData: UserFullData): Promise { if (isWebView && machine) { const result = await machine.UserList_SaveUserFull(JSON.stringify(userData)); return JSON.parse(result); } else { return this.wsRequest('USERLIST_SAVE_USER_FULL', 'USERLIST_USER_FULL_SAVED', { userData }); } } public async deleteGroupUser(userId: string): Promise { if (isWebView && machine) { const result = await machine.UserList_DeleteGroupUser(userId); return JSON.parse(result); } else { return this.wsRequest('USERLIST_DELETE_GROUP_USER', 'USERLIST_DELETED', { userId }); } } // ===== JobReport API (JobReport 뷰/테이블) ===== public async getJobReportList(sd: string, ed: string, uid: string = '', searchKey: string = ''): Promise> { if (isWebView && machine) { const result = await machine.Jobreport_GetList(sd, ed, uid, '', searchKey); return JSON.parse(result); } else { return this.wsRequest>('JOBREPORT_GET_LIST', 'JOBREPORT_LIST_DATA', { sd, ed, uid, searchKey }); } } public async getJobReportUsers(): Promise { if (isWebView && machine) { const result = await machine.Jobreport_GetUsers(); return JSON.parse(result); } else { return this.wsRequest('JOBREPORT_GET_USERS', 'JOBREPORT_USERS_DATA'); } } public async getJobReportDetail(idx: number): Promise> { if (isWebView && machine) { const result = await machine.Jobreport_GetDetail(idx); return JSON.parse(result); } else { return this.wsRequest>('JOBREPORT_GET_DETAIL', 'JOBREPORT_DETAIL_DATA', { idx }); } } public async addJobReport( pdate: string, projectName: string, requestpart: string, package_: string, type: string, process: string, status: string, description: string, hrs: number, ot: number, jobgrp: string, tag: string ): Promise { if (isWebView && machine) { const result = await machine.Jobreport_Add(pdate, projectName, requestpart, package_, type, process, status, description, hrs, ot, jobgrp, tag); return JSON.parse(result); } else { return this.wsRequest('JOBREPORT_ADD', 'JOBREPORT_ADDED', { pdate, projectName, requestpart, package: package_, type, process, status, description, hrs, ot, jobgrp, tag }); } } public async editJobReport( idx: number, pdate: string, projectName: string, requestpart: string, package_: string, type: string, process: string, status: string, description: string, hrs: number, ot: number, jobgrp: string, tag: string ): Promise { if (isWebView && machine) { const result = await machine.Jobreport_Edit(idx, pdate, projectName, requestpart, package_, type, process, status, description, hrs, ot, jobgrp, tag); return JSON.parse(result); } else { return this.wsRequest('JOBREPORT_EDIT', 'JOBREPORT_EDITED', { idx, pdate, projectName, requestpart, package: package_, type, process, status, description, hrs, ot, jobgrp, tag }); } } public async deleteJobReport(idx: number): Promise { if (isWebView && machine) { const result = await machine.Jobreport_Delete(idx); return JSON.parse(result); } else { return this.wsRequest('JOBREPORT_DELETE', 'JOBREPORT_DELETED', { idx }); } } public async getJobReportPermission(targetUserId: string): Promise { if (isWebView && machine) { const result = await machine.Jobreport_GetPermission(targetUserId); return JSON.parse(result); } else { return this.wsRequest('JOBREPORT_GET_PERMISSION', 'JOBREPORT_PERMISSION', { targetUserId }); } } public async getJobTypes(process: string = ''): Promise> { if (isWebView && machine) { const result = await machine.Jobreport_GetJobTypes(process); return JSON.parse(result); } else { return this.wsRequest>('JOBREPORT_GET_JOBTYPES', 'JOBREPORT_JOBTYPES', { process }); } } public async getAppVersion(): Promise { if (isWebView && machine) { const result = await machine.GetAppVersion(); return JSON.parse(result); } else { return this.wsRequest('GET_APP_VERSION', 'APP_VERSION', {}); } } // ===== Holiday API (월별근무표) ===== public async getHolidayList(month: string): Promise> { if (isWebView && machine) { const result = await machine.Holiday_GetList(month); return JSON.parse(result); } else { return this.wsRequest>('HOLIDAY_GET_LIST', 'HOLIDAY_LIST_DATA', { month }); } } public async saveHolidays(month: string, holidays: HolidayItem[]): Promise { if (isWebView && machine) { const result = await machine.Holiday_Save(month, JSON.stringify(holidays)); return JSON.parse(result); } else { return this.wsRequest('HOLIDAY_SAVE', 'HOLIDAY_SAVED', { month, holidays }); } } public async initializeHoliday(month: string): Promise> { if (isWebView && machine) { const result = await machine.Holiday_Initialize(month); return JSON.parse(result); } else { return this.wsRequest>('HOLIDAY_INITIALIZE', 'HOLIDAY_INITIALIZED', { month }); } } // ===== MailForm API (메일양식) ===== public async getMailFormList(): Promise> { if (isWebView && machine) { const result = await machine.MailForm_GetList(); return JSON.parse(result); } else { return this.wsRequest>('MAILFORM_GET_LIST', 'MAILFORM_LIST_DATA'); } } public async getMailFormDetail(idx: number): Promise> { if (isWebView && machine) { const result = await machine.MailForm_GetDetail(idx); return JSON.parse(result); } else { return this.wsRequest>('MAILFORM_GET_DETAIL', 'MAILFORM_DETAIL_DATA', { idx }); } } public async addMailForm( cate: string, title: string, tolist: string, bcc: string, cc: string, subject: string, tail: string, body: string, selfTo: boolean, selfCC: boolean, selfBCC: boolean, exceptmail: string, exceptmailcc: string ): Promise { if (isWebView && machine) { const result = await machine.MailForm_Add(cate, title, tolist, bcc, cc, subject, tail, body, selfTo, selfCC, selfBCC, exceptmail, exceptmailcc); return JSON.parse(result); } else { return this.wsRequest('MAILFORM_ADD', 'MAILFORM_ADDED', { cate, title, tolist, bcc, cc, subject, tail, body, selfTo, selfCC, selfBCC, exceptmail, exceptmailcc }); } } public async editMailForm( idx: number, cate: string, title: string, tolist: string, bcc: string, cc: string, subject: string, tail: string, body: string, selfTo: boolean, selfCC: boolean, selfBCC: boolean, exceptmail: string, exceptmailcc: string ): Promise { if (isWebView && machine) { const result = await machine.MailForm_Edit(idx, cate, title, tolist, bcc, cc, subject, tail, body, selfTo, selfCC, selfBCC, exceptmail, exceptmailcc); return JSON.parse(result); } else { return this.wsRequest('MAILFORM_EDIT', 'MAILFORM_EDITED', { idx, cate, title, tolist, bcc, cc, subject, tail, body, selfTo, selfCC, selfBCC, exceptmail, exceptmailcc }); } } public async deleteMailForm(idx: number): Promise { if (isWebView && machine) { const result = await machine.MailForm_Delete(idx); return JSON.parse(result); } else { return this.wsRequest('MAILFORM_DELETE', 'MAILFORM_DELETED', { idx }); } } // ===== UserGroup API (그룹정보/권한설정) ===== public async getUserGroupList(): Promise> { if (isWebView && machine) { const result = await machine.UserGroup_GetList(); return JSON.parse(result); } else { return this.wsRequest>('USERGROUP_GET_LIST', 'USERGROUP_LIST_DATA'); } } public async addUserGroup( dept: string, path_kj: string, permission: number, advpurchase: boolean, advkisul: boolean, managerinfo: string, devinfo: string, usemail: boolean ): Promise { if (isWebView && machine) { const result = await machine.UserGroup_Add(dept, path_kj, permission, advpurchase, advkisul, managerinfo, devinfo, usemail); return JSON.parse(result); } else { return this.wsRequest('USERGROUP_ADD', 'USERGROUP_ADDED', { dept, path_kj, permission, advpurchase, advkisul, managerinfo, devinfo, usemail }); } } public async editUserGroup( originalDept: string, dept: string, path_kj: string, permission: number, advpurchase: boolean, advkisul: boolean, managerinfo: string, devinfo: string, usemail: boolean ): Promise { if (isWebView && machine) { const result = await machine.UserGroup_Edit(originalDept, dept, path_kj, permission, advpurchase, advkisul, managerinfo, devinfo, usemail); return JSON.parse(result); } else { return this.wsRequest('USERGROUP_EDIT', 'USERGROUP_EDITED', { originalDept, dept, path_kj, permission, advpurchase, advkisul, managerinfo, devinfo, usemail }); } } public async deleteUserGroup(dept: string): Promise { if (isWebView && machine) { const result = await machine.UserGroup_Delete(dept); return JSON.parse(result); } else { return this.wsRequest('USERGROUP_DELETE', 'USERGROUP_DELETED', { dept }); } } public async getPermissionInfo(): Promise> { if (isWebView && machine) { const result = await machine.UserGroup_GetPermissionInfo(); return JSON.parse(result); } else { return this.wsRequest>('USERGROUP_GET_PERMISSION_INFO', 'USERGROUP_PERMISSION_INFO'); } } } export const comms = new CommunicationLayer();