优化检测URL是否有效方法,使其更贴近浏览器访问效果,以及当HEAD请求后收到403响应时,再用GET请求一次(也许Web服务器禁止了HEAD请求)。

This commit is contained in:
Allen 2025-05-17 20:42:46 +08:00
parent 51087027c8
commit 85036de5c3
4 changed files with 125 additions and 26 deletions

View File

@ -216,7 +216,7 @@ export interface TagResolutionConfig {
/**
*
*/
Percentage: number;
Percentage: string | number;
/**
*
*/
@ -234,7 +234,7 @@ export interface SpecialPUrlItem {
/**
*
*/
SPPer: number;
SPPer: string | number;
}
/**

View File

@ -5,6 +5,8 @@ import * as http from 'http';
import * as https from 'https';
import { URL } from 'url';
const isDevelopment = process.env.NODE_ENV === 'development';
// Ensure only one instance is running
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
@ -32,34 +34,64 @@ ipcMain.handle('get-primary-display', () => screen.getPrimaryDisplay());
// Check if the URL is available
ipcMain.handle('check-url-available', async (event, rawUrl: string) => {
try {
const url = new URL(rawUrl);
const url: URL = new URL(rawUrl);
const lib = url.protocol === 'https:' ? https : http;
return await new Promise((resolve) => {
const req = lib.request(
// 先用HEAD请求如果遇到403则再用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',
hostname: url.hostname,
port: url.port || undefined,
path: url.pathname + url.search,
timeout: 3000,
...requestOptions
},
(res) => {
resolve({ ok: true, status: res.statusCode })
req.destroy()
console.log('check-url-available HEAD', url.toString(), res.statusCode, res.statusMessage);
if (res.statusCode === 403) {
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, error: err.message });
});
getReq.on('timeout', () => {
resolve({ ok: false, error: 'Timeout' });
getReq.destroy();
});
} else {
resolve({ ok: true, status: res.statusCode, message: res.statusMessage });
headReq.destroy();
}
}
);
req.on('error', (err) => {
resolve({ ok: false, error: err.message })
headReq.on('error', (err) => {
resolve({ ok: false, error: err.message });
});
req.on('timeout', () => {
req.destroy()
resolve({ ok: false, error: 'Timeout' })
headReq.on('timeout', () => {
resolve({ ok: false, error: 'Timeout' });
headReq.destroy();
});
req.end();
headReq.end();
})
} catch (e) {
return { ok: false, error: 'Invalid URL' };
@ -141,7 +173,9 @@ const createWindow = () => {
win.webContents.on('did-attach-webview', (event, webContents) => {
webContents.setWindowOpenHandler((details) => {
console.log('webview-new-window', webContents, details);
if (isDevelopment) {
console.log('webview-new-window', webContents, details);
}
win.webContents.send('webview-new-window', webContents.id, details.url);
return { action: 'deny' };
});
@ -172,6 +206,9 @@ const createWindow = () => {
}
};
globalShortcut.register("CommandOrControl+Shift+I", handleDevTools);
if (isDevelopment) {
win.webContents.openDevTools();
}
// and load the login.html of the app.
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
@ -185,7 +222,7 @@ const createWindow = () => {
{ label: '显示窗口', click: () => win.show() },
{ label: '退出程序', click: () => app.exit() }
]);
const iconPath = process.env.NODE_ENV === 'development' ? path.join(__dirname, '../../assets/tray.png') : path.join(process.resourcesPath, 'assets', 'tray.png');
const iconPath = isDevelopment ? path.join(__dirname, '../../assets/tray.png') : path.join(process.resourcesPath, 'assets', 'tray.png');
const tray = new Tray(iconPath);
tray.setToolTip('中国电信-工作台');
tray.setContextMenu(contextMenu);

View File

@ -4,36 +4,63 @@
import { contextBridge, ipcRenderer } from 'electron';
contextBridge.exposeInMainWorld('electronAPI', {
// 打开新标签页
/**
* URL
* @param callback webContentId和urlwebContentId是请求打开URL的webview的id
*/
onOpenTab: (callback: (webContentId: number, url: string) => void) => {
ipcRenderer.on('webview-new-window', (_event, webContentId, url) => callback(webContentId, url));
},
// 获取当前屏幕的缩放比例和分辨率
getPrimaryDisplay: () => ipcRenderer.invoke('get-primary-display'),
/**
*
* @returns
*/
getPrimaryDisplay: () => ipcRenderer.invoke('get-primary-display') as Promise<Electron.Display>,
// 检查URL是否可用
/**
* URL是否可用
* @param url URL
* @returns
*/
checkUrlAvailable: (url: string) => ipcRenderer.invoke('check-url-available', url),
// 设置webview的cookie
/**
* webview的cookie
* @param url cookie的URL
* @param cookie cookie字符串
*/
setWebviewCookie: (url: string, cookie: string) => ipcRenderer.invoke('set-webview-cookie', url, cookie),
// 设置 sessionStorage
/**
* sessionStorage
* @param key
* @param value
*/
setSessionStorage: (key: string, value: string) => {
window.sessionStorage.setItem(key, value);
},
// 获取 sessionStorage
/**
* sessionStorage中获取指定键的值
* @param key
* @returns
*/
getSessionStorage: (key: string) => {
return window.sessionStorage.getItem(key);
},
// 删除 sessionStorage
/**
* sessionStorage中删除指定键的值
* @param key
*/
removeSessionStorage: (key: string) => {
window.sessionStorage.removeItem(key);
},
// 清空 sessionStorage
/**
* sessionStorage
*/
clearSessionStorage: () => {
window.sessionStorage.clear();
}

View File

@ -1,11 +1,46 @@
export interface ElectronAPI {
/**
* URL
* @param callback webContentId和urlwebContentId是请求打开URL的webview的id
*/
onOpenTab: (callback: (webContentId: number, url: string) => void) => void;
/**
*
* @returns
*/
getPrimaryDisplay: () => Promise<Electron.Display>;
/**
* URL是否可用
* @param url URL
* @returns
*/
checkUrlAvailable: (url: string) => Promise<{ ok: boolean; status: number; error?: string }>;
/**
* webview的cookie
* @param url cookie的URL
* @param cookie cookie字符串
*/
setWebviewCookie: (url: string, cookie: string) => Promise<boolean>;
/**
* sessionStorage
* @param key
* @param value
*/
setSessionStorage: (key: string, value: string) => void;
/**
* sessionStorage中获取指定键的值
* @param key
* @returns
*/
getSessionStorage: (key: string) => string | null;
/**
* sessionStorage中删除指定键的值
* @param key
*/
removeSessionStorage: (key: string) => void;
/**
* sessionStorage
*/
clearSessionStorage: () => void;
}