コンテキスト分離
それは何ですか?
コンテキスト分離とは、preload
スクリプトと Electron の内部ロジックの両方が、webContents
にロードする Web サイトとは別のコンテキストで実行されるようにする機能です。これは、Web サイトが Electron の内部機能や preload スクリプトがアクセスできる強力な API にアクセスするのを防ぐのに役立つため、セキュリティ上重要です。
これは、preload スクリプトがアクセスできる window
オブジェクトが、実際には Web サイトがアクセスできるオブジェクトとは異なるオブジェクトであることを意味します。たとえば、preload スクリプトで window.hello = 'wave'
を設定し、コンテキスト分離が有効になっている場合、Web サイトがアクセスしようとすると window.hello
は未定義になります。
コンテキスト分離は、Electron 12 以降デフォルトで有効になっており、すべてのアプリケーションに推奨されるセキュリティ設定です。
移行
コンテキスト分離がない場合、以前は
window.X = apiObject
を使用して preload スクリプトから API を提供していました。今はどうすればいいですか?
以前:コンテキスト分離が無効
preload スクリプトからレンダラープロセスにロードされた Web サイトに API を公開するのは一般的なユースケースです。コンテキスト分離が無効になっている場合、preload スクリプトはレンダラーと共通のグローバルな window
オブジェクトを共有します。その後、preload スクリプトに任意のプロパティをアタッチできます。
// preload with contextIsolation disabled
window.myAPI = {
doAThing: () => {}
}
doAThing()
関数は、レンダラープロセスで直接使用できます。
// use the exposed API in the renderer
window.myAPI.doAThing()
後:コンテキスト分離が有効
Electron には、これを簡単に行うための専用モジュールがあります。 contextBridge
モジュールを使用すると、preload スクリプトの分離されたコンテキストから Web サイトが実行されているコンテキストに API を安全に公開できます。API は、以前と同様に window.myAPI
で Web サイトからアクセスできるようになります。
// preload with contextIsolation enabled
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('myAPI', {
doAThing: () => {}
})
// use the exposed API in the renderer
window.myAPI.doAThing()
contextBridge
のドキュメントを上記リンク先でよく読んで、その制限事項を完全に理解してください。たとえば、カスタムプロトタイプやシンボルをブリッジ経由で送信することはできません。
セキュリティに関する考慮事項
contextIsolation
を有効にして contextBridge
を使用しただけでは、すべてが安全であるとは限りません。たとえば、このコードは安全ではありません。
// ❌ Bad code
contextBridge.exposeInMainWorld('myAPI', {
send: ipcRenderer.send
})
引数フィルタリングなしに強力な API を直接公開しています。これにより、任意の Web サイトが任意の IPC メッセージを送信できるようになり、それは望ましくありません。IPC ベースの API を公開する正しい方法は、IPC メッセージごとに 1 つのメソッドを提供することです。
// ✅ Good code
contextBridge.exposeInMainWorld('myAPI', {
loadPreferences: () => ipcRenderer.invoke('load-prefs')
})
TypeScript での使用
TypeScript で Electron アプリを構築している場合は、コンテキストブリッジ経由で公開される API に型を追加する必要があります。レンダラーの window
オブジェクトは、宣言ファイルで型を拡張しない限り、正しい型定義を持ちません。
たとえば、次の preload.ts
スクリプトの場合
contextBridge.exposeInMainWorld('electronAPI', {
loadPreferences: () => ipcRenderer.invoke('load-prefs')
})
interface.d.ts
宣言ファイルを作成し、グローバルに Window
インターフェースを拡張できます。
export interface IElectronAPI {
loadPreferences: () => Promise<void>,
}
declare global {
interface Window {
electronAPI: IElectronAPI
}
}
こうすることで、レンダラープロセスでスクリプトを記述するときに、TypeScript コンパイラーがグローバルな window
オブジェクトの electronAPI
プロパティを認識するようになります。
window.electronAPI.loadPreferences()