339 lines
11 KiB
TypeScript
339 lines
11 KiB
TypeScript
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<string, any>();
|
||
|
||
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<string | null> => {
|
||
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<number> => {
|
||
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<boolean> => {
|
||
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<FaultReportingResponse> = 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<string> {
|
||
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<MenuItem[]> = null;
|
||
let menuDataReadyPromise: Promise<ApiResponse<MenuItem[]>>;
|
||
let tagResolutionData: ApiResponse<TagResolutionConfig[]> = null;
|
||
let tagResolutionDataReadyPromise: Promise<ApiResponse<TagResolutionConfig[]>>;
|
||
let helperDescriptionData: ApiResponse<HelperDescription[]> = null;
|
||
let helperDescriptionDataReadyPromise: Promise<ApiResponse<HelperDescription[]>>;
|
||
|
||
/**
|
||
* 获取菜单数据
|
||
*/
|
||
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();
|
||
}
|