Skip to content

Transports

A transport is the channel expose() talks over. osra accepts the platform objects below directly; anything else can be wrapped in a plain { emit, receive } pair.

Transports are either structured-clone (Worker, Window, MessagePort, SharedWorker) or JSON (WebSocket, web extension messaging, custom transports with isJson: true). JSON mode forces JSON-safe boxing: values that depend on structured clone (RegExp, SharedArrayBuffer, ImageBitmap, …) are rejected at the type level, while everything with a dedicated revivable module (Date, Map, ArrayBuffer via base64, functions, streams, …) still works. osra only stringifies envelopes itself on WebSocket; custom function emitters handle their own serialization. See JSON vs clone for exactly what degrades, and supported types for the full matrix.

Transport Mode Notes
Window clone origin applies (inbound filter + outbound targetOrigin).
Worker clone
DedicatedWorkerGlobalScope clone Pass globalThis (or self) inside the worker.
SharedWorker clone Page side. Messages ride .port (handled internally).
MessagePort clone .start() is called internally on receive.
WebSocket JSON Envelopes are JSON.stringifyed; sends while CONNECTING queue until open.
ServiceWorker clone Emit only.
ServiceWorkerContainer clone Receive only. Combine: { emit: registration.active, receive: navigator.serviceWorker }.
WebExtension runtime / Port / onConnect / onMessage JSON runtime/onConnect are identity-matched against the browser/chrome global; Port/onMessage are detected purely structurally and work without the global (lookalike objects can misclassify as these).
Custom { emit?, receive?, isJson? } per isJson / probed See Custom transports.

Pass the Worker on the page side and globalThis (the DedicatedWorkerGlobalScope) inside the worker — the worker scope isn’t part of the Transport type union, so cast it:

// worker.ts
expose<unknown, readonly [typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), typeof import("osra/build/revivables/date"), typeof import("osra/build/revivables/headers"), typeof import("osra/build/revivables/error"), typeof import("osra/build/revivables/typed-array"), ... 20 more ..., {
...;
}], Transport, {
...;
}>(value: {
...;
}, options: StartConnectionsOptions<...> & {
...;
}): Promise<...>
expose
(
const api: {
add: (a: number, b: number) => Promise<number>;
}
api
, {
transport: Transport
transport
:
module globalThis
globalThis
as unknown as
type Transport = PlatformTransport | CustomTransport
Transport
})
// main.ts
const
const worker: Worker
worker
= new
var Worker: new (scriptURL: string | URL, options?: WorkerOptions) => Worker

The Worker interface of the Web Workers API represents a background task that can be created via script, which can send messages back to its creator.

MDN Reference

Worker
(new
var URL: new (url: string | URL, base?: string | URL) => URL

The URL interface is used to parse, construct, normalize, and encode URL.

MDN Reference

URL
('./worker.ts', import.

The type of import.meta.

If you need to declare that a given property exists on import.meta, this type may be augmented via interface merging.

meta
.
ImportMeta.url: string

The absolute file: URL of the module.

This is defined exactly the same as it is in browsers providing the URL of the current module file.

This enables useful patterns such as relative file loading:

import { readFileSync } from 'node:fs';
const buffer = readFileSync(new URL('./data.proto', import.meta.url));

url
), {
WorkerOptions.type?: WorkerType | undefined
type
: 'module' })
const
const remote: {
add: (a: number, b: number) => Promise<number>;
}
remote
= await
expose<{
add: (a: number, b: number) => Promise<number>;
}, readonly [typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), typeof import("osra/build/revivables/date"), typeof import("osra/build/revivables/headers"), typeof import("osra/build/revivables/error"), ... 21 more ..., {
...;
}], Transport, Capable<...>>(value: Capable<...>, options: StartConnectionsOptions<...> & {
...;
}): Promise<...>
expose
<
type Api = {
add: (a: number, b: number) => Promise<number>;
}
Api
>({}, {
transport: Transport
transport
:
const worker: Worker
worker
})

The full worker walkthrough lives in getting started.

message events fire on the window that receives them, so each side pairs the other window for emit with its own window for receive. origin is applied in both directions: outbound it is the postMessage targetOrigin, inbound it drops events whose event.origin differs. Set it whenever you talk across origins — see security for the trust model and the one announce-beacon exception.

// parent
const
const iframe: HTMLIFrameElement
iframe
=
var document: Document

window.document returns a reference to the document contained in the window.

MDN Reference

