Skip to content

MessagePort Transport

MessagePort is a low-level transport primitive that works well for custom runtime topologies. It is especially useful when you want explicit channel wiring without being locked to a single container model.

Typical use cases:

  • custom handshakes between host shell and remote runtime;
  • bridge layers for popup/iframe/worker/session orchestration;
  • dev and diagnostics tools that need an explicit bidirectional channel.

@remote-ui/rpc already provides fromMessagePort(...). Once both sides hold connected ports, integration is the same run/release flow:

import { createEndpoint, fromMessagePort } from '@remote-ui/rpc'
const endpoint = createEndpoint<RemoteApi>(fromMessagePort(port))
await endpoint.call.run(receiver.receive, hostBridge)
import { MessageChannel } from 'node:worker_threads'
import { createEndpoint, fromMessagePort } from '@remote-ui/rpc'
type Api = { ping(message: string): string }
const { port1, port2 } = new MessageChannel()
const host = createEndpoint<Api>(fromMessagePort(port1))
const remote = createEndpoint(fromMessagePort(port2))
remote.expose({
ping(message: string) {
return `pong: ${message}`
},
})
const result = await host.call.ping('hello')
console.info(result) // pong: hello

In browser environments, use the native MessageChannel API with the same pattern.

  1. Host creates a MessageChannel.
  2. Host transfers port2 to remote container (iframe, popup, or worker bootstrap) via postMessage.
  3. Host keeps port1 and initializes createEndpoint(fromMessagePort(port1)).
  4. Remote receives transferred port and initializes its endpoint.
  5. Both sides run standard run/release.
  1. Isolation is context-dependent: MessagePort itself does not create sandbox boundaries.
  2. Transport semantics are strong: explicit async boundary and structured-clone constraints.
  3. Lifecycle must be explicit: close ports and release retained references during teardown.