Named-event communication layer for Dedicated Workers, Shared Workers, and Service Workers. Built on top of @actualwave/messageport-dispatcher, it replaces raw postMessage/message boilerplate with typed named events on both sides of every worker channel.
npm install @actualwave/worker-dispatcher
# or
yarn add @actualwave/worker-dispatcherDedicatedWorkerDispatcher— wraps aWorkeron the main thread (orselfinside the worker).SharedWorkerDispatcher— wraps aSharedWorkeron the main thread; handles theMessagePortinternally.ServiceWorkerDispatcher— wraps aMessageChannelto the active Service Worker; auto-recovers when the worker restarts.- Server dispatchers (
SharedServerDispatcher,ServiceServerDispatcher) — used inside the worker script to receive connections and events. - Client dispatchers (
SharedClientDispatcher,ServiceClientDispatcher) — one instance per connected client, created automatically on each new connection. WorkerEvent— extendsEvent; carriesnativeEvent,client,type, anddata.WorkerType— string enum of all dispatcher type identifiers.
Main thread:
import { DedicatedWorkerDispatcher } from '@actualwave/worker-dispatcher';
const dispatcher = new DedicatedWorkerDispatcher('/workers/worker.js');
dispatcher.addEventListener('result', (event) => {
console.log('Worker result:', event.data);
});
dispatcher.dispatchEvent('compute', { input: [1, 2, 3] });Inside the worker (worker.js):
import { createForSelf } from '@actualwave/worker-dispatcher';
const dispatcher = createForSelf();
dispatcher.addEventListener('compute', (event) => {
dispatcher.dispatchEvent('result', { output: (event.data as any).input.map(Number) });
});Main thread:
import { SharedWorkerDispatcher } from '@actualwave/worker-dispatcher';
const dispatcher = new SharedWorkerDispatcher('/workers/shared.js');
// start() is called automatically in the constructor
dispatcher.addEventListener('broadcast', (event) => {
console.log('Broadcast from worker:', event.data);
});
dispatcher.dispatchEvent('subscribe', { topic: 'news' });Inside the shared worker (shared.js):
import { createForSelf } from '@actualwave/worker-dispatcher';
import WorkerEvent from '@actualwave/worker-dispatcher/WorkerEvent';
const server = createForSelf(); // returns SharedServerDispatcher
server.addEventListener(WorkerEvent.CONNECT, (event) => {
const client = event.client; // SharedClientDispatcher
client.start();
client.addEventListener('subscribe', (e) => {
console.log('Client subscribed to', (e.data as any).topic);
client.dispatchEvent('broadcast', { message: 'Welcome!' });
});
});Close a client when done:
client.close();Main thread (registers and communicates with a Service Worker):
import { ServiceWorkerDispatcher } from '@actualwave/worker-dispatcher';
const dispatcher = new ServiceWorkerDispatcher();
dispatcher.onReady((registration) => {
console.log('Service worker ready');
dispatcher.dispatchEvent('init', { config: { offline: true } });
});
dispatcher.addEventListener('push-received', (event) => {
console.log('Push data:', event.data);
});Inside the service worker (sw.js):
import { ServiceServerDispatcher } from '@actualwave/worker-dispatcher';
import WorkerEvent from '@actualwave/worker-dispatcher/WorkerEvent';
const server = new ServiceServerDispatcher();
server.addEventListener('init', (event) => {
console.log('Config received:', event.data);
const client = event.client; // ServiceClientDispatcher
if (client) {
client.dispatchEvent('ack', { ok: true });
}
});
// Lifecycle events are wired automatically
server.addEventListener(WorkerEvent.INSTALL, (e) => {
(e.nativeEvent as ExtendableEvent).waitUntil(caches.open('v1'));
});import {
create,
createForSelf,
createForDedicatedWorker,
createForSharedWorker,
createForServiceWorker,
WorkerType,
} from '@actualwave/worker-dispatcher';
// auto-detect from a target object
const d1 = create(worker, WorkerType.DEDICATED_WORKER);
const d2 = create(sharedWorker, WorkerType.SHARED_WORKER);
// inside a worker script — auto-detects context
const dispatcher = createForSelf();
// explicit per-type helpers
const dw = createForDedicatedWorker('/worker.js');
const sw = createForSharedWorker('/shared.js');
const svc = createForServiceWorker();Transform every incoming or outgoing event:
import type { EventProcessor } from '@actualwave/event-dispatcher';
const stamp: EventProcessor = (event) => ({
...event,
data: { ...(event.data as object), ts: Date.now() },
});
const dispatcher = new DedicatedWorkerDispatcher(
'/worker.js',
stamp, // receiverEventPreprocessor
stamp, // senderEventPreprocessor
);new DedicatedWorkerDispatcher(
worker?: Worker | string,
receiverEventPreprocessor?: EventProcessor | null,
senderEventPreprocessor?: EventProcessor | null,
)| Member | Description |
|---|---|
addEventListener(type, listener, priority?) |
Listen for events from the worker. |
hasEventListener(type) |
Check if listener exists. |
removeEventListener(type, listener) |
Remove a listener. |
removeAllEventListeners(type) |
Remove all listeners for a type. |
dispatchEvent(type, data?, transferList?) |
Send an event to the worker. |
receiver |
IEventDispatcher for incoming events. |
sender |
IEventDispatcher for outgoing echo. |
target |
Underlying Worker or WorkerLike. |
terminate() |
Terminate the worker and clean up listeners. |
new SharedWorkerDispatcher(
target: SharedWorker | string,
name?: string,
receiverEventPreprocessor?: EventProcessor | null,
senderEventPreprocessor?: EventProcessor | null,
)| Member | Description |
|---|---|
start() |
Start the port (called automatically). |
close() |
Close the port and clean up listeners. |
worker |
The underlying SharedWorker instance. |
| (inherits all dispatcher methods) |
new ServiceWorkerDispatcher(
receiverEventPreprocessor?: EventProcessor | null,
senderEventPreprocessor?: EventProcessor | null,
)| Member | Description |
|---|---|
start() |
Start the internal MessageChannel port. |
close() |
Close the port and remove error listeners. |
ready |
Promise<ServiceWorkerRegistration> |
onReady(handler) |
Calls handler when the SW registration resolves. Returns Promise<void>. |
| (inherits all dispatcher methods) |
Auto-recovers the MessageChannel when the active Service Worker changes (e.g., after an update).
new SharedServerDispatcher(
target?: EventTarget, // defaults to self
receiverEventPreprocessor?: EventProcessor,
clientReceiverEventPreprocessor?: EventProcessor,
clientSenderEventPreprocessor?: EventProcessor,
)| Member | Description |
|---|---|
addEventListener(type, listener) |
Listen for connect and worker-lifecycle events. |
destroy() |
Remove all native listeners from the target. |
receiver |
IEventDispatcher for dispatched events. |
Fires WorkerEvent.CONNECT whenever a new client connects. The event's .client is a SharedClientDispatcher.
new ServiceServerDispatcher(
target?: EventTarget, // defaults to self
receiverEventPreprocessor?: EventProcessor,
clientReceiverEventPreprocessor?: EventProcessor,
clientSenderEventPreprocessor?: EventProcessor,
)Automatically registers listeners for error, messageerror, install, activate, fetch, sync, and push on target. The event's .client is a ServiceClientDispatcher when a port was transferred.
| Member | Description |
|---|---|
destroy() |
Remove all native listeners from the target. |
One instance per client connection. Returned via event.client on connect/message events.
| Member | Description |
|---|---|
start() |
Start the MessagePort. |
close() |
Close the MessagePort. |
dispatchEvent(type, data?) |
Send an event to this specific client. |
| (inherits all dispatcher methods) |
Static event type constants (all prefixed worker:):
| Constant | Value | When fired |
|---|---|---|
WorkerEvent.CONNECT |
'worker:connect' |
New client connected (Shared Worker) |
WorkerEvent.MESSAGE |
'worker:message' |
Incoming message |
WorkerEvent.ERROR |
'worker:error' |
Worker error |
WorkerEvent.MESSAGEERROR |
'worker:messageerror' |
Message deserialization error |
WorkerEvent.LANGUAGECHANGE |
'worker:languagechange' |
Language changed |
WorkerEvent.ONLINE |
'worker:online' |
Network online |
WorkerEvent.OFFLINE |
'worker:offline' |
Network offline |
WorkerEvent.INSTALL |
'worker:install' |
SW install event |
WorkerEvent.ACTIVATE |
'worker:activate' |
SW activate event |
WorkerEvent.FETCH |
'worker:fetch' |
SW fetch event |
WorkerEvent.SYNC |
'worker:sync' |
SW background sync |
WorkerEvent.PUSH |
'worker:push' |
SW push notification |
Instance properties:
| Property | Type | Description |
|---|---|---|
type |
string |
The worker-prefixed event type |
data |
unknown |
Event payload |
nativeEvent |
unknown |
The original native DOM event |
client |
unknown |
Client dispatcher, or null |
String constants used with create():
| Constant | Value |
|---|---|
WorkerType.DEDICATED_WORKER |
'dedicated' |
WorkerType.SHARED_WORKER |
'shared' |
WorkerType.SERVICE_WORKER |
'service' |
Raw DOM event type strings: 'connect', 'message', 'error', 'messageerror', 'install', 'activate', 'fetch', 'sync', 'push', 'languagechange', 'online', 'offline'.
Lower-level helpers that register native event listeners on an EventTarget and forward translated WorkerEvents to an IEventDispatcher. Each returns a () => void cleanup function.
import { dispatchWorkerErrorEvent } from '@actualwave/worker-dispatcher/WorkerEvent';
const cleanup = dispatchWorkerErrorEvent(target, myDispatcher.receiver);
// later:
cleanup();import type { WorkerTypeValue } from '@actualwave/worker-dispatcher/WorkerType';
import type { NativeEventHandler } from '@actualwave/worker-dispatcher/WorkerEvent';
import type { WorkerLike } from '@actualwave/worker-dispatcher/AbstractDispatcher';
import type {
EventObject,
IEventDispatcher,
EventProcessor,
EventListener,
} from '@actualwave/event-dispatcher';- Service Worker recovery:
ServiceWorkerDispatcherautomatically recreates theMessageChanneland retransmits pendingport2when the active worker changes. The firstdispatchEvent()after construction always transfersport2. destroy()on server dispatchers: Calldestroy()when tearing down aSharedServerDispatcherorServiceServerDispatcherto remove all nativeaddEventListenerregistrations fromself.start()onSharedWorkerDispatcher:start()is called automatically in the constructor; calling it again is a no-op in most environments but safe.WorkerEvent.MESSAGEERRORvalue: The correct value is'worker:messageerror', not'messageerror'. Use the constant, not the string literal.createForSelf()auto-detection: Checkstypeof self.postMessage === 'function'(Dedicated Worker) then checksself.registration.scope(Service Worker), then falls back toSharedServerDispatcher.- Preprocessors apply on both sides: Pass the same preprocessor to receiver and sender if you want symmetric transformation; they are independent by default.