document
.
ParentNode.querySelector<"iframe">(selectors: "iframe"): HTMLIFrameElement | null (+4 overloads)

Returns the first element that is a descendant of node that matches selectors.

MDN Reference

querySelector
('iframe')!
const
const remote: {
render: (theme: "light" | "dark") => Promise<void>;
}
remote
= await
expose<IframeApi, readonly [typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), typeof import("osra/build/revivables/date"), typeof import("osra/build/revivables/headers"), typeof import("osra/build/revivables/error"), typeof import("osra/build/revivables/typed-array"), ... 20 more ..., {
...;
}], Transport, Capable<...>>(value: Capable<...>, options: StartConnectionsOptions<...> & {
...;
}): Promise<...>
expose
<
type IframeApi = {
render: (theme: "light" | "dark") => Promise<void>;
}
IframeApi
>(
const parentApi: {
getConfig: () => Promise<{
locale: string;
}>;
}
parentApi
, {
transport: Transport
transport
: {
emit: Window
emit
:
const iframe: HTMLIFrameElement
iframe
.
HTMLIFrameElement.contentWindow: Window | null

The contentWindow property returns the Window object of an HTMLIFrameElement.

MDN Reference

contentWindow
!,
receive: Window & typeof globalThis
receive
:
var window: Window & typeof globalThis

The window property of a Window object points to the window object itself.

MDN Reference

window
},
origin?: string | undefined
origin
: 'https://app.example.com',
})
// iframe
const
const remote: {
getConfig: () => Promise<{
locale: string;
}>;
}
remote
= await
expose<ParentApi, readonly [typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), typeof import("osra/build/revivables/date"), typeof import("osra/build/revivables/headers"), typeof import("osra/build/revivables/error"), typeof import("osra/build/revivables/typed-array"), ... 20 more ..., {
...;
}], Transport, Capable<...>>(value: Capable<...>, options: StartConnectionsOptions<...> & {
...;
}): Promise<...>
expose
<
type ParentApi = {
getConfig: () => Promise<{
locale: string;
}>;
}
ParentApi
>(
const iframeApi: {
render: (theme: "light" | "dark") => Promise<void>;
}
iframeApi
, {
transport: Transport
transport
: {
emit: Window
emit
:
var window: Window & typeof globalThis

The window property of a Window object points to the window object itself.

MDN Reference

window
.
parent: Window

The Window.parent property is a reference to the parent of the current window or subframe.

MDN Reference

parent
,
receive: Window & typeof globalThis
receive
:
var window: Window & typeof globalThis

The window property of a Window object points to the window object itself.

MDN Reference

window
},
origin?: string | undefined
origin
: 'https://host.example.com',
})

Pass the SharedWorker instance directly on the page side; osra rides its .port internally. Inside the worker, expose per connected port:

// page
const
const sharedWorker: SharedWorker
sharedWorker
= new
var SharedWorker: new (scriptURL: string | URL, options?: string | WorkerOptions) => SharedWorker

The SharedWorker interface represents a specific kind of worker that can be accessed from several browsing contexts, such as several windows, iframes or even workers.

MDN Reference

