@next-core/brick-kit 2.188.4 → 2.189.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.
- package/dist/index-9000b9fd.js +1586 -0
- package/dist/index-9000b9fd.js.map +1 -0
- package/dist/index.bundle.js +1664 -6
- package/dist/index.bundle.js.map +1 -1
- package/dist/index.esm.js +66 -2
- package/dist/index.esm.js.map +1 -1
- package/dist/types/core/AuthenticationChange.d.ts +3 -0
- package/dist/types/core/AuthenticationChange.d.ts.map +1 -0
- package/dist/types/core/Kernel.d.ts.map +1 -1
- package/dist/types/i18n/constants.d.ts +3 -1
- package/dist/types/i18n/constants.d.ts.map +1 -1
- package/dist/types/i18n/locales/en.d.ts.map +1 -1
- package/dist/types/i18n/locales/zh.d.ts.map +1 -1
- package/package.json +11 -10
package/dist/index.bundle.js
CHANGED
|
@@ -177,6 +177,8 @@
|
|
|
177
177
|
K["OTHER_ERROR"] = "OTHER_ERROR";
|
|
178
178
|
K["GO_BACK_PREVIOUS_PAGE"] = "GO_BACK_PREVIOUS_PAGE";
|
|
179
179
|
K["GO_BACK_HOME_PAGE"] = "GO_BACK_HOME_PAGE";
|
|
180
|
+
K["LOGIN_CHANGED"] = "LOGIN_CHANGED";
|
|
181
|
+
K["LOGOUT_APPLIED"] = "LOGOUT_APPLIED";
|
|
180
182
|
})(K || (K = {}));
|
|
181
183
|
|
|
182
184
|
/**
|
|
@@ -1206,7 +1208,9 @@
|
|
|
1206
1208
|
[K.NO_PERMISSION]: "Unauthorized access, unable to retrieve the required resources for this page",
|
|
1207
1209
|
[K.OTHER_ERROR]: "Oops! Something went wrong",
|
|
1208
1210
|
[K.GO_BACK_PREVIOUS_PAGE]: "Go back to previous page",
|
|
1209
|
-
[K.GO_BACK_HOME_PAGE]: "Back to home page"
|
|
1211
|
+
[K.GO_BACK_HOME_PAGE]: "Back to home page",
|
|
1212
|
+
[K.LOGIN_CHANGED]: "You have logged in as another account, click OK to refresh the page.",
|
|
1213
|
+
[K.LOGOUT_APPLIED]: "Your account has been logged out, click OK to refresh the page."
|
|
1210
1214
|
};
|
|
1211
1215
|
var en = locale$7;
|
|
1212
1216
|
|
|
@@ -1223,7 +1227,9 @@
|
|
|
1223
1227
|
[K.NO_PERMISSION]: "没有权限,无法获取页面所需要的资源",
|
|
1224
1228
|
[K.OTHER_ERROR]: "糟糕!页面出现了一些问题",
|
|
1225
1229
|
[K.GO_BACK_PREVIOUS_PAGE]: "回到上一页",
|
|
1226
|
-
[K.GO_BACK_HOME_PAGE]: "回到首页"
|
|
1230
|
+
[K.GO_BACK_HOME_PAGE]: "回到首页",
|
|
1231
|
+
[K.LOGIN_CHANGED]: "您已经登录另一个账号,点击确定刷新页面。",
|
|
1232
|
+
[K.LOGOUT_APPLIED]: "您的账号已经登出,点击确定刷新页面。"
|
|
1227
1233
|
};
|
|
1228
1234
|
var zh = locale$6;
|
|
1229
1235
|
|
|
@@ -1626,9 +1632,9 @@
|
|
|
1626
1632
|
}
|
|
1627
1633
|
|
|
1628
1634
|
/*! (c) Andrea Giammarchi - ISC */
|
|
1629
|
-
var self = {};
|
|
1635
|
+
var self$1 = {};
|
|
1630
1636
|
try {
|
|
1631
|
-
self.EventTarget = new EventTarget().constructor;
|
|
1637
|
+
self$1.EventTarget = new EventTarget().constructor;
|
|
1632
1638
|
} catch (EventTarget) {
|
|
1633
1639
|
(function (Object, wm) {
|
|
1634
1640
|
var create = Object.create;
|
|
@@ -1665,7 +1671,7 @@
|
|
|
1665
1671
|
}
|
|
1666
1672
|
}
|
|
1667
1673
|
});
|
|
1668
|
-
self.EventTarget = EventTarget;
|
|
1674
|
+
self$1.EventTarget = EventTarget;
|
|
1669
1675
|
function EventTarget() {
|
|
1670
1676
|
|
|
1671
1677
|
wm.set(this, create(null));
|
|
@@ -1685,7 +1691,7 @@
|
|
|
1685
1691
|
}
|
|
1686
1692
|
})(Object, new WeakMap());
|
|
1687
1693
|
}
|
|
1688
|
-
var EventTarget$1 = self.EventTarget;
|
|
1694
|
+
var EventTarget$1 = self$1.EventTarget;
|
|
1689
1695
|
|
|
1690
1696
|
/** @internal */
|
|
1691
1697
|
function looseCheckIf$1(ifContainer, context) {
|
|
@@ -8786,6 +8792,63 @@
|
|
|
8786
8792
|
return misc;
|
|
8787
8793
|
}
|
|
8788
8794
|
|
|
8795
|
+
var REFRESH_CHANNEL = "brick-next:authentication-change";
|
|
8796
|
+
var warnedToRefresh = false;
|
|
8797
|
+
function refreshPageIfAuthenticationChanged(_x) {
|
|
8798
|
+
return _refreshPageIfAuthenticationChanged.apply(this, arguments);
|
|
8799
|
+
}
|
|
8800
|
+
function _refreshPageIfAuthenticationChanged() {
|
|
8801
|
+
_refreshPageIfAuthenticationChanged = _asyncToGenerator__default["default"](function* (auth) {
|
|
8802
|
+
if (window.BroadcastChannel) {
|
|
8803
|
+
var channel = new BroadcastChannel(REFRESH_CHANNEL);
|
|
8804
|
+
channel.postMessage({
|
|
8805
|
+
type: "check-login",
|
|
8806
|
+
org: auth.org,
|
|
8807
|
+
username: auth.username
|
|
8808
|
+
});
|
|
8809
|
+
channel.addEventListener("message", e => {
|
|
8810
|
+
handleAuthenticationChange(e.data, auth);
|
|
8811
|
+
});
|
|
8812
|
+
} else {
|
|
8813
|
+
var PolyfillChannel = (yield Promise.resolve().then(function () { return index; })).BroadcastChannel;
|
|
8814
|
+
var _channel = new PolyfillChannel(REFRESH_CHANNEL);
|
|
8815
|
+
_channel.postMessage({
|
|
8816
|
+
type: "check-login",
|
|
8817
|
+
org: auth.org,
|
|
8818
|
+
username: auth.username
|
|
8819
|
+
});
|
|
8820
|
+
// The npm package `broadcast-channel` dispatch message as raw data instead of a MessageEvent.
|
|
8821
|
+
_channel.addEventListener("message", data => {
|
|
8822
|
+
handleAuthenticationChange(data, auth);
|
|
8823
|
+
});
|
|
8824
|
+
}
|
|
8825
|
+
});
|
|
8826
|
+
return _refreshPageIfAuthenticationChanged.apply(this, arguments);
|
|
8827
|
+
}
|
|
8828
|
+
function handleAuthenticationChange(_x2, _x3) {
|
|
8829
|
+
return _handleAuthenticationChange.apply(this, arguments);
|
|
8830
|
+
}
|
|
8831
|
+
function _handleAuthenticationChange() {
|
|
8832
|
+
_handleAuthenticationChange = _asyncToGenerator__default["default"](function* (payload, auth) {
|
|
8833
|
+
if (!warnedToRefresh && (payload.type === "logout" || payload.type === "check-login" && (payload.org !== auth.org || payload.username !== auth.username)) && getRuntime().getFeatureFlags()["refresh-page-if-authentication-changed"]) {
|
|
8834
|
+
// Only warn the user once.
|
|
8835
|
+
warnedToRefresh = true;
|
|
8836
|
+
// Open a modal to ask the user to refresh the page.
|
|
8837
|
+
// window.alert(i18next.t(`${NS_BRICK_KIT}:${payload.type === "logout" ? K.LOGOUT_APPLIED : K.LOGIN_CHANGED}`));
|
|
8838
|
+
antd.Modal.warning({
|
|
8839
|
+
icon: /*#__PURE__*/React__default["default"].createElement(icons.ExclamationCircleOutlined, null),
|
|
8840
|
+
content: i18next__default["default"].t("".concat(NS_BRICK_KIT, ":").concat(payload.type === "logout" ? K.LOGOUT_APPLIED : K.LOGIN_CHANGED)),
|
|
8841
|
+
okText: i18next__default["default"].t("".concat(NS_BRICK_KIT, ":").concat(K.MODAL_OK)),
|
|
8842
|
+
keyboard: false,
|
|
8843
|
+
onOk: () => {
|
|
8844
|
+
location.reload();
|
|
8845
|
+
}
|
|
8846
|
+
});
|
|
8847
|
+
}
|
|
8848
|
+
});
|
|
8849
|
+
return _handleAuthenticationChange.apply(this, arguments);
|
|
8850
|
+
}
|
|
8851
|
+
|
|
8789
8852
|
var _excluded$3 = ["params"];
|
|
8790
8853
|
var V3WidgetMates = ["basic.v3-widget-mate"];
|
|
8791
8854
|
class Kernel {
|
|
@@ -8992,6 +9055,7 @@
|
|
|
8992
9055
|
var auth = yield checkLogin();
|
|
8993
9056
|
if (auth.loggedIn) {
|
|
8994
9057
|
authenticate(auth);
|
|
9058
|
+
refreshPageIfAuthenticationChanged(auth);
|
|
8995
9059
|
}
|
|
8996
9060
|
}
|
|
8997
9061
|
})();
|
|
@@ -15008,6 +15072,1600 @@
|
|
|
15008
15072
|
});
|
|
15009
15073
|
}
|
|
15010
15074
|
|
|
15075
|
+
/**
|
|
15076
|
+
* returns true if the given object is a promise
|
|
15077
|
+
*/
|
|
15078
|
+
function isPromise(obj) {
|
|
15079
|
+
return obj && typeof obj.then === 'function';
|
|
15080
|
+
}
|
|
15081
|
+
var PROMISE_RESOLVED_FALSE = Promise.resolve(false);
|
|
15082
|
+
var PROMISE_RESOLVED_TRUE = Promise.resolve(true);
|
|
15083
|
+
var PROMISE_RESOLVED_VOID = Promise.resolve();
|
|
15084
|
+
function sleep(time, resolveWith) {
|
|
15085
|
+
if (!time) time = 0;
|
|
15086
|
+
return new Promise(function (res) {
|
|
15087
|
+
return setTimeout(function () {
|
|
15088
|
+
return res(resolveWith);
|
|
15089
|
+
}, time);
|
|
15090
|
+
});
|
|
15091
|
+
}
|
|
15092
|
+
function randomInt(min, max) {
|
|
15093
|
+
return Math.floor(Math.random() * (max - min + 1) + min);
|
|
15094
|
+
}
|
|
15095
|
+
|
|
15096
|
+
/**
|
|
15097
|
+
* https://stackoverflow.com/a/8084248
|
|
15098
|
+
*/
|
|
15099
|
+
function randomToken() {
|
|
15100
|
+
return Math.random().toString(36).substring(2);
|
|
15101
|
+
}
|
|
15102
|
+
var lastMs = 0;
|
|
15103
|
+
var additional = 0;
|
|
15104
|
+
|
|
15105
|
+
/**
|
|
15106
|
+
* returns the current time in micro-seconds,
|
|
15107
|
+
* WARNING: This is a pseudo-function
|
|
15108
|
+
* Performance.now is not reliable in webworkers, so we just make sure to never return the same time.
|
|
15109
|
+
* This is enough in browsers, and this function will not be used in nodejs.
|
|
15110
|
+
* The main reason for this hack is to ensure that BroadcastChannel behaves equal to production when it is used in fast-running unit tests.
|
|
15111
|
+
*/
|
|
15112
|
+
function microSeconds$4() {
|
|
15113
|
+
var ms = Date.now();
|
|
15114
|
+
if (ms === lastMs) {
|
|
15115
|
+
additional++;
|
|
15116
|
+
return ms * 1000 + additional;
|
|
15117
|
+
} else {
|
|
15118
|
+
lastMs = ms;
|
|
15119
|
+
additional = 0;
|
|
15120
|
+
return ms * 1000;
|
|
15121
|
+
}
|
|
15122
|
+
}
|
|
15123
|
+
|
|
15124
|
+
/**
|
|
15125
|
+
* Check if WebLock API is supported.
|
|
15126
|
+
* @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API
|
|
15127
|
+
*/
|
|
15128
|
+
function supportsWebLockAPI() {
|
|
15129
|
+
if (typeof navigator !== 'undefined' && typeof navigator.locks !== 'undefined' && typeof navigator.locks.request === 'function') {
|
|
15130
|
+
return true;
|
|
15131
|
+
} else {
|
|
15132
|
+
return false;
|
|
15133
|
+
}
|
|
15134
|
+
}
|
|
15135
|
+
|
|
15136
|
+
var microSeconds$3 = microSeconds$4;
|
|
15137
|
+
var type$3 = 'native';
|
|
15138
|
+
function create$3(channelName) {
|
|
15139
|
+
var state = {
|
|
15140
|
+
messagesCallback: null,
|
|
15141
|
+
bc: new BroadcastChannel(channelName),
|
|
15142
|
+
subFns: [] // subscriberFunctions
|
|
15143
|
+
};
|
|
15144
|
+
|
|
15145
|
+
state.bc.onmessage = function (msg) {
|
|
15146
|
+
if (state.messagesCallback) {
|
|
15147
|
+
state.messagesCallback(msg.data);
|
|
15148
|
+
}
|
|
15149
|
+
};
|
|
15150
|
+
return state;
|
|
15151
|
+
}
|
|
15152
|
+
function close$3(channelState) {
|
|
15153
|
+
channelState.bc.close();
|
|
15154
|
+
channelState.subFns = [];
|
|
15155
|
+
}
|
|
15156
|
+
function postMessage$3(channelState, messageJson) {
|
|
15157
|
+
try {
|
|
15158
|
+
channelState.bc.postMessage(messageJson, false);
|
|
15159
|
+
return PROMISE_RESOLVED_VOID;
|
|
15160
|
+
} catch (err) {
|
|
15161
|
+
return Promise.reject(err);
|
|
15162
|
+
}
|
|
15163
|
+
}
|
|
15164
|
+
function onMessage$3(channelState, fn) {
|
|
15165
|
+
channelState.messagesCallback = fn;
|
|
15166
|
+
}
|
|
15167
|
+
function canBeUsed$3() {
|
|
15168
|
+
if ((typeof window !== 'undefined' || typeof self !== 'undefined') && typeof BroadcastChannel === 'function') {
|
|
15169
|
+
if (BroadcastChannel._pubkey) {
|
|
15170
|
+
throw new Error('BroadcastChannel: Do not overwrite window.BroadcastChannel with this module, this is not a polyfill');
|
|
15171
|
+
}
|
|
15172
|
+
return true;
|
|
15173
|
+
} else {
|
|
15174
|
+
return false;
|
|
15175
|
+
}
|
|
15176
|
+
}
|
|
15177
|
+
function averageResponseTime$3() {
|
|
15178
|
+
return 150;
|
|
15179
|
+
}
|
|
15180
|
+
var NativeMethod = {
|
|
15181
|
+
create: create$3,
|
|
15182
|
+
close: close$3,
|
|
15183
|
+
onMessage: onMessage$3,
|
|
15184
|
+
postMessage: postMessage$3,
|
|
15185
|
+
canBeUsed: canBeUsed$3,
|
|
15186
|
+
type: type$3,
|
|
15187
|
+
averageResponseTime: averageResponseTime$3,
|
|
15188
|
+
microSeconds: microSeconds$3
|
|
15189
|
+
};
|
|
15190
|
+
|
|
15191
|
+
/**
|
|
15192
|
+
* this is a set which automatically forgets
|
|
15193
|
+
* a given entry when a new entry is set and the ttl
|
|
15194
|
+
* of the old one is over
|
|
15195
|
+
*/
|
|
15196
|
+
var ObliviousSet = /** @class */function () {
|
|
15197
|
+
function ObliviousSet(ttl) {
|
|
15198
|
+
this.ttl = ttl;
|
|
15199
|
+
this.map = new Map();
|
|
15200
|
+
/**
|
|
15201
|
+
* Creating calls to setTimeout() is expensive,
|
|
15202
|
+
* so we only do that if there is not timeout already open.
|
|
15203
|
+
*/
|
|
15204
|
+
this._to = false;
|
|
15205
|
+
}
|
|
15206
|
+
ObliviousSet.prototype.has = function (value) {
|
|
15207
|
+
return this.map.has(value);
|
|
15208
|
+
};
|
|
15209
|
+
ObliviousSet.prototype.add = function (value) {
|
|
15210
|
+
var _this = this;
|
|
15211
|
+
this.map.set(value, now());
|
|
15212
|
+
/**
|
|
15213
|
+
* When a new value is added,
|
|
15214
|
+
* start the cleanup at the next tick
|
|
15215
|
+
* to not block the cpu for more important stuff
|
|
15216
|
+
* that might happen.
|
|
15217
|
+
*/
|
|
15218
|
+
if (!this._to) {
|
|
15219
|
+
this._to = true;
|
|
15220
|
+
setTimeout(function () {
|
|
15221
|
+
_this._to = false;
|
|
15222
|
+
removeTooOldValues(_this);
|
|
15223
|
+
}, 0);
|
|
15224
|
+
}
|
|
15225
|
+
};
|
|
15226
|
+
ObliviousSet.prototype.clear = function () {
|
|
15227
|
+
this.map.clear();
|
|
15228
|
+
};
|
|
15229
|
+
return ObliviousSet;
|
|
15230
|
+
}();
|
|
15231
|
+
/**
|
|
15232
|
+
* Removes all entries from the set
|
|
15233
|
+
* where the TTL has expired
|
|
15234
|
+
*/
|
|
15235
|
+
function removeTooOldValues(obliviousSet) {
|
|
15236
|
+
var olderThen = now() - obliviousSet.ttl;
|
|
15237
|
+
var iterator = obliviousSet.map[Symbol.iterator]();
|
|
15238
|
+
/**
|
|
15239
|
+
* Because we can assume the new values are added at the bottom,
|
|
15240
|
+
* we start from the top and stop as soon as we reach a non-too-old value.
|
|
15241
|
+
*/
|
|
15242
|
+
while (true) {
|
|
15243
|
+
var next = iterator.next().value;
|
|
15244
|
+
if (!next) {
|
|
15245
|
+
return; // no more elements
|
|
15246
|
+
}
|
|
15247
|
+
|
|
15248
|
+
var value = next[0];
|
|
15249
|
+
var time = next[1];
|
|
15250
|
+
if (time < olderThen) {
|
|
15251
|
+
obliviousSet.map.delete(value);
|
|
15252
|
+
} else {
|
|
15253
|
+
// We reached a value that is not old enough
|
|
15254
|
+
return;
|
|
15255
|
+
}
|
|
15256
|
+
}
|
|
15257
|
+
}
|
|
15258
|
+
function now() {
|
|
15259
|
+
return new Date().getTime();
|
|
15260
|
+
}
|
|
15261
|
+
|
|
15262
|
+
function fillOptionsWithDefaults$1() {
|
|
15263
|
+
var originalOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
15264
|
+
var options = JSON.parse(JSON.stringify(originalOptions));
|
|
15265
|
+
|
|
15266
|
+
// main
|
|
15267
|
+
if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true;
|
|
15268
|
+
|
|
15269
|
+
// indexed-db
|
|
15270
|
+
if (!options.idb) options.idb = {};
|
|
15271
|
+
// after this time the messages get deleted
|
|
15272
|
+
if (!options.idb.ttl) options.idb.ttl = 1000 * 45;
|
|
15273
|
+
if (!options.idb.fallbackInterval) options.idb.fallbackInterval = 150;
|
|
15274
|
+
// handles abrupt db onclose events.
|
|
15275
|
+
if (originalOptions.idb && typeof originalOptions.idb.onclose === 'function') options.idb.onclose = originalOptions.idb.onclose;
|
|
15276
|
+
|
|
15277
|
+
// localstorage
|
|
15278
|
+
if (!options.localstorage) options.localstorage = {};
|
|
15279
|
+
if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60;
|
|
15280
|
+
|
|
15281
|
+
// custom methods
|
|
15282
|
+
if (originalOptions.methods) options.methods = originalOptions.methods;
|
|
15283
|
+
|
|
15284
|
+
// node
|
|
15285
|
+
if (!options.node) options.node = {};
|
|
15286
|
+
if (!options.node.ttl) options.node.ttl = 1000 * 60 * 2; // 2 minutes;
|
|
15287
|
+
/**
|
|
15288
|
+
* On linux use 'ulimit -Hn' to get the limit of open files.
|
|
15289
|
+
* On ubuntu this was 4096 for me, so we use half of that as maxParallelWrites default.
|
|
15290
|
+
*/
|
|
15291
|
+
if (!options.node.maxParallelWrites) options.node.maxParallelWrites = 2048;
|
|
15292
|
+
if (typeof options.node.useFastPath === 'undefined') options.node.useFastPath = true;
|
|
15293
|
+
return options;
|
|
15294
|
+
}
|
|
15295
|
+
|
|
15296
|
+
/**
|
|
15297
|
+
* this method uses indexeddb to store the messages
|
|
15298
|
+
* There is currently no observerAPI for idb
|
|
15299
|
+
* @link https://github.com/w3c/IndexedDB/issues/51
|
|
15300
|
+
*
|
|
15301
|
+
* When working on this, ensure to use these performance optimizations:
|
|
15302
|
+
* @link https://rxdb.info/slow-indexeddb.html
|
|
15303
|
+
*/
|
|
15304
|
+
var microSeconds$2 = microSeconds$4;
|
|
15305
|
+
var DB_PREFIX = 'pubkey.broadcast-channel-0-';
|
|
15306
|
+
var OBJECT_STORE_ID = 'messages';
|
|
15307
|
+
|
|
15308
|
+
/**
|
|
15309
|
+
* Use relaxed durability for faster performance on all transactions.
|
|
15310
|
+
* @link https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/
|
|
15311
|
+
*/
|
|
15312
|
+
var TRANSACTION_SETTINGS = {
|
|
15313
|
+
durability: 'relaxed'
|
|
15314
|
+
};
|
|
15315
|
+
var type$2 = 'idb';
|
|
15316
|
+
function getIdb() {
|
|
15317
|
+
if (typeof indexedDB !== 'undefined') return indexedDB;
|
|
15318
|
+
if (typeof window !== 'undefined') {
|
|
15319
|
+
if (typeof window.mozIndexedDB !== 'undefined') return window.mozIndexedDB;
|
|
15320
|
+
if (typeof window.webkitIndexedDB !== 'undefined') return window.webkitIndexedDB;
|
|
15321
|
+
if (typeof window.msIndexedDB !== 'undefined') return window.msIndexedDB;
|
|
15322
|
+
}
|
|
15323
|
+
return false;
|
|
15324
|
+
}
|
|
15325
|
+
|
|
15326
|
+
/**
|
|
15327
|
+
* If possible, we should explicitly commit IndexedDB transactions
|
|
15328
|
+
* for better performance.
|
|
15329
|
+
* @link https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/
|
|
15330
|
+
*/
|
|
15331
|
+
function commitIndexedDBTransaction(tx) {
|
|
15332
|
+
if (tx.commit) {
|
|
15333
|
+
tx.commit();
|
|
15334
|
+
}
|
|
15335
|
+
}
|
|
15336
|
+
function createDatabase(channelName) {
|
|
15337
|
+
var IndexedDB = getIdb();
|
|
15338
|
+
|
|
15339
|
+
// create table
|
|
15340
|
+
var dbName = DB_PREFIX + channelName;
|
|
15341
|
+
|
|
15342
|
+
/**
|
|
15343
|
+
* All IndexedDB databases are opened without version
|
|
15344
|
+
* because it is a bit faster, especially on firefox
|
|
15345
|
+
* @link http://nparashuram.com/IndexedDB/perf/#Open%20Database%20with%20version
|
|
15346
|
+
*/
|
|
15347
|
+
var openRequest = IndexedDB.open(dbName);
|
|
15348
|
+
openRequest.onupgradeneeded = function (ev) {
|
|
15349
|
+
var db = ev.target.result;
|
|
15350
|
+
db.createObjectStore(OBJECT_STORE_ID, {
|
|
15351
|
+
keyPath: 'id',
|
|
15352
|
+
autoIncrement: true
|
|
15353
|
+
});
|
|
15354
|
+
};
|
|
15355
|
+
return new Promise(function (res, rej) {
|
|
15356
|
+
openRequest.onerror = function (ev) {
|
|
15357
|
+
return rej(ev);
|
|
15358
|
+
};
|
|
15359
|
+
openRequest.onsuccess = function () {
|
|
15360
|
+
res(openRequest.result);
|
|
15361
|
+
};
|
|
15362
|
+
});
|
|
15363
|
+
}
|
|
15364
|
+
|
|
15365
|
+
/**
|
|
15366
|
+
* writes the new message to the database
|
|
15367
|
+
* so other readers can find it
|
|
15368
|
+
*/
|
|
15369
|
+
function writeMessage(db, readerUuid, messageJson) {
|
|
15370
|
+
var time = Date.now();
|
|
15371
|
+
var writeObject = {
|
|
15372
|
+
uuid: readerUuid,
|
|
15373
|
+
time: time,
|
|
15374
|
+
data: messageJson
|
|
15375
|
+
};
|
|
15376
|
+
var tx = db.transaction([OBJECT_STORE_ID], 'readwrite', TRANSACTION_SETTINGS);
|
|
15377
|
+
return new Promise(function (res, rej) {
|
|
15378
|
+
tx.oncomplete = function () {
|
|
15379
|
+
return res();
|
|
15380
|
+
};
|
|
15381
|
+
tx.onerror = function (ev) {
|
|
15382
|
+
return rej(ev);
|
|
15383
|
+
};
|
|
15384
|
+
var objectStore = tx.objectStore(OBJECT_STORE_ID);
|
|
15385
|
+
objectStore.add(writeObject);
|
|
15386
|
+
commitIndexedDBTransaction(tx);
|
|
15387
|
+
});
|
|
15388
|
+
}
|
|
15389
|
+
function getMessagesHigherThan(db, lastCursorId) {
|
|
15390
|
+
var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS);
|
|
15391
|
+
var objectStore = tx.objectStore(OBJECT_STORE_ID);
|
|
15392
|
+
var ret = [];
|
|
15393
|
+
var keyRangeValue = IDBKeyRange.bound(lastCursorId + 1, Infinity);
|
|
15394
|
+
|
|
15395
|
+
/**
|
|
15396
|
+
* Optimization shortcut,
|
|
15397
|
+
* if getAll() can be used, do not use a cursor.
|
|
15398
|
+
* @link https://rxdb.info/slow-indexeddb.html
|
|
15399
|
+
*/
|
|
15400
|
+
if (objectStore.getAll) {
|
|
15401
|
+
var getAllRequest = objectStore.getAll(keyRangeValue);
|
|
15402
|
+
return new Promise(function (res, rej) {
|
|
15403
|
+
getAllRequest.onerror = function (err) {
|
|
15404
|
+
return rej(err);
|
|
15405
|
+
};
|
|
15406
|
+
getAllRequest.onsuccess = function (e) {
|
|
15407
|
+
res(e.target.result);
|
|
15408
|
+
};
|
|
15409
|
+
});
|
|
15410
|
+
}
|
|
15411
|
+
function openCursor() {
|
|
15412
|
+
// Occasionally Safari will fail on IDBKeyRange.bound, this
|
|
15413
|
+
// catches that error, having it open the cursor to the first
|
|
15414
|
+
// item. When it gets data it will advance to the desired key.
|
|
15415
|
+
try {
|
|
15416
|
+
keyRangeValue = IDBKeyRange.bound(lastCursorId + 1, Infinity);
|
|
15417
|
+
return objectStore.openCursor(keyRangeValue);
|
|
15418
|
+
} catch (e) {
|
|
15419
|
+
return objectStore.openCursor();
|
|
15420
|
+
}
|
|
15421
|
+
}
|
|
15422
|
+
return new Promise(function (res, rej) {
|
|
15423
|
+
var openCursorRequest = openCursor();
|
|
15424
|
+
openCursorRequest.onerror = function (err) {
|
|
15425
|
+
return rej(err);
|
|
15426
|
+
};
|
|
15427
|
+
openCursorRequest.onsuccess = function (ev) {
|
|
15428
|
+
var cursor = ev.target.result;
|
|
15429
|
+
if (cursor) {
|
|
15430
|
+
if (cursor.value.id < lastCursorId + 1) {
|
|
15431
|
+
cursor["continue"](lastCursorId + 1);
|
|
15432
|
+
} else {
|
|
15433
|
+
ret.push(cursor.value);
|
|
15434
|
+
cursor["continue"]();
|
|
15435
|
+
}
|
|
15436
|
+
} else {
|
|
15437
|
+
commitIndexedDBTransaction(tx);
|
|
15438
|
+
res(ret);
|
|
15439
|
+
}
|
|
15440
|
+
};
|
|
15441
|
+
});
|
|
15442
|
+
}
|
|
15443
|
+
function removeMessagesById(channelState, ids) {
|
|
15444
|
+
if (channelState.closed) {
|
|
15445
|
+
return Promise.resolve([]);
|
|
15446
|
+
}
|
|
15447
|
+
var tx = channelState.db.transaction(OBJECT_STORE_ID, 'readwrite', TRANSACTION_SETTINGS);
|
|
15448
|
+
var objectStore = tx.objectStore(OBJECT_STORE_ID);
|
|
15449
|
+
return Promise.all(ids.map(function (id) {
|
|
15450
|
+
var deleteRequest = objectStore["delete"](id);
|
|
15451
|
+
return new Promise(function (res) {
|
|
15452
|
+
deleteRequest.onsuccess = function () {
|
|
15453
|
+
return res();
|
|
15454
|
+
};
|
|
15455
|
+
});
|
|
15456
|
+
}));
|
|
15457
|
+
}
|
|
15458
|
+
function getOldMessages(db, ttl) {
|
|
15459
|
+
var olderThen = Date.now() - ttl;
|
|
15460
|
+
var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS);
|
|
15461
|
+
var objectStore = tx.objectStore(OBJECT_STORE_ID);
|
|
15462
|
+
var ret = [];
|
|
15463
|
+
return new Promise(function (res) {
|
|
15464
|
+
objectStore.openCursor().onsuccess = function (ev) {
|
|
15465
|
+
var cursor = ev.target.result;
|
|
15466
|
+
if (cursor) {
|
|
15467
|
+
var msgObk = cursor.value;
|
|
15468
|
+
if (msgObk.time < olderThen) {
|
|
15469
|
+
ret.push(msgObk);
|
|
15470
|
+
//alert("Name for SSN " + cursor.key + " is " + cursor.value.name);
|
|
15471
|
+
cursor["continue"]();
|
|
15472
|
+
} else {
|
|
15473
|
+
// no more old messages,
|
|
15474
|
+
commitIndexedDBTransaction(tx);
|
|
15475
|
+
res(ret);
|
|
15476
|
+
}
|
|
15477
|
+
} else {
|
|
15478
|
+
res(ret);
|
|
15479
|
+
}
|
|
15480
|
+
};
|
|
15481
|
+
});
|
|
15482
|
+
}
|
|
15483
|
+
function cleanOldMessages(channelState) {
|
|
15484
|
+
return getOldMessages(channelState.db, channelState.options.idb.ttl).then(function (tooOld) {
|
|
15485
|
+
return removeMessagesById(channelState, tooOld.map(function (msg) {
|
|
15486
|
+
return msg.id;
|
|
15487
|
+
}));
|
|
15488
|
+
});
|
|
15489
|
+
}
|
|
15490
|
+
function create$2(channelName, options) {
|
|
15491
|
+
options = fillOptionsWithDefaults$1(options);
|
|
15492
|
+
return createDatabase(channelName).then(function (db) {
|
|
15493
|
+
var state = {
|
|
15494
|
+
closed: false,
|
|
15495
|
+
lastCursorId: 0,
|
|
15496
|
+
channelName: channelName,
|
|
15497
|
+
options: options,
|
|
15498
|
+
uuid: randomToken(),
|
|
15499
|
+
/**
|
|
15500
|
+
* emittedMessagesIds
|
|
15501
|
+
* contains all messages that have been emitted before
|
|
15502
|
+
* @type {ObliviousSet}
|
|
15503
|
+
*/
|
|
15504
|
+
eMIs: new ObliviousSet(options.idb.ttl * 2),
|
|
15505
|
+
// ensures we do not read messages in parallel
|
|
15506
|
+
writeBlockPromise: PROMISE_RESOLVED_VOID,
|
|
15507
|
+
messagesCallback: null,
|
|
15508
|
+
readQueuePromises: [],
|
|
15509
|
+
db: db
|
|
15510
|
+
};
|
|
15511
|
+
|
|
15512
|
+
/**
|
|
15513
|
+
* Handle abrupt closes that do not originate from db.close().
|
|
15514
|
+
* This could happen, for example, if the underlying storage is
|
|
15515
|
+
* removed or if the user clears the database in the browser's
|
|
15516
|
+
* history preferences.
|
|
15517
|
+
*/
|
|
15518
|
+
db.onclose = function () {
|
|
15519
|
+
state.closed = true;
|
|
15520
|
+
if (options.idb.onclose) options.idb.onclose();
|
|
15521
|
+
};
|
|
15522
|
+
|
|
15523
|
+
/**
|
|
15524
|
+
* if service-workers are used,
|
|
15525
|
+
* we have no 'storage'-event if they post a message,
|
|
15526
|
+
* therefore we also have to set an interval
|
|
15527
|
+
*/
|
|
15528
|
+
_readLoop(state);
|
|
15529
|
+
return state;
|
|
15530
|
+
});
|
|
15531
|
+
}
|
|
15532
|
+
function _readLoop(state) {
|
|
15533
|
+
if (state.closed) return;
|
|
15534
|
+
readNewMessages(state).then(function () {
|
|
15535
|
+
return sleep(state.options.idb.fallbackInterval);
|
|
15536
|
+
}).then(function () {
|
|
15537
|
+
return _readLoop(state);
|
|
15538
|
+
});
|
|
15539
|
+
}
|
|
15540
|
+
function _filterMessage(msgObj, state) {
|
|
15541
|
+
if (msgObj.uuid === state.uuid) return false; // send by own
|
|
15542
|
+
if (state.eMIs.has(msgObj.id)) return false; // already emitted
|
|
15543
|
+
if (msgObj.data.time < state.messagesCallbackTime) return false; // older then onMessageCallback
|
|
15544
|
+
return true;
|
|
15545
|
+
}
|
|
15546
|
+
|
|
15547
|
+
/**
|
|
15548
|
+
* reads all new messages from the database and emits them
|
|
15549
|
+
*/
|
|
15550
|
+
function readNewMessages(state) {
|
|
15551
|
+
// channel already closed
|
|
15552
|
+
if (state.closed) return PROMISE_RESOLVED_VOID;
|
|
15553
|
+
|
|
15554
|
+
// if no one is listening, we do not need to scan for new messages
|
|
15555
|
+
if (!state.messagesCallback) return PROMISE_RESOLVED_VOID;
|
|
15556
|
+
return getMessagesHigherThan(state.db, state.lastCursorId).then(function (newerMessages) {
|
|
15557
|
+
var useMessages = newerMessages
|
|
15558
|
+
/**
|
|
15559
|
+
* there is a bug in iOS where the msgObj can be undefined sometimes
|
|
15560
|
+
* so we filter them out
|
|
15561
|
+
* @link https://github.com/pubkey/broadcast-channel/issues/19
|
|
15562
|
+
*/.filter(function (msgObj) {
|
|
15563
|
+
return !!msgObj;
|
|
15564
|
+
}).map(function (msgObj) {
|
|
15565
|
+
if (msgObj.id > state.lastCursorId) {
|
|
15566
|
+
state.lastCursorId = msgObj.id;
|
|
15567
|
+
}
|
|
15568
|
+
return msgObj;
|
|
15569
|
+
}).filter(function (msgObj) {
|
|
15570
|
+
return _filterMessage(msgObj, state);
|
|
15571
|
+
}).sort(function (msgObjA, msgObjB) {
|
|
15572
|
+
return msgObjA.time - msgObjB.time;
|
|
15573
|
+
}); // sort by time
|
|
15574
|
+
useMessages.forEach(function (msgObj) {
|
|
15575
|
+
if (state.messagesCallback) {
|
|
15576
|
+
state.eMIs.add(msgObj.id);
|
|
15577
|
+
state.messagesCallback(msgObj.data);
|
|
15578
|
+
}
|
|
15579
|
+
});
|
|
15580
|
+
return PROMISE_RESOLVED_VOID;
|
|
15581
|
+
});
|
|
15582
|
+
}
|
|
15583
|
+
function close$2(channelState) {
|
|
15584
|
+
channelState.closed = true;
|
|
15585
|
+
channelState.db.close();
|
|
15586
|
+
}
|
|
15587
|
+
function postMessage$2(channelState, messageJson) {
|
|
15588
|
+
channelState.writeBlockPromise = channelState.writeBlockPromise.then(function () {
|
|
15589
|
+
return writeMessage(channelState.db, channelState.uuid, messageJson);
|
|
15590
|
+
}).then(function () {
|
|
15591
|
+
if (randomInt(0, 10) === 0) {
|
|
15592
|
+
/* await (do not await) */
|
|
15593
|
+
cleanOldMessages(channelState);
|
|
15594
|
+
}
|
|
15595
|
+
});
|
|
15596
|
+
return channelState.writeBlockPromise;
|
|
15597
|
+
}
|
|
15598
|
+
function onMessage$2(channelState, fn, time) {
|
|
15599
|
+
channelState.messagesCallbackTime = time;
|
|
15600
|
+
channelState.messagesCallback = fn;
|
|
15601
|
+
readNewMessages(channelState);
|
|
15602
|
+
}
|
|
15603
|
+
function canBeUsed$2() {
|
|
15604
|
+
return !!getIdb();
|
|
15605
|
+
}
|
|
15606
|
+
function averageResponseTime$2(options) {
|
|
15607
|
+
return options.idb.fallbackInterval * 2;
|
|
15608
|
+
}
|
|
15609
|
+
var IndexedDBMethod = {
|
|
15610
|
+
create: create$2,
|
|
15611
|
+
close: close$2,
|
|
15612
|
+
onMessage: onMessage$2,
|
|
15613
|
+
postMessage: postMessage$2,
|
|
15614
|
+
canBeUsed: canBeUsed$2,
|
|
15615
|
+
type: type$2,
|
|
15616
|
+
averageResponseTime: averageResponseTime$2,
|
|
15617
|
+
microSeconds: microSeconds$2
|
|
15618
|
+
};
|
|
15619
|
+
|
|
15620
|
+
/**
|
|
15621
|
+
* A localStorage-only method which uses localstorage and its 'storage'-event
|
|
15622
|
+
* This does not work inside webworkers because they have no access to localstorage
|
|
15623
|
+
* This is basically implemented to support IE9 or your grandmother's toaster.
|
|
15624
|
+
* @link https://caniuse.com/#feat=namevalue-storage
|
|
15625
|
+
* @link https://caniuse.com/#feat=indexeddb
|
|
15626
|
+
*/
|
|
15627
|
+
var microSeconds$1 = microSeconds$4;
|
|
15628
|
+
var KEY_PREFIX = 'pubkey.broadcastChannel-';
|
|
15629
|
+
var type$1 = 'localstorage';
|
|
15630
|
+
|
|
15631
|
+
/**
|
|
15632
|
+
* copied from crosstab
|
|
15633
|
+
* @link https://github.com/tejacques/crosstab/blob/master/src/crosstab.js#L32
|
|
15634
|
+
*/
|
|
15635
|
+
function getLocalStorage() {
|
|
15636
|
+
var localStorage;
|
|
15637
|
+
if (typeof window === 'undefined') return null;
|
|
15638
|
+
try {
|
|
15639
|
+
localStorage = window.localStorage;
|
|
15640
|
+
localStorage = window['ie8-eventlistener/storage'] || window.localStorage;
|
|
15641
|
+
} catch (e) {
|
|
15642
|
+
// New versions of Firefox throw a Security exception
|
|
15643
|
+
// if cookies are disabled. See
|
|
15644
|
+
// https://bugzilla.mozilla.org/show_bug.cgi?id=1028153
|
|
15645
|
+
}
|
|
15646
|
+
return localStorage;
|
|
15647
|
+
}
|
|
15648
|
+
function storageKey(channelName) {
|
|
15649
|
+
return KEY_PREFIX + channelName;
|
|
15650
|
+
}
|
|
15651
|
+
|
|
15652
|
+
/**
|
|
15653
|
+
* writes the new message to the storage
|
|
15654
|
+
* and fires the storage-event so other readers can find it
|
|
15655
|
+
*/
|
|
15656
|
+
function postMessage$1(channelState, messageJson) {
|
|
15657
|
+
return new Promise(function (res) {
|
|
15658
|
+
sleep().then(function () {
|
|
15659
|
+
var key = storageKey(channelState.channelName);
|
|
15660
|
+
var writeObj = {
|
|
15661
|
+
token: randomToken(),
|
|
15662
|
+
time: Date.now(),
|
|
15663
|
+
data: messageJson,
|
|
15664
|
+
uuid: channelState.uuid
|
|
15665
|
+
};
|
|
15666
|
+
var value = JSON.stringify(writeObj);
|
|
15667
|
+
getLocalStorage().setItem(key, value);
|
|
15668
|
+
|
|
15669
|
+
/**
|
|
15670
|
+
* StorageEvent does not fire the 'storage' event
|
|
15671
|
+
* in the window that changes the state of the local storage.
|
|
15672
|
+
* So we fire it manually
|
|
15673
|
+
*/
|
|
15674
|
+
var ev = document.createEvent('Event');
|
|
15675
|
+
ev.initEvent('storage', true, true);
|
|
15676
|
+
ev.key = key;
|
|
15677
|
+
ev.newValue = value;
|
|
15678
|
+
window.dispatchEvent(ev);
|
|
15679
|
+
res();
|
|
15680
|
+
});
|
|
15681
|
+
});
|
|
15682
|
+
}
|
|
15683
|
+
function addStorageEventListener(channelName, fn) {
|
|
15684
|
+
var key = storageKey(channelName);
|
|
15685
|
+
var listener = function listener(ev) {
|
|
15686
|
+
if (ev.key === key) {
|
|
15687
|
+
fn(JSON.parse(ev.newValue));
|
|
15688
|
+
}
|
|
15689
|
+
};
|
|
15690
|
+
window.addEventListener('storage', listener);
|
|
15691
|
+
return listener;
|
|
15692
|
+
}
|
|
15693
|
+
function removeStorageEventListener(listener) {
|
|
15694
|
+
window.removeEventListener('storage', listener);
|
|
15695
|
+
}
|
|
15696
|
+
function create$1(channelName, options) {
|
|
15697
|
+
options = fillOptionsWithDefaults$1(options);
|
|
15698
|
+
if (!canBeUsed$1()) {
|
|
15699
|
+
throw new Error('BroadcastChannel: localstorage cannot be used');
|
|
15700
|
+
}
|
|
15701
|
+
var uuid = randomToken();
|
|
15702
|
+
|
|
15703
|
+
/**
|
|
15704
|
+
* eMIs
|
|
15705
|
+
* contains all messages that have been emitted before
|
|
15706
|
+
* @type {ObliviousSet}
|
|
15707
|
+
*/
|
|
15708
|
+
var eMIs = new ObliviousSet(options.localstorage.removeTimeout);
|
|
15709
|
+
var state = {
|
|
15710
|
+
channelName: channelName,
|
|
15711
|
+
uuid: uuid,
|
|
15712
|
+
eMIs: eMIs // emittedMessagesIds
|
|
15713
|
+
};
|
|
15714
|
+
|
|
15715
|
+
state.listener = addStorageEventListener(channelName, function (msgObj) {
|
|
15716
|
+
if (!state.messagesCallback) return; // no listener
|
|
15717
|
+
if (msgObj.uuid === uuid) return; // own message
|
|
15718
|
+
if (!msgObj.token || eMIs.has(msgObj.token)) return; // already emitted
|
|
15719
|
+
if (msgObj.data.time && msgObj.data.time < state.messagesCallbackTime) return; // too old
|
|
15720
|
+
|
|
15721
|
+
eMIs.add(msgObj.token);
|
|
15722
|
+
state.messagesCallback(msgObj.data);
|
|
15723
|
+
});
|
|
15724
|
+
return state;
|
|
15725
|
+
}
|
|
15726
|
+
function close$1(channelState) {
|
|
15727
|
+
removeStorageEventListener(channelState.listener);
|
|
15728
|
+
}
|
|
15729
|
+
function onMessage$1(channelState, fn, time) {
|
|
15730
|
+
channelState.messagesCallbackTime = time;
|
|
15731
|
+
channelState.messagesCallback = fn;
|
|
15732
|
+
}
|
|
15733
|
+
function canBeUsed$1() {
|
|
15734
|
+
var ls = getLocalStorage();
|
|
15735
|
+
if (!ls) return false;
|
|
15736
|
+
try {
|
|
15737
|
+
var key = '__broadcastchannel_check';
|
|
15738
|
+
ls.setItem(key, 'works');
|
|
15739
|
+
ls.removeItem(key);
|
|
15740
|
+
} catch (e) {
|
|
15741
|
+
// Safari 10 in private mode will not allow write access to local
|
|
15742
|
+
// storage and fail with a QuotaExceededError. See
|
|
15743
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API#Private_Browsing_Incognito_modes
|
|
15744
|
+
return false;
|
|
15745
|
+
}
|
|
15746
|
+
return true;
|
|
15747
|
+
}
|
|
15748
|
+
function averageResponseTime$1() {
|
|
15749
|
+
var defaultTime = 120;
|
|
15750
|
+
var userAgent = navigator.userAgent.toLowerCase();
|
|
15751
|
+
if (userAgent.includes('safari') && !userAgent.includes('chrome')) {
|
|
15752
|
+
// safari is much slower so this time is higher
|
|
15753
|
+
return defaultTime * 2;
|
|
15754
|
+
}
|
|
15755
|
+
return defaultTime;
|
|
15756
|
+
}
|
|
15757
|
+
var LocalstorageMethod = {
|
|
15758
|
+
create: create$1,
|
|
15759
|
+
close: close$1,
|
|
15760
|
+
onMessage: onMessage$1,
|
|
15761
|
+
postMessage: postMessage$1,
|
|
15762
|
+
canBeUsed: canBeUsed$1,
|
|
15763
|
+
type: type$1,
|
|
15764
|
+
averageResponseTime: averageResponseTime$1,
|
|
15765
|
+
microSeconds: microSeconds$1
|
|
15766
|
+
};
|
|
15767
|
+
|
|
15768
|
+
var microSeconds = microSeconds$4;
|
|
15769
|
+
var type = 'simulate';
|
|
15770
|
+
var SIMULATE_CHANNELS = new Set();
|
|
15771
|
+
function create(channelName) {
|
|
15772
|
+
var state = {
|
|
15773
|
+
name: channelName,
|
|
15774
|
+
messagesCallback: null
|
|
15775
|
+
};
|
|
15776
|
+
SIMULATE_CHANNELS.add(state);
|
|
15777
|
+
return state;
|
|
15778
|
+
}
|
|
15779
|
+
function close(channelState) {
|
|
15780
|
+
SIMULATE_CHANNELS["delete"](channelState);
|
|
15781
|
+
}
|
|
15782
|
+
function postMessage(channelState, messageJson) {
|
|
15783
|
+
return new Promise(function (res) {
|
|
15784
|
+
return setTimeout(function () {
|
|
15785
|
+
var channelArray = Array.from(SIMULATE_CHANNELS);
|
|
15786
|
+
channelArray.filter(function (channel) {
|
|
15787
|
+
return channel.name === channelState.name;
|
|
15788
|
+
}).filter(function (channel) {
|
|
15789
|
+
return channel !== channelState;
|
|
15790
|
+
}).filter(function (channel) {
|
|
15791
|
+
return !!channel.messagesCallback;
|
|
15792
|
+
}).forEach(function (channel) {
|
|
15793
|
+
return channel.messagesCallback(messageJson);
|
|
15794
|
+
});
|
|
15795
|
+
res();
|
|
15796
|
+
}, 5);
|
|
15797
|
+
});
|
|
15798
|
+
}
|
|
15799
|
+
function onMessage(channelState, fn) {
|
|
15800
|
+
channelState.messagesCallback = fn;
|
|
15801
|
+
}
|
|
15802
|
+
function canBeUsed() {
|
|
15803
|
+
return true;
|
|
15804
|
+
}
|
|
15805
|
+
function averageResponseTime() {
|
|
15806
|
+
return 5;
|
|
15807
|
+
}
|
|
15808
|
+
var SimulateMethod = {
|
|
15809
|
+
create: create,
|
|
15810
|
+
close: close,
|
|
15811
|
+
onMessage: onMessage,
|
|
15812
|
+
postMessage: postMessage,
|
|
15813
|
+
canBeUsed: canBeUsed,
|
|
15814
|
+
type: type,
|
|
15815
|
+
averageResponseTime: averageResponseTime,
|
|
15816
|
+
microSeconds: microSeconds
|
|
15817
|
+
};
|
|
15818
|
+
|
|
15819
|
+
// the line below will be removed from es5/browser builds
|
|
15820
|
+
|
|
15821
|
+
// order is important
|
|
15822
|
+
var METHODS = [NativeMethod,
|
|
15823
|
+
// fastest
|
|
15824
|
+
IndexedDBMethod, LocalstorageMethod];
|
|
15825
|
+
function chooseMethod(options) {
|
|
15826
|
+
var chooseMethods = [].concat(options.methods, METHODS).filter(Boolean);
|
|
15827
|
+
|
|
15828
|
+
// the line below will be removed from es5/browser builds
|
|
15829
|
+
|
|
15830
|
+
// directly chosen
|
|
15831
|
+
if (options.type) {
|
|
15832
|
+
if (options.type === 'simulate') {
|
|
15833
|
+
// only use simulate-method if directly chosen
|
|
15834
|
+
return SimulateMethod;
|
|
15835
|
+
}
|
|
15836
|
+
var ret = chooseMethods.find(function (m) {
|
|
15837
|
+
return m.type === options.type;
|
|
15838
|
+
});
|
|
15839
|
+
if (!ret) throw new Error('method-type ' + options.type + ' not found');else return ret;
|
|
15840
|
+
}
|
|
15841
|
+
|
|
15842
|
+
/**
|
|
15843
|
+
* if no webworker support is needed,
|
|
15844
|
+
* remove idb from the list so that localstorage will be chosen
|
|
15845
|
+
*/
|
|
15846
|
+
if (!options.webWorkerSupport) {
|
|
15847
|
+
chooseMethods = chooseMethods.filter(function (m) {
|
|
15848
|
+
return m.type !== 'idb';
|
|
15849
|
+
});
|
|
15850
|
+
}
|
|
15851
|
+
var useMethod = chooseMethods.find(function (method) {
|
|
15852
|
+
return method.canBeUsed();
|
|
15853
|
+
});
|
|
15854
|
+
if (!useMethod) {
|
|
15855
|
+
throw new Error("No usable method found in " + JSON.stringify(METHODS.map(function (m) {
|
|
15856
|
+
return m.type;
|
|
15857
|
+
})));
|
|
15858
|
+
} else {
|
|
15859
|
+
return useMethod;
|
|
15860
|
+
}
|
|
15861
|
+
}
|
|
15862
|
+
|
|
15863
|
+
/**
|
|
15864
|
+
* Contains all open channels,
|
|
15865
|
+
* used in tests to ensure everything is closed.
|
|
15866
|
+
*/
|
|
15867
|
+
var OPEN_BROADCAST_CHANNELS = new Set();
|
|
15868
|
+
var lastId = 0;
|
|
15869
|
+
var BroadcastChannel$1 = function BroadcastChannel(name, options) {
|
|
15870
|
+
// identifier of the channel to debug stuff
|
|
15871
|
+
this.id = lastId++;
|
|
15872
|
+
OPEN_BROADCAST_CHANNELS.add(this);
|
|
15873
|
+
this.name = name;
|
|
15874
|
+
if (ENFORCED_OPTIONS) {
|
|
15875
|
+
options = ENFORCED_OPTIONS;
|
|
15876
|
+
}
|
|
15877
|
+
this.options = fillOptionsWithDefaults$1(options);
|
|
15878
|
+
this.method = chooseMethod(this.options);
|
|
15879
|
+
|
|
15880
|
+
// isListening
|
|
15881
|
+
this._iL = false;
|
|
15882
|
+
|
|
15883
|
+
/**
|
|
15884
|
+
* _onMessageListener
|
|
15885
|
+
* setting onmessage twice,
|
|
15886
|
+
* will overwrite the first listener
|
|
15887
|
+
*/
|
|
15888
|
+
this._onML = null;
|
|
15889
|
+
|
|
15890
|
+
/**
|
|
15891
|
+
* _addEventListeners
|
|
15892
|
+
*/
|
|
15893
|
+
this._addEL = {
|
|
15894
|
+
message: [],
|
|
15895
|
+
internal: []
|
|
15896
|
+
};
|
|
15897
|
+
|
|
15898
|
+
/**
|
|
15899
|
+
* Unsent message promises
|
|
15900
|
+
* where the sending is still in progress
|
|
15901
|
+
* @type {Set<Promise>}
|
|
15902
|
+
*/
|
|
15903
|
+
this._uMP = new Set();
|
|
15904
|
+
|
|
15905
|
+
/**
|
|
15906
|
+
* _beforeClose
|
|
15907
|
+
* array of promises that will be awaited
|
|
15908
|
+
* before the channel is closed
|
|
15909
|
+
*/
|
|
15910
|
+
this._befC = [];
|
|
15911
|
+
|
|
15912
|
+
/**
|
|
15913
|
+
* _preparePromise
|
|
15914
|
+
*/
|
|
15915
|
+
this._prepP = null;
|
|
15916
|
+
_prepareChannel(this);
|
|
15917
|
+
};
|
|
15918
|
+
|
|
15919
|
+
// STATICS
|
|
15920
|
+
|
|
15921
|
+
/**
|
|
15922
|
+
* used to identify if someone overwrites
|
|
15923
|
+
* window.BroadcastChannel with this
|
|
15924
|
+
* See methods/native.js
|
|
15925
|
+
*/
|
|
15926
|
+
BroadcastChannel$1._pubkey = true;
|
|
15927
|
+
|
|
15928
|
+
/**
|
|
15929
|
+
* clears the tmp-folder if is node
|
|
15930
|
+
* @return {Promise<boolean>} true if has run, false if not node
|
|
15931
|
+
*/
|
|
15932
|
+
function clearNodeFolder(options) {
|
|
15933
|
+
options = fillOptionsWithDefaults$1(options);
|
|
15934
|
+
var method = chooseMethod(options);
|
|
15935
|
+
if (method.type === 'node') {
|
|
15936
|
+
return method.clearNodeFolder().then(function () {
|
|
15937
|
+
return true;
|
|
15938
|
+
});
|
|
15939
|
+
} else {
|
|
15940
|
+
return PROMISE_RESOLVED_FALSE;
|
|
15941
|
+
}
|
|
15942
|
+
}
|
|
15943
|
+
|
|
15944
|
+
/**
|
|
15945
|
+
* if set, this method is enforced,
|
|
15946
|
+
* no mather what the options are
|
|
15947
|
+
*/
|
|
15948
|
+
var ENFORCED_OPTIONS;
|
|
15949
|
+
function enforceOptions(options) {
|
|
15950
|
+
ENFORCED_OPTIONS = options;
|
|
15951
|
+
}
|
|
15952
|
+
|
|
15953
|
+
// PROTOTYPE
|
|
15954
|
+
BroadcastChannel$1.prototype = {
|
|
15955
|
+
postMessage: function postMessage(msg) {
|
|
15956
|
+
if (this.closed) {
|
|
15957
|
+
throw new Error('BroadcastChannel.postMessage(): ' + 'Cannot post message after channel has closed ' +
|
|
15958
|
+
/**
|
|
15959
|
+
* In the past when this error appeared, it was really hard to debug.
|
|
15960
|
+
* So now we log the msg together with the error so it at least
|
|
15961
|
+
* gives some clue about where in your application this happens.
|
|
15962
|
+
*/
|
|
15963
|
+
JSON.stringify(msg));
|
|
15964
|
+
}
|
|
15965
|
+
return _post(this, 'message', msg);
|
|
15966
|
+
},
|
|
15967
|
+
postInternal: function postInternal(msg) {
|
|
15968
|
+
return _post(this, 'internal', msg);
|
|
15969
|
+
},
|
|
15970
|
+
set onmessage(fn) {
|
|
15971
|
+
var time = this.method.microSeconds();
|
|
15972
|
+
var listenObj = {
|
|
15973
|
+
time: time,
|
|
15974
|
+
fn: fn
|
|
15975
|
+
};
|
|
15976
|
+
_removeListenerObject(this, 'message', this._onML);
|
|
15977
|
+
if (fn && typeof fn === 'function') {
|
|
15978
|
+
this._onML = listenObj;
|
|
15979
|
+
_addListenerObject(this, 'message', listenObj);
|
|
15980
|
+
} else {
|
|
15981
|
+
this._onML = null;
|
|
15982
|
+
}
|
|
15983
|
+
},
|
|
15984
|
+
addEventListener: function addEventListener(type, fn) {
|
|
15985
|
+
var time = this.method.microSeconds();
|
|
15986
|
+
var listenObj = {
|
|
15987
|
+
time: time,
|
|
15988
|
+
fn: fn
|
|
15989
|
+
};
|
|
15990
|
+
_addListenerObject(this, type, listenObj);
|
|
15991
|
+
},
|
|
15992
|
+
removeEventListener: function removeEventListener(type, fn) {
|
|
15993
|
+
var obj = this._addEL[type].find(function (obj) {
|
|
15994
|
+
return obj.fn === fn;
|
|
15995
|
+
});
|
|
15996
|
+
_removeListenerObject(this, type, obj);
|
|
15997
|
+
},
|
|
15998
|
+
close: function close() {
|
|
15999
|
+
var _this = this;
|
|
16000
|
+
if (this.closed) {
|
|
16001
|
+
return;
|
|
16002
|
+
}
|
|
16003
|
+
OPEN_BROADCAST_CHANNELS["delete"](this);
|
|
16004
|
+
this.closed = true;
|
|
16005
|
+
var awaitPrepare = this._prepP ? this._prepP : PROMISE_RESOLVED_VOID;
|
|
16006
|
+
this._onML = null;
|
|
16007
|
+
this._addEL.message = [];
|
|
16008
|
+
return awaitPrepare
|
|
16009
|
+
// wait until all current sending are processed
|
|
16010
|
+
.then(function () {
|
|
16011
|
+
return Promise.all(Array.from(_this._uMP));
|
|
16012
|
+
})
|
|
16013
|
+
// run before-close hooks
|
|
16014
|
+
.then(function () {
|
|
16015
|
+
return Promise.all(_this._befC.map(function (fn) {
|
|
16016
|
+
return fn();
|
|
16017
|
+
}));
|
|
16018
|
+
})
|
|
16019
|
+
// close the channel
|
|
16020
|
+
.then(function () {
|
|
16021
|
+
return _this.method.close(_this._state);
|
|
16022
|
+
});
|
|
16023
|
+
},
|
|
16024
|
+
get type() {
|
|
16025
|
+
return this.method.type;
|
|
16026
|
+
},
|
|
16027
|
+
get isClosed() {
|
|
16028
|
+
return this.closed;
|
|
16029
|
+
}
|
|
16030
|
+
};
|
|
16031
|
+
|
|
16032
|
+
/**
|
|
16033
|
+
* Post a message over the channel
|
|
16034
|
+
* @returns {Promise} that resolved when the message sending is done
|
|
16035
|
+
*/
|
|
16036
|
+
function _post(broadcastChannel, type, msg) {
|
|
16037
|
+
var time = broadcastChannel.method.microSeconds();
|
|
16038
|
+
var msgObj = {
|
|
16039
|
+
time: time,
|
|
16040
|
+
type: type,
|
|
16041
|
+
data: msg
|
|
16042
|
+
};
|
|
16043
|
+
var awaitPrepare = broadcastChannel._prepP ? broadcastChannel._prepP : PROMISE_RESOLVED_VOID;
|
|
16044
|
+
return awaitPrepare.then(function () {
|
|
16045
|
+
var sendPromise = broadcastChannel.method.postMessage(broadcastChannel._state, msgObj);
|
|
16046
|
+
|
|
16047
|
+
// add/remove to unsent messages list
|
|
16048
|
+
broadcastChannel._uMP.add(sendPromise);
|
|
16049
|
+
sendPromise["catch"]().then(function () {
|
|
16050
|
+
return broadcastChannel._uMP["delete"](sendPromise);
|
|
16051
|
+
});
|
|
16052
|
+
return sendPromise;
|
|
16053
|
+
});
|
|
16054
|
+
}
|
|
16055
|
+
function _prepareChannel(channel) {
|
|
16056
|
+
var maybePromise = channel.method.create(channel.name, channel.options);
|
|
16057
|
+
if (isPromise(maybePromise)) {
|
|
16058
|
+
channel._prepP = maybePromise;
|
|
16059
|
+
maybePromise.then(function (s) {
|
|
16060
|
+
// used in tests to simulate slow runtime
|
|
16061
|
+
/*if (channel.options.prepareDelay) {
|
|
16062
|
+
await new Promise(res => setTimeout(res, this.options.prepareDelay));
|
|
16063
|
+
}*/
|
|
16064
|
+
channel._state = s;
|
|
16065
|
+
});
|
|
16066
|
+
} else {
|
|
16067
|
+
channel._state = maybePromise;
|
|
16068
|
+
}
|
|
16069
|
+
}
|
|
16070
|
+
function _hasMessageListeners(channel) {
|
|
16071
|
+
if (channel._addEL.message.length > 0) return true;
|
|
16072
|
+
if (channel._addEL.internal.length > 0) return true;
|
|
16073
|
+
return false;
|
|
16074
|
+
}
|
|
16075
|
+
function _addListenerObject(channel, type, obj) {
|
|
16076
|
+
channel._addEL[type].push(obj);
|
|
16077
|
+
_startListening(channel);
|
|
16078
|
+
}
|
|
16079
|
+
function _removeListenerObject(channel, type, obj) {
|
|
16080
|
+
channel._addEL[type] = channel._addEL[type].filter(function (o) {
|
|
16081
|
+
return o !== obj;
|
|
16082
|
+
});
|
|
16083
|
+
_stopListening(channel);
|
|
16084
|
+
}
|
|
16085
|
+
function _startListening(channel) {
|
|
16086
|
+
if (!channel._iL && _hasMessageListeners(channel)) {
|
|
16087
|
+
// someone is listening, start subscribing
|
|
16088
|
+
|
|
16089
|
+
var listenerFn = function listenerFn(msgObj) {
|
|
16090
|
+
channel._addEL[msgObj.type].forEach(function (listenerObject) {
|
|
16091
|
+
/**
|
|
16092
|
+
* Getting the current time in JavaScript has no good precision.
|
|
16093
|
+
* So instead of only listening to events that happened 'after' the listener
|
|
16094
|
+
* was added, we also listen to events that happened 100ms before it.
|
|
16095
|
+
* This ensures that when another process, like a WebWorker, sends events
|
|
16096
|
+
* we do not miss them out because their timestamp is a bit off compared to the main process.
|
|
16097
|
+
* Not doing this would make messages missing when we send data directly after subscribing and awaiting a response.
|
|
16098
|
+
* @link https://johnresig.com/blog/accuracy-of-javascript-time/
|
|
16099
|
+
*/
|
|
16100
|
+
var hundredMsInMicro = 100 * 1000;
|
|
16101
|
+
var minMessageTime = listenerObject.time - hundredMsInMicro;
|
|
16102
|
+
if (msgObj.time >= minMessageTime) {
|
|
16103
|
+
listenerObject.fn(msgObj.data);
|
|
16104
|
+
}
|
|
16105
|
+
});
|
|
16106
|
+
};
|
|
16107
|
+
var time = channel.method.microSeconds();
|
|
16108
|
+
if (channel._prepP) {
|
|
16109
|
+
channel._prepP.then(function () {
|
|
16110
|
+
channel._iL = true;
|
|
16111
|
+
channel.method.onMessage(channel._state, listenerFn, time);
|
|
16112
|
+
});
|
|
16113
|
+
} else {
|
|
16114
|
+
channel._iL = true;
|
|
16115
|
+
channel.method.onMessage(channel._state, listenerFn, time);
|
|
16116
|
+
}
|
|
16117
|
+
}
|
|
16118
|
+
}
|
|
16119
|
+
function _stopListening(channel) {
|
|
16120
|
+
if (channel._iL && !_hasMessageListeners(channel)) {
|
|
16121
|
+
// no one is listening, stop subscribing
|
|
16122
|
+
channel._iL = false;
|
|
16123
|
+
var time = channel.method.microSeconds();
|
|
16124
|
+
channel.method.onMessage(channel._state, null, time);
|
|
16125
|
+
}
|
|
16126
|
+
}
|
|
16127
|
+
|
|
16128
|
+
/* global WorkerGlobalScope */
|
|
16129
|
+
|
|
16130
|
+
function addBrowser(fn) {
|
|
16131
|
+
if (typeof WorkerGlobalScope === 'function' && self instanceof WorkerGlobalScope) {
|
|
16132
|
+
/**
|
|
16133
|
+
* Because killing a worker does directly stop the excution
|
|
16134
|
+
* of the code, our only chance is to overwrite the close function
|
|
16135
|
+
* which could work some times.
|
|
16136
|
+
* @link https://stackoverflow.com/q/72903255/3443137
|
|
16137
|
+
*/
|
|
16138
|
+
var oldClose = self.close.bind(self);
|
|
16139
|
+
self.close = function () {
|
|
16140
|
+
fn();
|
|
16141
|
+
return oldClose();
|
|
16142
|
+
};
|
|
16143
|
+
} else {
|
|
16144
|
+
/**
|
|
16145
|
+
* if we are on react-native, there is no window.addEventListener
|
|
16146
|
+
* @link https://github.com/pubkey/unload/issues/6
|
|
16147
|
+
*/
|
|
16148
|
+
if (typeof window.addEventListener !== 'function') {
|
|
16149
|
+
return;
|
|
16150
|
+
}
|
|
16151
|
+
|
|
16152
|
+
/**
|
|
16153
|
+
* for normal browser-windows, we use the beforeunload-event
|
|
16154
|
+
*/
|
|
16155
|
+
window.addEventListener('beforeunload', function () {
|
|
16156
|
+
fn();
|
|
16157
|
+
}, true);
|
|
16158
|
+
|
|
16159
|
+
/**
|
|
16160
|
+
* for iframes, we have to use the unload-event
|
|
16161
|
+
* @link https://stackoverflow.com/q/47533670/3443137
|
|
16162
|
+
*/
|
|
16163
|
+
window.addEventListener('unload', function () {
|
|
16164
|
+
fn();
|
|
16165
|
+
}, true);
|
|
16166
|
+
}
|
|
16167
|
+
|
|
16168
|
+
/**
|
|
16169
|
+
* TODO add fallback for safari-mobile
|
|
16170
|
+
* @link https://stackoverflow.com/a/26193516/3443137
|
|
16171
|
+
*/
|
|
16172
|
+
}
|
|
16173
|
+
|
|
16174
|
+
function addNode(fn) {
|
|
16175
|
+
process.on('exit', function () {
|
|
16176
|
+
return fn();
|
|
16177
|
+
});
|
|
16178
|
+
|
|
16179
|
+
/**
|
|
16180
|
+
* on the following events,
|
|
16181
|
+
* the process will not end if there are
|
|
16182
|
+
* event-handlers attached,
|
|
16183
|
+
* therefore we have to call process.exit()
|
|
16184
|
+
*/
|
|
16185
|
+
process.on('beforeExit', function () {
|
|
16186
|
+
return fn().then(function () {
|
|
16187
|
+
return process.exit();
|
|
16188
|
+
});
|
|
16189
|
+
});
|
|
16190
|
+
// catches ctrl+c event
|
|
16191
|
+
process.on('SIGINT', function () {
|
|
16192
|
+
return fn().then(function () {
|
|
16193
|
+
return process.exit();
|
|
16194
|
+
});
|
|
16195
|
+
});
|
|
16196
|
+
// catches uncaught exceptions
|
|
16197
|
+
process.on('uncaughtException', function (err) {
|
|
16198
|
+
return fn().then(function () {
|
|
16199
|
+
console.trace(err);
|
|
16200
|
+
process.exit(101);
|
|
16201
|
+
});
|
|
16202
|
+
});
|
|
16203
|
+
}
|
|
16204
|
+
|
|
16205
|
+
/**
|
|
16206
|
+
* Use the code directly to prevent import problems
|
|
16207
|
+
* with the detect-node package.
|
|
16208
|
+
* @link https://github.com/iliakan/detect-node/blob/master/index.js
|
|
16209
|
+
*/
|
|
16210
|
+
var isNode = Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]';
|
|
16211
|
+
var USE_METHOD = isNode ? addNode : addBrowser;
|
|
16212
|
+
var LISTENERS = new Set();
|
|
16213
|
+
var startedListening = false;
|
|
16214
|
+
function startListening() {
|
|
16215
|
+
if (startedListening) {
|
|
16216
|
+
return;
|
|
16217
|
+
}
|
|
16218
|
+
startedListening = true;
|
|
16219
|
+
USE_METHOD(runAll);
|
|
16220
|
+
}
|
|
16221
|
+
function add(fn) {
|
|
16222
|
+
startListening();
|
|
16223
|
+
if (typeof fn !== 'function') {
|
|
16224
|
+
throw new Error('Listener is no function');
|
|
16225
|
+
}
|
|
16226
|
+
LISTENERS.add(fn);
|
|
16227
|
+
var addReturn = {
|
|
16228
|
+
remove: function remove() {
|
|
16229
|
+
return LISTENERS["delete"](fn);
|
|
16230
|
+
},
|
|
16231
|
+
run: function run() {
|
|
16232
|
+
LISTENERS["delete"](fn);
|
|
16233
|
+
return fn();
|
|
16234
|
+
}
|
|
16235
|
+
};
|
|
16236
|
+
return addReturn;
|
|
16237
|
+
}
|
|
16238
|
+
function runAll() {
|
|
16239
|
+
var promises = [];
|
|
16240
|
+
LISTENERS.forEach(function (fn) {
|
|
16241
|
+
promises.push(fn());
|
|
16242
|
+
LISTENERS["delete"](fn);
|
|
16243
|
+
});
|
|
16244
|
+
return Promise.all(promises);
|
|
16245
|
+
}
|
|
16246
|
+
|
|
16247
|
+
/**
|
|
16248
|
+
* sends and internal message over the broadcast-channel
|
|
16249
|
+
*/
|
|
16250
|
+
function sendLeaderMessage(leaderElector, action) {
|
|
16251
|
+
var msgJson = {
|
|
16252
|
+
context: 'leader',
|
|
16253
|
+
action: action,
|
|
16254
|
+
token: leaderElector.token
|
|
16255
|
+
};
|
|
16256
|
+
return leaderElector.broadcastChannel.postInternal(msgJson);
|
|
16257
|
+
}
|
|
16258
|
+
function beLeader(leaderElector) {
|
|
16259
|
+
leaderElector.isLeader = true;
|
|
16260
|
+
leaderElector._hasLeader = true;
|
|
16261
|
+
var unloadFn = add(function () {
|
|
16262
|
+
return leaderElector.die();
|
|
16263
|
+
});
|
|
16264
|
+
leaderElector._unl.push(unloadFn);
|
|
16265
|
+
var isLeaderListener = function isLeaderListener(msg) {
|
|
16266
|
+
if (msg.context === 'leader' && msg.action === 'apply') {
|
|
16267
|
+
sendLeaderMessage(leaderElector, 'tell');
|
|
16268
|
+
}
|
|
16269
|
+
if (msg.context === 'leader' && msg.action === 'tell' && !leaderElector._dpLC) {
|
|
16270
|
+
/**
|
|
16271
|
+
* another instance is also leader!
|
|
16272
|
+
* This can happen on rare events
|
|
16273
|
+
* like when the CPU is at 100% for long time
|
|
16274
|
+
* or the tabs are open very long and the browser throttles them.
|
|
16275
|
+
* @link https://github.com/pubkey/broadcast-channel/issues/414
|
|
16276
|
+
* @link https://github.com/pubkey/broadcast-channel/issues/385
|
|
16277
|
+
*/
|
|
16278
|
+
leaderElector._dpLC = true;
|
|
16279
|
+
leaderElector._dpL(); // message the lib user so the app can handle the problem
|
|
16280
|
+
sendLeaderMessage(leaderElector, 'tell'); // ensure other leader also knows the problem
|
|
16281
|
+
}
|
|
16282
|
+
};
|
|
16283
|
+
|
|
16284
|
+
leaderElector.broadcastChannel.addEventListener('internal', isLeaderListener);
|
|
16285
|
+
leaderElector._lstns.push(isLeaderListener);
|
|
16286
|
+
return sendLeaderMessage(leaderElector, 'tell');
|
|
16287
|
+
}
|
|
16288
|
+
|
|
16289
|
+
/**
|
|
16290
|
+
* A faster version of the leader elector that uses the WebLock API
|
|
16291
|
+
* @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API
|
|
16292
|
+
*/
|
|
16293
|
+
var LeaderElectionWebLock = function LeaderElectionWebLock(broadcastChannel, options) {
|
|
16294
|
+
var _this = this;
|
|
16295
|
+
this.broadcastChannel = broadcastChannel;
|
|
16296
|
+
broadcastChannel._befC.push(function () {
|
|
16297
|
+
return _this.die();
|
|
16298
|
+
});
|
|
16299
|
+
this._options = options;
|
|
16300
|
+
this.isLeader = false;
|
|
16301
|
+
this.isDead = false;
|
|
16302
|
+
this.token = randomToken();
|
|
16303
|
+
this._lstns = [];
|
|
16304
|
+
this._unl = [];
|
|
16305
|
+
this._dpL = function () {}; // onduplicate listener
|
|
16306
|
+
this._dpLC = false; // true when onduplicate called
|
|
16307
|
+
|
|
16308
|
+
this._wKMC = {}; // stuff for cleanup
|
|
16309
|
+
|
|
16310
|
+
// lock name
|
|
16311
|
+
this.lN = 'pubkey-bc||' + broadcastChannel.method.type + '||' + broadcastChannel.name;
|
|
16312
|
+
};
|
|
16313
|
+
LeaderElectionWebLock.prototype = {
|
|
16314
|
+
hasLeader: function hasLeader() {
|
|
16315
|
+
var _this2 = this;
|
|
16316
|
+
return navigator.locks.query().then(function (locks) {
|
|
16317
|
+
var relevantLocks = locks.held ? locks.held.filter(function (lock) {
|
|
16318
|
+
return lock.name === _this2.lN;
|
|
16319
|
+
}) : [];
|
|
16320
|
+
if (relevantLocks && relevantLocks.length > 0) {
|
|
16321
|
+
return true;
|
|
16322
|
+
} else {
|
|
16323
|
+
return false;
|
|
16324
|
+
}
|
|
16325
|
+
});
|
|
16326
|
+
},
|
|
16327
|
+
awaitLeadership: function awaitLeadership() {
|
|
16328
|
+
var _this3 = this;
|
|
16329
|
+
if (!this._wLMP) {
|
|
16330
|
+
this._wKMC.c = new AbortController();
|
|
16331
|
+
var returnPromise = new Promise(function (res, rej) {
|
|
16332
|
+
_this3._wKMC.res = res;
|
|
16333
|
+
_this3._wKMC.rej = rej;
|
|
16334
|
+
});
|
|
16335
|
+
this._wLMP = new Promise(function (res) {
|
|
16336
|
+
navigator.locks.request(_this3.lN, {
|
|
16337
|
+
signal: _this3._wKMC.c.signal
|
|
16338
|
+
}, function () {
|
|
16339
|
+
// if the lock resolved, we can drop the abort controller
|
|
16340
|
+
_this3._wKMC.c = undefined;
|
|
16341
|
+
beLeader(_this3);
|
|
16342
|
+
res();
|
|
16343
|
+
return returnPromise;
|
|
16344
|
+
})["catch"](function () {});
|
|
16345
|
+
});
|
|
16346
|
+
}
|
|
16347
|
+
return this._wLMP;
|
|
16348
|
+
},
|
|
16349
|
+
set onduplicate(_fn) {
|
|
16350
|
+
// Do nothing because there are no duplicates in the WebLock version
|
|
16351
|
+
},
|
|
16352
|
+
die: function die() {
|
|
16353
|
+
var _this4 = this;
|
|
16354
|
+
this._lstns.forEach(function (listener) {
|
|
16355
|
+
return _this4.broadcastChannel.removeEventListener('internal', listener);
|
|
16356
|
+
});
|
|
16357
|
+
this._lstns = [];
|
|
16358
|
+
this._unl.forEach(function (uFn) {
|
|
16359
|
+
return uFn.remove();
|
|
16360
|
+
});
|
|
16361
|
+
this._unl = [];
|
|
16362
|
+
if (this.isLeader) {
|
|
16363
|
+
this.isLeader = false;
|
|
16364
|
+
}
|
|
16365
|
+
this.isDead = true;
|
|
16366
|
+
if (this._wKMC.res) {
|
|
16367
|
+
this._wKMC.res();
|
|
16368
|
+
}
|
|
16369
|
+
if (this._wKMC.c) {
|
|
16370
|
+
this._wKMC.c.abort('LeaderElectionWebLock.die() called');
|
|
16371
|
+
}
|
|
16372
|
+
return sendLeaderMessage(this, 'death');
|
|
16373
|
+
}
|
|
16374
|
+
};
|
|
16375
|
+
|
|
16376
|
+
var LeaderElection = function LeaderElection(broadcastChannel, options) {
|
|
16377
|
+
var _this = this;
|
|
16378
|
+
this.broadcastChannel = broadcastChannel;
|
|
16379
|
+
this._options = options;
|
|
16380
|
+
this.isLeader = false;
|
|
16381
|
+
this._hasLeader = false;
|
|
16382
|
+
this.isDead = false;
|
|
16383
|
+
this.token = randomToken();
|
|
16384
|
+
|
|
16385
|
+
/**
|
|
16386
|
+
* Apply Queue,
|
|
16387
|
+
* used to ensure we do not run applyOnce()
|
|
16388
|
+
* in parallel.
|
|
16389
|
+
*/
|
|
16390
|
+
this._aplQ = PROMISE_RESOLVED_VOID;
|
|
16391
|
+
// amount of unfinished applyOnce() calls
|
|
16392
|
+
this._aplQC = 0;
|
|
16393
|
+
|
|
16394
|
+
// things to clean up
|
|
16395
|
+
this._unl = []; // _unloads
|
|
16396
|
+
this._lstns = []; // _listeners
|
|
16397
|
+
this._dpL = function () {}; // onduplicate listener
|
|
16398
|
+
this._dpLC = false; // true when onduplicate called
|
|
16399
|
+
|
|
16400
|
+
/**
|
|
16401
|
+
* Even when the own instance is not applying,
|
|
16402
|
+
* we still listen to messages to ensure the hasLeader flag
|
|
16403
|
+
* is set correctly.
|
|
16404
|
+
*/
|
|
16405
|
+
var hasLeaderListener = function hasLeaderListener(msg) {
|
|
16406
|
+
if (msg.context === 'leader') {
|
|
16407
|
+
if (msg.action === 'death') {
|
|
16408
|
+
_this._hasLeader = false;
|
|
16409
|
+
}
|
|
16410
|
+
if (msg.action === 'tell') {
|
|
16411
|
+
_this._hasLeader = true;
|
|
16412
|
+
}
|
|
16413
|
+
}
|
|
16414
|
+
};
|
|
16415
|
+
this.broadcastChannel.addEventListener('internal', hasLeaderListener);
|
|
16416
|
+
this._lstns.push(hasLeaderListener);
|
|
16417
|
+
};
|
|
16418
|
+
LeaderElection.prototype = {
|
|
16419
|
+
hasLeader: function hasLeader() {
|
|
16420
|
+
return Promise.resolve(this._hasLeader);
|
|
16421
|
+
},
|
|
16422
|
+
/**
|
|
16423
|
+
* Returns true if the instance is leader,
|
|
16424
|
+
* false if not.
|
|
16425
|
+
* @async
|
|
16426
|
+
*/
|
|
16427
|
+
applyOnce: function applyOnce(
|
|
16428
|
+
// true if the applyOnce() call came from the fallbackInterval cycle
|
|
16429
|
+
isFromFallbackInterval) {
|
|
16430
|
+
var _this2 = this;
|
|
16431
|
+
if (this.isLeader) {
|
|
16432
|
+
return sleep(0, true);
|
|
16433
|
+
}
|
|
16434
|
+
if (this.isDead) {
|
|
16435
|
+
return sleep(0, false);
|
|
16436
|
+
}
|
|
16437
|
+
|
|
16438
|
+
/**
|
|
16439
|
+
* Already applying more than once,
|
|
16440
|
+
* -> wait for the apply queue to be finished.
|
|
16441
|
+
*/
|
|
16442
|
+
if (this._aplQC > 1) {
|
|
16443
|
+
return this._aplQ;
|
|
16444
|
+
}
|
|
16445
|
+
|
|
16446
|
+
/**
|
|
16447
|
+
* Add a new apply-run
|
|
16448
|
+
*/
|
|
16449
|
+
var applyRun = function applyRun() {
|
|
16450
|
+
/**
|
|
16451
|
+
* Optimization shortcuts.
|
|
16452
|
+
* Directly return if a previous run
|
|
16453
|
+
* has already elected a leader.
|
|
16454
|
+
*/
|
|
16455
|
+
if (_this2.isLeader) {
|
|
16456
|
+
return PROMISE_RESOLVED_TRUE;
|
|
16457
|
+
}
|
|
16458
|
+
var stopCriteria = false;
|
|
16459
|
+
var stopCriteriaPromiseResolve;
|
|
16460
|
+
/**
|
|
16461
|
+
* Resolves when a stop criteria is reached.
|
|
16462
|
+
* Uses as a performance shortcut so we do not
|
|
16463
|
+
* have to await the responseTime when it is already clear
|
|
16464
|
+
* that the election failed.
|
|
16465
|
+
*/
|
|
16466
|
+
var stopCriteriaPromise = new Promise(function (res) {
|
|
16467
|
+
stopCriteriaPromiseResolve = function stopCriteriaPromiseResolve() {
|
|
16468
|
+
stopCriteria = true;
|
|
16469
|
+
res();
|
|
16470
|
+
};
|
|
16471
|
+
});
|
|
16472
|
+
var handleMessage = function handleMessage(msg) {
|
|
16473
|
+
if (msg.context === 'leader' && msg.token != _this2.token) {
|
|
16474
|
+
if (msg.action === 'apply') {
|
|
16475
|
+
// other is applying
|
|
16476
|
+
if (msg.token > _this2.token) {
|
|
16477
|
+
/**
|
|
16478
|
+
* other has higher token
|
|
16479
|
+
* -> stop applying and let other become leader.
|
|
16480
|
+
*/
|
|
16481
|
+
stopCriteriaPromiseResolve();
|
|
16482
|
+
}
|
|
16483
|
+
}
|
|
16484
|
+
if (msg.action === 'tell') {
|
|
16485
|
+
// other is already leader
|
|
16486
|
+
stopCriteriaPromiseResolve();
|
|
16487
|
+
_this2._hasLeader = true;
|
|
16488
|
+
}
|
|
16489
|
+
}
|
|
16490
|
+
};
|
|
16491
|
+
_this2.broadcastChannel.addEventListener('internal', handleMessage);
|
|
16492
|
+
|
|
16493
|
+
/**
|
|
16494
|
+
* If the applyOnce() call came from the fallbackInterval,
|
|
16495
|
+
* we can assume that the election runs in the background and
|
|
16496
|
+
* not critical process is waiting for it.
|
|
16497
|
+
* When this is true, we give the other instances
|
|
16498
|
+
* more time to answer to messages in the election cycle.
|
|
16499
|
+
* This makes it less likely to elect duplicate leaders.
|
|
16500
|
+
* But also it takes longer which is not a problem because we anyway
|
|
16501
|
+
* run in the background.
|
|
16502
|
+
*/
|
|
16503
|
+
var waitForAnswerTime = isFromFallbackInterval ? _this2._options.responseTime * 4 : _this2._options.responseTime;
|
|
16504
|
+
return sendLeaderMessage(_this2, 'apply') // send out that this one is applying
|
|
16505
|
+
.then(function () {
|
|
16506
|
+
return Promise.race([sleep(waitForAnswerTime), stopCriteriaPromise.then(function () {
|
|
16507
|
+
return Promise.reject(new Error());
|
|
16508
|
+
})]);
|
|
16509
|
+
})
|
|
16510
|
+
// send again in case another instance was just created
|
|
16511
|
+
.then(function () {
|
|
16512
|
+
return sendLeaderMessage(_this2, 'apply');
|
|
16513
|
+
})
|
|
16514
|
+
// let others time to respond
|
|
16515
|
+
.then(function () {
|
|
16516
|
+
return Promise.race([sleep(waitForAnswerTime), stopCriteriaPromise.then(function () {
|
|
16517
|
+
return Promise.reject(new Error());
|
|
16518
|
+
})]);
|
|
16519
|
+
})["catch"](function () {}).then(function () {
|
|
16520
|
+
_this2.broadcastChannel.removeEventListener('internal', handleMessage);
|
|
16521
|
+
if (!stopCriteria) {
|
|
16522
|
+
// no stop criteria -> own is leader
|
|
16523
|
+
return beLeader(_this2).then(function () {
|
|
16524
|
+
return true;
|
|
16525
|
+
});
|
|
16526
|
+
} else {
|
|
16527
|
+
// other is leader
|
|
16528
|
+
return false;
|
|
16529
|
+
}
|
|
16530
|
+
});
|
|
16531
|
+
};
|
|
16532
|
+
this._aplQC = this._aplQC + 1;
|
|
16533
|
+
this._aplQ = this._aplQ.then(function () {
|
|
16534
|
+
return applyRun();
|
|
16535
|
+
}).then(function () {
|
|
16536
|
+
_this2._aplQC = _this2._aplQC - 1;
|
|
16537
|
+
});
|
|
16538
|
+
return this._aplQ.then(function () {
|
|
16539
|
+
return _this2.isLeader;
|
|
16540
|
+
});
|
|
16541
|
+
},
|
|
16542
|
+
awaitLeadership: function awaitLeadership() {
|
|
16543
|
+
if ( /* _awaitLeadershipPromise */
|
|
16544
|
+
!this._aLP) {
|
|
16545
|
+
this._aLP = _awaitLeadershipOnce(this);
|
|
16546
|
+
}
|
|
16547
|
+
return this._aLP;
|
|
16548
|
+
},
|
|
16549
|
+
set onduplicate(fn) {
|
|
16550
|
+
this._dpL = fn;
|
|
16551
|
+
},
|
|
16552
|
+
die: function die() {
|
|
16553
|
+
var _this3 = this;
|
|
16554
|
+
this._lstns.forEach(function (listener) {
|
|
16555
|
+
return _this3.broadcastChannel.removeEventListener('internal', listener);
|
|
16556
|
+
});
|
|
16557
|
+
this._lstns = [];
|
|
16558
|
+
this._unl.forEach(function (uFn) {
|
|
16559
|
+
return uFn.remove();
|
|
16560
|
+
});
|
|
16561
|
+
this._unl = [];
|
|
16562
|
+
if (this.isLeader) {
|
|
16563
|
+
this._hasLeader = false;
|
|
16564
|
+
this.isLeader = false;
|
|
16565
|
+
}
|
|
16566
|
+
this.isDead = true;
|
|
16567
|
+
return sendLeaderMessage(this, 'death');
|
|
16568
|
+
}
|
|
16569
|
+
};
|
|
16570
|
+
|
|
16571
|
+
/**
|
|
16572
|
+
* @param leaderElector {LeaderElector}
|
|
16573
|
+
*/
|
|
16574
|
+
function _awaitLeadershipOnce(leaderElector) {
|
|
16575
|
+
if (leaderElector.isLeader) {
|
|
16576
|
+
return PROMISE_RESOLVED_VOID;
|
|
16577
|
+
}
|
|
16578
|
+
return new Promise(function (res) {
|
|
16579
|
+
var resolved = false;
|
|
16580
|
+
function finish() {
|
|
16581
|
+
if (resolved) {
|
|
16582
|
+
return;
|
|
16583
|
+
}
|
|
16584
|
+
resolved = true;
|
|
16585
|
+
leaderElector.broadcastChannel.removeEventListener('internal', whenDeathListener);
|
|
16586
|
+
res(true);
|
|
16587
|
+
}
|
|
16588
|
+
|
|
16589
|
+
// try once now
|
|
16590
|
+
leaderElector.applyOnce().then(function () {
|
|
16591
|
+
if (leaderElector.isLeader) {
|
|
16592
|
+
finish();
|
|
16593
|
+
}
|
|
16594
|
+
});
|
|
16595
|
+
|
|
16596
|
+
/**
|
|
16597
|
+
* Try on fallbackInterval
|
|
16598
|
+
* @recursive
|
|
16599
|
+
*/
|
|
16600
|
+
var tryOnFallBack = function tryOnFallBack() {
|
|
16601
|
+
return sleep(leaderElector._options.fallbackInterval).then(function () {
|
|
16602
|
+
if (leaderElector.isDead || resolved) {
|
|
16603
|
+
return;
|
|
16604
|
+
}
|
|
16605
|
+
if (leaderElector.isLeader) {
|
|
16606
|
+
finish();
|
|
16607
|
+
} else {
|
|
16608
|
+
return leaderElector.applyOnce(true).then(function () {
|
|
16609
|
+
if (leaderElector.isLeader) {
|
|
16610
|
+
finish();
|
|
16611
|
+
} else {
|
|
16612
|
+
tryOnFallBack();
|
|
16613
|
+
}
|
|
16614
|
+
});
|
|
16615
|
+
}
|
|
16616
|
+
});
|
|
16617
|
+
};
|
|
16618
|
+
tryOnFallBack();
|
|
16619
|
+
|
|
16620
|
+
// try when other leader dies
|
|
16621
|
+
var whenDeathListener = function whenDeathListener(msg) {
|
|
16622
|
+
if (msg.context === 'leader' && msg.action === 'death') {
|
|
16623
|
+
leaderElector._hasLeader = false;
|
|
16624
|
+
leaderElector.applyOnce().then(function () {
|
|
16625
|
+
if (leaderElector.isLeader) {
|
|
16626
|
+
finish();
|
|
16627
|
+
}
|
|
16628
|
+
});
|
|
16629
|
+
}
|
|
16630
|
+
};
|
|
16631
|
+
leaderElector.broadcastChannel.addEventListener('internal', whenDeathListener);
|
|
16632
|
+
leaderElector._lstns.push(whenDeathListener);
|
|
16633
|
+
});
|
|
16634
|
+
}
|
|
16635
|
+
function fillOptionsWithDefaults(options, channel) {
|
|
16636
|
+
if (!options) options = {};
|
|
16637
|
+
options = JSON.parse(JSON.stringify(options));
|
|
16638
|
+
if (!options.fallbackInterval) {
|
|
16639
|
+
options.fallbackInterval = 3000;
|
|
16640
|
+
}
|
|
16641
|
+
if (!options.responseTime) {
|
|
16642
|
+
options.responseTime = channel.method.averageResponseTime(channel.options);
|
|
16643
|
+
}
|
|
16644
|
+
return options;
|
|
16645
|
+
}
|
|
16646
|
+
function createLeaderElection(channel, options) {
|
|
16647
|
+
if (channel._leaderElector) {
|
|
16648
|
+
throw new Error('BroadcastChannel already has a leader-elector');
|
|
16649
|
+
}
|
|
16650
|
+
options = fillOptionsWithDefaults(options, channel);
|
|
16651
|
+
var elector = supportsWebLockAPI() ? new LeaderElectionWebLock(channel, options) : new LeaderElection(channel, options);
|
|
16652
|
+
channel._befC.push(function () {
|
|
16653
|
+
return elector.die();
|
|
16654
|
+
});
|
|
16655
|
+
channel._leaderElector = elector;
|
|
16656
|
+
return elector;
|
|
16657
|
+
}
|
|
16658
|
+
|
|
16659
|
+
var index = /*#__PURE__*/Object.freeze({
|
|
16660
|
+
__proto__: null,
|
|
16661
|
+
BroadcastChannel: BroadcastChannel$1,
|
|
16662
|
+
clearNodeFolder: clearNodeFolder,
|
|
16663
|
+
enforceOptions: enforceOptions,
|
|
16664
|
+
OPEN_BROADCAST_CHANNELS: OPEN_BROADCAST_CHANNELS,
|
|
16665
|
+
createLeaderElection: createLeaderElection,
|
|
16666
|
+
beLeader: beLeader
|
|
16667
|
+
});
|
|
16668
|
+
|
|
15011
16669
|
exports.BrickAsComponent = BrickAsComponent;
|
|
15012
16670
|
exports.BrickAsComponentFactory = BrickAsComponentFactory;
|
|
15013
16671
|
exports.BrickWrapper = BrickWrapper;
|