Context Isolation上下文隔离
What is it?这是怎么一回事?
Context Isolation is a feature that ensures that both your 上下文隔离是一项功能,可确保您的preload
scripts and Electron's internal logic run in a separate context to the website you load in a webContents. preload
脚本和Electron的内部逻辑在webContents中加载的网站的单独上下文中运行。This is important for security purposes as it helps prevent the website from accessing Electron internals or the powerful APIs your preload script has access to.这对于安全目的很重要,因为它有助于防止网站访问Electron内部或预加载脚本可以访问的强大API。
This means that the 这意味着预加载脚本可以访问的window
object that your preload script has access to is actually a different object than the website would have access to. window
对象实际上与网站可以访问的对象不同。For example, if you set 例如,如果您在预加载脚本中设置window.hello = 'wave'
in your preload script and context isolation is enabled, window.hello
will be undefined if the website tries to access it.window.hello = 'wave'
,并且启用了上下文隔离,则如果网站试图访问window.hello
,则window.hello
将未定义。
Context isolation has been enabled by default since Electron 12, and it is a recommended security setting for all applications.自Electron 12以来,默认情况下启用了上下文隔离,这是所有应用程序的推荐安全设置。
Migration迁移
Without context isolation, I used to provide APIs from my preload script using在没有上下文隔离的情况下,我使用window.X = apiObject
.window.X=apiObject
从预加载脚本中提供API。Now what?现在呢?
Before: context isolation disabled之前:禁用上下文隔离
Exposing APIs from your preload script to a loaded website in the renderer process is a common use-case. 在渲染器过程中,将预加载脚本中的API公开到加载的网站是一个常见的用例。With context isolation disabled, your preload script would share a common global 禁用上下文隔离后,预加载脚本将与渲染器共享一个公共全局window
object with the renderer. window
对象。You could then attach arbitrary properties to a preload script:然后可以将任意属性附加到预加载脚本:
// preload with contextIsolation disabled
window.myAPI = {
doAThing: () => {}
}
The 然后,doAThing()
function could then be used directly in the renderer process:doAThing()
函数可以直接在渲染器进程中使用:
// use the exposed API in the renderer
window.myAPI.doAThing()
After: context isolation enabled之后:启用上下文隔离
There is a dedicated module in Electron to help you do this in a painless way. Electron中有一个专门的模块,可以帮助您以一种无痛的方式完成这项工作。The contextBridge module can be used to safely expose APIs from your preload script's isolated context to the context the website is running in. contextBridge模块可用于安全地将API从预加载脚本的隔离上下文公开到网站运行的上下文。The API will also be accessible from the website on 该API也可以从window.myAPI
just like it was before.window.myAPI
上的网站访问,就像以前一样。
// preload with contextIsolation enabled
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('myAPI', {
doAThing: () => {}
})
// use the exposed API in the renderer
window.myAPI.doAThing()
Please read the 请阅读上面链接的contextBridge
documentation linked above to fully understand its limitations. contextBridge
文档,以充分了解其局限性。For instance, you can't send custom prototypes or symbols over the bridge.例如,您不能通过桥发送自定义原型或符号。
Security considerations安全考虑
Just enabling 仅仅启用contextIsolation
and using contextBridge
does not automatically mean that everything you do is safe. contextIsolation
和使用contextBridge
并不意味着您所做的一切都是安全的。For instance, this code is unsafe.例如,此代码不安全。
// ❌ Bad code
contextBridge.exposeInMainWorld('myAPI', {
send: ipcRenderer.send
})
It directly exposes a powerful API without any kind of argument filtering. 它直接公开了一个强大的API,而不需要任何类型的参数筛选。This would allow any website to send arbitrary IPC messages, which you do not want to be possible. 这将允许任何网站发送任意IPC消息,这是您不希望的。The correct way to expose IPC-based APIs would instead be to provide one method per IPC message.公开基于IPC的API的正确方法是为每个IPC消息提供一个方法。
// ✅ Good code
contextBridge.exposeInMainWorld('myAPI', {
loadPreferences: () => ipcRenderer.invoke('load-prefs')
})
Usage with TypeScript与TypeScript一起使用
If you're building your Electron app with TypeScript, you'll want to add types to your APIs exposed over the context bridge. 如果您正在使用TypeScript构建您的Electron应用程序,那么您需要将类型添加到通过上下文桥公开的API中。The renderer's 除非使用声明文件扩展类型,否则渲染器的window
object won't have the correct typings unless you extend the types with a declaration file.window
对象将没有正确的类型。
For example, given this 例如,给定此preload.ts
script:preload.ts
脚本:
contextBridge.exposeInMainWorld('electronAPI', {
loadPreferences: () => ipcRenderer.invoke('load-prefs')
})
You can create a 您可以创建一个renderer.d.ts
declaration file and globally augment the Window
interface:renderer.d.ts
声明文件并全局扩展Window
界面:
export interface IElectronAPI {
loadPreferences: () => Promise<void>,
}
declare global {
interface Window {
electronAPI: IElectronAPI
}
}
Doing so will ensure that the TypeScript compiler will know about the 这样做将确保TypeScript编译器在渲染器进程中编写脚本时了解全局electronAPI
property on your global window
object when writing scripts in your renderer process:window
对象上的electronAPI
属性:
window.electronAPI.loadPreferences()