contextBridge
历史
在隔离的上下文之间创建安全、双向、同步的桥梁
进程:渲染器
下面提供了一个如何从隔离的 preload 脚本向渲染器暴露 API 的示例
// Preload (Isolated World)
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld(
'electron',
{
doThing: () => ipcRenderer.send('do-a-thing')
}
)
// Renderer (Main World)
window.electron.doThing()
术语表
主世界 (Main World)
“主世界 (Main World)”是你的主要渲染器代码运行的 JavaScript 上下文。默认情况下,你在渲染器中加载的页面会在此世界中执行代码。
隔离世界 (Isolated World)
当你的 webPreferences
中启用了 contextIsolation
(自 Electron 12.0.0 起,这是默认行为)时,你的 preload
脚本会在“隔离世界 (Isolated World)”中运行。你可以在安全文档中阅读更多关于上下文隔离及其影响的信息。
方法
contextBridge
模块提供以下方法
contextBridge.exposeInMainWorld(apiKey, api)
apiKey
字符串 - 用于将 API 注入到window
对象上的键。API 将通过window[apiKey]
访问。api
任意类型 - 你的 API,有关此 API 可以是什么以及如何工作的更多信息,请参见下文。
contextBridge.exposeInIsolatedWorld(worldId, apiKey, api)
worldId
整数 - 用于注入 API 的世界的 ID。0
是默认世界,999
是 Electron 的contextIsolation
功能使用的世界。使用 999 会为 preload 上下文暴露对象。建议在创建隔离世界时使用 1000+ 的 ID。apiKey
字符串 - 用于将 API 注入到window
对象上的键。API 将通过window[apiKey]
访问。api
任意类型 - 你的 API,有关此 API 可以是什么以及如何工作的更多信息,请参见下文。
contextBridge.executeInMainWorld(executionScript)
实验性
executionScript
对象func
(...args: any[]) => any - 要执行的 JavaScript 函数。此函数将被序列化,这意味着任何绑定的参数和执行上下文都将丢失。args
any[] (可选) - 要传递给提供函数的参数数组。这些参数将根据支持类型的表格在世界之间复制。
返回 any
- 在主世界中执行函数所产生值的副本。参考表格了解值如何在世界之间复制。
用法
API
提供给exposeInMainWorld
的 api
必须是 Function
、string
、number
、Array
、boolean
,或者一个对象,其键是字符串,值是 Function
、string
、number
、Array
、boolean
或符合相同条件的嵌套对象。
Function
类型的值会被代理到另一个上下文,而所有其他值则被**复制**并**冻结**。API 中发送的任何数据/原始类型将变为不可变,桥接两侧的更新不会导致另一侧的更新。
下面展示了一个复杂 API 的示例
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld(
'electron',
{
doThing: () => ipcRenderer.send('do-a-thing'),
myPromises: [Promise.resolve(), Promise.reject(new Error('whoops'))],
anAsyncFunction: async () => 123,
data: {
myFlags: ['a', 'b', 'c'],
bootTime: 1234
},
nestedAPI: {
evenDeeper: {
youCanDoThisAsMuchAsYouWant: {
fn: () => ({
returnData: 123
})
}
}
}
}
)
下面展示了一个 exposeInIsolatedWorld
的示例
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInIsolatedWorld(
1004,
'electron',
{
doThing: () => ipcRenderer.send('do-a-thing')
}
)
// Renderer (In isolated world id1004)
window.electron.doThing()
API 函数
通过 contextBridge
绑定的 Function
类型值会通过 Electron 进行代理,以确保上下文保持隔离。这导致了一些关键限制,我们已在下文列出。
参数 / 错误 / 返回类型支持
由于参数、错误和返回值在通过桥接发送时会被**复制**,因此只有某些类型可以使用。总体而言,如果你想使用的类型可以被序列化和反序列化为同一对象,它就可以工作。下面为了完整性包含了一份类型支持表格
类型 | 复杂性 | 参数支持 | 返回值支持 | 限制 |
---|---|---|---|---|
string | 简单 | ✅ | ✅ | 不适用 |
number | 简单 | ✅ | ✅ | 不适用 |
boolean | 简单 | ✅ | ✅ | 不适用 |
Object | 复杂 | ✅ | ✅ | 键必须使用本表中仅支持“简单”类型。值必须在本表中受支持。原型修改会被丢弃。发送自定义类会复制值但不会复制原型。 |
Array | 复杂 | ✅ | ✅ | 与 Object 类型相同的限制 |
Error | 复杂 | ✅ | ✅ | 抛出的错误也会被复制,这可能导致错误消息和堆栈跟踪因在不同上下文中抛出而略有改变,并且 Error 对象上的任何自定义属性将丢失 |
Promise | 复杂 | ✅ | ✅ | 不适用 |
Function | 复杂 | ✅ | ✅ | 原型修改会被丢弃。发送类或构造函数将不起作用。 |
可克隆类型 (Cloneable Types) | 简单 | ✅ | ✅ | 请参阅关于可克隆类型的链接文档 |
Element | 复杂 | ✅ | ✅ | 原型修改会被丢弃。发送自定义元素将不起作用。 |
Blob | 复杂 | ✅ | ✅ | 不适用 |
Symbol | 不适用 | ❌ | ❌ | Symbol 不能跨上下文复制,因此会被丢弃 |
如果你关心的类型不在上表中,则可能不受支持。
暴露 ipcRenderer
尝试通过 contextBridge
将整个 ipcRenderer
模块作为对象发送会导致桥接的接收端收到一个空对象。完整发送 ipcRenderer
会让任何代码都能发送任何消息,这是一个安全隐患。要通过 ipcRenderer
进行交互,请提供一个安全的包装器,如下所示
// Preload (Isolated World)
contextBridge.exposeInMainWorld('electron', {
onMyEventName: (callback) => ipcRenderer.on('MyEventName', (e, ...args) => callback(args))
})
// Renderer (Main World)
window.electron.onMyEventName(data => { /* ... */ })
暴露 Node 全局符号
preload 脚本可以使用 contextBridge
来让你的渲染器访问 Node API。上面描述的支持类型表格也适用于你通过 contextBridge
暴露的 Node API。请注意,许多 Node API 授予对本地系统资源的访问权限。对于暴露给不受信任的远程内容的全局对象和 API,请务必谨慎。
const { contextBridge } = require('electron')
const crypto = require('node:crypto')
contextBridge.exposeInMainWorld('nodeCrypto', {
sha256sum (data) {
const hash = crypto.createHash('sha256')
hash.update(data)
return hash.digest('hex')
}
})