@next-core/brick-kit 2.188.4 → 2.190.0

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.
@@ -0,0 +1,1586 @@
1
+ /**
2
+ * returns true if the given object is a promise
3
+ */
4
+ function isPromise(obj) {
5
+ return obj && typeof obj.then === 'function';
6
+ }
7
+ var PROMISE_RESOLVED_FALSE = Promise.resolve(false);
8
+ var PROMISE_RESOLVED_TRUE = Promise.resolve(true);
9
+ var PROMISE_RESOLVED_VOID = Promise.resolve();
10
+ function sleep(time, resolveWith) {
11
+ if (!time) time = 0;
12
+ return new Promise(function (res) {
13
+ return setTimeout(function () {
14
+ return res(resolveWith);
15
+ }, time);
16
+ });
17
+ }
18
+ function randomInt(min, max) {
19
+ return Math.floor(Math.random() * (max - min + 1) + min);
20
+ }
21
+
22
+ /**
23
+ * https://stackoverflow.com/a/8084248
24
+ */
25
+ function randomToken() {
26
+ return Math.random().toString(36).substring(2);
27
+ }
28
+ var lastMs = 0;
29
+ var additional = 0;
30
+
31
+ /**
32
+ * returns the current time in micro-seconds,
33
+ * WARNING: This is a pseudo-function
34
+ * Performance.now is not reliable in webworkers, so we just make sure to never return the same time.
35
+ * This is enough in browsers, and this function will not be used in nodejs.
36
+ * The main reason for this hack is to ensure that BroadcastChannel behaves equal to production when it is used in fast-running unit tests.
37
+ */
38
+ function microSeconds$4() {
39
+ var ms = Date.now();
40
+ if (ms === lastMs) {
41
+ additional++;
42
+ return ms * 1000 + additional;
43
+ } else {
44
+ lastMs = ms;
45
+ additional = 0;
46
+ return ms * 1000;
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Check if WebLock API is supported.
52
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API
53
+ */
54
+ function supportsWebLockAPI() {
55
+ if (typeof navigator !== 'undefined' && typeof navigator.locks !== 'undefined' && typeof navigator.locks.request === 'function') {
56
+ return true;
57
+ } else {
58
+ return false;
59
+ }
60
+ }
61
+
62
+ var microSeconds$3 = microSeconds$4;
63
+ var type$3 = 'native';
64
+ function create$3(channelName) {
65
+ var state = {
66
+ messagesCallback: null,
67
+ bc: new BroadcastChannel(channelName),
68
+ subFns: [] // subscriberFunctions
69
+ };
70
+
71
+ state.bc.onmessage = function (msg) {
72
+ if (state.messagesCallback) {
73
+ state.messagesCallback(msg.data);
74
+ }
75
+ };
76
+ return state;
77
+ }
78
+ function close$3(channelState) {
79
+ channelState.bc.close();
80
+ channelState.subFns = [];
81
+ }
82
+ function postMessage$3(channelState, messageJson) {
83
+ try {
84
+ channelState.bc.postMessage(messageJson, false);
85
+ return PROMISE_RESOLVED_VOID;
86
+ } catch (err) {
87
+ return Promise.reject(err);
88
+ }
89
+ }
90
+ function onMessage$3(channelState, fn) {
91
+ channelState.messagesCallback = fn;
92
+ }
93
+ function canBeUsed$3() {
94
+ if ((typeof window !== 'undefined' || typeof self !== 'undefined') && typeof BroadcastChannel === 'function') {
95
+ if (BroadcastChannel._pubkey) {
96
+ throw new Error('BroadcastChannel: Do not overwrite window.BroadcastChannel with this module, this is not a polyfill');
97
+ }
98
+ return true;
99
+ } else {
100
+ return false;
101
+ }
102
+ }
103
+ function averageResponseTime$3() {
104
+ return 150;
105
+ }
106
+ var NativeMethod = {
107
+ create: create$3,
108
+ close: close$3,
109
+ onMessage: onMessage$3,
110
+ postMessage: postMessage$3,
111
+ canBeUsed: canBeUsed$3,
112
+ type: type$3,
113
+ averageResponseTime: averageResponseTime$3,
114
+ microSeconds: microSeconds$3
115
+ };
116
+
117
+ /**
118
+ * this is a set which automatically forgets
119
+ * a given entry when a new entry is set and the ttl
120
+ * of the old one is over
121
+ */
122
+ var ObliviousSet = /** @class */function () {
123
+ function ObliviousSet(ttl) {
124
+ this.ttl = ttl;
125
+ this.map = new Map();
126
+ /**
127
+ * Creating calls to setTimeout() is expensive,
128
+ * so we only do that if there is not timeout already open.
129
+ */
130
+ this._to = false;
131
+ }
132
+ ObliviousSet.prototype.has = function (value) {
133
+ return this.map.has(value);
134
+ };
135
+ ObliviousSet.prototype.add = function (value) {
136
+ var _this = this;
137
+ this.map.set(value, now());
138
+ /**
139
+ * When a new value is added,
140
+ * start the cleanup at the next tick
141
+ * to not block the cpu for more important stuff
142
+ * that might happen.
143
+ */
144
+ if (!this._to) {
145
+ this._to = true;
146
+ setTimeout(function () {
147
+ _this._to = false;
148
+ removeTooOldValues(_this);
149
+ }, 0);
150
+ }
151
+ };
152
+ ObliviousSet.prototype.clear = function () {
153
+ this.map.clear();
154
+ };
155
+ return ObliviousSet;
156
+ }();
157
+ /**
158
+ * Removes all entries from the set
159
+ * where the TTL has expired
160
+ */
161
+ function removeTooOldValues(obliviousSet) {
162
+ var olderThen = now() - obliviousSet.ttl;
163
+ var iterator = obliviousSet.map[Symbol.iterator]();
164
+ /**
165
+ * Because we can assume the new values are added at the bottom,
166
+ * we start from the top and stop as soon as we reach a non-too-old value.
167
+ */
168
+ while (true) {
169
+ var next = iterator.next().value;
170
+ if (!next) {
171
+ return; // no more elements
172
+ }
173
+
174
+ var value = next[0];
175
+ var time = next[1];
176
+ if (time < olderThen) {
177
+ obliviousSet.map.delete(value);
178
+ } else {
179
+ // We reached a value that is not old enough
180
+ return;
181
+ }
182
+ }
183
+ }
184
+ function now() {
185
+ return new Date().getTime();
186
+ }
187
+
188
+ function fillOptionsWithDefaults$1() {
189
+ var originalOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
190
+ var options = JSON.parse(JSON.stringify(originalOptions));
191
+
192
+ // main
193
+ if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true;
194
+
195
+ // indexed-db
196
+ if (!options.idb) options.idb = {};
197
+ // after this time the messages get deleted
198
+ if (!options.idb.ttl) options.idb.ttl = 1000 * 45;
199
+ if (!options.idb.fallbackInterval) options.idb.fallbackInterval = 150;
200
+ // handles abrupt db onclose events.
201
+ if (originalOptions.idb && typeof originalOptions.idb.onclose === 'function') options.idb.onclose = originalOptions.idb.onclose;
202
+
203
+ // localstorage
204
+ if (!options.localstorage) options.localstorage = {};
205
+ if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60;
206
+
207
+ // custom methods
208
+ if (originalOptions.methods) options.methods = originalOptions.methods;
209
+
210
+ // node
211
+ if (!options.node) options.node = {};
212
+ if (!options.node.ttl) options.node.ttl = 1000 * 60 * 2; // 2 minutes;
213
+ /**
214
+ * On linux use 'ulimit -Hn' to get the limit of open files.
215
+ * On ubuntu this was 4096 for me, so we use half of that as maxParallelWrites default.
216
+ */
217
+ if (!options.node.maxParallelWrites) options.node.maxParallelWrites = 2048;
218
+ if (typeof options.node.useFastPath === 'undefined') options.node.useFastPath = true;
219
+ return options;
220
+ }
221
+
222
+ /**
223
+ * this method uses indexeddb to store the messages
224
+ * There is currently no observerAPI for idb
225
+ * @link https://github.com/w3c/IndexedDB/issues/51
226
+ *
227
+ * When working on this, ensure to use these performance optimizations:
228
+ * @link https://rxdb.info/slow-indexeddb.html
229
+ */
230
+ var microSeconds$2 = microSeconds$4;
231
+ var DB_PREFIX = 'pubkey.broadcast-channel-0-';
232
+ var OBJECT_STORE_ID = 'messages';
233
+
234
+ /**
235
+ * Use relaxed durability for faster performance on all transactions.
236
+ * @link https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/
237
+ */
238
+ var TRANSACTION_SETTINGS = {
239
+ durability: 'relaxed'
240
+ };
241
+ var type$2 = 'idb';
242
+ function getIdb() {
243
+ if (typeof indexedDB !== 'undefined') return indexedDB;
244
+ if (typeof window !== 'undefined') {
245
+ if (typeof window.mozIndexedDB !== 'undefined') return window.mozIndexedDB;
246
+ if (typeof window.webkitIndexedDB !== 'undefined') return window.webkitIndexedDB;
247
+ if (typeof window.msIndexedDB !== 'undefined') return window.msIndexedDB;
248
+ }
249
+ return false;
250
+ }
251
+
252
+ /**
253
+ * If possible, we should explicitly commit IndexedDB transactions
254
+ * for better performance.
255
+ * @link https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/
256
+ */
257
+ function commitIndexedDBTransaction(tx) {
258
+ if (tx.commit) {
259
+ tx.commit();
260
+ }
261
+ }
262
+ function createDatabase(channelName) {
263
+ var IndexedDB = getIdb();
264
+
265
+ // create table
266
+ var dbName = DB_PREFIX + channelName;
267
+
268
+ /**
269
+ * All IndexedDB databases are opened without version
270
+ * because it is a bit faster, especially on firefox
271
+ * @link http://nparashuram.com/IndexedDB/perf/#Open%20Database%20with%20version
272
+ */
273
+ var openRequest = IndexedDB.open(dbName);
274
+ openRequest.onupgradeneeded = function (ev) {
275
+ var db = ev.target.result;
276
+ db.createObjectStore(OBJECT_STORE_ID, {
277
+ keyPath: 'id',
278
+ autoIncrement: true
279
+ });
280
+ };
281
+ return new Promise(function (res, rej) {
282
+ openRequest.onerror = function (ev) {
283
+ return rej(ev);
284
+ };
285
+ openRequest.onsuccess = function () {
286
+ res(openRequest.result);
287
+ };
288
+ });
289
+ }
290
+
291
+ /**
292
+ * writes the new message to the database
293
+ * so other readers can find it
294
+ */
295
+ function writeMessage(db, readerUuid, messageJson) {
296
+ var time = Date.now();
297
+ var writeObject = {
298
+ uuid: readerUuid,
299
+ time: time,
300
+ data: messageJson
301
+ };
302
+ var tx = db.transaction([OBJECT_STORE_ID], 'readwrite', TRANSACTION_SETTINGS);
303
+ return new Promise(function (res, rej) {
304
+ tx.oncomplete = function () {
305
+ return res();
306
+ };
307
+ tx.onerror = function (ev) {
308
+ return rej(ev);
309
+ };
310
+ var objectStore = tx.objectStore(OBJECT_STORE_ID);
311
+ objectStore.add(writeObject);
312
+ commitIndexedDBTransaction(tx);
313
+ });
314
+ }
315
+ function getMessagesHigherThan(db, lastCursorId) {
316
+ var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS);
317
+ var objectStore = tx.objectStore(OBJECT_STORE_ID);
318
+ var ret = [];
319
+ var keyRangeValue = IDBKeyRange.bound(lastCursorId + 1, Infinity);
320
+
321
+ /**
322
+ * Optimization shortcut,
323
+ * if getAll() can be used, do not use a cursor.
324
+ * @link https://rxdb.info/slow-indexeddb.html
325
+ */
326
+ if (objectStore.getAll) {
327
+ var getAllRequest = objectStore.getAll(keyRangeValue);
328
+ return new Promise(function (res, rej) {
329
+ getAllRequest.onerror = function (err) {
330
+ return rej(err);
331
+ };
332
+ getAllRequest.onsuccess = function (e) {
333
+ res(e.target.result);
334
+ };
335
+ });
336
+ }
337
+ function openCursor() {
338
+ // Occasionally Safari will fail on IDBKeyRange.bound, this
339
+ // catches that error, having it open the cursor to the first
340
+ // item. When it gets data it will advance to the desired key.
341
+ try {
342
+ keyRangeValue = IDBKeyRange.bound(lastCursorId + 1, Infinity);
343
+ return objectStore.openCursor(keyRangeValue);
344
+ } catch (e) {
345
+ return objectStore.openCursor();
346
+ }
347
+ }
348
+ return new Promise(function (res, rej) {
349
+ var openCursorRequest = openCursor();
350
+ openCursorRequest.onerror = function (err) {
351
+ return rej(err);
352
+ };
353
+ openCursorRequest.onsuccess = function (ev) {
354
+ var cursor = ev.target.result;
355
+ if (cursor) {
356
+ if (cursor.value.id < lastCursorId + 1) {
357
+ cursor["continue"](lastCursorId + 1);
358
+ } else {
359
+ ret.push(cursor.value);
360
+ cursor["continue"]();
361
+ }
362
+ } else {
363
+ commitIndexedDBTransaction(tx);
364
+ res(ret);
365
+ }
366
+ };
367
+ });
368
+ }
369
+ function removeMessagesById(channelState, ids) {
370
+ if (channelState.closed) {
371
+ return Promise.resolve([]);
372
+ }
373
+ var tx = channelState.db.transaction(OBJECT_STORE_ID, 'readwrite', TRANSACTION_SETTINGS);
374
+ var objectStore = tx.objectStore(OBJECT_STORE_ID);
375
+ return Promise.all(ids.map(function (id) {
376
+ var deleteRequest = objectStore["delete"](id);
377
+ return new Promise(function (res) {
378
+ deleteRequest.onsuccess = function () {
379
+ return res();
380
+ };
381
+ });
382
+ }));
383
+ }
384
+ function getOldMessages(db, ttl) {
385
+ var olderThen = Date.now() - ttl;
386
+ var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS);
387
+ var objectStore = tx.objectStore(OBJECT_STORE_ID);
388
+ var ret = [];
389
+ return new Promise(function (res) {
390
+ objectStore.openCursor().onsuccess = function (ev) {
391
+ var cursor = ev.target.result;
392
+ if (cursor) {
393
+ var msgObk = cursor.value;
394
+ if (msgObk.time < olderThen) {
395
+ ret.push(msgObk);
396
+ //alert("Name for SSN " + cursor.key + " is " + cursor.value.name);
397
+ cursor["continue"]();
398
+ } else {
399
+ // no more old messages,
400
+ commitIndexedDBTransaction(tx);
401
+ res(ret);
402
+ }
403
+ } else {
404
+ res(ret);
405
+ }
406
+ };
407
+ });
408
+ }
409
+ function cleanOldMessages(channelState) {
410
+ return getOldMessages(channelState.db, channelState.options.idb.ttl).then(function (tooOld) {
411
+ return removeMessagesById(channelState, tooOld.map(function (msg) {
412
+ return msg.id;
413
+ }));
414
+ });
415
+ }
416
+ function create$2(channelName, options) {
417
+ options = fillOptionsWithDefaults$1(options);
418
+ return createDatabase(channelName).then(function (db) {
419
+ var state = {
420
+ closed: false,
421
+ lastCursorId: 0,
422
+ channelName: channelName,
423
+ options: options,
424
+ uuid: randomToken(),
425
+ /**
426
+ * emittedMessagesIds
427
+ * contains all messages that have been emitted before
428
+ * @type {ObliviousSet}
429
+ */
430
+ eMIs: new ObliviousSet(options.idb.ttl * 2),
431
+ // ensures we do not read messages in parallel
432
+ writeBlockPromise: PROMISE_RESOLVED_VOID,
433
+ messagesCallback: null,
434
+ readQueuePromises: [],
435
+ db: db
436
+ };
437
+
438
+ /**
439
+ * Handle abrupt closes that do not originate from db.close().
440
+ * This could happen, for example, if the underlying storage is
441
+ * removed or if the user clears the database in the browser's
442
+ * history preferences.
443
+ */
444
+ db.onclose = function () {
445
+ state.closed = true;
446
+ if (options.idb.onclose) options.idb.onclose();
447
+ };
448
+
449
+ /**
450
+ * if service-workers are used,
451
+ * we have no 'storage'-event if they post a message,
452
+ * therefore we also have to set an interval
453
+ */
454
+ _readLoop(state);
455
+ return state;
456
+ });
457
+ }
458
+ function _readLoop(state) {
459
+ if (state.closed) return;
460
+ readNewMessages(state).then(function () {
461
+ return sleep(state.options.idb.fallbackInterval);
462
+ }).then(function () {
463
+ return _readLoop(state);
464
+ });
465
+ }
466
+ function _filterMessage(msgObj, state) {
467
+ if (msgObj.uuid === state.uuid) return false; // send by own
468
+ if (state.eMIs.has(msgObj.id)) return false; // already emitted
469
+ if (msgObj.data.time < state.messagesCallbackTime) return false; // older then onMessageCallback
470
+ return true;
471
+ }
472
+
473
+ /**
474
+ * reads all new messages from the database and emits them
475
+ */
476
+ function readNewMessages(state) {
477
+ // channel already closed
478
+ if (state.closed) return PROMISE_RESOLVED_VOID;
479
+
480
+ // if no one is listening, we do not need to scan for new messages
481
+ if (!state.messagesCallback) return PROMISE_RESOLVED_VOID;
482
+ return getMessagesHigherThan(state.db, state.lastCursorId).then(function (newerMessages) {
483
+ var useMessages = newerMessages
484
+ /**
485
+ * there is a bug in iOS where the msgObj can be undefined sometimes
486
+ * so we filter them out
487
+ * @link https://github.com/pubkey/broadcast-channel/issues/19
488
+ */.filter(function (msgObj) {
489
+ return !!msgObj;
490
+ }).map(function (msgObj) {
491
+ if (msgObj.id > state.lastCursorId) {
492
+ state.lastCursorId = msgObj.id;
493
+ }
494
+ return msgObj;
495
+ }).filter(function (msgObj) {
496
+ return _filterMessage(msgObj, state);
497
+ }).sort(function (msgObjA, msgObjB) {
498
+ return msgObjA.time - msgObjB.time;
499
+ }); // sort by time
500
+ useMessages.forEach(function (msgObj) {
501
+ if (state.messagesCallback) {
502
+ state.eMIs.add(msgObj.id);
503
+ state.messagesCallback(msgObj.data);
504
+ }
505
+ });
506
+ return PROMISE_RESOLVED_VOID;
507
+ });
508
+ }
509
+ function close$2(channelState) {
510
+ channelState.closed = true;
511
+ channelState.db.close();
512
+ }
513
+ function postMessage$2(channelState, messageJson) {
514
+ channelState.writeBlockPromise = channelState.writeBlockPromise.then(function () {
515
+ return writeMessage(channelState.db, channelState.uuid, messageJson);
516
+ }).then(function () {
517
+ if (randomInt(0, 10) === 0) {
518
+ /* await (do not await) */
519
+ cleanOldMessages(channelState);
520
+ }
521
+ });
522
+ return channelState.writeBlockPromise;
523
+ }
524
+ function onMessage$2(channelState, fn, time) {
525
+ channelState.messagesCallbackTime = time;
526
+ channelState.messagesCallback = fn;
527
+ readNewMessages(channelState);
528
+ }
529
+ function canBeUsed$2() {
530
+ return !!getIdb();
531
+ }
532
+ function averageResponseTime$2(options) {
533
+ return options.idb.fallbackInterval * 2;
534
+ }
535
+ var IndexedDBMethod = {
536
+ create: create$2,
537
+ close: close$2,
538
+ onMessage: onMessage$2,
539
+ postMessage: postMessage$2,
540
+ canBeUsed: canBeUsed$2,
541
+ type: type$2,
542
+ averageResponseTime: averageResponseTime$2,
543
+ microSeconds: microSeconds$2
544
+ };
545
+
546
+ /**
547
+ * A localStorage-only method which uses localstorage and its 'storage'-event
548
+ * This does not work inside webworkers because they have no access to localstorage
549
+ * This is basically implemented to support IE9 or your grandmother's toaster.
550
+ * @link https://caniuse.com/#feat=namevalue-storage
551
+ * @link https://caniuse.com/#feat=indexeddb
552
+ */
553
+ var microSeconds$1 = microSeconds$4;
554
+ var KEY_PREFIX = 'pubkey.broadcastChannel-';
555
+ var type$1 = 'localstorage';
556
+
557
+ /**
558
+ * copied from crosstab
559
+ * @link https://github.com/tejacques/crosstab/blob/master/src/crosstab.js#L32
560
+ */
561
+ function getLocalStorage() {
562
+ var localStorage;
563
+ if (typeof window === 'undefined') return null;
564
+ try {
565
+ localStorage = window.localStorage;
566
+ localStorage = window['ie8-eventlistener/storage'] || window.localStorage;
567
+ } catch (e) {
568
+ // New versions of Firefox throw a Security exception
569
+ // if cookies are disabled. See
570
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1028153
571
+ }
572
+ return localStorage;
573
+ }
574
+ function storageKey(channelName) {
575
+ return KEY_PREFIX + channelName;
576
+ }
577
+
578
+ /**
579
+ * writes the new message to the storage
580
+ * and fires the storage-event so other readers can find it
581
+ */
582
+ function postMessage$1(channelState, messageJson) {
583
+ return new Promise(function (res) {
584
+ sleep().then(function () {
585
+ var key = storageKey(channelState.channelName);
586
+ var writeObj = {
587
+ token: randomToken(),
588
+ time: Date.now(),
589
+ data: messageJson,
590
+ uuid: channelState.uuid
591
+ };
592
+ var value = JSON.stringify(writeObj);
593
+ getLocalStorage().setItem(key, value);
594
+
595
+ /**
596
+ * StorageEvent does not fire the 'storage' event
597
+ * in the window that changes the state of the local storage.
598
+ * So we fire it manually
599
+ */
600
+ var ev = document.createEvent('Event');
601
+ ev.initEvent('storage', true, true);
602
+ ev.key = key;
603
+ ev.newValue = value;
604
+ window.dispatchEvent(ev);
605
+ res();
606
+ });
607
+ });
608
+ }
609
+ function addStorageEventListener(channelName, fn) {
610
+ var key = storageKey(channelName);
611
+ var listener = function listener(ev) {
612
+ if (ev.key === key) {
613
+ fn(JSON.parse(ev.newValue));
614
+ }
615
+ };
616
+ window.addEventListener('storage', listener);
617
+ return listener;
618
+ }
619
+ function removeStorageEventListener(listener) {
620
+ window.removeEventListener('storage', listener);
621
+ }
622
+ function create$1(channelName, options) {
623
+ options = fillOptionsWithDefaults$1(options);
624
+ if (!canBeUsed$1()) {
625
+ throw new Error('BroadcastChannel: localstorage cannot be used');
626
+ }
627
+ var uuid = randomToken();
628
+
629
+ /**
630
+ * eMIs
631
+ * contains all messages that have been emitted before
632
+ * @type {ObliviousSet}
633
+ */
634
+ var eMIs = new ObliviousSet(options.localstorage.removeTimeout);
635
+ var state = {
636
+ channelName: channelName,
637
+ uuid: uuid,
638
+ eMIs: eMIs // emittedMessagesIds
639
+ };
640
+
641
+ state.listener = addStorageEventListener(channelName, function (msgObj) {
642
+ if (!state.messagesCallback) return; // no listener
643
+ if (msgObj.uuid === uuid) return; // own message
644
+ if (!msgObj.token || eMIs.has(msgObj.token)) return; // already emitted
645
+ if (msgObj.data.time && msgObj.data.time < state.messagesCallbackTime) return; // too old
646
+
647
+ eMIs.add(msgObj.token);
648
+ state.messagesCallback(msgObj.data);
649
+ });
650
+ return state;
651
+ }
652
+ function close$1(channelState) {
653
+ removeStorageEventListener(channelState.listener);
654
+ }
655
+ function onMessage$1(channelState, fn, time) {
656
+ channelState.messagesCallbackTime = time;
657
+ channelState.messagesCallback = fn;
658
+ }
659
+ function canBeUsed$1() {
660
+ var ls = getLocalStorage();
661
+ if (!ls) return false;
662
+ try {
663
+ var key = '__broadcastchannel_check';
664
+ ls.setItem(key, 'works');
665
+ ls.removeItem(key);
666
+ } catch (e) {
667
+ // Safari 10 in private mode will not allow write access to local
668
+ // storage and fail with a QuotaExceededError. See
669
+ // https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API#Private_Browsing_Incognito_modes
670
+ return false;
671
+ }
672
+ return true;
673
+ }
674
+ function averageResponseTime$1() {
675
+ var defaultTime = 120;
676
+ var userAgent = navigator.userAgent.toLowerCase();
677
+ if (userAgent.includes('safari') && !userAgent.includes('chrome')) {
678
+ // safari is much slower so this time is higher
679
+ return defaultTime * 2;
680
+ }
681
+ return defaultTime;
682
+ }
683
+ var LocalstorageMethod = {
684
+ create: create$1,
685
+ close: close$1,
686
+ onMessage: onMessage$1,
687
+ postMessage: postMessage$1,
688
+ canBeUsed: canBeUsed$1,
689
+ type: type$1,
690
+ averageResponseTime: averageResponseTime$1,
691
+ microSeconds: microSeconds$1
692
+ };
693
+
694
+ var microSeconds = microSeconds$4;
695
+ var type = 'simulate';
696
+ var SIMULATE_CHANNELS = new Set();
697
+ function create(channelName) {
698
+ var state = {
699
+ name: channelName,
700
+ messagesCallback: null
701
+ };
702
+ SIMULATE_CHANNELS.add(state);
703
+ return state;
704
+ }
705
+ function close(channelState) {
706
+ SIMULATE_CHANNELS["delete"](channelState);
707
+ }
708
+ function postMessage(channelState, messageJson) {
709
+ return new Promise(function (res) {
710
+ return setTimeout(function () {
711
+ var channelArray = Array.from(SIMULATE_CHANNELS);
712
+ channelArray.filter(function (channel) {
713
+ return channel.name === channelState.name;
714
+ }).filter(function (channel) {
715
+ return channel !== channelState;
716
+ }).filter(function (channel) {
717
+ return !!channel.messagesCallback;
718
+ }).forEach(function (channel) {
719
+ return channel.messagesCallback(messageJson);
720
+ });
721
+ res();
722
+ }, 5);
723
+ });
724
+ }
725
+ function onMessage(channelState, fn) {
726
+ channelState.messagesCallback = fn;
727
+ }
728
+ function canBeUsed() {
729
+ return true;
730
+ }
731
+ function averageResponseTime() {
732
+ return 5;
733
+ }
734
+ var SimulateMethod = {
735
+ create: create,
736
+ close: close,
737
+ onMessage: onMessage,
738
+ postMessage: postMessage,
739
+ canBeUsed: canBeUsed,
740
+ type: type,
741
+ averageResponseTime: averageResponseTime,
742
+ microSeconds: microSeconds
743
+ };
744
+
745
+ // the line below will be removed from es5/browser builds
746
+
747
+ // order is important
748
+ var METHODS = [NativeMethod,
749
+ // fastest
750
+ IndexedDBMethod, LocalstorageMethod];
751
+ function chooseMethod(options) {
752
+ var chooseMethods = [].concat(options.methods, METHODS).filter(Boolean);
753
+
754
+ // the line below will be removed from es5/browser builds
755
+
756
+ // directly chosen
757
+ if (options.type) {
758
+ if (options.type === 'simulate') {
759
+ // only use simulate-method if directly chosen
760
+ return SimulateMethod;
761
+ }
762
+ var ret = chooseMethods.find(function (m) {
763
+ return m.type === options.type;
764
+ });
765
+ if (!ret) throw new Error('method-type ' + options.type + ' not found');else return ret;
766
+ }
767
+
768
+ /**
769
+ * if no webworker support is needed,
770
+ * remove idb from the list so that localstorage will be chosen
771
+ */
772
+ if (!options.webWorkerSupport) {
773
+ chooseMethods = chooseMethods.filter(function (m) {
774
+ return m.type !== 'idb';
775
+ });
776
+ }
777
+ var useMethod = chooseMethods.find(function (method) {
778
+ return method.canBeUsed();
779
+ });
780
+ if (!useMethod) {
781
+ throw new Error("No usable method found in " + JSON.stringify(METHODS.map(function (m) {
782
+ return m.type;
783
+ })));
784
+ } else {
785
+ return useMethod;
786
+ }
787
+ }
788
+
789
+ /**
790
+ * Contains all open channels,
791
+ * used in tests to ensure everything is closed.
792
+ */
793
+ var OPEN_BROADCAST_CHANNELS = new Set();
794
+ var lastId = 0;
795
+ var BroadcastChannel$1 = function BroadcastChannel(name, options) {
796
+ // identifier of the channel to debug stuff
797
+ this.id = lastId++;
798
+ OPEN_BROADCAST_CHANNELS.add(this);
799
+ this.name = name;
800
+ if (ENFORCED_OPTIONS) {
801
+ options = ENFORCED_OPTIONS;
802
+ }
803
+ this.options = fillOptionsWithDefaults$1(options);
804
+ this.method = chooseMethod(this.options);
805
+
806
+ // isListening
807
+ this._iL = false;
808
+
809
+ /**
810
+ * _onMessageListener
811
+ * setting onmessage twice,
812
+ * will overwrite the first listener
813
+ */
814
+ this._onML = null;
815
+
816
+ /**
817
+ * _addEventListeners
818
+ */
819
+ this._addEL = {
820
+ message: [],
821
+ internal: []
822
+ };
823
+
824
+ /**
825
+ * Unsent message promises
826
+ * where the sending is still in progress
827
+ * @type {Set<Promise>}
828
+ */
829
+ this._uMP = new Set();
830
+
831
+ /**
832
+ * _beforeClose
833
+ * array of promises that will be awaited
834
+ * before the channel is closed
835
+ */
836
+ this._befC = [];
837
+
838
+ /**
839
+ * _preparePromise
840
+ */
841
+ this._prepP = null;
842
+ _prepareChannel(this);
843
+ };
844
+
845
+ // STATICS
846
+
847
+ /**
848
+ * used to identify if someone overwrites
849
+ * window.BroadcastChannel with this
850
+ * See methods/native.js
851
+ */
852
+ BroadcastChannel$1._pubkey = true;
853
+
854
+ /**
855
+ * clears the tmp-folder if is node
856
+ * @return {Promise<boolean>} true if has run, false if not node
857
+ */
858
+ function clearNodeFolder(options) {
859
+ options = fillOptionsWithDefaults$1(options);
860
+ var method = chooseMethod(options);
861
+ if (method.type === 'node') {
862
+ return method.clearNodeFolder().then(function () {
863
+ return true;
864
+ });
865
+ } else {
866
+ return PROMISE_RESOLVED_FALSE;
867
+ }
868
+ }
869
+
870
+ /**
871
+ * if set, this method is enforced,
872
+ * no mather what the options are
873
+ */
874
+ var ENFORCED_OPTIONS;
875
+ function enforceOptions(options) {
876
+ ENFORCED_OPTIONS = options;
877
+ }
878
+
879
+ // PROTOTYPE
880
+ BroadcastChannel$1.prototype = {
881
+ postMessage: function postMessage(msg) {
882
+ if (this.closed) {
883
+ throw new Error('BroadcastChannel.postMessage(): ' + 'Cannot post message after channel has closed ' +
884
+ /**
885
+ * In the past when this error appeared, it was really hard to debug.
886
+ * So now we log the msg together with the error so it at least
887
+ * gives some clue about where in your application this happens.
888
+ */
889
+ JSON.stringify(msg));
890
+ }
891
+ return _post(this, 'message', msg);
892
+ },
893
+ postInternal: function postInternal(msg) {
894
+ return _post(this, 'internal', msg);
895
+ },
896
+ set onmessage(fn) {
897
+ var time = this.method.microSeconds();
898
+ var listenObj = {
899
+ time: time,
900
+ fn: fn
901
+ };
902
+ _removeListenerObject(this, 'message', this._onML);
903
+ if (fn && typeof fn === 'function') {
904
+ this._onML = listenObj;
905
+ _addListenerObject(this, 'message', listenObj);
906
+ } else {
907
+ this._onML = null;
908
+ }
909
+ },
910
+ addEventListener: function addEventListener(type, fn) {
911
+ var time = this.method.microSeconds();
912
+ var listenObj = {
913
+ time: time,
914
+ fn: fn
915
+ };
916
+ _addListenerObject(this, type, listenObj);
917
+ },
918
+ removeEventListener: function removeEventListener(type, fn) {
919
+ var obj = this._addEL[type].find(function (obj) {
920
+ return obj.fn === fn;
921
+ });
922
+ _removeListenerObject(this, type, obj);
923
+ },
924
+ close: function close() {
925
+ var _this = this;
926
+ if (this.closed) {
927
+ return;
928
+ }
929
+ OPEN_BROADCAST_CHANNELS["delete"](this);
930
+ this.closed = true;
931
+ var awaitPrepare = this._prepP ? this._prepP : PROMISE_RESOLVED_VOID;
932
+ this._onML = null;
933
+ this._addEL.message = [];
934
+ return awaitPrepare
935
+ // wait until all current sending are processed
936
+ .then(function () {
937
+ return Promise.all(Array.from(_this._uMP));
938
+ })
939
+ // run before-close hooks
940
+ .then(function () {
941
+ return Promise.all(_this._befC.map(function (fn) {
942
+ return fn();
943
+ }));
944
+ })
945
+ // close the channel
946
+ .then(function () {
947
+ return _this.method.close(_this._state);
948
+ });
949
+ },
950
+ get type() {
951
+ return this.method.type;
952
+ },
953
+ get isClosed() {
954
+ return this.closed;
955
+ }
956
+ };
957
+
958
+ /**
959
+ * Post a message over the channel
960
+ * @returns {Promise} that resolved when the message sending is done
961
+ */
962
+ function _post(broadcastChannel, type, msg) {
963
+ var time = broadcastChannel.method.microSeconds();
964
+ var msgObj = {
965
+ time: time,
966
+ type: type,
967
+ data: msg
968
+ };
969
+ var awaitPrepare = broadcastChannel._prepP ? broadcastChannel._prepP : PROMISE_RESOLVED_VOID;
970
+ return awaitPrepare.then(function () {
971
+ var sendPromise = broadcastChannel.method.postMessage(broadcastChannel._state, msgObj);
972
+
973
+ // add/remove to unsent messages list
974
+ broadcastChannel._uMP.add(sendPromise);
975
+ sendPromise["catch"]().then(function () {
976
+ return broadcastChannel._uMP["delete"](sendPromise);
977
+ });
978
+ return sendPromise;
979
+ });
980
+ }
981
+ function _prepareChannel(channel) {
982
+ var maybePromise = channel.method.create(channel.name, channel.options);
983
+ if (isPromise(maybePromise)) {
984
+ channel._prepP = maybePromise;
985
+ maybePromise.then(function (s) {
986
+ // used in tests to simulate slow runtime
987
+ /*if (channel.options.prepareDelay) {
988
+ await new Promise(res => setTimeout(res, this.options.prepareDelay));
989
+ }*/
990
+ channel._state = s;
991
+ });
992
+ } else {
993
+ channel._state = maybePromise;
994
+ }
995
+ }
996
+ function _hasMessageListeners(channel) {
997
+ if (channel._addEL.message.length > 0) return true;
998
+ if (channel._addEL.internal.length > 0) return true;
999
+ return false;
1000
+ }
1001
+ function _addListenerObject(channel, type, obj) {
1002
+ channel._addEL[type].push(obj);
1003
+ _startListening(channel);
1004
+ }
1005
+ function _removeListenerObject(channel, type, obj) {
1006
+ channel._addEL[type] = channel._addEL[type].filter(function (o) {
1007
+ return o !== obj;
1008
+ });
1009
+ _stopListening(channel);
1010
+ }
1011
+ function _startListening(channel) {
1012
+ if (!channel._iL && _hasMessageListeners(channel)) {
1013
+ // someone is listening, start subscribing
1014
+
1015
+ var listenerFn = function listenerFn(msgObj) {
1016
+ channel._addEL[msgObj.type].forEach(function (listenerObject) {
1017
+ /**
1018
+ * Getting the current time in JavaScript has no good precision.
1019
+ * So instead of only listening to events that happened 'after' the listener
1020
+ * was added, we also listen to events that happened 100ms before it.
1021
+ * This ensures that when another process, like a WebWorker, sends events
1022
+ * we do not miss them out because their timestamp is a bit off compared to the main process.
1023
+ * Not doing this would make messages missing when we send data directly after subscribing and awaiting a response.
1024
+ * @link https://johnresig.com/blog/accuracy-of-javascript-time/
1025
+ */
1026
+ var hundredMsInMicro = 100 * 1000;
1027
+ var minMessageTime = listenerObject.time - hundredMsInMicro;
1028
+ if (msgObj.time >= minMessageTime) {
1029
+ listenerObject.fn(msgObj.data);
1030
+ }
1031
+ });
1032
+ };
1033
+ var time = channel.method.microSeconds();
1034
+ if (channel._prepP) {
1035
+ channel._prepP.then(function () {
1036
+ channel._iL = true;
1037
+ channel.method.onMessage(channel._state, listenerFn, time);
1038
+ });
1039
+ } else {
1040
+ channel._iL = true;
1041
+ channel.method.onMessage(channel._state, listenerFn, time);
1042
+ }
1043
+ }
1044
+ }
1045
+ function _stopListening(channel) {
1046
+ if (channel._iL && !_hasMessageListeners(channel)) {
1047
+ // no one is listening, stop subscribing
1048
+ channel._iL = false;
1049
+ var time = channel.method.microSeconds();
1050
+ channel.method.onMessage(channel._state, null, time);
1051
+ }
1052
+ }
1053
+
1054
+ /* global WorkerGlobalScope */
1055
+
1056
+ function addBrowser(fn) {
1057
+ if (typeof WorkerGlobalScope === 'function' && self instanceof WorkerGlobalScope) {
1058
+ /**
1059
+ * Because killing a worker does directly stop the excution
1060
+ * of the code, our only chance is to overwrite the close function
1061
+ * which could work some times.
1062
+ * @link https://stackoverflow.com/q/72903255/3443137
1063
+ */
1064
+ var oldClose = self.close.bind(self);
1065
+ self.close = function () {
1066
+ fn();
1067
+ return oldClose();
1068
+ };
1069
+ } else {
1070
+ /**
1071
+ * if we are on react-native, there is no window.addEventListener
1072
+ * @link https://github.com/pubkey/unload/issues/6
1073
+ */
1074
+ if (typeof window.addEventListener !== 'function') {
1075
+ return;
1076
+ }
1077
+
1078
+ /**
1079
+ * for normal browser-windows, we use the beforeunload-event
1080
+ */
1081
+ window.addEventListener('beforeunload', function () {
1082
+ fn();
1083
+ }, true);
1084
+
1085
+ /**
1086
+ * for iframes, we have to use the unload-event
1087
+ * @link https://stackoverflow.com/q/47533670/3443137
1088
+ */
1089
+ window.addEventListener('unload', function () {
1090
+ fn();
1091
+ }, true);
1092
+ }
1093
+
1094
+ /**
1095
+ * TODO add fallback for safari-mobile
1096
+ * @link https://stackoverflow.com/a/26193516/3443137
1097
+ */
1098
+ }
1099
+
1100
+ function addNode(fn) {
1101
+ process.on('exit', function () {
1102
+ return fn();
1103
+ });
1104
+
1105
+ /**
1106
+ * on the following events,
1107
+ * the process will not end if there are
1108
+ * event-handlers attached,
1109
+ * therefore we have to call process.exit()
1110
+ */
1111
+ process.on('beforeExit', function () {
1112
+ return fn().then(function () {
1113
+ return process.exit();
1114
+ });
1115
+ });
1116
+ // catches ctrl+c event
1117
+ process.on('SIGINT', function () {
1118
+ return fn().then(function () {
1119
+ return process.exit();
1120
+ });
1121
+ });
1122
+ // catches uncaught exceptions
1123
+ process.on('uncaughtException', function (err) {
1124
+ return fn().then(function () {
1125
+ console.trace(err);
1126
+ process.exit(101);
1127
+ });
1128
+ });
1129
+ }
1130
+
1131
+ /**
1132
+ * Use the code directly to prevent import problems
1133
+ * with the detect-node package.
1134
+ * @link https://github.com/iliakan/detect-node/blob/master/index.js
1135
+ */
1136
+ var isNode = Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]';
1137
+ var USE_METHOD = isNode ? addNode : addBrowser;
1138
+ var LISTENERS = new Set();
1139
+ var startedListening = false;
1140
+ function startListening() {
1141
+ if (startedListening) {
1142
+ return;
1143
+ }
1144
+ startedListening = true;
1145
+ USE_METHOD(runAll);
1146
+ }
1147
+ function add(fn) {
1148
+ startListening();
1149
+ if (typeof fn !== 'function') {
1150
+ throw new Error('Listener is no function');
1151
+ }
1152
+ LISTENERS.add(fn);
1153
+ var addReturn = {
1154
+ remove: function remove() {
1155
+ return LISTENERS["delete"](fn);
1156
+ },
1157
+ run: function run() {
1158
+ LISTENERS["delete"](fn);
1159
+ return fn();
1160
+ }
1161
+ };
1162
+ return addReturn;
1163
+ }
1164
+ function runAll() {
1165
+ var promises = [];
1166
+ LISTENERS.forEach(function (fn) {
1167
+ promises.push(fn());
1168
+ LISTENERS["delete"](fn);
1169
+ });
1170
+ return Promise.all(promises);
1171
+ }
1172
+
1173
+ /**
1174
+ * sends and internal message over the broadcast-channel
1175
+ */
1176
+ function sendLeaderMessage(leaderElector, action) {
1177
+ var msgJson = {
1178
+ context: 'leader',
1179
+ action: action,
1180
+ token: leaderElector.token
1181
+ };
1182
+ return leaderElector.broadcastChannel.postInternal(msgJson);
1183
+ }
1184
+ function beLeader(leaderElector) {
1185
+ leaderElector.isLeader = true;
1186
+ leaderElector._hasLeader = true;
1187
+ var unloadFn = add(function () {
1188
+ return leaderElector.die();
1189
+ });
1190
+ leaderElector._unl.push(unloadFn);
1191
+ var isLeaderListener = function isLeaderListener(msg) {
1192
+ if (msg.context === 'leader' && msg.action === 'apply') {
1193
+ sendLeaderMessage(leaderElector, 'tell');
1194
+ }
1195
+ if (msg.context === 'leader' && msg.action === 'tell' && !leaderElector._dpLC) {
1196
+ /**
1197
+ * another instance is also leader!
1198
+ * This can happen on rare events
1199
+ * like when the CPU is at 100% for long time
1200
+ * or the tabs are open very long and the browser throttles them.
1201
+ * @link https://github.com/pubkey/broadcast-channel/issues/414
1202
+ * @link https://github.com/pubkey/broadcast-channel/issues/385
1203
+ */
1204
+ leaderElector._dpLC = true;
1205
+ leaderElector._dpL(); // message the lib user so the app can handle the problem
1206
+ sendLeaderMessage(leaderElector, 'tell'); // ensure other leader also knows the problem
1207
+ }
1208
+ };
1209
+
1210
+ leaderElector.broadcastChannel.addEventListener('internal', isLeaderListener);
1211
+ leaderElector._lstns.push(isLeaderListener);
1212
+ return sendLeaderMessage(leaderElector, 'tell');
1213
+ }
1214
+
1215
+ /**
1216
+ * A faster version of the leader elector that uses the WebLock API
1217
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API
1218
+ */
1219
+ var LeaderElectionWebLock = function LeaderElectionWebLock(broadcastChannel, options) {
1220
+ var _this = this;
1221
+ this.broadcastChannel = broadcastChannel;
1222
+ broadcastChannel._befC.push(function () {
1223
+ return _this.die();
1224
+ });
1225
+ this._options = options;
1226
+ this.isLeader = false;
1227
+ this.isDead = false;
1228
+ this.token = randomToken();
1229
+ this._lstns = [];
1230
+ this._unl = [];
1231
+ this._dpL = function () {}; // onduplicate listener
1232
+ this._dpLC = false; // true when onduplicate called
1233
+
1234
+ this._wKMC = {}; // stuff for cleanup
1235
+
1236
+ // lock name
1237
+ this.lN = 'pubkey-bc||' + broadcastChannel.method.type + '||' + broadcastChannel.name;
1238
+ };
1239
+ LeaderElectionWebLock.prototype = {
1240
+ hasLeader: function hasLeader() {
1241
+ var _this2 = this;
1242
+ return navigator.locks.query().then(function (locks) {
1243
+ var relevantLocks = locks.held ? locks.held.filter(function (lock) {
1244
+ return lock.name === _this2.lN;
1245
+ }) : [];
1246
+ if (relevantLocks && relevantLocks.length > 0) {
1247
+ return true;
1248
+ } else {
1249
+ return false;
1250
+ }
1251
+ });
1252
+ },
1253
+ awaitLeadership: function awaitLeadership() {
1254
+ var _this3 = this;
1255
+ if (!this._wLMP) {
1256
+ this._wKMC.c = new AbortController();
1257
+ var returnPromise = new Promise(function (res, rej) {
1258
+ _this3._wKMC.res = res;
1259
+ _this3._wKMC.rej = rej;
1260
+ });
1261
+ this._wLMP = new Promise(function (res) {
1262
+ navigator.locks.request(_this3.lN, {
1263
+ signal: _this3._wKMC.c.signal
1264
+ }, function () {
1265
+ // if the lock resolved, we can drop the abort controller
1266
+ _this3._wKMC.c = undefined;
1267
+ beLeader(_this3);
1268
+ res();
1269
+ return returnPromise;
1270
+ })["catch"](function () {});
1271
+ });
1272
+ }
1273
+ return this._wLMP;
1274
+ },
1275
+ set onduplicate(_fn) {
1276
+ // Do nothing because there are no duplicates in the WebLock version
1277
+ },
1278
+ die: function die() {
1279
+ var _this4 = this;
1280
+ this._lstns.forEach(function (listener) {
1281
+ return _this4.broadcastChannel.removeEventListener('internal', listener);
1282
+ });
1283
+ this._lstns = [];
1284
+ this._unl.forEach(function (uFn) {
1285
+ return uFn.remove();
1286
+ });
1287
+ this._unl = [];
1288
+ if (this.isLeader) {
1289
+ this.isLeader = false;
1290
+ }
1291
+ this.isDead = true;
1292
+ if (this._wKMC.res) {
1293
+ this._wKMC.res();
1294
+ }
1295
+ if (this._wKMC.c) {
1296
+ this._wKMC.c.abort('LeaderElectionWebLock.die() called');
1297
+ }
1298
+ return sendLeaderMessage(this, 'death');
1299
+ }
1300
+ };
1301
+
1302
+ var LeaderElection = function LeaderElection(broadcastChannel, options) {
1303
+ var _this = this;
1304
+ this.broadcastChannel = broadcastChannel;
1305
+ this._options = options;
1306
+ this.isLeader = false;
1307
+ this._hasLeader = false;
1308
+ this.isDead = false;
1309
+ this.token = randomToken();
1310
+
1311
+ /**
1312
+ * Apply Queue,
1313
+ * used to ensure we do not run applyOnce()
1314
+ * in parallel.
1315
+ */
1316
+ this._aplQ = PROMISE_RESOLVED_VOID;
1317
+ // amount of unfinished applyOnce() calls
1318
+ this._aplQC = 0;
1319
+
1320
+ // things to clean up
1321
+ this._unl = []; // _unloads
1322
+ this._lstns = []; // _listeners
1323
+ this._dpL = function () {}; // onduplicate listener
1324
+ this._dpLC = false; // true when onduplicate called
1325
+
1326
+ /**
1327
+ * Even when the own instance is not applying,
1328
+ * we still listen to messages to ensure the hasLeader flag
1329
+ * is set correctly.
1330
+ */
1331
+ var hasLeaderListener = function hasLeaderListener(msg) {
1332
+ if (msg.context === 'leader') {
1333
+ if (msg.action === 'death') {
1334
+ _this._hasLeader = false;
1335
+ }
1336
+ if (msg.action === 'tell') {
1337
+ _this._hasLeader = true;
1338
+ }
1339
+ }
1340
+ };
1341
+ this.broadcastChannel.addEventListener('internal', hasLeaderListener);
1342
+ this._lstns.push(hasLeaderListener);
1343
+ };
1344
+ LeaderElection.prototype = {
1345
+ hasLeader: function hasLeader() {
1346
+ return Promise.resolve(this._hasLeader);
1347
+ },
1348
+ /**
1349
+ * Returns true if the instance is leader,
1350
+ * false if not.
1351
+ * @async
1352
+ */
1353
+ applyOnce: function applyOnce(
1354
+ // true if the applyOnce() call came from the fallbackInterval cycle
1355
+ isFromFallbackInterval) {
1356
+ var _this2 = this;
1357
+ if (this.isLeader) {
1358
+ return sleep(0, true);
1359
+ }
1360
+ if (this.isDead) {
1361
+ return sleep(0, false);
1362
+ }
1363
+
1364
+ /**
1365
+ * Already applying more than once,
1366
+ * -> wait for the apply queue to be finished.
1367
+ */
1368
+ if (this._aplQC > 1) {
1369
+ return this._aplQ;
1370
+ }
1371
+
1372
+ /**
1373
+ * Add a new apply-run
1374
+ */
1375
+ var applyRun = function applyRun() {
1376
+ /**
1377
+ * Optimization shortcuts.
1378
+ * Directly return if a previous run
1379
+ * has already elected a leader.
1380
+ */
1381
+ if (_this2.isLeader) {
1382
+ return PROMISE_RESOLVED_TRUE;
1383
+ }
1384
+ var stopCriteria = false;
1385
+ var stopCriteriaPromiseResolve;
1386
+ /**
1387
+ * Resolves when a stop criteria is reached.
1388
+ * Uses as a performance shortcut so we do not
1389
+ * have to await the responseTime when it is already clear
1390
+ * that the election failed.
1391
+ */
1392
+ var stopCriteriaPromise = new Promise(function (res) {
1393
+ stopCriteriaPromiseResolve = function stopCriteriaPromiseResolve() {
1394
+ stopCriteria = true;
1395
+ res();
1396
+ };
1397
+ });
1398
+ var handleMessage = function handleMessage(msg) {
1399
+ if (msg.context === 'leader' && msg.token != _this2.token) {
1400
+ if (msg.action === 'apply') {
1401
+ // other is applying
1402
+ if (msg.token > _this2.token) {
1403
+ /**
1404
+ * other has higher token
1405
+ * -> stop applying and let other become leader.
1406
+ */
1407
+ stopCriteriaPromiseResolve();
1408
+ }
1409
+ }
1410
+ if (msg.action === 'tell') {
1411
+ // other is already leader
1412
+ stopCriteriaPromiseResolve();
1413
+ _this2._hasLeader = true;
1414
+ }
1415
+ }
1416
+ };
1417
+ _this2.broadcastChannel.addEventListener('internal', handleMessage);
1418
+
1419
+ /**
1420
+ * If the applyOnce() call came from the fallbackInterval,
1421
+ * we can assume that the election runs in the background and
1422
+ * not critical process is waiting for it.
1423
+ * When this is true, we give the other instances
1424
+ * more time to answer to messages in the election cycle.
1425
+ * This makes it less likely to elect duplicate leaders.
1426
+ * But also it takes longer which is not a problem because we anyway
1427
+ * run in the background.
1428
+ */
1429
+ var waitForAnswerTime = isFromFallbackInterval ? _this2._options.responseTime * 4 : _this2._options.responseTime;
1430
+ return sendLeaderMessage(_this2, 'apply') // send out that this one is applying
1431
+ .then(function () {
1432
+ return Promise.race([sleep(waitForAnswerTime), stopCriteriaPromise.then(function () {
1433
+ return Promise.reject(new Error());
1434
+ })]);
1435
+ })
1436
+ // send again in case another instance was just created
1437
+ .then(function () {
1438
+ return sendLeaderMessage(_this2, 'apply');
1439
+ })
1440
+ // let others time to respond
1441
+ .then(function () {
1442
+ return Promise.race([sleep(waitForAnswerTime), stopCriteriaPromise.then(function () {
1443
+ return Promise.reject(new Error());
1444
+ })]);
1445
+ })["catch"](function () {}).then(function () {
1446
+ _this2.broadcastChannel.removeEventListener('internal', handleMessage);
1447
+ if (!stopCriteria) {
1448
+ // no stop criteria -> own is leader
1449
+ return beLeader(_this2).then(function () {
1450
+ return true;
1451
+ });
1452
+ } else {
1453
+ // other is leader
1454
+ return false;
1455
+ }
1456
+ });
1457
+ };
1458
+ this._aplQC = this._aplQC + 1;
1459
+ this._aplQ = this._aplQ.then(function () {
1460
+ return applyRun();
1461
+ }).then(function () {
1462
+ _this2._aplQC = _this2._aplQC - 1;
1463
+ });
1464
+ return this._aplQ.then(function () {
1465
+ return _this2.isLeader;
1466
+ });
1467
+ },
1468
+ awaitLeadership: function awaitLeadership() {
1469
+ if ( /* _awaitLeadershipPromise */
1470
+ !this._aLP) {
1471
+ this._aLP = _awaitLeadershipOnce(this);
1472
+ }
1473
+ return this._aLP;
1474
+ },
1475
+ set onduplicate(fn) {
1476
+ this._dpL = fn;
1477
+ },
1478
+ die: function die() {
1479
+ var _this3 = this;
1480
+ this._lstns.forEach(function (listener) {
1481
+ return _this3.broadcastChannel.removeEventListener('internal', listener);
1482
+ });
1483
+ this._lstns = [];
1484
+ this._unl.forEach(function (uFn) {
1485
+ return uFn.remove();
1486
+ });
1487
+ this._unl = [];
1488
+ if (this.isLeader) {
1489
+ this._hasLeader = false;
1490
+ this.isLeader = false;
1491
+ }
1492
+ this.isDead = true;
1493
+ return sendLeaderMessage(this, 'death');
1494
+ }
1495
+ };
1496
+
1497
+ /**
1498
+ * @param leaderElector {LeaderElector}
1499
+ */
1500
+ function _awaitLeadershipOnce(leaderElector) {
1501
+ if (leaderElector.isLeader) {
1502
+ return PROMISE_RESOLVED_VOID;
1503
+ }
1504
+ return new Promise(function (res) {
1505
+ var resolved = false;
1506
+ function finish() {
1507
+ if (resolved) {
1508
+ return;
1509
+ }
1510
+ resolved = true;
1511
+ leaderElector.broadcastChannel.removeEventListener('internal', whenDeathListener);
1512
+ res(true);
1513
+ }
1514
+
1515
+ // try once now
1516
+ leaderElector.applyOnce().then(function () {
1517
+ if (leaderElector.isLeader) {
1518
+ finish();
1519
+ }
1520
+ });
1521
+
1522
+ /**
1523
+ * Try on fallbackInterval
1524
+ * @recursive
1525
+ */
1526
+ var tryOnFallBack = function tryOnFallBack() {
1527
+ return sleep(leaderElector._options.fallbackInterval).then(function () {
1528
+ if (leaderElector.isDead || resolved) {
1529
+ return;
1530
+ }
1531
+ if (leaderElector.isLeader) {
1532
+ finish();
1533
+ } else {
1534
+ return leaderElector.applyOnce(true).then(function () {
1535
+ if (leaderElector.isLeader) {
1536
+ finish();
1537
+ } else {
1538
+ tryOnFallBack();
1539
+ }
1540
+ });
1541
+ }
1542
+ });
1543
+ };
1544
+ tryOnFallBack();
1545
+
1546
+ // try when other leader dies
1547
+ var whenDeathListener = function whenDeathListener(msg) {
1548
+ if (msg.context === 'leader' && msg.action === 'death') {
1549
+ leaderElector._hasLeader = false;
1550
+ leaderElector.applyOnce().then(function () {
1551
+ if (leaderElector.isLeader) {
1552
+ finish();
1553
+ }
1554
+ });
1555
+ }
1556
+ };
1557
+ leaderElector.broadcastChannel.addEventListener('internal', whenDeathListener);
1558
+ leaderElector._lstns.push(whenDeathListener);
1559
+ });
1560
+ }
1561
+ function fillOptionsWithDefaults(options, channel) {
1562
+ if (!options) options = {};
1563
+ options = JSON.parse(JSON.stringify(options));
1564
+ if (!options.fallbackInterval) {
1565
+ options.fallbackInterval = 3000;
1566
+ }
1567
+ if (!options.responseTime) {
1568
+ options.responseTime = channel.method.averageResponseTime(channel.options);
1569
+ }
1570
+ return options;
1571
+ }
1572
+ function createLeaderElection(channel, options) {
1573
+ if (channel._leaderElector) {
1574
+ throw new Error('BroadcastChannel already has a leader-elector');
1575
+ }
1576
+ options = fillOptionsWithDefaults(options, channel);
1577
+ var elector = supportsWebLockAPI() ? new LeaderElectionWebLock(channel, options) : new LeaderElection(channel, options);
1578
+ channel._befC.push(function () {
1579
+ return elector.die();
1580
+ });
1581
+ channel._leaderElector = elector;
1582
+ return elector;
1583
+ }
1584
+
1585
+ export { BroadcastChannel$1 as BroadcastChannel, OPEN_BROADCAST_CHANNELS, beLeader, clearNodeFolder, createLeaderElection, enforceOptions };
1586
+ //# sourceMappingURL=index-9000b9fd.js.map