SharedWorker Connection
When running in a browser that supports SharedWorker, Teleportal can offload the network connection to a shared worker. All open tabs then share a single underlying WebSocket (or HTTP) connection to the sync server, reducing resource usage and keeping state consistent across tabs.
This is opt-in: pass a workerUrl to createConnection() or use WorkerProvider.create(). When SharedWorker is unavailable (Node.js, older browsers, file:// origins, restrictive CSP), the system transparently falls back to a direct in-thread connection.
What it demonstrates
Section titled “What it demonstrates”- Offloading the network connection to a SharedWorker
- Connection pooling across browser tabs
- Automatic fallback when SharedWorker is unavailable
- Grace period to survive page reloads without reconnection
Quick Start
Section titled “Quick Start”The simplest way to use SharedWorker support is through WorkerProvider:
import { WorkerProvider } from "teleportal/providers/worker";import { websocketTransport, httpTransport } from "teleportal/providers";
const provider = await WorkerProvider.create({ // Point to your worker script (see "Worker Script" below) workerUrl: new URL("./worker.ts", import.meta.url),
// Server URL url: "wss://example.com/sync",
// Transports used as fallback if SharedWorker is unavailable transports: [websocketTransport(), httpTransport()],
// Document to sync document: "my-document", encryptionKey: createEncryptionKey(),});
await provider.synced;
// Use exactly like a regular Providerprovider.doc.getText("content").insert(0, "Hello from SharedWorker!");Worker Script
Section titled “Worker Script”The worker script runs inside the SharedWorker and maps serializable transport descriptors to real transport instances. Teleportal ships a default entry point at teleportal/providers/worker/connection-worker, or you can write your own:
import { ConnectionWorkerManager } from "teleportal/providers/worker";import { websocketTransport } from "teleportal/providers";import { httpTransport } from "teleportal/providers";
const manager = new ConnectionWorkerManager((options) => { if (options.transports && options.transports.length > 0) { return options.transports.map((desc) => { switch (desc.type) { case "websocket": return websocketTransport(desc.options); case "http": return httpTransport(desc.options); default: throw new Error(`Unknown transport type: ${desc.type}`); } }); } // Default transports when none are specified return [websocketTransport({ timeout: 5000 }), httpTransport()];});
declare const self: { onconnect: ((event: MessageEvent) => void) | null };self.onconnect = (event: MessageEvent) => { manager.addPort(event.ports[0]);};Using createConnection Directly
Section titled “Using createConnection Directly”For more control, use createConnection to get a Connection instance and pair it with a Provider yourself:
import { createConnection } from "teleportal/providers/worker";import { Provider } from "teleportal/providers";import { websocketTransport, httpTransport } from "teleportal/providers";
const connection = createConnection({ workerUrl: new URL("./worker.ts", import.meta.url), url: "wss://example.com/sync", token: { token: jwt }, transports: [websocketTransport(), httpTransport()],
// Optional: descriptors forwarded to the worker workerTransports: [ { type: "websocket", options: { timeout: 5000 } }, { type: "http", options: {} }, ],
// Called if the SharedWorker crashes or heartbeat times out onWorkerDeath: () => { console.warn("SharedWorker died — consider reloading"); },});
const provider = new Provider({ connection, document: "my-document", encryptionKey: createEncryptionKey(),});
await provider.synced;How Connection Pooling Works
Section titled “How Connection Pooling Works”The ConnectionWorkerManager inside the worker pools connections using a connection key derived from the URL and authentication token:
key = url + "::" + token- Same user, same server: all tabs share one WebSocket connection.
- Different tokens (different users or auth sessions): separate connections, keeping attribution isolated.
- Page reload: the grace period (default 5 seconds) keeps the connection alive so the reloaded tab reconnects instantly without a server handshake.
Custom Pooling Key
Section titled “Custom Pooling Key”Override the default key function to control which tabs share connections:
const manager = new ConnectionWorkerManager(transportFactory, { getConnectionKey: (options) => { // Key on user ID so token refreshes don't create new connections const claims = decodeJwt(options.token); return `${options.url}::${claims.sub}`; }, gracePeriodMs: 10_000, // Keep connection alive 10s after last tab leaves});Architecture
Section titled “Architecture”graph TD
subgraph Tabs["Browser Tabs"]
A["Tab A<br/>WorkerConnection"]
B["Tab B<br/>WorkerConnection"]
C["Tab C<br/>WorkerConnection"]
end
A -- MessagePort --> CWM
B -- MessagePort --> CWM
C -- MessagePort --> CWM
subgraph SW["SharedWorker"]
CWM["ConnectionWorkerManager"]
MC1["ManagedConnection<br/><i>key: url+tokenA</i>"]
MC2["ManagedConnection<br/><i>key: url+tokenB</i>"]
DC1["DirectConnection"]
DC2["DirectConnection"]
CWM --> MC1
CWM --> MC2
MC1 --> DC1
MC2 --> DC2
end
DC1 -- "WebSocket / HTTP" --> S["Sync Server"]
DC2 -- "WebSocket / HTTP" --> S
Each ManagedConnection wraps a single DirectConnection and handles:
- Event forwarding to all attached ports
- Online/offline reconciliation (connection stays online if any tab is online)
- Grace period cleanup when the last tab disconnects
Heartbeat and Liveness
Section titled “Heartbeat and Liveness”Each tab runs a heartbeat loop (default: 5-second interval, 2 max missed pings) to detect if the SharedWorker has crashed:
- The main thread sends a
heartbeatmessage every interval - The worker replies with
heartbeat-ack - If more than
maxMissesconsecutive acks are missed, the worker is declared dead - The
onWorkerDeathcallback fires, allowing you to fall back or notify the user
Online/Offline Handling
Section titled “Online/Offline Handling”Each tab forwards browser online/offline events to the worker. The worker uses an any-tab-online policy:
- If any tab reports online, the connection stays up and reconnection is allowed
- Only when all tabs report offline does the connection go offline
- During the grace period (no tabs attached), the connection defaults to online
This prevents a single backgrounded tab from taking the shared connection offline.
Fallback Behavior
Section titled “Fallback Behavior”createConnection handles fallback transparently:
- If
workerUrlis provided andSharedWorkeris available, creates aWorkerConnection - If
SharedWorkerconstruction fails (CSP,file://origin, etc.), falls back toDirectConnection - If
workerUrlis omitted, always creates aDirectConnection
The returned Connection implements the same interface in all cases. Your Provider code does not need to know whether it is backed by a SharedWorker or a direct connection.
Configuration Reference
Section titled “Configuration Reference”createConnection Options
Section titled “createConnection Options”| Option | Type | Default | Description |
|---|---|---|---|
workerUrl |
string | URL |
— | URL of the SharedWorker script. Omit to use a direct connection. |
url |
string |
(required) | Sync server URL. |
token |
TokenOptions |
— | Authentication token. |
transports |
ConnectionTransport[] |
(required) | Transport instances for the direct fallback path. |
workerTransports |
TransportDescriptor[] |
— | Serializable transport descriptors forwarded to the worker. |
onWorkerDeath |
() => void |
— | Called if the SharedWorker crashes or heartbeat times out. |
connect |
boolean |
true |
Auto-connect on creation. |
maxReconnectAttempts |
number |
10 |
Max reconnection attempts. |
initialReconnectDelay |
number |
100 |
Initial backoff delay (ms). |
maxBackoffTime |
number |
30000 |
Maximum backoff delay (ms). |
ConnectionWorkerManager Options
Section titled “ConnectionWorkerManager Options”| Option | Type | Default | Description |
|---|---|---|---|
gracePeriodMs |
number |
5000 |
How long to keep a connection alive after the last tab disconnects. |
getConnectionKey |
(options) => string |
URL + token | Custom function to determine which tabs share a connection. |
Next Steps
Section titled “Next Steps”- Fallback Connection - Automatic WebSocket-to-HTTP fallback
- WebSocket Only - Minimal WebSocket setup
- Performance - Performance optimization strategies