SharedWorker
(new
var URL: new (url: string | URL, base?: string | URL) => URL

The URL interface is used to parse, construct, normalize, and encode URL.

MDN Reference

URL
('./shared.ts', import.

The type of import.meta.

If you need to declare that a given property exists on import.meta, this type may be augmented via interface merging.

meta
.
ImportMeta.url: string

The absolute file: URL of the module.

This is defined exactly the same as it is in browsers providing the URL of the current module file.

This enables useful patterns such as relative file loading:

import { readFileSync } from 'node:fs';
const buffer = readFileSync(new URL('./data.proto', import.meta.url));

url
), {
WorkerOptions.type?: WorkerType | undefined
type
: 'module' })
const
const remote: {
add: (a: number, b: number) => Promise<number>;
}
remote
= await
expose<Api, readonly [typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), typeof import("osra/build/revivables/date"), typeof import("osra/build/revivables/headers"), typeof import("osra/build/revivables/error"), typeof import("osra/build/revivables/typed-array"), ... 20 more ..., {
...;
}], Transport, Capable<...>>(value: Capable<...>, options: StartConnectionsOptions<...> & {
...;
}): Promise<...>
expose
<
type Api = {
add: (a: number, b: number) => Promise<number>;
}
Api
>({}, {
transport: Transport
transport
:
const sharedWorker: SharedWorker
sharedWorker
})
shared.ts
import {
const expose: <T = unknown, const TModules extends readonly RevivableModule[] = readonly [typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), typeof import("osra/build/revivables/date"), typeof import("osra/build/revivables/headers"), typeof import("osra/build/revivables/error"), ... 21 more ..., {
...;
}], const TTransport extends Transport = Transport, const TValue = Capable<...>>(value: CapableCheck<...>, options: StartConnectionsOptions<TModules> & {
transport: TTransport;
}) => Promise<Remote<T>>
expose
} from 'osra'
const
const api: {
add: (a: number, b: number) => Promise<number>;
}
api
= {
add: (a: number, b: number) => Promise<number>
add
: async (
a: number
a
: number,
b: number
b
: number) =>
a: number
a
+
b: number
b
}
module globalThis
globalThis
.
function addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void (+1 overload)
addEventListener
('connect',
event: Event
event
=> {
for (const
const port: MessagePort
port
of (
event: Event
event
as
interface MessageEvent<T = any>

The MessageEvent interface represents a message received by a target object.

MDN Reference

MessageEvent
).
MessageEvent<any>.ports: readonly MessagePort[]

The ports read-only property of the containing all MessagePort objects sent with the message, in order.

MDN Reference

ports
)
expose<unknown, readonly [typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), typeof import("osra/build/revivables/date"), typeof import("osra/build/revivables/headers"), typeof import("osra/build/revivables/error"), typeof import("osra/build/revivables/typed-array"), ... 20 more ..., {
...;
}], MessagePort, {
...;
}>(value: {
...;
}, options: StartConnectionsOptions<...> & {
...;
}): Promise<...>
expose
(
const api: {
add: (a: number, b: number) => Promise<number>;
}
api
, {
transport: Transport & MessagePort
transport
:
const port: MessagePort
port
})
})

Per-port expose() gives each connecting page its own connection; multi-peer connections explains why this is the recommended pattern.

JSON mode. You can expose() while the socket is still CONNECTING; outbound envelopes queue until open. The other end is anything that relays frames to a peer also running osra:

const
const socket: WebSocket
socket
= new
var WebSocket: new (url: string | URL, protocols?: string | string[]) => WebSocket

The WebSocket object provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection.

MDN Reference

WebSocket
('wss://relay.example.com')
const
const remote: {
broadcast: (text: string) => Promise<void>;
}
remote
= await
expose<PeerApi, readonly [typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), typeof import("osra/build/revivables/date"), typeof import("osra/build/revivables/headers"), typeof import("osra/build/revivables/error"), typeof import("osra/build/revivables/typed-array"), ... 20 more ..., {
...;
}], Transport, Capable<...>>(value: Capable<...>, options: StartConnectionsOptions<...> & {
...;
}): Promise<...>
expose
<
type PeerApi = {
broadcast: (text: string) => Promise<void>;
}
PeerApi
>(
const localApi: {
notify: (text: string) => Promise<void>;
}
localApi
, {
transport: Transport
transport
:
const socket: WebSocket
socket
})

A ServiceWorker can only emit and a ServiceWorkerContainer can only receive, so combine them as a custom pair:

const
const registration: ServiceWorkerRegistration
registration
= await
var navigator: Navigator

The Window.navigator read-only property returns a reference to the Navigator object, which has methods and properties about the application running the script.

MDN Reference

navigator
.
Navigator.serviceWorker: ServiceWorkerContainer

The serviceWorker read-only property of the Navigator interface returns the ServiceWorkerContainer object for the associated document, which provides access to registration, removal, upgrade, and communication with the ServiceWorker. Available only in secure contexts.

MDN Reference

serviceWorker
.
ServiceWorkerContainer.ready: Promise<ServiceWorkerRegistration>

The ready read-only property of the ServiceWorkerContainer interface provides a way of delaying code execution until a service worker is active.

MDN Reference

