Eletron
原理
electron 基本原理
- app 模块,它控制应用程序的事件生命周期。
- BrowserWindow 模块,它创建和管理应用程序 窗口。
js
// index.js
// commonjs 写法
const { app, BrowserWindow } = require('electron')
const path = require('node:path')
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
// console.log(path.join(__dirname, 'preload.js'))
// D:\\WorkSpace\\1MOM\\my-electron-app\\preload.js
win.loadFile('index.html')
}
//esm 写法
import { fileURLToPath } from 'node:url'
import {app, BrowserWindow, ipcMain} from "electron"
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: fileURLToPath(new URL('./preload.js',import.meta.url).href)
// 等同
// preload: fileURLToPath(import.meta.resolve('./preload.js'))
}
})
// console.log("new URL('./preload.js',import.meta.url)",new URL('./preload.js',import.meta.url))//1
// console.log("new URL('./preload.js',import.meta.url).href", new URL('./preload.js',import.meta.url).href) //2
// console.log("import.meta.resolve('./preload.js')",import.meta.resolve('./preload.js'))//3
// 2等同3
// console.log("fileURLToPath(new URL('./preload.js',import.meta.url).href)",fileURLToPath(new URL('./preload.js',import.meta.url).href))//4
// console.log("import.meta.resolve('./preload.js')",fileURLToPath(import.meta.resolve('./preload.js')))//5
// 4等同5
win.loadFile('index.html')
}
// 共同
app.whenReady().then(() => {
createWindow()
ipcMain.handle('ping', () => 'pong')
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
- 使用预加载脚本来增强渲染器:为了将 Electron 的不同类型的进程桥接在一起,我们需要使用被称为 预加载 的特殊脚本。
js
// preload.js
const { contextBridge,ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron,
ping: () => ipcRenderer.invoke('ping')
// 除函数之外,我们也可以暴露变量
})
- 渲染进程
js
// renderer.js
const information = document.getElementById('info')
information.innerText = `本应用正在使用
Chrome (v${versions.chrome()}),
Node.js (v${versions.node()}), 和
Electron (v${versions.electron()})`
const func = async () => {
const response = await window.versions.ping()
console.log(response) // 打印 'pong'
}
func()
实现原理
渲染进程对 Node.js API 的直接访问
- nodeIntegration: true
- Electron 使用 V8 引擎同时运行 Chromium 和 Node.js,这使得在渲染进程中可以直接注入 Node.js 的核心模块。
- Electron 会通过注入机制将 Node.js 的全局变量(如 require、process)添加到渲染进程的 JavaScript 上下文中,使得你可以像在 Node.js 环境中一样访问这些模块。
- contextIsolation: false
- 禁用了上下文隔离,网页 JavaScript 和 Electron 的内部上下文运行在同一个环境中,直接共享全局对象。
- 由于没有隔离,网页的 JS 代码和 Electron 注入的 Node.js API(如 require、process)运行在同一作用域中,开发者可以直接调用 Node.js 的功能。
- 安全性问题
- 直接访问 Node.js:因为 nodeIntegration: true 允许网页 JavaScript 访问 Node.js API,这意味着任何在渲染进程中运行的脚本(包括第三方库或恶意代码)都可以执行文件操作、网络请求或访问敏感数据。
- 缺乏隔离:contextIsolation: false 会进一步增加安全风险,因为它允许不受信任的网页代码直接与 Node.js API 交互。
- 技术细节的核心原理
- Bridge Injection: Electron 创建了一个桥接层,将 Node.js 的模块加载系统注入到渲染进程的上下文中。
- 全局对象挂载:
- global 对象(Node.js)与 window 对象(DOM)合并,允许 JS 脚本调用 Node.js API。
- 例如,require('fs') 会通过 Node.js 的模块系统加载模块,而这些功能本应不存在于普通浏览器环境中。
- 内置模块加载器:
- Electron 在渲染进程启动时会加载内置的模块加载器,将 Node.js 和 Web 环境无缝结合。
- 建议
- 实际开发中不推荐使用此配置,建议通过 preload 脚本显式暴露必要的 API 并启用 contextIsolation 来保障安全性。
实践
进程之间的通信
渲染进程 -> 主进程 通信
- invoke / handle (通过 channel 向主过程发送消息,并异步等待结果)js
// 渲染进程 import {ipcRenderer} from 'electron' async () => { const result = await ipcRenderer.invoke('my-invokable-ipc', arg1, arg2) // ... } // 主进程 import {ipcMain} from 'electron' ipcMain.handle('my-invokable-ipc', async (event, ...args) => { const result = await somePromise(...args) return result })
- send / on (通过channel向主进程发送异步消息,可以发送任意参数)js
// 渲染进程 import {ipcRenderer} from 'electron' ipcRenderer.send(channel, ...args) // 主进程 import {ipcMain} from 'electron' ipcMain.on(channel, listener)
- postMessage / onjs
// 渲染进程 import {ipcRenderer} from 'electron' const channel = new MessageChannel() const port1 = channel.port1 const port2 = channel.port2 port2.postMessage({ answer: 42 }) ipcRenderer.postMessage('port', null, [port1]) // 主进程 import {ipcMain} from 'electron' ipcMain.on('port', (event) => { const port = event.ports[0] port.on('message', (event) => { const data = event.data }) port.start() })
主进程 -> 渲染进程 通信
- send / onjs
// 主进程 import { BrowserWindow } from 'electron' const allWindows = BrowserWindow.getAllWindows() for (const win of allWindows) { win.webContents.send("browserWindow-webContents", value) } // 渲染进程 import {ipcRenderer} from 'electron' ipcRenderer.on("browserWindow-webContents",(value)=>{ console.log(value) })
- postMessage / onjavascript
const { BrowserWindow, app, ipcMain, MessageChannelMain } = require('electron') app.whenReady().then(async () => { // Worker 进程是一个隐藏的 BrowserWindow // 它具有访问完整的Blink上下文(包括例如 canvas、音频、fetch()等)的权限 const worker = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } }) await worker.loadFile('worker.html') // main window 将发送内容给 worker process 同时通过 MessagePort 接收返回值 const mainWindow = new BrowserWindow({ webPreferences: { nodeIntegration: true } }) mainWindow.loadFile('app.html') // 在这里我们不能使用 ipcMain.handle() , 因为回复需要传输 // MessagePort. // 监听从顶级 frame 发来的消息 mainWindow.webContents.mainFrame.ipc.on('request-worker-channel', (event) => { // 建立新通道 ... const { port1, port2 } = new MessageChannelMain() // ... 将其中一个端口发送给 Worker ... worker.webContents.postMessage('new-client', null, [port1]) // ... 将另一个端口发送给主窗口 event.senderFrame.postMessage('provide-worker-channel', null, [port2]) // 现在主窗口和工作进程可以直接相互通信,无需经过主进程! }) }) // 渲染进程 1 const { ipcRenderer } = require('electron') const doWork = (input) => { // 一些对CPU要求较高的任务 return input * 2 } // 我们可能会得到多个 clients, 比如有多个 windows, // 或者假如 main window 重新加载了. ipcRenderer.on('new-client', (event) => { const [ port ] = event.ports port.onmessage = (event) => { // 事件数据可以是任何可序列化的对象 (事件甚至可以 // 携带其他 MessagePorts 对象!) const result = doWork(event.data) port.postMessage(result) } }) // 渲染进程 2 const { ipcRenderer } = require('electron') // 我们请求主进程向我们发送一个通道 // 以便我们可以用它与 Worker 进程建立通信 ipcRenderer.send('request-worker-channel') ipcRenderer.once('provide-worker-channel', (event) => { // 一旦收到回复, 我们可以这样做... const [ port ] = event.ports // ... 注册一个接收结果处理器 ... port.onmessage = (event) => { console.log('received result:', event.data) } // ... 并开始发送消息给 work! port.postMessage(21) })
- postmessage 优势
- 传输复杂数据(如果数据中包含非可序列化对象(如 Map、Set、ArrayBuffer 或循环引用)或需要更高效的数据传输)
- 兼容window.postMessage,支持与 MessagePort 交互的场景
- 参考:
- 其他通信参考
主进程 api
ipcMain
- ipcMain.handle(channel, listener)
- 为一个 invokeable的IPC 添加一个handler。 每当一个渲染进程调用 ipcRenderer.invoke(channel, ...args) 时这个处理器就会被调用。
js
import {ipcMain} from 'electron'
ipcMain.handle('my-invokable-ipc', async (event, ...args) => {
const result = await somePromise(...args)
return result
})
app
- app.getAppPath() 返回 string - 当前应用程序目录。
js
console.log(app.getAppPath())
// D:\WorkSpace\Project_Name
- app.getPath(name)
- name string - 您可以通过名称请求以下路径:
- appData 每个用户的应用程序数据目录,默认情况下指向:
userData
储存你应用程序配置文件的文件夹,默认是 appData 文件夹附加应用的名称 按照习惯用户存储的数据文件应该写在此目录,同时不建议在这写大文件,因为某些环境会备份此目录到云端存储。- sessionData 此目录存储由 Session 生成的数据,例如 localStorage,cookies,磁盘缓存,下载的字典,网络 状态,开发者工具文件等。 默认为 userData 目录。 Chromium 可能在此处写入非常大的磁盘缓存,因此,如果您的应用不依赖于浏览器存储(如 localStorage 或 cookie)来保存用户数据,建议将此目录设置为其他位置,以避免污染 userData 目录。
- temp 临时文件夹
- downloads 用户下载目录的路径
- logs 应用程序的日志文件夹
- name string - 您可以通过名称请求以下路径:
js
import { app } from 'electron'
console.log("app.getPath('userData')", app.getPath('userData'))
console.log("app.getPath('appData')", app.getPath('appData'))
console.log("app.getPath('sessionData')", app.getPath('sessionData'))
console.log("app.getPath('temp')", app.getPath('temp'))
console.log("app.getPath('downloads')", app.getPath('downloads'))
console.log("app.getPath('logs')", app.getPath('logs'))
// app.getPath('userData') C:\Users\User_Name\AppData\Roaming\App_Name
// app.getPath('appData') C:\Users\User_Name\AppData\Roaming
// app.getPath('sessionData') C:\Users\User_Name\AppData\Roaming\App_Name
// app.getPath('temp') C:\Users\ABCDE~1.FGH\AppData\Local\Temp
// app.getPath('downloads') C:\Users\User_Name\Downloads
// app.getPath('logs') C:\Users\User_Name\AppData\Roaming\App_Name\logs
- 补充:8.3 文件名格式
- C:\Users\ABCDE~1.FGH 是一种 8.3 文件名格式,也称为 短文件名格式,
- 这种格式限制文件或目录名为 8 个字符,扩展名为 3 个字符
- Electron 使用的 app.getPath() 方法依赖于操作系统 API,可能返回短文件名以确保兼容旧版 Windows 和 MS-DOS 等不支持长文件名的系统。
- app.getVersion()
- 返回 string - 加载应用程序的版本号。 如果应用程序的 package. json 文件中找不到版本号, 则返回当前包或者可执行文件的版本。
- app.commandLine 操作Chromium读取的应用程序的命令行参数js
app.commandLine.appendSwitch('charset', 'utf8') app.commandLine.appendSwitch('lang', 'en')
- app.requestSingleInstanceLock
- 如果当前进程是应用程序的主要实例,则此方法返回true,同时你的应用会继续运行。
- 如果当它返回 false如果你的程序没有取得锁,它应该立刻退出
js
const lock= app.requestSingleInstanceLock({ appCode: "test" })
if(lock){
// 继续
}else{
app.quit()
}
BrowserWindow
- BaseWindowConstructorOptions(类参数)
- webPreferences
- partition
- 决定页面所使用的 session,不同的 partition 值可以隔离页面的会话数据;
- 如果 partition 以persist:开头, 那么该页面会使用持久化的会话,这些数据会保存在磁盘中,即使应用关闭后再次启动,这些数据仍然存在;
- 如果没有 persist: 前缀, 页面会使用内存中的会话. 通过分配相同的 partition, 多个页可以共享同一会话;
- 如果没有设置 partition,页面将使用 Electron 提供的 默认会话
- webview
- 启用后,可以在渲染进程中使用
<webview>
标签来嵌入其他网页。 <webview>
标签类似于 iframe,但具有更多的功能,例如独立的上下文和进程隔离,提供额外的控制能力,例如与主进程通信。- 利用 will-attach-webview 事件可帮助防范潜在风险。
- 参考:WebView性能、体验分析与优化
- 启用后,可以在渲染进程中使用
- skipTaskbar 是否在任务栏中显示窗口。默认为 false
- partition
- frame 指定 false 以创建无框架窗口
- webPreferences
- 实例
- win <=
const win = new BrowserWindow()
- win.webContent 渲染以及控制 web 页面
- win <=
win.webContent
- win.webContents.setZoomFactor
- 设置窗口缩放比
- win.webContent.setWindowOpenHandler
- 用于处理由窗口内的网页触发的 window.open() 调用。
- 通过这个方法,开发者可以完全控制新窗口的行为,例如允许、拒绝打开,或者对新窗口的配置进行自定义
- win.webContents.session
- setPermissionCheckHandler(handler: (webContents, permission, requestingOrigin) => boolean)
- 用于拦截和处理权限检查。
- 在特定权限(如通知、摄像头、麦克风等)被请求时,调用自定义逻辑以决定是否允许。
- 返回值为布尔值:true 表示允许,false 表示拒绝。
jswin.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin) => { if (permission === 'notifications') return true; // 允许通知 return false; // 禁止其他权限 });
- setDevicePermissionHandler(handler: (details) => boolean)
- 用于处理设备权限(如访问 USB、hid(蓝牙)、串口设备等)的请求。
- 返回值为布尔值:true 表示允许访问设备,false 表示拒绝。
- 参数 details 提供有关请求的信息,包括设备类型和来源。
jswin.webContents.session.setDevicePermissionHandler((details) => { console.log(details.deviceType); // 打印请求的设备类型 return details.deviceType === 'usb'; // 仅允许 USB 设备 });
- setCertificateVerifyProc(proc: (request, callback) => void)
- 自定义证书验证逻辑。在安全连接中遇到证书问题(如自签名证书或无效证书)时调用,用于决定是否信任该证书。
jswin.webContents.session.setCertificateVerifyProc((request, callback) => { // 每当一个服务器证书请求验证,proc 将被这样 proc(request, callback) 调用,为 session 设置证书验证过程。 // 回调函数 callback(0) 接受证书,callback(-2) 驳回证书。 console.log(request.hostname); // 打印请求的主机名 if (request.hostname === 'trusted.com') { callback(0); // 信任证书 } else { callback(-2); // 拒绝证书 } });
- setCertificateVerifyProc 解决内网 HTTPS 请求问题:
- SSL 验证的核心是浏览器通过检查证书链的有效性,确保通信的安全性和合法性。
- setCertificateVerifyProc 替代了 Chromium 内部的默认证书验证流程,使你可以完全自定义验证逻辑。callback(0) 明确告诉 Electron 跳过所有验证,即认为任何证书都是合法的。
- 完整安全验证js
const { session } = require('electron'); // 设置自定义证书验证过程 session.defaultSession.setCertificateVerifyProc((request, callback) => { const { hostname, certificate, errorCode } = request; // 定义内网可信的域名列表(根据实际情况配置) const trustedInternalDomains = ['intranet.local', 'internal.company.com']; // 定义内网的可信证书指纹(SHA-256) const trustedFingerprints = [ '11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44', ]; // 定义内网环境的 IP 地址范围(如 10.0.0.0/8 或 192.168.0.0/16 等) const isInternalIP = (hostname) => { return /^10\.\d+\.\d+\.\d+$/.test(hostname) || /^192\.168\.\d+\.\d+$/.test(hostname); }; // 1. 允许内网可信域名 if (trustedInternalDomains.includes(hostname)) { console.log(`Trusted internal domain: ${hostname}`); callback(0); // 信任 return; } // 2. 允许内网可信证书指纹 if (trustedFingerprints.includes(certificate.fingerprint)) { console.log(`Trusted certificate fingerprint: ${certificate.fingerprint}`); callback(0); // 信任 return; } // 3. 允许特定的内网 IP 地址 if (isInternalIP(hostname)) { console.log(`Trusted internal IP: ${hostname}`); callback(0); // 信任 return; } // 4. 在开发环境中可以放宽限制 if (process.env.NODE_ENV === 'development') { console.warn(`Development mode: Ignoring untrusted certificate for ${hostname}`); callback(0); // 信任 return; } // 默认拒绝未知的证书 console.error(`Untrusted certificate for ${hostname}`); callback(-2); // 拒绝 });
- win.webContents.session.on('select-serial-port',...)
- setPermissionCheckHandler(handler: (webContents, permission, requestingOrigin) => boolean)
session
session 管理浏览器会话、cookie、缓存、代理设置等。
- 创建自定义会话
js
// 从指定的 partition(webPreferences: {partition:"***"})获取一个对应的会话 (session) 对象
const customSession = session.fromPartition('persist:user1');
const customWindow = new BrowserWindow({
webPreferences: {
session: customSession,
},
});
// 动态设置是否始终为 HTTP NTLM 发送凭据或协商身份验证。
session.defaultSession.allowNTLMCredentialsForDomains('*')
process
Electron's process 对象继承 Node.js process object。 它新增了事件、属性和方法
- process.resourcesPath 只读
- 一个 string 值,表示 resources 目录的路径
- 这个目录通常包含 Electron 应用程序的静态资源文件。
- 在开发模式下,process.resourcesPath 通常指向 Electron 自带的 resources 目录,而不是你的项目目录。
- 在生产模式下,process.resourcesPath 指向 Electron 应用的打包资源路径。
shell
js
import { shell } from 'electron'
shell.openPath('path') //以桌面的默认方式打开给定的文件。
shell.openExternal('https://github.com') // 打开网址
crashReporter
js
// 将崩溃日志提交给远程服务器
import { crashReporter } from 'electron'
crashReporter.start({
uploadToServer: "",
})
渲染进程 api
ipcRenderer
- ipcRenderer.invokejs
import {ipcRenderer} from 'electron' async () => { const result = await ipcRenderer.invoke('my-invokable-ipc', arg1, arg2) // ... }
window.open
从渲染进程打开窗口 window.open(url[, frameName][, features])
开发Lib
- build工具
- @tomjs/electron-devtools-installer(中文 )为 Electron 安装 Chrome 扩展
- vite-plugin-electron vite支持electron
- electron-log electron 打印日志
- winax Windows C++ Node.JS 插件,实现 COM IDispatch 对象包装器,模拟 cscript.exe 上的 ActiveXObject
- config -> electron-store 主进程进行数据持久化存储
- serialport 窜口模块
electron-builder
electron forge
- 使用
- 脚手架初始化一个项目 vite + ts 初始化bash
npm init electron-app@latest my-new-app -- --template=vite-typescript
- 在原项目使用 electron-forgebash
npm install --save-dev @electron-forge/cli npx electron-forge import
json// package.json //... "scripts": { "start": "electron-forge start", "package": "electron-forge package", "make": "electron-forge make" }, //...
bash# 分发文件 运行了 electron-forge make 命令 npm run make
- 支持vitebash
npm install --save-dev @electron-forge/plugin-vite
- 脚手架初始化一个项目 vite + ts 初始化
- bug
- An unhandled rejection has occurred inside Forge:Error: Unable to use specified module loaders for ".ts".
- 将
forge.config.ts
改为forge.config.mts
解决 不知道为什么识别不.ts
- 参考:
- 详解 Electron 打包 Electron Builder、Electron Forge对比
- 详解 Electron 中的 asar 文件 @electron/asar
- 前端工程化之强大的glob语法
config / electron-store
- electron-store 相对于 localStorage 的优势
- localStorage 仅在浏览器进程中工作。
- localStorage 的容错能力较差,因此如果您的应用程序遇到错误并意外退出,您可能会丢失数据。
- localStorage 仅支持持久字符串。该模块支持任何 JSON 支持的类型。
- 该模块的 API 更好。您可以设置和获取嵌套属性。您可以设置默认初始配置。
winax COM接口
- winax
- Windows C++ Node.JS 插件,实现 COM IDispatch 对象包装器,模拟 cscript.exe 上的 ActiveXObject
- 涉及名词解释
- COM 是微软开发的一种组件软件框架,用于在 Windows 应用程序之间提供互操作性。它允许不同语言编写的程序共享功能和对象。
- IDispatch: 这是 COM 的一个接口,支持运行时动态调用(例如方法调用、属性访问)。
- ActiveXObject 是 JavaScript 在 Windows 环境(如 Internet Explorer 或 cscript.exe 脚本宿主)中用于访问和操作 COM 对象的内置接口。
new ActiveXObject('Scripting.FileSystemObject')
- cscript.exe: cscript 是 Windows 提供的命令行脚本解释器,用于运行 .vbs 或 .js 脚本。它允许开发者直接在 Windows 环境下通过脚本调用 COM 接口。
- COM 集成到 Node.js: winax 实现了一个 C++ 插件,用于在 Node.js 中调用 COM 对象。它模拟了 ActiveXObject 的功能,让 Node.js 程序能够像在 cscript.exe 中一样通过 COM 操作 Windows 应用。
- ActiveXObject 是一个专门用于 Windows 脚本的功能,但 Node.js 本身没有类似能力。winax 实际上复现了 ActiveXObject 的核心功能,即通过 IDispatch 动态操作 COM 对象。因此,这种实现方式被称为“模拟”。
- winax作用
- Node.js 的 npm 包,专门帮助在 Windows 系统上通过
COM(Component Object Model 组件对象模型)接口
与本地应用程序进行交互
。 - 特别是在 Electron 项目中,winax 常用于自动化任务,比如控制 Microsoft Office 等 Windows 原生应用程序,调用 Windows 系统级 API,访问文件系统或服务等。
- 通过 JavaScript 调用 Windows 的 COM 对象、访问和执行 COM 方法、属性以及处理事件。
js/** * Create ADO Connection throw global function * 创建ADO连接抛出全局函数 */ require('winax'); var con = new ActiveXObject('ADODB.Connection'); /** * Or using Object prototype * 或者使用对象原型 */ var winax = require('winax'); var con = new winax.Object('ADODB.Connection'); const app = new winax.Object('Lppx2.Application', { activate: true, }) /** * Release COM objects (but other temporary objects may be keep references too) * 释放 COM 对象(但其他临时对象也可能保留引用) */ winax.release(con, rs, fields)
- Node.js 的 npm 包,专门帮助在 Windows 系统上通过
- 其他
- COM
- 参考:COM 官方
- Lppx2.Application
- COM 对象的 ProgID(程序标识符),标识了应用程序的 COM 接口,用来实例化应用对象
- 'Lppx2.Application' 指的是一个通过 COM 对象与某个特定 Windows 应用程序进行交互的实例。
- 參考:
- codesoft
- CodeSoft 的 COM 接口文档并没有完全公开到网上,通常只有在购买了产品并成为其授权用户之后,才能获得开发者文档。
- 注册表查找:
- 1、通过注册表查找 ProgID
- 按下 Win + R,输入 regedit,然后按 Enter,打开注册表编辑器。
- HKEY_CLASSES_ROOT\CLSID:包含注册的所有 COM 类标识符(CLSID)信息。
- HKEY_CLASSES_ROOT<ProgID>:包含与 ProgID 相关的详细信息。
- 使用 Ctrl + F 搜索特定的 ProgID,例如 Excel.Application,或者直接搜索软件相关的名称,注册表会返回匹配项。
- 2、OLE/COM Object Viewer。
- 1、通过注册表查找 ProgID
- COM
serialport 窜口
js
import { SerialPort, type SerialPortOpenOptions } from 'serialport'
// 连接窜口
const port = new SerialPort({
path:'/dev/ttyUSB',
baudRate: 9600, // 波特率
dataBits: 8, // 数据位:5, 6, 7, 8
parity: 'none', // 校验位:'none', 'even', 'odd', 'mark', 'space'
stopBits: 1, // 停止位:1, 1.5, 2
flowControl: false // 是否启用流控制
});
// 窜口列表
SerialPort.list()
// 发送数据
port?.write(res)
// 关闭连接
port?.close((err) => {})
// 连接成功 - 监听事件
port?.on('open', () => { console.log(`Connected`) })
// 报错 - 监听事件
port?.on('error', (err) => { port = null })
// 接受数据 - 监听事件
port.on('data', (data) => {})
// 断开连接 - 监听事件
port?.on('close', ()=>{ port = null })
electron-updater 更新应用程序
参考:Auto Update
其他框架
- NW.js 中文
- Tauri
- NW.js 通过修改源码合并了 Node.js 和 Chromium 的事件循环机制;
- Electron 则是通过各操作系统的,打通了 Node.js 和 Chromium 的事件循环机制(新版本的 Electron 是通过一个独立的线程完成这项工作的)。
- Tauri使用Rust作为底层,通过Web技术(HTML、CSS和JavaScript)构建用户界面。它与Chromium和Node.js没有直接依赖关系,因此可以更轻量级和高效。
- 参考:NW.js和Electron优缺点综合对比
功能实现思路
启动窗口
- 是否单例锁
- 启动时向 Chromium 命令行传递参数
- 崩溃日志
- 启动窗口
- 自定义控制新窗口的行为
- 注册监听事件
- session 权限设置
- 计算窗口大小
- 从partition获取session,安装浏览器插件
- 设置代理(app、session)
- 初始化托盘 Tray
- 注册 ipcMain
- 注册版本升级
- 注册全局快捷键
关闭窗口
- 退出登录
- 注销监听事件
- 注销 session 权限设置
- 移除所有事件
win.removeAllListeners()
- 注销版本升级
- 注销全局快捷键
import { globalShortcut } from 'electron'
- 注销 ipcMain
- 第三方软件quit
接口请求
- 登录:公钥加密密码
- 发起登录请求:
- fetch:
session.defaultSession.fetch(url,opts)
- opts:
{header:{Accept: 'application/json; charset=utf-8',}}
等其他请求头
- fetch:
- 存储token到store
- 其他接口请求:
- 获取token
header:{Authorization:
Bearer ${token}}
+ 其他请求头
获取token
- 1、是否有accessToken,
- 有 ✅
- 没有:则没有登录
- 2、是否过期
- 未过期 ✅
- 过期:如果当前时间 > 过期时间-间隔时间
- 3、是否有 tokenRefreshTask
- 有 ✅
- 没有
- 4、是否有refreshToken:
- 没有:没有登录
- 有:tokenRefreshTask
- 5、发起刷新token请求
- tokenRefreshTask(tokenData.accessToken, tokenData.refreshToken)
- 成功:返回token✅