Electron 网络与外部通信
net 模块、fetch、WebSocket、代理、拦截请求与跨域。
两种网络栈
| Where | API | CORS | Cookie |
|---|---|---|---|
| Renderer | fetch / XMLHttpRequest | 受限 | 自动带上 |
| Main | net / net.fetch | 不受限 | 需手动处理 |
// 渲染进程:普通 fetch,受 CORS 约束
const r = await fetch('https://api.example.com/data');
const data = await r.json();// 主进程:net 走 Chromium 的网络栈,但不受同源策略限制
const { net } = require('electron');
const res = await net.fetch('https://api.example.com/data');
const data = await res.json();When a third-party API trips CORS, the clean fix is to make the request in the main process and pass the result over IPC. Never disable webSecurity — that removes same-origin protection entirely.
用 IPC 代理请求
// main.js
const { ipcMain, net } = require('electron');
ipcMain.handle('api:get', async (_e, url) => {
const allow = ['https://api.example.com/'];
if (!allow.some(p => url.startsWith(p))) {
throw new Error('不允许的域名');
}
const res = await net.fetch(url, { headers: { Accept: 'application/json' } });
return { status: res.status, body: await res.text() };
});// preload.js
contextBridge.exposeInMainWorld('api', {
get: (url) => ipcRenderer.invoke('api:get', url)
});The allow-list is mandatory. Without it the page gets an arbitrary-URL fetcher and can hit internal endpoints (SSRF). Permit only the domains you actually need.
WebSocket
const ws = new WebSocket('wss://example.com/live');
ws.addEventListener('open', () => ws.send(JSON.stringify({ type: 'subscribe' })));
ws.addEventListener('message', (e) => console.log(JSON.parse(e.data)));
ws.addEventListener('error', () => console.warn('连接出错'));
ws.addEventListener('close', () => {
setTimeout(reconnect, 2000); // 简单退避重连
});代理
const { session } = require('electron');
// 全局代理
session.defaultSession.setProxy({
proxyRules: 'http://127.0.0.1:7890',
proxyBypassRules: '<local>'
});
// 查看当前代理
const cfg = await session.defaultSession.resolveProxy('https://example.com');Corporate apps often need a proxy. setProxy is per-session, so different windows with different sessions can proxy or connect directly independently.
拦截与修改请求
session.defaultSession.webRequest.onBeforeSendHeaders(
{ urls: ['https://api.example.com/*'] },
(details, callback) => {
details.requestHeaders['X-Client'] = 'desktop/1.0';
callback({ requestHeaders: details.requestHeaders });
}
);
session.defaultSession.webRequest.onHeadersReceived(
{ urls: ['*://*/*'] },
(details, callback) => {
const h = details.responseHeaders;
delete h['X-Frame-Options'];
callback({ responseHeaders: h });
}
);离线检测
// 渲染进程
window.addEventListener('online', () => setStatus('已连接'));
window.addEventListener('offline', () => setStatus('离线,数据将在恢复后同步'));
// 主进程
const { net } = require('electron');
net.isOnline(); // 只代表「有网络接口」,不代表真的能上网net.isOnline() only means an interface is up; a hotel Wi-Fi needing login still returns true. The reliable check is a lightweight request that must succeed.
两种网络栈
| 位置 | 接口 | 跨域 | Cookie |
|---|---|---|---|
| 渲染进程 | fetch / XMLHttpRequest | 受限 | 自动带上 |
| 主进程 | net / net.fetch | 不受限 | 需手动处理 |
// 渲染进程:普通 fetch,受 CORS 约束
const r = await fetch('https://api.example.com/data');
const data = await r.json();// 主进程:net 走 Chromium 的网络栈,但不受同源策略限制
const { net } = require('electron');
const res = await net.fetch('https://api.example.com/data');
const data = await res.json();调第三方 API 遇到跨域报错时,最干净的办法是把请求放到主进程,通过 IPC 把结果给页面。不要关 webSecurity——那等于关掉浏览器的同源保护。
用 IPC 代理请求
// main.js
const { ipcMain, net } = require('electron');
ipcMain.handle('api:get', async (_e, url) => {
const allow = ['https://api.example.com/'];
if (!allow.some(p => url.startsWith(p))) {
throw new Error('不允许的域名');
}
const res = await net.fetch(url, { headers: { Accept: 'application/json' } });
return { status: res.status, body: await res.text() };
});// preload.js
contextBridge.exposeInMainWorld('api', {
get: (url) => ipcRenderer.invoke('api:get', url)
});白名单校验是必须的。不校验就等于给了页面一个「任意 URL 请求器」,可以打内网接口(SSRF)。只允许明确需要的那几个域名。
WebSocket
const ws = new WebSocket('wss://example.com/live');
ws.addEventListener('open', () => ws.send(JSON.stringify({ type: 'subscribe' })));
ws.addEventListener('message', (e) => console.log(JSON.parse(e.data)));
ws.addEventListener('error', () => console.warn('连接出错'));
ws.addEventListener('close', () => {
setTimeout(reconnect, 2000); // 简单退避重连
});代理
const { session } = require('electron');
// 全局代理
session.defaultSession.setProxy({
proxyRules: 'http://127.0.0.1:7890',
proxyBypassRules: '<local>'
});
// 查看当前代理
const cfg = await session.defaultSession.resolveProxy('https://example.com');企业内网应用常需要走代理。setProxy 是会话级的,可以给不同窗口用不同 session,实现「一个窗口走代理、一个直连」。
拦截与修改请求
session.defaultSession.webRequest.onBeforeSendHeaders(
{ urls: ['https://api.example.com/*'] },
(details, callback) => {
details.requestHeaders['X-Client'] = 'desktop/1.0';
callback({ requestHeaders: details.requestHeaders });
}
);
session.defaultSession.webRequest.onHeadersReceived(
{ urls: ['*://*/*'] },
(details, callback) => {
const h = details.responseHeaders;
delete h['X-Frame-Options'];
callback({ responseHeaders: h });
}
);onHeadersReceived 改响应头能绕过很多限制,但也会让应用脱离真实的安全边界。只在你完全掌控的域名上这么干,别对所有请求一刀切。离线检测
// 渲染进程
window.addEventListener('online', () => setStatus('已连接'));
window.addEventListener('offline', () => setStatus('离线,数据将在恢复后同步'));
// 主进程
const { net } = require('electron');
net.isOnline(); // 只代表「有网络接口」,不代表真的能上网net.isOnline() 只能说明网卡连着,酒店 WiFi 需要认证时它依然返回 true。真正可靠的判断是发一个轻量请求看是否成功。