Skip to main content

contextBridge

History

격리된 컨텍스트 간에 안전하고 양방향이며 동기적인 브리지를 생성한다.

프로세스: 렌더러

아래는 격리된 프리로드 스크립트에서 렌더러로 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에 제공되는 apiFunction, 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')
}
})