oRPC Browser Adapter

Type-safe communication between browser scripts using Message Port Adapter.

SKILL.md

GitHub

Browser Adapter

Enable type-safe communication between browser scripts using the Message Port Adapter.

Between Extension Scripts

Browser extension Message Passing API doesn't support transferring binary data. Workaround: extend the RPC JSON Serializer to encode binary data as Base64.

Server (background/content script):

import { RPCHandler } from '@orpc/server/message-port'
import { onError } from '@orpc/server'

const handler = new RPCHandler(router, {
  interceptors: [onError(e => console.error(e))],
})

browser.runtime.onConnect.addListener((port) => {
  handler.upgrade(port, { context: {} })
})

Client:

import { RPCLink } from '@orpc/client/message-port'

const port = browser.runtime.connect()

const link = new RPCLink({ port })

Window to Window

Opener:

const handler = new RPCHandler(router)

window.addEventListener('message', (event) => {
  if (event.data instanceof MessagePort) {
    handler.upgrade(event.data, { context: {} })
    event.data.start()
  }
})

window.open('/example/popup', 'popup', 'width=680,height=520')

Popup:

const { port1: serverPort, port2: clientPort } = new MessageChannel()

window.opener.postMessage(serverPort, '*', [serverPort])

const link = new RPCLink({ port: clientPort })

clientPort.start()

Advanced Relay Pattern

For scripts running in the "MAIN" world that can't access browser.runtime, use a relay content script in the "ISOLATED" world:

// relay
window.addEventListener('message', (event) => {
  if (event.data instanceof MessagePort) {
    const port = browser.runtime.connect()

    event.data.addEventListener('message', e => port.postMessage(e.data))
    event.data.addEventListener('close', () => port.disconnect())
    port.onMessage.addListener(msg => event.data.postMessage(msg))
    port.onDisconnect.addListener(() => event.data.close())

    event.data.start()
  }
})