ready
const
const remote: {
getCachedUrls: () => Promise<string[]>;
}
remote
= await
expose<SwApi, readonly [typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), typeof import("osra/build/revivables/date"), typeof import("osra/build/revivables/headers"), typeof import("osra/build/revivables/error"), typeof import("osra/build/revivables/typed-array"), ... 20 more ..., {
...;
}], Transport, Capable<...>>(value: Capable<...>, options: StartConnectionsOptions<...> & {
...;
}): Promise<...>
expose
<
type SwApi = {
getCachedUrls: () => Promise<string[]>;
}
SwApi
>(
const pageApi: {
reload: () => Promise<void>;
}
pageApi
, {
transport: Transport
transport
: {
emit: ServiceWorker
emit
:
const registration: ServiceWorkerRegistration
registration
.
ServiceWorkerRegistration.active: ServiceWorker | null

The active read-only property of the This property is initially set to null.

MDN Reference

active
!,
receive: ServiceWorkerContainer
receive
:
var navigator: Navigator

The Window.navigator read-only property returns a reference to the Navigator object, which has methods and properties about the application running the script.

MDN Reference

navigator
.
Navigator.serviceWorker: ServiceWorkerContainer

The serviceWorker read-only property of the Navigator interface returns the ServiceWorkerContainer object for the associated document, which provides access to registration, removal, upgrade, and communication with the ServiceWorker. Available only in secure contexts.

MDN Reference

serviceWorker
},
})

JSON mode. runtime.Port, the runtime itself (sendMessage/onMessage), onConnect, and onMessage are all accepted:

// content script
const
const port: Runtime.Port
port
=
const browser: Browser
browser
.
Browser.Browser.runtime: Runtime.Static

Use the browser.runtime API to retrieve the background page, return details about the manifest, and listen for and respond to events in the app or extension lifecycle. You can also use this API to convert the relative path of URLs to fully-qualified URLs.

runtime
.
Runtime.Static.connect(extensionId?: string, connectInfo?: Runtime.ConnectConnectInfoType): Runtime.Port (+1 overload)

Attempts to connect to connect listeners within an extension/app (such as the background page), or other extensions/apps. This is useful for content scripts connecting to their extension processes, inter-app/extension communication, and $(topic:manifest/externally_connectable)[web messaging]. Note that this does not connect to any listeners in a content script. Extensions may connect to content scripts embedded in tabs via $(ref:tabs.connect).

@paramextensionId Optional. The ID of the extension or app to connect to. If omitted, a connection will be attempted with your own extension. Required if sending messages from a web page for $(topic:manifest/externally_connectable)[web messaging].

@paramconnectInfo Optional.

@returnsPort through which messages can be sent and received. The port's $(ref:runtime.Port.onDisconnect) event is fired if the extension/app does not exist.

connect
()
const
const background: {
fetchData: (url: string) => Promise<string>;
}
background
= await
expose<BackgroundApi, readonly [typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), typeof import("osra/build/revivables/date"), typeof import("osra/build/revivables/headers"), typeof import("osra/build/revivables/error"), ... 21 more ..., {
...;
}], Transport, Capable<...>>(value: Capable<...>, options: StartConnectionsOptions<...> & {
...;
}): Promise<...>
expose
<
type BackgroundApi = {
fetchData: (url: string) => Promise<string>;
}
BackgroundApi
>(
const contentApi: {
getSelection: () => Promise<string>;
}
contentApi
, {
transport: Transport
transport
:
const port: Runtime.Port
port
})
// background
const browser: Browser
browser
.
Browser.Browser.runtime: Runtime.Static

Use the browser.runtime API to retrieve the background page, return details about the manifest, and listen for and respond to events in the app or extension lifecycle. You can also use this API to convert the relative path of URLs to fully-qualified URLs.

runtime
.
Runtime.Static.onConnect: Events.Event<(port: Runtime.Port) => void>

Fired when a connection is made from either an extension process or a content script.

onConnect
.
Events.Event<(port: Runtime.Port) => void>.addListener(callback: (port: Runtime.Port) => void, ...params: unknown[]): void

Registers an event listener callback to an event.

@paramcallback Called when an event occurs. The parameters of this function depend on the type of event.

@param...params Further parameters, depending on the event.

addListener
(
port: Runtime.Port
port
=> {
expose<unknown, readonly [typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), typeof import("osra/build/revivables/date"), typeof import("osra/build/revivables/headers"), typeof import("osra/build/revivables/error"), typeof import("osra/build/revivables/typed-array"), ... 20 more ..., {
...;
}], Runtime.Port, {
...;
}>(value: {
...;
}, options: StartConnectionsOptions<...> & {
...;
}): Promise<...>
expose
(
const backgroundApi: {
fetchData: (url: string) => Promise<string>;
}
backgroundApi
, {
transport: Transport & Runtime.Port
transport
:
port: Runtime.Port
port
})
})

Any plain object with emit and receive works as a transport — a BroadcastChannel, a native bridge, a text protocol of your own. See custom transports.