contextBridge
History
Version(s) | Changes |
---|---|
None |
|
격리된 컨텍스트 간에 안전하고 양방향이며 동기적인 브리지를 생성한다.
프로세스: 렌더러
아래는 격리된 프리로드 스크립트에서 렌더러로 API를 노출하는 예제다:
// 프리로드 (격리된 세계)
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld(
'electron',
{
doThing: () => ipcRenderer.send('do-a-thing')
}
)
// 렌더러 (메인 세계)
window.electron.doThing()
용어 사전
메인 월드
"메인 월드"는 주요 렌더러 코드가 실행되는 자바스크립트 컨텍스트를 의미한다. 기본적으로 렌더러에서 로드하는 페이지의 코드는 이 세계에서 실행된다.
격리된 세계
webPreferences
에서 contextIsolation
을 활성화하면 (이 설정은 Electron 12.0.0부터 기본값으로 적용됨), preload
스크립트는 "격리된 세계(Isolated World)"에서 실행된다. 컨텍스트 격리와 그 영향에 대해 더 자세히 알고 싶다면 보안 문서를 참고하면 된다.
메서드
contextBridge
모듈은 다음과 같은 메서드를 제공한다:
contextBridge.exposeInMainWorld(apiKey, api)
apiKey
string - API를window
객체에 주입할 때 사용할 키. 이 API는window[apiKey]
로 접근할 수 있다.api
any - 주입할 API. 이 API가 무엇이고 어떻게 동작하는지에 대한 자세한 내용은 아래에서 확인할 수 있다.
contextBridge.exposeInIsolatedWorld(worldId, apiKey, api)
worldId
Integer - API를 주입할 세계의 ID.0
은 기본 세계이며,999
는 Electron의contextIsolation
기능이 사용하는 세계. 999를 사용하면 프리로드 컨텍스트에 객체를 노출. 고립된 세계를 생성할 때는 1000 이상을 사용하는 것을 권장.apiKey
string -window
에 API를 주입할 때 사용할 키. API는window[apiKey]
로 접근 가능.api
any - 주입할 API. 이 API가 무엇이고 어떻게 동작하는지에 대한 자세한 정보는 아래에서 확인 가능.
사용법
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 (isolated world id1004에서)
window.electron.doThing()
API 함수
contextBridge
를 통해 바인딩하는 Function
값은 컨텍스트가 격리된 상태를 유지하도록 Electron을 통해 프록시 처리된다. 이로 인해 몇 가지 주요 제한 사항이 발생하며, 아래에서 이를 자세히 설명한다.
파라미터 / 에러 / 반환 타입 지원
파라미터, 에러, 반환 값은 브릿지를 통해 전송될 때 복사되기 때문에 특정 타입만 사용할 수 있다. 기본적으로, 사용하려는 타입이 직렬화(serialize)와 역직렬화(deserialize)가 가능하고 동일한 객체로 복원될 수 있다면 사용할 수 있다. 아래 표는 지원되는 타입을 완전히 이해하기 위해 제공한다:
타입 | 복잡도 | 파라미터 지원 | 반환 값 지원 | 제한 사항 |
---|---|---|---|---|
string | 단순 | ✅ | ✅ | 없음 |
number | 단순 | ✅ | ✅ | 없음 |
boolean | 단순 | ✅ | ✅ | 없음 |
Object | 복잡 | ✅ | ✅ | 키는 이 표의 "단순" 타입만 지원해야 한다. 값은 이 표에서 지원되는 타입이어야 한다. 프로토타입 수정은 제거된다. 커스텀 클래스를 전송하면 값은 복사되지만 프로토타입은 복사되지 않는다. |
Array | 복잡 | ✅ | ✅ | Object 타입과 동일한 제한 사항 적용 |
Error | 복잡 | ✅ | ✅ | 에러가 발생하면 복사되며, 다른 컨텍스트에서 발생하기 때문에 메시지와 스택 트레이스가 약간 변경될 수 있다. 또한 에러 객체의 커스텀 프로퍼티는 손실된다. |
Promise | 복잡 | ✅ | ✅ | 없음 |
Function | 복잡 | ✅ | ✅ | 프로토타입 수정은 제거된다. 클래스나 생성자를 전송하면 작동하지 않는다. |
복제 가능 타입 | 단순 | ✅ | ✅ | 링크된 문서에서 복제 가능 타입 참조 |
Element | 복잡 | ✅ | ✅ | 프로토타입 수정은 제거된다. 커스텀 엘리먼트를 전송하면 작동하지 않는다. |
Blob | 복잡 | ✅ | ✅ | 없음 |
Symbol | 해당 없음 | ❌ | ❌ | 심볼은 컨텍스트 간 복사가 불가능하므로 제거된다. |
위 표에 없는 타입은 아마 지원되지 않을 것이다.
ipcRenderer
모듈 전체를 contextBridge
를 통해 객체로 전송하려고 하면, 브릿지의 수신 측에서 빈 객체만 받게 된다. 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 글로벌 심볼 노출하기
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')
}
})