import { BrowserWindow, ipcMain, screen, session, app } from "electron"; import http from 'http'; import https from 'https'; import os from 'os'; import { URL } from 'url'; import { ApiResponse, MenuItem, TagResolutionConfig, EIACDesktopApi, SpecialPUrlItem, FaultReportingResponse, HelperDescription } from './EIAC_Desktop_Api'; const memoryCache = new Map(); export function initialize(): void { // Close app ipcMain.handle('app:close', (): void => { app.exit(0); // 使用 exit 而不是 quit,确保立即退出 }); // Set cache ipcMain.handle('cache:set', (_event, key: string, value: any): void => { memoryCache.set(key, value); }); // Get cache ipcMain.handle('cache:get', (_event, key: string): any | null => { return memoryCache.get(key) ?? null; }); // Get menu cache ipcMain.handle('get-menu-cache', async () => menuData ?? await menuDataReadyPromise); // Get helper descrip by code ipcMain.handle('get-helper-descrip', async (event, code: string): Promise => { if (!helperDescriptionData) { await helperDescriptionDataReadyPromise; } if (helperDescriptionData.code != 200 || helperDescriptionData.status != 0) { console.error('Get config failed:', helperDescriptionData.msg + ', status: ' + helperDescriptionData.status + ', code: ' + helperDescriptionData.code); return null; } let helperDescrip: HelperDescription | null = helperDescriptionData.data.find(h => h.Code === code); if (!helperDescrip) { helperDescrip = helperDescriptionData.data.find(h => h.Code === '*'); } return helperDescrip ? helperDescrip.Descrip : null; }); // Get zoom factor by url ipcMain.handle('get-zoom-factor-by-url', async (event, url: string): Promise => { const display: Electron.Display = screen.getPrimaryDisplay(); const physicalSize: Electron.Size = { width: display.size.width * display.scaleFactor, height: display.size.height * display.scaleFactor }; const resolution: string = `${physicalSize.width}*${physicalSize.height}`; console.log('PhysicalResolution:', resolution); console.log(`Resolution: ${display.size.width}*${display.size.height}`); console.log(`ScaleFactor: ${display.scaleFactor}`); if (!tagResolutionData) { await tagResolutionDataReadyPromise; } if (tagResolutionData.code != 200 || tagResolutionData.status != 0) { console.error('Get config failed:', tagResolutionData.msg + ', status: ' + tagResolutionData.status + ', code: ' + tagResolutionData.code); return display.scaleFactor; } const configList: TagResolutionConfig[] = tagResolutionData.data; let config: TagResolutionConfig = configList.find(c => c.Resolutions.includes(resolution)); if (!config) { config = configList.find(c => c.Resolutions === '*'); } if (!config) { config = configList[0]; } console.log('Match Config:', config); let specialPUrl: SpecialPUrlItem = config.SpecialPUrl.find(s => s.SPUrl.toLowerCase() === url.toLowerCase()); if (!specialPUrl) { specialPUrl = config.SpecialPUrl.find(s => url.toLowerCase().startsWith(s.SPUrl.toLowerCase())); } console.log('specialPUrl:', specialPUrl); let percentage: number = typeof config.Percentage === 'string' ? parseInt(config.Percentage) : config.Percentage; if (specialPUrl) { percentage = typeof specialPUrl.SPPer === 'string' ? parseInt(specialPUrl.SPPer) : specialPUrl.SPPer; } console.log('Percentage:', percentage); return percentage; }); // Check if the URL is available ipcMain.handle('check-url-available', async (event, rawUrl: string): Promise<{ ok: boolean, status: number, message: string }> => { try { const url: URL = new URL(rawUrl); const lib = url.protocol === 'https:' ? https : http; return await new Promise((resolve) => { // 先用HEAD请求,如果遇到403或404,则再用GET请求再试一次(部分服务器可能禁止HEAD请求)。 const requestOptions: http.RequestOptions | https.RequestOptions = { hostname: url.hostname, port: url.port || undefined, path: url.pathname + url.search, headers: { 'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate, br', 'Connection': 'keep-alive', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' } }; const headReq = lib.request( { method: 'HEAD', timeout: 3000, ...requestOptions }, (res) => { console.log('check-url-available HEAD', url.toString(), res.statusCode, res.statusMessage); /** * 403: 禁止访问 * 404: 未找到 * 405: 方法不允许 */ const requiresRetryStatusCodes: number[] = [403, 404, 405]; if (requiresRetryStatusCodes.includes(res.statusCode)) { headReq.destroy() const getReq = lib.get({ method: 'GET', ...requestOptions }, (res) => { console.log('check-url-available GET', url.toString(), res.statusCode, res.statusMessage); resolve({ ok: true, status: res.statusCode, message: res.statusMessage }); getReq.destroy() }); getReq.on('error', (err) => { resolve({ ok: false, status: -1, message: err.message }); }); getReq.on('timeout', () => { resolve({ ok: false, status: -1, message: 'GET Timeout' }); getReq.destroy(); }); } else { resolve({ ok: true, status: res.statusCode, message: res.statusMessage }); headReq.destroy(); } } ); headReq.on('error', (err) => { resolve({ ok: false, status: -1, message: err.message }); }); headReq.on('timeout', () => { resolve({ ok: false, status: -1, message: 'HEAD Timeout' }); headReq.destroy(); }); headReq.end(); }) } catch (e) { return { ok: false, status: -1, message: 'Invalid URL' }; } }); // Set webview's cookie ipcMain.handle('set-webview-cookie', async (event, url: string, cookie: string): Promise => { try { const parsedUrl = new URL(url); const cookies: Array<{ Key: string, Value: string }> = JSON.parse(cookie); for (const cookieItem of cookies) { await session.defaultSession.cookies.set({ url: url, name: cookieItem.Key.trim(), value: cookieItem.Value.trim(), domain: parsedUrl.hostname, path: '/', secure: parsedUrl.protocol === 'https:', httpOnly: true }); } return true; } catch (error) { console.error('设置cookie失败:', error); return false; } }); // Fault reporting ipcMain.handle('fault-reporting', async (event, url: string, message: string, status: number): Promise<{ ok: boolean, status: number, message: string }> => { console.log('Fault reporting:', url, message, status); const webContents = event.sender; const win = BrowserWindow.fromWebContents(webContents); if (!win) { throw new Error('Not found BrowserWindow'); } const base64 = await captureWindowAsBase64(win); console.debug('base64:', base64); const account: string = memoryCache.get('Account'); if (!account) { throw new Error('Not found account'); } console.log('account:', account); const ip: string = getLocalIPAddress(); if (!ip) { throw new Error('Not found ip'); } console.log('ip:', ip); try { const response: ApiResponse = await EIACDesktopApi.Help.FaultReportingAsync({ Account: account, IP: ip, Url: url, ImgBase64: base64, Explain: `message: ${message}, status: ${status}` }); console.log('Fault reporting response:', response); if (response.code != 200 || response.status != 0) { console.error('故障上报失败:', response.msg + ', status: ' + response.status + ', code: ' + response.code); return { ok: false, status: -1, message: `故障上报失败: ${response.msg}, status: ${response.status}, code: ${response.code}` }; } return { ok: true, status: response.status, message: response.msg }; } catch (error) { console.error('故障上报失败:', error); return { ok: false, status: -1, message: error.message }; } }); } /** * 获取本机IP地址 * @returns 本机IP地址 */ function getLocalIPAddress(): string | null { const interfaces = os.networkInterfaces(); for (const name of Object.keys(interfaces)) { for (const net of interfaces[name] || []) { // 排除 IPv6 和 127.0.0.1 内环地址 if (net.family === 'IPv4' && !net.internal) { return net.address; // 返回第一个非内网 IPv4 地址 } } } return null; // 没有找到 } /** * 捕获当前窗口并且转换为Base64格式的图像 * @param win 窗口 * @returns 返回Base64格式的图像 */ async function captureWindowAsBase64(win: BrowserWindow): Promise { const image = await win.capturePage(); // 截图 const pngBuffer = image.toPNG(); // 转为 PNG Buffer const base64 = pngBuffer.toString('base64'); // 转为 Base64 字符串 return `data:image/png;base64,${base64}`; // 返回 DataURL } let menuData: ApiResponse = null; let menuDataReadyPromise: Promise>; let tagResolutionData: ApiResponse = null; let tagResolutionDataReadyPromise: Promise>; let helperDescriptionData: ApiResponse = null; let helperDescriptionDataReadyPromise: Promise>; /** * 获取菜单数据 */ function getMenuAsync(): void { if (!menuDataReadyPromise) { menuDataReadyPromise = new Promise((resolve, reject) => { EIACDesktopApi.Menu.GetMenuAsync() .then(data => { menuData = data; resolve(data); }) .catch(err => { reject(err); }); }); } } /** * 获取分辨率配置 */ function getTagResolutionsAsync(): void { if (!tagResolutionDataReadyPromise) { tagResolutionDataReadyPromise = new Promise((resolve, reject) => { EIACDesktopApi.Menu.GetTagResolutionsAsync() .then(data => { tagResolutionData = data; resolve(data); }) .catch(err => { reject(err); }); }); } } /** * 获取帮助描述 */ function getHelperDescriptionsAsync(): void { if (!helperDescriptionDataReadyPromise) { helperDescriptionDataReadyPromise = new Promise((resolve, reject) => { EIACDesktopApi.Menu.GetHelperDescripsAsync() .then(data => { helperDescriptionData = data; resolve(data); }) .catch(err => { reject(err); }); }); } } /** * 预加载数据 */ export function preloadData(): void { getMenuAsync(); getTagResolutionsAsync(); getHelperDescriptionsAsync(); }