CSAPP/src/IpcMainHandler.ts

261 lines
8.9 KiB
TypeScript
Raw Normal View History

import { BrowserWindow, ipcMain, screen, session } 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 } from './EIAC_Desktop_Api';
const memoryCache = new Map<string, any>();
export function initialize(): void {
// Set cache
ipcMain.handle('cache:set', (_event, key: string, value: any) => {
memoryCache.set(key, value);
});
// Get cache
ipcMain.handle('cache:get', (_event, key: string) => {
return memoryCache.get(key) ?? null;
});
// Get menu cache
ipcMain.handle('get-menu-cache', async () => menuData ?? await menuDataReadyPromise);
// Get config cache
ipcMain.handle('get-config-cache', async () => configData ?? await configDataReadyPromise);
// Get zoom factor
ipcMain.handle('get-zoom-factor-by-url', async (event, url: string) => {
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 (!configData) {
await configDataReadyPromise;
}
if (configData.code != 200 || configData.status != 0) {
console.error('Get config failed:', configData.msg + ', status: ' + configData.status + ', code: ' + configData.code);
return display.scaleFactor;
}
const configList: TagResolutionConfig[] = configData.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) => {
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);
if (res.statusCode === 403 || res.statusCode === 404) {
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 webviews cookie
ipcMain.handle('set-webview-cookie', async (event, url: string, cookie: string) => {
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) => {
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.log('base64:', base64);
try {
const response: ApiResponse<FaultReportingResponse> = await EIACDesktopApi.Help.FaultReportingAsync({
Account: memoryCache.get('Account'),
IP: getLocalIPAddress(),
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 };
}
});
}
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; // 没有找到
}
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 configData: ApiResponse<TagResolutionConfig[]> = null;
let configDataReadyPromise: Promise<ApiResponse<TagResolutionConfig[]>>;
function getMenuAsync(): void {
if (!menuDataReadyPromise) {
menuDataReadyPromise = new Promise((resolve, reject) => {
EIACDesktopApi.Menu.GetMenuAsync()
.then(data => {
menuData = data;
resolve(data);
})
.catch(err => {
reject(err);
});
});
}
}
function getConfigAsync(): void {
if (!configDataReadyPromise) {
configDataReadyPromise = new Promise((resolve, reject) => {
EIACDesktopApi.Menu.GetConfigAsync()
.then(data => {
configData = data;
resolve(data);
})
.catch(err => {
reject(err);
});
});
}
}
export function preloadData(): void {
getMenuAsync();
getConfigAsync();
}