CSAPP/src/IpcMainHandler.ts

339 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();
}