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

View File

@ -4,36 +4,63 @@
import { contextBridge, ipcRenderer } from 'electron'; import { contextBridge, ipcRenderer } from 'electron';
contextBridge.exposeInMainWorld('electronAPI', { contextBridge.exposeInMainWorld('electronAPI', {
// 打开新标签页 /**
* URL
* @param callback webContentId和urlwebContentId是请求打开URL的webview的id
*/
onOpenTab: (callback: (webContentId: number, url: string) => void) => { onOpenTab: (callback: (webContentId: number, url: string) => void) => {
ipcRenderer.on('webview-new-window', (_event, webContentId, url) => callback(webContentId, url)); 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), 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), setWebviewCookie: (url: string, cookie: string) => ipcRenderer.invoke('set-webview-cookie', url, cookie),
// 设置 sessionStorage /**
* sessionStorage
* @param key
* @param value
*/
setSessionStorage: (key: string, value: string) => { setSessionStorage: (key: string, value: string) => {
window.sessionStorage.setItem(key, value); window.sessionStorage.setItem(key, value);
}, },
// 获取 sessionStorage /**
* sessionStorage中获取指定键的值
* @param key
* @returns
*/
getSessionStorage: (key: string) => { getSessionStorage: (key: string) => {
return window.sessionStorage.getItem(key); return window.sessionStorage.getItem(key);
}, },
// 删除 sessionStorage /**
* sessionStorage中删除指定键的值
* @param key
*/
removeSessionStorage: (key: string) => { removeSessionStorage: (key: string) => {
window.sessionStorage.removeItem(key); window.sessionStorage.removeItem(key);
}, },
// 清空 sessionStorage /**
* sessionStorage
*/
clearSessionStorage: () => { clearSessionStorage: () => {
window.sessionStorage.clear(); window.sessionStorage.clear();
} }

View File

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