promise-worker-bi 3.0.2 → 4.0.3
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 +104 -95
- package/dist/commonjs.js +434 -0
- package/dist/esmodules.js +429 -0
- package/dist/index.d.ts +38 -0
- package/dist/test/test.js +3774 -0
- package/dist/test/worker-bidirectional-echo.js +3169 -0
- package/dist/test/worker-echo-custom-2.js +308 -0
- package/dist/test/worker-echo-custom.js +308 -0
- package/dist/test/worker-echo.js +301 -0
- package/dist/test/worker-empty.js +300 -0
- package/dist/test/worker-error-async.js +303 -0
- package/dist/test/worker-error-sync.js +301 -0
- package/dist/test/worker-host-echo-custom.js +3183 -0
- package/dist/test/worker-host-echo-multiple.js +3182 -0
- package/dist/test/worker-host-echo.js +3164 -0
- package/dist/test/worker-host-empty.js +3172 -0
- package/dist/test/worker-host-error-async.js +3169 -0
- package/dist/test/worker-host-error-outside-response.js +303 -0
- package/dist/test/worker-host-error-sync.js +3169 -0
- package/dist/test/worker-host-ping.js +3164 -0
- package/dist/test/worker-hostid.js +302 -0
- package/dist/test/worker-pong-promise.js +301 -0
- package/dist/test/worker-pong.js +301 -0
- package/dist/test/worker-shared.js +309 -0
- package/package.json +41 -37
- package/CHANGELOG.md +0 -49
- package/dist/index.js +0 -375
package/dist/commonjs.js
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
let messageIDs = 0;
|
|
6
|
+
const MSGTYPE_QUERY = 0;
|
|
7
|
+
const MSGTYPE_RESPONSE = 1;
|
|
8
|
+
const MSGTYPE_HOST_ID = 2;
|
|
9
|
+
const MSGTYPE_HOST_CLOSE = 3;
|
|
10
|
+
const MSGTYPE_WORKER_ERROR = 4;
|
|
11
|
+
const MSGTYPES = [MSGTYPE_QUERY, MSGTYPE_RESPONSE, MSGTYPE_HOST_ID, MSGTYPE_HOST_CLOSE, MSGTYPE_WORKER_ERROR]; // Inlined from https://github.com/then/is-promise
|
|
12
|
+
|
|
13
|
+
const isPromise = obj => !!obj && (typeof obj === "object" || typeof obj === "function") && typeof obj.then === "function";
|
|
14
|
+
|
|
15
|
+
const toFakeError = error => {
|
|
16
|
+
const fakeError = {
|
|
17
|
+
name: error.name,
|
|
18
|
+
message: error.message
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
if (typeof error.stack === "string") {
|
|
22
|
+
fakeError.stack = error.stack;
|
|
23
|
+
} // These are non-standard properties, I think only in some versions of Firefox
|
|
24
|
+
// @ts-ignore
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if (typeof error.fileName === "string") {
|
|
28
|
+
// @ts-ignore
|
|
29
|
+
fakeError.fileName = error.fileName;
|
|
30
|
+
} // @ts-ignore
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
if (typeof error.columnNumber === "number") {
|
|
34
|
+
// @ts-ignore
|
|
35
|
+
fakeError.columnNumber = error.columnNumber;
|
|
36
|
+
} // @ts-ignore
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
if (typeof error.lineNumber === "number") {
|
|
40
|
+
// @ts-ignore
|
|
41
|
+
fakeError.lineNumber = error.lineNumber;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return fakeError;
|
|
45
|
+
}; // Object rather than FakeError for convenience
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
const fromFakeError = fakeError => {
|
|
49
|
+
const error = new Error();
|
|
50
|
+
return Object.assign(error, fakeError);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const logError = err => {
|
|
54
|
+
// Logging in the console makes debugging in the worker easier
|
|
55
|
+
|
|
56
|
+
/* eslint-disable no-console */
|
|
57
|
+
console.error("Error in Worker:");
|
|
58
|
+
console.error(err); // Safari needs it on new line
|
|
59
|
+
|
|
60
|
+
/* eslint-enable no-console */
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
class PWBBase {
|
|
64
|
+
constructor() {
|
|
65
|
+
// console.log('constructor', worker);
|
|
66
|
+
this._callbacks = new Map();
|
|
67
|
+
|
|
68
|
+
this._queryCallback = () => {}; // @ts-ignore
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
this._onMessage = this._onMessage.bind(this);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
register(cb) {
|
|
75
|
+
// console.log('register', cb);
|
|
76
|
+
this._queryCallback = cb;
|
|
77
|
+
} // eslint-disable-next-line
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
_postMessage(obj, targetHostID) {
|
|
81
|
+
throw new Error("Not implemented");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
_postResponse(messageID, error, result, hostID) {
|
|
85
|
+
// console.log('_postResponse', messageID, error, result);
|
|
86
|
+
if (error) {
|
|
87
|
+
logError(error);
|
|
88
|
+
|
|
89
|
+
this._postMessage([MSGTYPE_RESPONSE, messageID, toFakeError(error)], hostID);
|
|
90
|
+
} else {
|
|
91
|
+
this._postMessage([MSGTYPE_RESPONSE, messageID, null, result], hostID);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
_handleQuery(messageID, query, hostID) {
|
|
96
|
+
// console.log('_handleQuery', messageID, query);
|
|
97
|
+
try {
|
|
98
|
+
const result = this._queryCallback(query, hostID);
|
|
99
|
+
|
|
100
|
+
if (!isPromise(result)) {
|
|
101
|
+
this._postResponse(messageID, null, result, hostID);
|
|
102
|
+
} else {
|
|
103
|
+
result.then(finalResult => {
|
|
104
|
+
this._postResponse(messageID, null, finalResult, hostID);
|
|
105
|
+
}, finalError => {
|
|
106
|
+
this._postResponse(messageID, finalError, hostID);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
} catch (err) {
|
|
110
|
+
this._postResponse(messageID, err);
|
|
111
|
+
}
|
|
112
|
+
} // Either return messageID and type if further processing is needed, or undefined otherwise
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
_onMessageCommon(e) {
|
|
116
|
+
// eslint-disable-line no-undef
|
|
117
|
+
// console.log('_onMessage', e.data);
|
|
118
|
+
const message = e.data;
|
|
119
|
+
|
|
120
|
+
if (!Array.isArray(message) || message.length < 3 || message.length > 4) {
|
|
121
|
+
return; // Ignore - this message is not for us
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (typeof message[0] !== "number" || MSGTYPES.indexOf(message[0]) < 0) {
|
|
125
|
+
throw new Error("Invalid messageID");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const type = message[0];
|
|
129
|
+
|
|
130
|
+
if (typeof message[1] !== "number") {
|
|
131
|
+
throw new Error("Invalid messageID");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const messageID = message[1];
|
|
135
|
+
|
|
136
|
+
if (type === MSGTYPE_QUERY) {
|
|
137
|
+
const query = message[2];
|
|
138
|
+
|
|
139
|
+
if (typeof message[3] !== "number" && message[3] !== undefined) {
|
|
140
|
+
throw new Error("Invalid hostID");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const hostID = message[3];
|
|
144
|
+
|
|
145
|
+
this._handleQuery(messageID, query, hostID);
|
|
146
|
+
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (type === MSGTYPE_RESPONSE) {
|
|
151
|
+
if (message[2] !== null && typeof message[2] !== "object") {
|
|
152
|
+
throw new Error("Invalid error");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const error = message[2] === null ? null : fromFakeError(message[2]);
|
|
156
|
+
const result = message[3];
|
|
157
|
+
|
|
158
|
+
const callback = this._callbacks.get(messageID);
|
|
159
|
+
|
|
160
|
+
if (callback === undefined) {
|
|
161
|
+
// Ignore - user might have created multiple PromiseWorkers.
|
|
162
|
+
// This message is not for us.
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this._callbacks.delete(messageID);
|
|
167
|
+
|
|
168
|
+
callback(error, result);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
message,
|
|
174
|
+
type
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
class PWBHost extends PWBBase {
|
|
181
|
+
// Only defined on host
|
|
182
|
+
constructor(worker) {
|
|
183
|
+
super(); // The following if statement used to check `worker instanceof Worker` but I have recieved
|
|
184
|
+
// reports that in some weird cases, Safari will inappropriately return false for that, even
|
|
185
|
+
// in obvious cases like:
|
|
186
|
+
//
|
|
187
|
+
// blob = new Blob(["self.onmessage = function() {};"], { type: "text/javascript" });
|
|
188
|
+
// worker = new Worker(window.URL.createObjectURL(blob));
|
|
189
|
+
// console.log(worker instanceof Worker);
|
|
190
|
+
//
|
|
191
|
+
// So instead, let's do this test for worker.port which only exists on shared workers.
|
|
192
|
+
// @ts-ignore
|
|
193
|
+
|
|
194
|
+
if (worker.port === undefined) {
|
|
195
|
+
this._workerType = "Worker"; // @ts-ignore
|
|
196
|
+
|
|
197
|
+
worker.addEventListener("message", this._onMessage);
|
|
198
|
+
} else {
|
|
199
|
+
this._workerType = "SharedWorker"; // @ts-ignore - it doesn't know if _worker is Worker or SharedWorker, but I do
|
|
200
|
+
|
|
201
|
+
worker.port.addEventListener("message", this._onMessage); // @ts-ignore - it doesn't know if _worker is Worker or SharedWorker, but I do
|
|
202
|
+
|
|
203
|
+
worker.port.start(); // Handle tab close. This isn't perfect, but there is no perfect method
|
|
204
|
+
// http://stackoverflow.com/q/13662089/786644 and this should work like
|
|
205
|
+
// 99% of the time. It is a memory leak if it fails, but for most use
|
|
206
|
+
// cases, it shouldn't be noticeable.
|
|
207
|
+
|
|
208
|
+
window.addEventListener("beforeunload", () => {
|
|
209
|
+
// Prevent firing if we don't know hostID yet
|
|
210
|
+
if (this._hostID !== undefined) {
|
|
211
|
+
this._postMessage([MSGTYPE_HOST_CLOSE, -1, this._hostID]);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
this._worker = worker;
|
|
217
|
+
this._hostIDQueue = [];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
registerError(cb) {
|
|
221
|
+
// console.log('registerError', cb);
|
|
222
|
+
this._errorCallback = cb; // Some browsers (Firefox) call onerror on every host, while others
|
|
223
|
+
// (Chrome) do nothing. Let's disable that everywhere, for consistency.
|
|
224
|
+
|
|
225
|
+
this._worker.addEventListener("error", e => {
|
|
226
|
+
e.preventDefault();
|
|
227
|
+
e.stopPropagation();
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
_postMessage(obj) {
|
|
232
|
+
// console.log('_postMessage', obj);
|
|
233
|
+
if (this._workerType === "Worker") {
|
|
234
|
+
// @ts-ignore - it doesn't know if _worker is Worker or SharedWorker, but I do
|
|
235
|
+
this._worker.postMessage(obj);
|
|
236
|
+
} else if (this._workerType === "SharedWorker") {
|
|
237
|
+
// @ts-ignore - it doesn't know if _worker is Worker or SharedWorker, but I do
|
|
238
|
+
this._worker.port.postMessage(obj);
|
|
239
|
+
} else {
|
|
240
|
+
throw new Error("WTF");
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
postMessage(userMessage) {
|
|
245
|
+
// console.log('postMessage', userMessage, targetHostID);
|
|
246
|
+
const actuallyPostMessage = (resolve, reject) => {
|
|
247
|
+
const messageID = messageIDs;
|
|
248
|
+
messageIDs += 1;
|
|
249
|
+
const messageToSend = [MSGTYPE_QUERY, messageID, userMessage, this._hostID];
|
|
250
|
+
|
|
251
|
+
this._callbacks.set(messageID, (error, result) => {
|
|
252
|
+
if (error) {
|
|
253
|
+
reject(error);
|
|
254
|
+
} else {
|
|
255
|
+
resolve(result);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
this._postMessage(messageToSend);
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
return new Promise((resolve, reject) => {
|
|
263
|
+
// Don't send a message until hostID is known, otherwise it's a race
|
|
264
|
+
// condition and sometimes hostID will be undefined.
|
|
265
|
+
if (this._hostIDQueue !== undefined) {
|
|
266
|
+
this._hostIDQueue.push(() => {
|
|
267
|
+
actuallyPostMessage(resolve, reject);
|
|
268
|
+
});
|
|
269
|
+
} else {
|
|
270
|
+
actuallyPostMessage(resolve, reject);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
_onMessage(e) {
|
|
276
|
+
const common = this._onMessageCommon(e);
|
|
277
|
+
|
|
278
|
+
if (!common) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const message = common.message,
|
|
283
|
+
type = common.type;
|
|
284
|
+
|
|
285
|
+
if (type === MSGTYPE_HOST_ID) {
|
|
286
|
+
if (message[2] !== undefined && typeof message[2] !== "number") {
|
|
287
|
+
throw new Error("Invalid hostID");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const hostID = message[2];
|
|
291
|
+
this._hostID = hostID;
|
|
292
|
+
|
|
293
|
+
if (this._hostIDQueue !== undefined) {
|
|
294
|
+
this._hostIDQueue.forEach(func => {
|
|
295
|
+
// Not entirely sure why setTimeout is needed, might be just for unit tests
|
|
296
|
+
setTimeout(() => {
|
|
297
|
+
func();
|
|
298
|
+
}, 0);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
this._hostIDQueue = undefined; // Never needed again after initial setup
|
|
302
|
+
}
|
|
303
|
+
} else if (type === MSGTYPE_WORKER_ERROR) {
|
|
304
|
+
if (message[2] !== undefined && message[2] !== null && typeof message[2] === "object") {
|
|
305
|
+
const error = fromFakeError(message[2]);
|
|
306
|
+
|
|
307
|
+
if (this._errorCallback !== undefined) {
|
|
308
|
+
this._errorCallback(error);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
class PWBWorker extends PWBBase {
|
|
317
|
+
constructor() {
|
|
318
|
+
super(); // Only actually used for SharedWorker
|
|
319
|
+
|
|
320
|
+
this._hosts = new Map();
|
|
321
|
+
this._maxHostID = -1;
|
|
322
|
+
|
|
323
|
+
if ( // @ts-ignore
|
|
324
|
+
typeof SharedWorkerGlobalScope !== "undefined" && // @ts-ignore
|
|
325
|
+
self instanceof SharedWorkerGlobalScope) {
|
|
326
|
+
this._workerType = "SharedWorker";
|
|
327
|
+
self.addEventListener("connect", e => {
|
|
328
|
+
// @ts-ignore
|
|
329
|
+
const port = e.ports[0];
|
|
330
|
+
port.addEventListener("message", e2 => this._onMessage(e2)); // eslint-disable-line no-undef
|
|
331
|
+
|
|
332
|
+
port.start();
|
|
333
|
+
this._maxHostID += 1;
|
|
334
|
+
const hostID = this._maxHostID;
|
|
335
|
+
|
|
336
|
+
this._hosts.set(hostID, {
|
|
337
|
+
port
|
|
338
|
+
}); // Send back hostID to this host, otherwise it has no way to know it
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
this._postMessage([MSGTYPE_HOST_ID, -1, hostID], hostID);
|
|
342
|
+
});
|
|
343
|
+
self.addEventListener("error", e => {
|
|
344
|
+
logError(e.error); // Just send to first host, so as to not duplicate error tracking
|
|
345
|
+
|
|
346
|
+
const hostID = this._hosts.keys().next().value;
|
|
347
|
+
|
|
348
|
+
if (hostID !== undefined) {
|
|
349
|
+
this._postMessage([MSGTYPE_WORKER_ERROR, -1, toFakeError(e.error)], hostID);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
} else {
|
|
353
|
+
this._workerType = "Worker";
|
|
354
|
+
self.addEventListener("message", this._onMessage); // Since this is not a Shared Worker, hostID is always 0 so it's not strictly required to
|
|
355
|
+
// send this back, but it makes the API a bit more consistent if there is the same
|
|
356
|
+
// initialization handshake in both cases.
|
|
357
|
+
|
|
358
|
+
this._postMessage([MSGTYPE_HOST_ID, -1, 0], 0);
|
|
359
|
+
|
|
360
|
+
self.addEventListener("error", e => {
|
|
361
|
+
logError(e.error);
|
|
362
|
+
|
|
363
|
+
this._postMessage([MSGTYPE_WORKER_ERROR, -1, toFakeError(e.error)]);
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
_postMessage(obj, targetHostID) {
|
|
369
|
+
// console.log('_postMessage', obj, targetHostID);
|
|
370
|
+
if (this._workerType === "SharedWorker") {
|
|
371
|
+
// If targetHostID has been deleted, this will do nothing, which is fine I think
|
|
372
|
+
this._hosts.forEach(({
|
|
373
|
+
port
|
|
374
|
+
}, hostID) => {
|
|
375
|
+
if (targetHostID === undefined || targetHostID === hostID) {
|
|
376
|
+
port.postMessage(obj);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
} else if (this._workerType === "Worker") {
|
|
380
|
+
// @ts-ignore
|
|
381
|
+
self.postMessage(obj);
|
|
382
|
+
} else {
|
|
383
|
+
throw new Error("WTF");
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
postMessage(userMessage, targetHostID) {
|
|
388
|
+
// console.log('postMessage', userMessage, targetHostID);
|
|
389
|
+
const actuallyPostMessage = (resolve, reject) => {
|
|
390
|
+
const messageID = messageIDs;
|
|
391
|
+
messageIDs += 1;
|
|
392
|
+
const messageToSend = [MSGTYPE_QUERY, messageID, userMessage];
|
|
393
|
+
|
|
394
|
+
this._callbacks.set(messageID, (error, result) => {
|
|
395
|
+
if (error) {
|
|
396
|
+
reject(error);
|
|
397
|
+
} else {
|
|
398
|
+
resolve(result);
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
this._postMessage(messageToSend, targetHostID);
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
return new Promise((resolve, reject) => {
|
|
406
|
+
actuallyPostMessage(resolve, reject);
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
_onMessage(e) {
|
|
411
|
+
const common = this._onMessageCommon(e);
|
|
412
|
+
|
|
413
|
+
if (!common) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const message = common.message,
|
|
418
|
+
type = common.type;
|
|
419
|
+
|
|
420
|
+
if (type === MSGTYPE_HOST_CLOSE) {
|
|
421
|
+
if (typeof message[2] !== "number") {
|
|
422
|
+
throw new Error("Invalid hostID");
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const hostID = message[2];
|
|
426
|
+
|
|
427
|
+
this._hosts.delete(hostID);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
exports.PWBHost = PWBHost;
|
|
434
|
+
exports.PWBWorker = PWBWorker;
|