Skip to content

Multi-peer connections

A single expose() call can be reached by more than one peer: several pages connecting to one SharedWorker, several contexts on one broadcast channel. osra connects them all, but the returned promise only ever carries one value.

The promise returned by expose() resolves with the first peer’s value. Later peers still connect, can call your exposed value, and keep their own connection state, but there is no public accessor for their values.

When you need a value per peer (the SharedWorker case), expose once per port instead:

// shared-worker.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'
export 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
.
var onconnect: ((event: MessageEvent) => void) | null
onconnect
= (
event: MessageEvent<any>
event
:
interface MessageEvent<T = any>

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

MDN Reference

MessageEvent
) => {
for (const
const port: MessagePort
port
of
event: MessageEvent<any>
event
.
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
})
}
// page
import type {
const api: {
add: (a: number, b: number) => Promise<number>;
}
api
} from './shared-worker.ts'
const
const worker: SharedWorker
worker
= 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
('./shared-worker.ts', {
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
<typeof
const api: {
add: (a: number, b: number) => Promise<number>;
}
api
>({}, {
transport: Transport
transport
:
const worker: SharedWorker
worker
})

The page passes the SharedWorker object itself; osra sends and listens on its .port (and starts it) internally. The worker side gets one expose() (and one first-wins promise) per connecting page, so each connection is isolated and each page’s value is addressable.

To connect two peers that share no direct channel (two workers, or an iframe and a worker), a context that owns both transports can forward envelopes between them with relay().