跳到主要内容

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

提供给exposeInMainWorldapi 必须是 FunctionstringnumberArrayboolean,或者一个对象,其键是字符串,值是 FunctionstringnumberArrayboolean 或符合相同条件的嵌套对象。

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')
}
})