promise-worker-bi 5.0.2 → 6.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,10 @@
1
- # promise-worker-bi [![Build Status](https://travis-ci.org/dumbmatter/promise-worker-bi.svg?branch=master)](https://travis-ci.org/dumbmatter/promise-worker-bi)
1
+ # promise-worker-bi
2
+
3
+ [![Build Status](https://github.com/dumbmatter/promise-worker-bi/actions/workflows/nodejs.yml/badge.svg)](https://github.com/dumbmatter/promise-worker-bi/actions/workflows/nodejs.yml)
4
+ [![GitHub Repo stars](https://img.shields.io/github/stars/dumbmatter/promise-worker-bi)](https://github.com/dumbmatter/promise-worker-bi)
5
+ [![GitHub](https://img.shields.io/github/license/dumbmatter/promise-worker-bi)](https://github.com/dumbmatter/promise-worker-bi)
6
+ [![npm](https://img.shields.io/npm/v/promise-worker-bi)](https://www.npmjs.com/package/promise-worker-bi)
7
+ [![npm](https://img.shields.io/npm/dm/promise-worker-bi)](https://www.npmjs.com/package/promise-worker-bi)
2
8
 
3
9
  A small (~2 kB min+gz) and performant library for communicating with web workers and shared workers, using promises. Post a message from the browser to the worker, get a promise that resolves to the response. Post a message from the worker to the browser, get a promise that resolves to the response. And with shared workers, you can either broadcast to all browser tabs or send a message to a specific tab.
4
10
 
@@ -21,14 +27,14 @@ const promiseWorker = new PWBHost(worker);
21
27
 
22
28
  // Only needed if you send messages from the worker to the host
23
29
  promiseWorker.register((message) => {
24
- return "pong2";
30
+ return "pong2";
25
31
  });
26
32
 
27
33
  try {
28
- const response = await promiseWorker.postMessage("ping");
29
- // handle response 'pong'
34
+ const response = await promiseWorker.postMessage("ping");
35
+ // handle response 'pong'
30
36
  } catch (error) {
31
- // handle error
37
+ // handle error
32
38
  }
33
39
  ```
34
40
 
@@ -42,14 +48,14 @@ const promiseWorker = new PWBWorker();
42
48
 
43
49
  // Only needed if you send messages from the host to the worker
44
50
  promiseWorker.register((message) => {
45
- return "pong";
51
+ return "pong";
46
52
  });
47
53
 
48
54
  try {
49
- const response = await promiseWorker.postMessage("ping2");
50
- // handle response 'pong2'
55
+ const response = await promiseWorker.postMessage("ping2");
56
+ // handle response 'pong2'
51
57
  } catch (error) {
52
- // handle error
58
+ // handle error
53
59
  }
54
60
  ```
55
61
 
@@ -61,15 +67,15 @@ The message you send can be any object, array, string, number, etc. - anything t
61
67
 
62
68
  ```js
63
69
  await promiseWorker.postMessage({
64
- hello: "world",
65
- answer: 42,
66
- "this is fun": true,
70
+ hello: "world",
71
+ answer: 42,
72
+ "this is fun": true,
67
73
  });
68
74
  ```
69
75
 
70
76
  ```js
71
77
  promiseWorker.register((message) => {
72
- console.log(message); // { hello: 'world', answer: 42, 'this is fun': true }
78
+ console.log(message); // { hello: 'world', answer: 42, 'this is fun': true }
73
79
  });
74
80
  ```
75
81
 
@@ -79,7 +85,7 @@ The registered handler can return either a Promise or a normal value:
79
85
 
80
86
  ```js
81
87
  promiseWorker.register(async () => {
82
- "much async, very promise";
88
+ "much async, very promise";
83
89
  });
84
90
  ```
85
91
 
@@ -94,15 +100,15 @@ Any thrown errors or asynchronous rejections during a response will be propagate
94
100
 
95
101
  ```js
96
102
  promiseWorker.register((message) => {
97
- throw new Error("naughty!");
103
+ throw new Error("naughty!");
98
104
  });
99
105
  ```
100
106
 
101
107
  ```js
102
108
  try {
103
- await promiseWorker.postMessage("whoops");
109
+ await promiseWorker.postMessage("whoops");
104
110
  } catch (error) {
105
- console.log(error.message); // 'naughty!'
111
+ console.log(error.message); // 'naughty!'
106
112
  }
107
113
  ```
108
114
 
@@ -113,8 +119,8 @@ For a Web Worker, we could just use [the normal `error` event](https://developer
113
119
  Therefore, promise-worker-bi includes a unified API that works in Web Workers and Shared Workers. Include some code like this in your host:
114
120
 
115
121
  ```js
116
- promiseWorker.registerError((error) => {
117
- console.log("Error inside worker!", err);
122
+ promiseWorker.addEventListener("error", (errorError) => {
123
+ console.log("Error inside worker!", errorError.error);
118
124
  });
119
125
  ```
120
126
 
@@ -130,21 +136,21 @@ If you need to send messages of multiple types to the worker, just add some type
130
136
 
131
137
  ```js
132
138
  const response1 = await promiseWorker.postMessage({
133
- type: "en",
139
+ type: "en",
134
140
  });
135
141
 
136
142
  const response2 = await promiseWorker.postMessage({
137
- type: "en",
143
+ type: "fr",
138
144
  });
139
145
  ```
140
146
 
141
147
  ```js
142
148
  promiseWorker.register((message) => {
143
- if (message.type === "en") {
144
- return "Hello!";
145
- } else if (message.type === "fr") {
146
- return "Bonjour!";
147
- }
149
+ if (message.type === "en") {
150
+ return "Hello!";
151
+ } else if (message.type === "fr") {
152
+ return "Bonjour!";
153
+ }
148
154
  });
149
155
  ```
150
156
 
@@ -162,14 +168,14 @@ const worker = new SharedWorker("worker.js");
162
168
  const promiseWorker = new PWBHost(worker);
163
169
 
164
170
  promiseWorker.register((message) => {
165
- console.log(message);
171
+ console.log(message);
166
172
  });
167
173
 
168
174
  // setTimeout is just to give you enough time to open main.js and main2.js in
169
175
  // two separate tabs.
170
176
  setTimeout(async () => {
171
- const response = await promiseWorker.postMessage("broadcast");
172
- console.log("Echoed response:", response);
177
+ const response = await promiseWorker.postMessage("broadcast");
178
+ console.log("Echoed response:", response);
173
179
  }, 1000);
174
180
  ```
175
181
 
@@ -199,13 +205,13 @@ import { PWBWorker } from "promise-worker-bi";
199
205
  const promiseWorker = new PWBWorker();
200
206
 
201
207
  promiseWorker.register((message, hostID) => {
202
- if (message === "broadcast") {
203
- promiseWorker.postMessage("to all tabs");
204
- } else {
205
- promiseWorker.postMessage(`hello host ${hostID}`, hostID);
206
- }
208
+ if (message === "broadcast") {
209
+ promiseWorker.postMessage("to all tabs");
210
+ } else {
211
+ promiseWorker.postMessage(`hello host ${hostID}`, hostID);
212
+ }
207
213
 
208
- return message;
214
+ return message;
209
215
  });
210
216
  ```
211
217
 
@@ -244,12 +250,12 @@ If you want to respond to a message by sending a transferable object back, such
244
250
 
245
251
  ```js
246
252
  promiseWorker.register(async (message) => {
247
- const processedMessage = await processMessage(message);
253
+ const processedMessage = await processMessage(message);
248
254
 
249
- return {
250
- message: processedMessage,
251
- _PWB_TRANSFER: [processedMessage.someTransferableProprty],
252
- };
255
+ return {
256
+ message: processedMessage,
257
+ _PWB_TRANSFER: [processedMessage.someTransferableProprty],
258
+ };
253
259
  });
254
260
  ```
255
261
 
@@ -279,13 +285,17 @@ Create a new instance of `PWBHost`, using the given worker.
279
285
 
280
286
  - `worker` - the `Worker`, `SharedWorker` or [PseudoWorker](https://github.com/nolanlawson/pseudo-worker) to use.
281
287
 
282
- #### `promiseWorker.registerError((error: Error) => void)`
288
+ #### `promiseWorker.addEventListener("error", (event: ErrorEvent) => void)`
289
+
290
+ When an error in your web/shared worker process occurs that is _not_ directly in response to a `promiseWorker.postMessage` call, you can see it by listening to the "error" event here.
291
+
292
+ `promiseWorker` is EventTarget so you can use `addEventListener`, `removeEventListener`, and all their normal options.
283
293
 
284
- This should only be called in the browser, not in a worker.
294
+ #### `promiseWorker.addEventListener("close", (event: Event) => void)`
285
295
 
286
- When an error in your web/shared worker process occurs that is _not_ directly in response to a `promiseWorker.postMessage` call, it will be sent to the callback you provide here to `promiseWorker.registerError`.
296
+ There is no built-in API to listen for when a web/shared worker closes, which can happen in various situations such as the OS killing it to free up memory. promise-worker-bi includes a "close" event you can use for this purpose.
287
297
 
288
- Although [normally stack traces are not sent from the worker to the main thread](https://github.com/mknichel/javascript-errors/blob/master/README.md#dedicated-workers), promise-worker-bi magically works around this problem so you will see a stack trace in `error`.
298
+ `promiseWorker` is EventTarget so you can use `addEventListener`, `removeEventListener`, and all their normal options.
289
299
 
290
300
  ### Worker bundle
291
301
 
@@ -295,17 +305,17 @@ Create a new instance of `PWBWorker`.
295
305
 
296
306
  ### Both bundles
297
307
 
298
- #### `promiseWorker.register((message: any, hostID?: number) => any)`
308
+ #### `promiseWorker.register((message: unknown, hostID?: number) => unknown)`
299
309
 
300
310
  Register a message handler wherever you will be receiving messages: in the worker, in the browser, or both. Your handler consumes a message and returns a promise or value.
301
311
 
302
312
  The `hostID` parameter is only defined inside a shared worker, in which case it is a unique number identifying the host that the message came from.
303
313
 
304
- The return value of the callback function (shown above as `any`) must resolve to something [structured cloneable](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm).
314
+ The return value of the callback function (shown above as `unknown`) must resolve to something [structured cloneable](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm).
305
315
 
306
- If you want to [transfer an object](https://developer.mozilla.org/en-US/docs/Glossary/Transferable_objects) in the return value of the callback, then the return type should be `{ message: any, _PWB_TRANSFER: Transferable[] }` rather than just `any`.
316
+ If you want to [transfer an object](https://developer.mozilla.org/en-US/docs/Glossary/Transferable_objects) in the return value of the callback, then the return type should be `{ message: unknown, _PWB_TRANSFER: Transferable[] }` rather than just `unknown`.
307
317
 
308
- #### `promiseWorker.postMessage(message: any, hostID?: number, transfer?: Transferable[]): Promise<any>`
318
+ #### `promiseWorker.postMessage(message: unknown, hostID?: number, transfer?: Transferable[]): Promise<unknown>`
309
319
 
310
320
  Send a message to the browser or worker and return a Promise. `message` must resolve to something [structured cloneable](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm).
311
321
 
package/dist/index.d.ts CHANGED
@@ -1,38 +1,65 @@
1
- type ErrorCallback = (a: Error) => void;
2
- type QueryCallback = (a: any[], b: number | undefined) => any;
3
- declare abstract class PWBBase {
4
- _callbacks: Map<number, (a: Error | null, b: any) => void>;
5
- _queryCallback: QueryCallback;
6
- _workerType: "SharedWorker" | "Worker" | undefined;
7
- constructor();
8
- register(cb: QueryCallback): void;
9
- abstract _postMessage(obj: any[], hostID?: number | undefined, transfer?: Transferable[] | undefined): void;
10
- _postResponse(messageID: number, error: Error | null, result?: any, hostID?: number | undefined): void;
11
- _handleQuery(messageID: number, query: any, hostID: number | undefined): void;
12
- _onMessageCommon(e: MessageEvent): {
13
- message: any;
14
- type: number;
15
- } | void;
1
+ //#region src/message.d.ts
2
+ declare const MSGTYPE_QUERY = 0;
3
+ declare const MSGTYPE_RESPONSE = 1;
4
+ declare const MSGTYPE_HOST_ID = 2;
5
+ declare const MSGTYPE_HOST_LOCK = 3;
6
+ declare const MSGTYPE_WORKER_LOCK = 4;
7
+ declare const MSGTYPE_WORKER_ERROR = 5;
8
+ type QueryMessage = [type: typeof MSGTYPE_QUERY, messageId: number, query: unknown] | [type: typeof MSGTYPE_QUERY, messageId: number, query: unknown, hostId: number | undefined];
9
+ type ResponseMessage = [type: typeof MSGTYPE_RESPONSE, messageId: number, error: Error] | [type: typeof MSGTYPE_RESPONSE, messageId: number, error: null, result: unknown];
10
+ type HostIdMessage = [type: typeof MSGTYPE_HOST_ID, hostId: number] | [type: typeof MSGTYPE_HOST_ID, hostId: number, workerLockId: string];
11
+ type HostLockMessage = [type: typeof MSGTYPE_HOST_LOCK, hostID: number, hostLockId: string];
12
+ type WorkerLockMessage = [type: typeof MSGTYPE_WORKER_LOCK, workerLockId: string];
13
+ type WorkerErrorMessage = [type: typeof MSGTYPE_WORKER_ERROR, error: Error];
14
+ type Message = QueryMessage | ResponseMessage | HostIdMessage | HostLockMessage | WorkerLockMessage | WorkerErrorMessage;
15
+ //#endregion
16
+ //#region src/PWBBase.d.ts
17
+ type QueryCallback = (a: unknown, b: number | undefined) => unknown;
18
+ type EventMap = Record<string, Event>;
19
+ declare abstract class PWBBase<Events extends EventMap> extends EventTarget {
20
+ protected _callbacks: Map<number, (a: Error | null, b: unknown) => void>;
21
+ protected _queryCallback: QueryCallback;
22
+ constructor();
23
+ addEventListener<K extends keyof Events>(type: K, listener: ((event: Events[K]) => void) | {
24
+ handleEvent(object: Events[K]): void;
25
+ } | null, options?: AddEventListenerOptions | boolean): void;
26
+ dispatchEvent<K extends keyof Events>(event: Events[K]): boolean;
27
+ removeEventListener<K extends keyof Events>(type: K, listener: ((event: Events[K]) => void) | {
28
+ handleEvent(object: Events[K]): void;
29
+ } | null, options?: EventListenerOptions | boolean): void;
30
+ register(cb: QueryCallback): void;
31
+ protected abstract _postMessage(obj: Message, hostID?: number | undefined, transfer?: Transferable[] | undefined): void;
32
+ private _postResponse;
33
+ private _handleQuery;
34
+ protected _onMessageCommon(e: MessageEvent): [type: 2, hostId: number] | [type: 2, hostId: number, workerLockId: string] | HostLockMessage | WorkerLockMessage | WorkerErrorMessage | undefined;
16
35
  }
17
- declare class PWBHost extends PWBBase {
18
- _errorCallback: ErrorCallback | undefined;
19
- _hostID: number | undefined;
20
- _hostIDQueue: (() => void)[] | undefined;
21
- _worker: SharedWorker | Worker;
22
- constructor(worker: SharedWorker | Worker);
23
- registerError(cb: ErrorCallback): void;
24
- _postMessage(obj: any[], _hostID?: unknown, transfer?: Transferable[] | undefined): void;
25
- postMessage(userMessage: any, _hostID?: undefined, transfer?: Transferable[] | undefined): Promise<any>;
26
- _onMessage(e: MessageEvent): void;
36
+ //#endregion
37
+ //#region src/PWBHost.d.ts
38
+ type HostEvents = {
39
+ close: Event;
40
+ error: ErrorEvent;
41
+ };
42
+ export declare class PWBHost extends PWBBase<HostEvents> {
43
+ private _hostID;
44
+ private _hostIDQueue;
45
+ private _worker;
46
+ constructor(worker: SharedWorker | Worker);
47
+ protected _postMessage(obj: QueryMessage | ResponseMessage | HostLockMessage, _hostID?: unknown, transfer?: Transferable[] | undefined): void;
48
+ postMessage(userMessage: unknown, _hostID?: undefined, transfer?: Transferable[] | undefined): Promise<any>;
49
+ private handleWorkerLock;
50
+ private _onMessage;
27
51
  }
28
- declare class PWBWorker extends PWBBase {
29
- _hosts: Map<number, {
30
- port: MessagePort;
31
- }>;
32
- _maxHostID: number;
33
- constructor();
34
- _postMessage(obj: any[], targetHostID?: number | undefined, transfer?: Transferable[] | undefined): void;
35
- postMessage(userMessage: any, targetHostID?: number | undefined, transfer?: Transferable[] | undefined): Promise<any>;
36
- _onMessage(e: MessageEvent): void;
52
+ //#endregion
53
+ //#region src/PWBWorker.d.ts
54
+ type WorkerEvents = Record<string, Event>;
55
+ export declare class PWBWorker extends PWBBase<WorkerEvents> {
56
+ private _hosts;
57
+ private _maxHostID;
58
+ private _sharedWorker;
59
+ private _workerLockAcquired;
60
+ constructor();
61
+ protected _postMessage(message: QueryMessage | ResponseMessage | HostIdMessage | WorkerLockMessage | WorkerErrorMessage, targetHostID?: number | undefined, transfer?: Transferable[] | undefined): void;
62
+ postMessage(userMessage: unknown, targetHostID?: number | undefined, transfer?: Transferable[] | undefined): Promise<unknown>;
63
+ private _onMessage;
37
64
  }
38
- export { PWBHost, PWBWorker };
65
+ //#endregion
package/dist/index.js CHANGED
@@ -1,377 +1,299 @@
1
- let messageIDs = 0;
2
- const MSGTYPE_QUERY = 0;
3
- const MSGTYPE_RESPONSE = 1;
4
- const MSGTYPE_HOST_ID = 2;
5
- const MSGTYPE_HOST_CLOSE = 3;
6
- const MSGTYPE_WORKER_ERROR = 4;
7
- const MSGTYPES = [MSGTYPE_QUERY, MSGTYPE_RESPONSE, MSGTYPE_HOST_ID, MSGTYPE_HOST_CLOSE, MSGTYPE_WORKER_ERROR];
8
-
9
- // Inlined from https://github.com/then/is-promise
10
- const isPromise = obj => !!obj && (typeof obj === "object" || typeof obj === "function") && typeof obj.then === "function";
11
- const toFakeError = error => {
12
- const fakeError = {
13
- name: error.name,
14
- message: error.message
15
- };
16
- if (typeof error.stack === "string") {
17
- fakeError.stack = error.stack;
18
- }
19
-
20
- // These are non-standard properties, I think only in some versions of Firefox
21
- // @ts-expect-error
22
- if (typeof error.fileName === "string") {
23
- // @ts-expect-error
24
- fakeError.fileName = error.fileName;
25
- }
26
- // @ts-expect-error
27
- if (typeof error.columnNumber === "number") {
28
- // @ts-expect-error
29
- fakeError.columnNumber = error.columnNumber;
30
- }
31
- // @ts-expect-error
32
- if (typeof error.lineNumber === "number") {
33
- // @ts-expect-error
34
- fakeError.lineNumber = error.lineNumber;
35
- }
36
- return fakeError;
1
+ const isError = (error) => {
2
+ if (Error.isError) return Error.isError(error);
3
+ return true;
37
4
  };
38
-
39
- // Object rather than FakeError for convenience
40
- const fromFakeError = fakeError => {
41
- const error = new Error();
42
- return Object.assign(error, fakeError);
5
+ const parseMessage = (message) => {
6
+ if (!Array.isArray(message) || message.length < 2 || message.length > 4) return;
7
+ const type = message[0];
8
+ if (type === 0) {
9
+ if (typeof message[1] !== "number") throw new Error("Invalid messageID");
10
+ if (message[3] !== void 0 && typeof message[3] !== "number") throw new Error("Invalid hostID");
11
+ return message;
12
+ }
13
+ if (type === 1) {
14
+ if (typeof message[1] !== "number") throw new Error("Invalid messageID");
15
+ if (message[2] !== null && !isError(message[2])) throw new Error("Invalid error");
16
+ return message;
17
+ }
18
+ if (type === 2) {
19
+ if (typeof message[1] !== "number") throw new Error("Invalid hostID");
20
+ if (message[2] !== void 0 && typeof message[2] !== "string") throw new Error("Invalid workerLockId");
21
+ return message;
22
+ }
23
+ if (type === 3) {
24
+ if (typeof message[1] !== "number") throw new Error("Invalid hostID");
25
+ if (typeof message[2] !== "string") throw new Error("Invalid hostLockId");
26
+ return message;
27
+ }
28
+ if (type === 4) {
29
+ if (typeof message[1] !== "string") throw new Error("Invalid workerLockId");
30
+ return message;
31
+ }
32
+ if (type === 5) {
33
+ if (!isError(message[1])) throw new Error("Invalid error");
34
+ return message;
35
+ }
36
+ throw new Error("Invalid message type");
43
37
  };
44
- const logError = err => {
45
- // Logging in the console makes debugging in the worker easier
46
- console.error("Error in Worker:");
47
- console.error(err); // Safari needs it on new line
38
+ //#endregion
39
+ //#region src/PWBBase.ts
40
+ const isPromise = (obj) => !!obj && (typeof obj === "object" || typeof obj === "function") && typeof obj.then === "function";
41
+ const logError = (err) => {
42
+ console.error("Error in worker:");
43
+ console.error(err);
48
44
  };
49
- class PWBBase {
50
- constructor() {
51
- // console.log('constructor', worker);
52
- this._callbacks = new Map();
53
- this._queryCallback = () => {};
54
-
55
- // @ts-expect-error
56
- this._onMessage = this._onMessage.bind(this);
57
- }
58
- register(cb) {
59
- // console.log('register', cb);
60
- this._queryCallback = cb;
61
- }
62
-
63
- // From worker, 2nd param could be hostID if sending to specific host. From UI, 2nd param could be an array of transferable objects
64
-
65
- _postResponse(messageID, error, result, hostID) {
66
- // console.log('_postResponse', messageID, error, result);
67
- if (error) {
68
- logError(error);
69
- this._postMessage([MSGTYPE_RESPONSE, messageID, toFakeError(error)], hostID);
70
- } else {
71
- // Hackily identify when message contains transferable objects
72
- if (typeof result === "object" && result !== null && Object.hasOwn(result, "message") && Object.hasOwn(result, "_PWB_TRANSFER")) {
73
- this._postMessage([MSGTYPE_RESPONSE, messageID, null, result.message], hostID, result._PWB_TRANSFER);
74
- } else {
75
- this._postMessage([MSGTYPE_RESPONSE, messageID, null, result], hostID);
76
- }
77
- }
78
- }
79
- _handleQuery(messageID, query, hostID) {
80
- // console.log('_handleQuery', messageID, query);
81
- try {
82
- const result = this._queryCallback(query, hostID);
83
- if (!isPromise(result)) {
84
- this._postResponse(messageID, null, result, hostID);
85
- } else {
86
- result.then(finalResult => {
87
- this._postResponse(messageID, null, finalResult, hostID);
88
- }, finalError => {
89
- this._postResponse(messageID, finalError, hostID);
90
- });
91
- }
92
- } catch (err) {
93
- this._postResponse(messageID, err);
94
- }
95
- }
96
-
97
- // Either return messageID and type if further processing is needed, or undefined otherwise
98
- _onMessageCommon(e) {
99
- // console.log('_onMessage', e.data);
100
- const message = e.data;
101
- if (!Array.isArray(message) || message.length < 3 || message.length > 4) {
102
- return; // Ignore - this message is not for us
103
- }
104
- if (typeof message[0] !== "number" || MSGTYPES.indexOf(message[0]) < 0) {
105
- throw new Error("Invalid messageID");
106
- }
107
- const type = message[0];
108
- if (typeof message[1] !== "number") {
109
- throw new Error("Invalid messageID");
110
- }
111
- const messageID = message[1];
112
- if (type === MSGTYPE_QUERY) {
113
- const query = message[2];
114
- if (typeof message[3] !== "number" && message[3] !== undefined) {
115
- throw new Error("Invalid hostID");
116
- }
117
- const hostID = message[3];
118
- this._handleQuery(messageID, query, hostID);
119
- return;
120
- }
121
- if (type === MSGTYPE_RESPONSE) {
122
- if (message[2] !== null && typeof message[2] !== "object") {
123
- throw new Error("Invalid error");
124
- }
125
- const error = message[2] === null ? null : fromFakeError(message[2]);
126
- const result = message[3];
127
- const callback = this._callbacks.get(messageID);
128
- if (callback === undefined) {
129
- // Ignore - user might have created multiple PromiseWorkers.
130
- // This message is not for us.
131
- return;
132
- }
133
- this._callbacks.delete(messageID);
134
- callback(error, result);
135
- return;
136
- }
137
- return {
138
- message,
139
- type
140
- };
141
- }
142
- }
143
- class PWBHost extends PWBBase {
144
- // Only defined on host
145
-
146
- constructor(worker) {
147
- super();
148
-
149
- // The following if statement used to check `worker instanceof Worker` but I have recieved
150
- // reports that in some weird cases, Safari will inappropriately return false for that, even
151
- // in obvious cases like:
152
- //
153
- // blob = new Blob(["self.onmessage = function() {};"], { type: "text/javascript" });
154
- // worker = new Worker(window.URL.createObjectURL(blob));
155
- // console.log(worker instanceof Worker);
156
- //
157
- // So instead, let's do this test for worker.port which only exists on shared workers.
158
- // @ts-expect-error
159
- if (worker.port === undefined) {
160
- this._workerType = "Worker";
161
-
162
- // @ts-expect-error
163
- worker.addEventListener("message", this._onMessage);
164
- } else {
165
- this._workerType = "SharedWorker";
166
-
167
- // @ts-expect-error - it doesn't know if _worker is Worker or SharedWorker, but I do
168
- worker.port.addEventListener("message", this._onMessage);
169
- // @ts-expect-error - it doesn't know if _worker is Worker or SharedWorker, but I do
170
- worker.port.start();
171
-
172
- // Handle tab close. This isn't perfect, but there is no perfect method
173
- // http://stackoverflow.com/q/13662089/786644 and this should work like
174
- // 99% of the time. It is a memory leak if it fails, but for most use
175
- // cases, it shouldn't be noticeable.
176
- window.addEventListener("beforeunload", () => {
177
- // Prevent firing if we don't know hostID yet
178
- if (this._hostID !== undefined) {
179
- this._postMessage([MSGTYPE_HOST_CLOSE, -1, this._hostID]);
180
- }
181
- });
182
- }
183
- this._worker = worker;
184
- this._hostIDQueue = [];
185
- }
186
- registerError(cb) {
187
- // console.log('registerError', cb);
188
- this._errorCallback = cb;
189
-
190
- // Some browsers (Firefox) call onerror on every host, while others
191
- // (Chrome) do nothing. Let's disable that everywhere, for consistency.
192
- this._worker.addEventListener("error", e => {
193
- e.preventDefault();
194
- e.stopPropagation();
195
- });
196
- }
197
- _postMessage(obj, _hostID, transfer) {
198
- // console.log('_postMessage', obj, _hostID, transfer);
199
- if (this._workerType === "Worker") {
200
- // @ts-expect-error - it doesn't know if _worker is Worker or SharedWorker, but I do
201
- this._worker.postMessage(obj, transfer);
202
- } else if (this._workerType === "SharedWorker") {
203
- // @ts-expect-error - it doesn't know if _worker is Worker or SharedWorker, but I do
204
- this._worker.port.postMessage(obj, transfer);
205
- } else {
206
- throw new Error("WTF");
207
- }
208
- }
209
- postMessage(userMessage, _hostID, transfer) {
210
- // console.log('postMessage', userMessage, _hostID, transfer);
211
- const actuallyPostMessage = (resolve, reject) => {
212
- const messageID = messageIDs;
213
- messageIDs += 1;
214
- const messageToSend = [MSGTYPE_QUERY, messageID, userMessage, this._hostID];
215
- this._callbacks.set(messageID, (error, result) => {
216
- if (error) {
217
- reject(error);
218
- } else {
219
- resolve(result);
220
- }
221
- });
222
- this._postMessage(messageToSend, undefined, transfer);
223
- };
224
- return new Promise((resolve, reject) => {
225
- // Don't send a message until hostID is known, otherwise it's a race
226
- // condition and sometimes hostID will be undefined.
227
- if (this._hostIDQueue !== undefined) {
228
- this._hostIDQueue.push(() => {
229
- actuallyPostMessage(resolve, reject);
230
- });
231
- } else {
232
- actuallyPostMessage(resolve, reject);
233
- }
234
- });
235
- }
236
- _onMessage(e) {
237
- const common = this._onMessageCommon(e);
238
- if (!common) {
239
- return;
240
- }
241
- const {
242
- message,
243
- type
244
- } = common;
245
- if (type === MSGTYPE_HOST_ID) {
246
- if (message[2] !== undefined && typeof message[2] !== "number") {
247
- throw new Error("Invalid hostID");
248
- }
249
- const hostID = message[2];
250
- this._hostID = hostID;
251
- if (this._hostIDQueue !== undefined) {
252
- this._hostIDQueue.forEach(func => {
253
- // Not entirely sure why setTimeout is needed, might be just for unit tests
254
- setTimeout(() => {
255
- func();
256
- }, 0);
257
- });
258
- this._hostIDQueue = undefined; // Never needed again after initial setup
259
- }
260
- } else if (type === MSGTYPE_WORKER_ERROR) {
261
- if (message[2] !== undefined && message[2] !== null && typeof message[2] === "object") {
262
- const error = fromFakeError(message[2]);
263
- if (this._errorCallback !== undefined) {
264
- this._errorCallback(error);
265
- }
266
- }
267
- }
268
- }
269
- }
270
- class PWBWorker extends PWBBase {
271
- constructor() {
272
- super();
273
-
274
- // Only actually used for SharedWorker
275
- this._hosts = new Map();
276
- this._maxHostID = -1;
277
- if (
278
- // @ts-expect-error
279
- typeof SharedWorkerGlobalScope !== "undefined" &&
280
- // @ts-expect-error
281
- self instanceof SharedWorkerGlobalScope) {
282
- this._workerType = "SharedWorker";
283
- self.addEventListener("connect", e => {
284
- // @ts-expect-error
285
- const port = e.ports[0];
286
- port.addEventListener("message", e2 => this._onMessage(e2));
287
- port.start();
288
- this._maxHostID += 1;
289
- const hostID = this._maxHostID;
290
- this._hosts.set(hostID, {
291
- port
292
- });
293
-
294
- // Send back hostID to this host, otherwise it has no way to know it
295
- this._postMessage([MSGTYPE_HOST_ID, -1, hostID], hostID);
296
- });
297
- self.addEventListener("error", e => {
298
- logError(e.error);
299
-
300
- // Just send to first host, so as to not duplicate error tracking
301
- const hostID = this._hosts.keys().next().value;
302
- if (hostID !== undefined) {
303
- this._postMessage([MSGTYPE_WORKER_ERROR, -1, toFakeError(e.error)], hostID);
304
- }
305
- });
306
- } else {
307
- this._workerType = "Worker";
308
- self.addEventListener("message", this._onMessage);
309
-
310
- // Since this is not a Shared Worker, hostID is always 0 so it's not strictly required to
311
- // send this back, but it makes the API a bit more consistent if there is the same
312
- // initialization handshake in both cases.
313
- this._postMessage([MSGTYPE_HOST_ID, -1, 0], 0);
314
- self.addEventListener("error", e => {
315
- logError(e.error);
316
- this._postMessage([MSGTYPE_WORKER_ERROR, -1, toFakeError(e.error)]);
317
- });
318
- }
319
- }
320
- _postMessage(obj, targetHostID, transfer) {
321
- // console.log('_postMessage', obj, targetHostID);
322
- if (this._workerType === "SharedWorker") {
323
- // If targetHostID has been deleted, this will do nothing, which is fine I think
324
- this._hosts.forEach(({
325
- port
326
- }, hostID) => {
327
- if (targetHostID === undefined || targetHostID === hostID) {
328
- // @ts-expect-error TypeScript thinks transfer can't be undefined
329
- port.postMessage(obj, transfer);
330
- }
331
- });
332
- } else if (this._workerType === "Worker") {
333
- // @ts-expect-error TypeScript thinks self is window, which has a different postMessage call signature. In a worker, this is correct.
334
- self.postMessage(obj, transfer);
335
- } else {
336
- throw new Error("WTF");
337
- }
338
- }
339
- postMessage(userMessage, targetHostID, transfer) {
340
- // console.log('postMessage', userMessage, targetHostID);
341
- const actuallyPostMessage = (resolve, reject) => {
342
- const messageID = messageIDs;
343
- messageIDs += 1;
344
- const messageToSend = [MSGTYPE_QUERY, messageID, userMessage];
345
- this._callbacks.set(messageID, (error, result) => {
346
- if (error) {
347
- reject(error);
348
- } else {
349
- resolve(result);
350
- }
351
- });
352
- this._postMessage(messageToSend, targetHostID, transfer);
353
- };
354
- return new Promise((resolve, reject) => {
355
- actuallyPostMessage(resolve, reject);
356
- });
357
- }
358
- _onMessage(e) {
359
- const common = this._onMessageCommon(e);
360
- if (!common) {
361
- return;
362
- }
363
- const {
364
- message,
365
- type
366
- } = common;
367
- if (type === MSGTYPE_HOST_CLOSE) {
368
- if (typeof message[2] !== "number") {
369
- throw new Error("Invalid hostID");
370
- }
371
- const hostID = message[2];
372
- this._hosts.delete(hostID);
373
- }
374
- }
375
- }
376
-
45
+ const isSharedWorker = (worker) => {
46
+ return worker.port !== void 0;
47
+ };
48
+ var PWBBase = class extends EventTarget {
49
+ _callbacks;
50
+ _queryCallback;
51
+ constructor() {
52
+ super();
53
+ this._callbacks = /* @__PURE__ */ new Map();
54
+ this._queryCallback = () => {};
55
+ this._onMessage = this._onMessage.bind(this);
56
+ }
57
+ addEventListener(type, listener, options) {
58
+ super.addEventListener(type, listener, options);
59
+ }
60
+ dispatchEvent(event) {
61
+ return super.dispatchEvent(event);
62
+ }
63
+ removeEventListener(type, listener, options) {
64
+ super.removeEventListener(type, listener, options);
65
+ }
66
+ register(cb) {
67
+ this._queryCallback = cb;
68
+ }
69
+ _postResponse(messageID, error, result, hostID) {
70
+ if (error) {
71
+ logError(error);
72
+ this._postMessage([
73
+ 1,
74
+ messageID,
75
+ error
76
+ ], hostID);
77
+ } else if (typeof result === "object" && result !== null && Object.hasOwn(result, "message") && Object.hasOwn(result, "_PWB_TRANSFER")) this._postMessage([
78
+ 1,
79
+ messageID,
80
+ null,
81
+ result.message
82
+ ], hostID, result._PWB_TRANSFER);
83
+ else this._postMessage([
84
+ 1,
85
+ messageID,
86
+ null,
87
+ result
88
+ ], hostID);
89
+ }
90
+ _handleQuery(message) {
91
+ const messageID = message[1];
92
+ const query = message[2];
93
+ const hostID = message[3];
94
+ try {
95
+ const result = this._queryCallback(query, hostID);
96
+ if (!isPromise(result)) this._postResponse(messageID, null, result, hostID);
97
+ else result.then((finalResult) => {
98
+ this._postResponse(messageID, null, finalResult, hostID);
99
+ }, (finalError) => {
100
+ this._postResponse(messageID, finalError, void 0, hostID);
101
+ });
102
+ } catch (err) {
103
+ this._postResponse(messageID, err);
104
+ }
105
+ }
106
+ _onMessageCommon(e) {
107
+ const message = parseMessage(e.data);
108
+ if (!message) return;
109
+ if (message[0] === 0) {
110
+ this._handleQuery(message);
111
+ return;
112
+ }
113
+ if (message[0] === 1) {
114
+ const messageID = message[1];
115
+ const error = message[2] === null ? null : message[2];
116
+ const callback = this._callbacks.get(messageID);
117
+ if (callback === void 0) return;
118
+ this._callbacks.delete(messageID);
119
+ callback(error, message[3]);
120
+ return;
121
+ }
122
+ return message;
123
+ }
124
+ };
125
+ //#endregion
126
+ //#region src/PWBHost.ts
127
+ let nextMessageID$1 = 0;
128
+ var PWBHost = class extends PWBBase {
129
+ _hostID;
130
+ _hostIDQueue;
131
+ _worker;
132
+ constructor(worker) {
133
+ super();
134
+ if (!isSharedWorker(worker)) worker.addEventListener("message", this._onMessage);
135
+ else {
136
+ worker.port.addEventListener("message", this._onMessage);
137
+ worker.port.start();
138
+ }
139
+ this._worker = worker;
140
+ this._hostIDQueue = [];
141
+ }
142
+ _postMessage(obj, _hostID, transfer) {
143
+ if (isSharedWorker(this._worker)) {
144
+ if (transfer) this._worker.port.postMessage(obj, transfer);
145
+ else this._worker.port.postMessage(obj);
146
+ } else if (transfer) this._worker.postMessage(obj, transfer);
147
+ else this._worker.postMessage(obj);
148
+ }
149
+ postMessage(userMessage, _hostID, transfer) {
150
+ const actuallyPostMessage = (resolve, reject) => {
151
+ const messageID = nextMessageID$1;
152
+ nextMessageID$1 += 1;
153
+ const messageToSend = [
154
+ 0,
155
+ messageID,
156
+ userMessage,
157
+ this._hostID
158
+ ];
159
+ this._callbacks.set(messageID, (error, result) => {
160
+ if (error) reject(error);
161
+ else resolve(result);
162
+ });
163
+ this._postMessage(messageToSend, void 0, transfer);
164
+ };
165
+ return new Promise((resolve, reject) => {
166
+ if (this._hostIDQueue !== void 0) this._hostIDQueue.push(() => {
167
+ actuallyPostMessage(resolve, reject);
168
+ });
169
+ else actuallyPostMessage(resolve, reject);
170
+ });
171
+ }
172
+ handleWorkerLock(workerLockId) {
173
+ navigator.locks.request(workerLockId, { mode: "shared" }, async () => {
174
+ this.dispatchEvent(new Event("close"));
175
+ });
176
+ }
177
+ _onMessage(e) {
178
+ const message = this._onMessageCommon(e);
179
+ if (!message) return;
180
+ if (message[0] === 2) {
181
+ if (this._hostID !== void 0) throw new Error("Received duplicate hostID message");
182
+ const hostID = message[1];
183
+ this._hostID = hostID;
184
+ if (this._hostIDQueue !== void 0) {
185
+ for (const cb of this._hostIDQueue) setTimeout(() => {
186
+ cb();
187
+ }, 0);
188
+ this._hostIDQueue = void 0;
189
+ }
190
+ if (message[2] !== void 0) this.handleWorkerLock(message[2]);
191
+ const hostLockId = `pwb-host-${Math.random()}`;
192
+ navigator.locks.request(hostLockId, async () => {
193
+ this._postMessage([
194
+ 3,
195
+ hostID,
196
+ hostLockId
197
+ ]);
198
+ return new Promise(() => {});
199
+ });
200
+ } else if (message[0] === 5) {
201
+ const error = message[1];
202
+ this.dispatchEvent(new ErrorEvent("error", { error }));
203
+ } else if (message[0] === 4) this.handleWorkerLock(message[1]);
204
+ }
205
+ };
206
+ //#endregion
207
+ //#region src/PWBWorker.ts
208
+ let nextMessageID = 0;
209
+ var PWBWorker = class extends PWBBase {
210
+ _hosts = /* @__PURE__ */ new Map();
211
+ _maxHostID = -1;
212
+ _sharedWorker;
213
+ _workerLockAcquired = false;
214
+ constructor() {
215
+ super();
216
+ const workerLockId = `pwb-worker-${Math.random()}`;
217
+ navigator.locks.request(workerLockId, async () => {
218
+ this._workerLockAcquired = true;
219
+ this._postMessage([4, workerLockId]);
220
+ return new Promise(() => {});
221
+ });
222
+ if (typeof SharedWorkerGlobalScope !== "undefined" && self instanceof SharedWorkerGlobalScope) {
223
+ this._sharedWorker = true;
224
+ const _self = self;
225
+ _self.addEventListener("connect", (e) => {
226
+ const port = e.ports[0];
227
+ port.addEventListener("message", (e2) => this._onMessage(e2));
228
+ port.start();
229
+ this._maxHostID += 1;
230
+ const hostID = this._maxHostID;
231
+ this._hosts.set(hostID, { port });
232
+ let message;
233
+ if (this._workerLockAcquired) message = [
234
+ 2,
235
+ hostID,
236
+ workerLockId
237
+ ];
238
+ else message = [2, hostID];
239
+ this._postMessage(message, hostID);
240
+ });
241
+ _self.addEventListener("error", (e) => {
242
+ logError(e.error);
243
+ e.preventDefault();
244
+ const hostID = this._hosts.keys().next().value;
245
+ if (hostID !== void 0) this._postMessage([5, e.error], hostID);
246
+ });
247
+ } else {
248
+ this._sharedWorker = false;
249
+ const _self = self;
250
+ _self.addEventListener("message", this._onMessage);
251
+ this._postMessage([2, 0], 0);
252
+ _self.addEventListener("error", (e) => {
253
+ logError(e.error);
254
+ e.preventDefault();
255
+ this._postMessage([5, e.error]);
256
+ });
257
+ }
258
+ }
259
+ _postMessage(message, targetHostID, transfer) {
260
+ if (this._sharedWorker) {
261
+ for (const [hostID, { port }] of this._hosts) if (targetHostID === void 0 || targetHostID === hostID) {
262
+ if (transfer) port.postMessage(message, transfer);
263
+ else port.postMessage(message);
264
+ }
265
+ } else {
266
+ const _self = self;
267
+ if (transfer) _self.postMessage(message, transfer);
268
+ else _self.postMessage(message);
269
+ }
270
+ }
271
+ postMessage(userMessage, targetHostID, transfer) {
272
+ const actuallyPostMessage = (resolve, reject) => {
273
+ const messageID = nextMessageID;
274
+ nextMessageID += 1;
275
+ const messageToSend = [
276
+ 0,
277
+ messageID,
278
+ userMessage
279
+ ];
280
+ this._callbacks.set(messageID, (error, result) => {
281
+ if (error) reject(error);
282
+ else resolve(result);
283
+ });
284
+ this._postMessage(messageToSend, targetHostID, transfer);
285
+ };
286
+ return new Promise((resolve, reject) => {
287
+ actuallyPostMessage(resolve, reject);
288
+ });
289
+ }
290
+ _onMessage(e) {
291
+ const message = this._onMessageCommon(e);
292
+ if (!message) return;
293
+ if (message[0] === 3) navigator.locks.request(message[2], () => {
294
+ if (this._hosts.size > 1) this._hosts.delete(message[1]);
295
+ });
296
+ }
297
+ };
298
+ //#endregion
377
299
  export { PWBHost, PWBWorker };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "promise-worker-bi",
3
- "version": "5.0.2",
3
+ "version": "6.0.0-beta.1",
4
4
  "description": "Promise-based messaging for Web Workers and Shared Workers",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -12,13 +12,10 @@
12
12
  },
13
13
  "type": "module",
14
14
  "scripts": {
15
- "prepack": "node --run build",
16
- "build": "rm -rf dist && rollup -c && tsc --emitDeclarationOnly",
17
- "build-test": "node --run build && node scripts/build-test.js",
18
- "lint": "eslint .",
19
- "prettier": "prettier --write 'src/*.ts' && prettier --write 'test/*.js' && prettier --write 'scripts/*.js'",
20
- "karma": "node --run build-test && karma start",
21
- "test": "tsc --noEmit && node --run lint && node --run karma",
15
+ "prepack": "pnpm run build",
16
+ "build": "tsdown",
17
+ "lint": "pnpm run build && tsc && eslint .",
18
+ "test": "pnpm run build && vitest run",
22
19
  "prepare": "husky"
23
20
  },
24
21
  "repository": {
@@ -40,36 +37,26 @@
40
37
  },
41
38
  "homepage": "https://github.com/dumbmatter/promise-worker-bi",
42
39
  "devDependencies": {
43
- "@babel/core": "^7.29.0",
44
- "@babel/preset-typescript": "^7.28.5",
45
40
  "@eslint/js": "^10.0.1",
46
- "@rollup/plugin-babel": "^7.0.0",
47
- "@rollup/plugin-node-resolve": "^16.0.3",
48
- "@typescript-eslint/eslint-plugin": "^8.56.1",
49
- "@typescript-eslint/parser": "^8.56.1",
50
- "eslint": "^10.0.3",
51
- "eslint-plugin-import-x": "^4.16.1",
52
- "globals": "^17.4.0",
41
+ "@typescript-eslint/eslint-plugin": "^8.69.0",
42
+ "@typescript-eslint/parser": "^8.69.0",
43
+ "@vitest/browser-playwright": "^5.0.0",
44
+ "eslint": "^10.10.0",
45
+ "eslint-plugin-import-x": "^4.17.1",
46
+ "globals": "^17.12.0",
53
47
  "husky": "^9.1.7",
54
- "karma": "^6.4.1",
55
- "karma-chrome-launcher": "^3.1.1",
56
- "karma-firefox-launcher": "^2.1.2",
57
- "karma-mocha": "^2.0.1",
58
- "karma-mocha-reporter": "^2.2.5",
59
- "lint-staged": "^16.3.2",
60
- "mocha": "^11.7.5",
61
- "prettier": "^3.8.1",
62
- "rollup": "^4.59.0",
63
- "rollup-plugin-node-builtins": "^2.1.2",
64
- "rollup-plugin-node-globals": "^1.4.0",
65
- "tinyglobby": "^0.2.15",
66
- "typescript": "^5.9.3",
67
- "typescript-eslint": "^8.56.1"
48
+ "lint-staged": "^17.5.0",
49
+ "oxfmt": "^0.66.0",
50
+ "playwright": "^1.63.0",
51
+ "tsdown": "^0.23.0",
52
+ "typescript": "^6.0.3",
53
+ "typescript-eslint": "^8.69.0",
54
+ "vitest": "^5.0.0"
68
55
  },
69
56
  "files": [
70
57
  "dist"
71
58
  ],
72
59
  "lint-staged": {
73
- "*.{js,cjs,mjs,jsx,json,scss,ts,cts,mts,tsx}": "prettier --write"
60
+ "*": "oxfmt --no-error-on-unmatched-pattern"
74
61
  }
75
62
  }