diff --git a/.github/README.md b/.github/README.md index 94fbe3fa..e4bd3396 100644 --- a/.github/README.md +++ b/.github/README.md @@ -29,6 +29,22 @@ A BroadcastChannel that allows you to send data between different browser-tabs o This behaves similar to the [BroadcastChannel-API](https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API) which is currently only featured in [some browsers](https://caniuse.com/#feat=broadcastchannel). + +# Sponsored by + +

+ + Sponsored by RxDB +
+
+ The JavaScript Database +
+

+ ## Using the BroadcastChannel ```bash diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dc1f936..7a455a0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ ## X.X.X (comming soon) +## 4.18.1 (31 October 2022) + +- Updated dependencies + ## 4.18.0 (6 October 2022) diff --git a/README.md b/README.md index fdd2b8e1..a2f3d04a 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,8 @@

follow on Twitter + alt="follow on Twitter" /> +

![demo.gif](docs/files/demo.gif) @@ -34,3 +35,19 @@ A BroadcastChannel that allows you to send data between different browser-tabs o And a LeaderElection over the channels. # [Read the full documentation on github](https://github.com/pubkey/broadcast-channel) + + +# Sponsored by + +

+ + Sponsored by RxDB +
+
+ The JavaScript Database +
+

diff --git a/dist/es5node/broadcast-channel.js b/dist/es5node/broadcast-channel.js index 8f3fc9bf..d8162620 100644 --- a/dist/es5node/broadcast-channel.js +++ b/dist/es5node/broadcast-channel.js @@ -6,13 +6,9 @@ Object.defineProperty(exports, "__esModule", { exports.OPEN_BROADCAST_CHANNELS = exports.BroadcastChannel = void 0; exports.clearNodeFolder = clearNodeFolder; exports.enforceOptions = enforceOptions; - var _util = require("./util.js"); - var _methodChooser = require("./method-chooser.js"); - var _options = require("./options.js"); - /** * Contains all open channels, * used in tests to ensure everything is closed. @@ -20,77 +16,73 @@ var _options = require("./options.js"); var OPEN_BROADCAST_CHANNELS = new Set(); exports.OPEN_BROADCAST_CHANNELS = OPEN_BROADCAST_CHANNELS; var lastId = 0; - var BroadcastChannel = function BroadcastChannel(name, options) { // identifier of the channel to debug stuff this.id = lastId++; OPEN_BROADCAST_CHANNELS.add(this); this.name = name; - if (ENFORCED_OPTIONS) { options = ENFORCED_OPTIONS; } - this.options = (0, _options.fillOptionsWithDefaults)(options); - this.method = (0, _methodChooser.chooseMethod)(this.options); // isListening + this.method = (0, _methodChooser.chooseMethod)(this.options); + // isListening this._iL = false; + /** * _onMessageListener * setting onmessage twice, * will overwrite the first listener */ - this._onML = null; + /** * _addEventListeners */ - this._addEL = { message: [], internal: [] }; + /** * Unsend message promises * where the sending is still in progress * @type {Set} */ - this._uMP = new Set(); + /** * _beforeClose * array of promises that will be awaited * before the channel is closed */ - this._befC = []; + /** * _preparePromise */ - this._prepP = null; - _prepareChannel(this); -}; // STATICS +}; + +// STATICS /** * used to identify if someone overwrites * window.BroadcastChannel with this * See methods/native.js */ - - exports.BroadcastChannel = BroadcastChannel; BroadcastChannel._pubkey = true; + /** * clears the tmp-folder if is node * @return {Promise} true if has run, false if not node */ - function clearNodeFolder(options) { options = (0, _options.fillOptionsWithDefaults)(options); var method = (0, _methodChooser.chooseMethod)(options); - if (method.type === 'node') { return method.clearNodeFolder().then(function () { return true; @@ -99,19 +91,17 @@ function clearNodeFolder(options) { return _util.PROMISE_RESOLVED_FALSE; } } + /** * if set, this method is enforced, * no mather what the options are */ - - var ENFORCED_OPTIONS; - function enforceOptions(options) { ENFORCED_OPTIONS = options; -} // PROTOTYPE - +} +// PROTOTYPE BroadcastChannel.prototype = { postMessage: function postMessage(msg) { if (this.closed) { @@ -123,87 +113,77 @@ BroadcastChannel.prototype = { */ JSON.stringify(msg)); } - return _post(this, 'message', msg); }, postInternal: function postInternal(msg) { return _post(this, 'internal', msg); }, - set onmessage(fn) { var time = this.method.microSeconds(); var listenObj = { time: time, fn: fn }; - _removeListenerObject(this, 'message', this._onML); - if (fn && typeof fn === 'function') { this._onML = listenObj; - _addListenerObject(this, 'message', listenObj); } else { this._onML = null; } }, - addEventListener: function addEventListener(type, fn) { var time = this.method.microSeconds(); var listenObj = { time: time, fn: fn }; - _addListenerObject(this, type, listenObj); }, removeEventListener: function removeEventListener(type, fn) { var obj = this._addEL[type].find(function (obj) { return obj.fn === fn; }); - _removeListenerObject(this, type, obj); }, close: function close() { var _this = this; - if (this.closed) { return; } - OPEN_BROADCAST_CHANNELS["delete"](this); this.closed = true; var awaitPrepare = this._prepP ? this._prepP : _util.PROMISE_RESOLVED_VOID; this._onML = null; this._addEL.message = []; - return awaitPrepare // wait until all current sending are processed + return awaitPrepare + // wait until all current sending are processed .then(function () { return Promise.all(Array.from(_this._uMP)); - }) // run before-close hooks + }) + // run before-close hooks .then(function () { return Promise.all(_this._befC.map(function (fn) { return fn(); })); - }) // close the channel + }) + // close the channel .then(function () { return _this.method.close(_this._state); }); }, - get type() { return this.method.type; }, - get isClosed() { return this.closed; } - }; + /** * Post a message over the channel * @returns {Promise} that resolved when the message sending is done */ - function _post(broadcastChannel, type, msg) { var time = broadcastChannel.method.microSeconds(); var msgObj = { @@ -213,25 +193,22 @@ function _post(broadcastChannel, type, msg) { }; var awaitPrepare = broadcastChannel._prepP ? broadcastChannel._prepP : _util.PROMISE_RESOLVED_VOID; return awaitPrepare.then(function () { - var sendPromise = broadcastChannel.method.postMessage(broadcastChannel._state, msgObj); // add/remove to unsend messages list + var sendPromise = broadcastChannel.method.postMessage(broadcastChannel._state, msgObj); + // add/remove to unsend messages list broadcastChannel._uMP.add(sendPromise); - sendPromise["catch"]().then(function () { return broadcastChannel._uMP["delete"](sendPromise); }); return sendPromise; }); } - function _prepareChannel(channel) { var maybePromise = channel.method.create(channel.name, channel.options); - if ((0, _util.isPromise)(maybePromise)) { channel._prepP = maybePromise; maybePromise.then(function (s) { // used in tests to simulate slow runtime - /*if (channel.options.prepareDelay) { await new Promise(res => setTimeout(res, this.options.prepareDelay)); }*/ @@ -241,30 +218,25 @@ function _prepareChannel(channel) { channel._state = maybePromise; } } - function _hasMessageListeners(channel) { if (channel._addEL.message.length > 0) return true; if (channel._addEL.internal.length > 0) return true; return false; } - function _addListenerObject(channel, type, obj) { channel._addEL[type].push(obj); - _startListening(channel); } - function _removeListenerObject(channel, type, obj) { channel._addEL[type] = channel._addEL[type].filter(function (o) { return o !== obj; }); - _stopListening(channel); } - function _startListening(channel) { if (!channel._iL && _hasMessageListeners(channel)) { // someone is listening, start subscribing + var listenerFn = function listenerFn(msgObj) { channel._addEL[msgObj.type].forEach(function (listenerObject) { /** @@ -278,15 +250,12 @@ function _startListening(channel) { */ var hundredMsInMicro = 100 * 1000; var minMessageTime = listenerObject.time - hundredMsInMicro; - if (msgObj.time >= minMessageTime) { listenerObject.fn(msgObj.data); } }); }; - var time = channel.method.microSeconds(); - if (channel._prepP) { channel._prepP.then(function () { channel._iL = true; @@ -298,7 +267,6 @@ function _startListening(channel) { } } } - function _stopListening(channel) { if (channel._iL && !_hasMessageListeners(channel)) { // noone is listening, stop subscribing diff --git a/dist/es5node/browserify.index.js b/dist/es5node/browserify.index.js index a30fe523..f9966b51 100644 --- a/dist/es5node/browserify.index.js +++ b/dist/es5node/browserify.index.js @@ -1,7 +1,6 @@ "use strict"; var _module = require('./index.es5.js'); - var BroadcastChannel = _module.BroadcastChannel; var createLeaderElection = _module.createLeaderElection; window['BroadcastChannel2'] = BroadcastChannel; diff --git a/dist/es5node/index.es5.js b/dist/es5node/index.es5.js index e754438d..61d6ab29 100644 --- a/dist/es5node/index.es5.js +++ b/dist/es5node/index.es5.js @@ -1,7 +1,6 @@ "use strict"; var _index = require("./index.js"); - /** * because babel can only export on default-attribute, * we use this for the non-module-build @@ -10,6 +9,7 @@ var _index = require("./index.js"); * but * var BroadcastChannel = require('broadcast-channel'); */ + module.exports = { BroadcastChannel: _index.BroadcastChannel, createLeaderElection: _index.createLeaderElection, diff --git a/dist/es5node/index.js b/dist/es5node/index.js index 57d1b9aa..f3b4f471 100644 --- a/dist/es5node/index.js +++ b/dist/es5node/index.js @@ -39,7 +39,5 @@ Object.defineProperty(exports, "enforceOptions", { return _broadcastChannel.enforceOptions; } }); - var _broadcastChannel = require("./broadcast-channel.js"); - var _leaderElection = require("./leader-election.js"); \ No newline at end of file diff --git a/dist/es5node/leader-election.js b/dist/es5node/leader-election.js index 5c3a9715..1e2b8cb5 100644 --- a/dist/es5node/leader-election.js +++ b/dist/es5node/leader-election.js @@ -5,37 +5,30 @@ Object.defineProperty(exports, "__esModule", { }); exports.beLeader = beLeader; exports.createLeaderElection = createLeaderElection; - var _util = require("./util.js"); - var _unload = require("unload"); - var LeaderElection = function LeaderElection(broadcastChannel, options) { var _this = this; - this.broadcastChannel = broadcastChannel; this._options = options; this.isLeader = false; this.hasLeader = false; this.isDead = false; this.token = (0, _util.randomToken)(); + /** * Apply Queue, * used to ensure we do not run applyOnce() * in parallel. */ + this._aplQ = _util.PROMISE_RESOLVED_VOID; + // amount of unfinished applyOnce() calls + this._aplQC = 0; - this._aplQ = _util.PROMISE_RESOLVED_VOID; // amount of unfinished applyOnce() calls - - this._aplQC = 0; // things to clean up - + // things to clean up this._unl = []; // _unloads - this._lstns = []; // _listeners - this._dpL = function () {}; // onduplicate listener - - this._dpLC = false; // true when onduplicate called /** @@ -43,55 +36,47 @@ var LeaderElection = function LeaderElection(broadcastChannel, options) { * we still listen to messages to ensure the hasLeader flag * is set correctly. */ - var hasLeaderListener = function hasLeaderListener(msg) { if (msg.context === 'leader') { if (msg.action === 'death') { _this.hasLeader = false; } - if (msg.action === 'tell') { _this.hasLeader = true; } } }; - this.broadcastChannel.addEventListener('internal', hasLeaderListener); - this._lstns.push(hasLeaderListener); }; - LeaderElection.prototype = { /** * Returns true if the instance is leader, * false if not. * @async */ - applyOnce: function applyOnce( // true if the applyOnce() call came from the fallbackInterval cycle + applyOnce: function applyOnce( + // true if the applyOnce() call came from the fallbackInterval cycle isFromFallbackInterval) { var _this2 = this; - if (this.isLeader) { return (0, _util.sleep)(0, true); } - if (this.isDead) { return (0, _util.sleep)(0, false); } + /** * Already applying more then once, * -> wait for the apply queue to be finished. */ - - if (this._aplQC > 1) { return this._aplQ; } + /** * Add a new apply-run */ - - var applyRun = function applyRun() { /** * Optimization shortcuts. @@ -101,7 +86,6 @@ LeaderElection.prototype = { if (_this2.isLeader) { return _util.PROMISE_RESOLVED_TRUE; } - var stopCriteria = false; var stopCriteriaPromiseResolve; /** @@ -110,7 +94,6 @@ LeaderElection.prototype = { * have to await the responseTime when it is already clear * that the election failed. */ - var stopCriteriaPromise = new Promise(function (res) { stopCriteriaPromiseResolve = function stopCriteriaPromiseResolve() { stopCriteria = true; @@ -118,11 +101,9 @@ LeaderElection.prototype = { }; }); var recieved = []; - var handleMessage = function handleMessage(msg) { if (msg.context === 'leader' && msg.token != _this2.token) { recieved.push(msg); - if (msg.action === 'apply') { // other is applying if (msg.token > _this2.token) { @@ -133,7 +114,6 @@ LeaderElection.prototype = { stopCriteriaPromiseResolve(); } } - if (msg.action === 'tell') { // other is already leader stopCriteriaPromiseResolve(); @@ -141,8 +121,8 @@ LeaderElection.prototype = { } } }; - _this2.broadcastChannel.addEventListener('internal', handleMessage); + /** * If the applyOnce() call came from the fallbackInterval, * we can assume that the election runs in the background and @@ -153,26 +133,24 @@ LeaderElection.prototype = { * But also it takes longer which is not a problem because we anyway * run in the background. */ - - var waitForAnswerTime = isFromFallbackInterval ? _this2._options.responseTime * 4 : _this2._options.responseTime; - var applyPromise = _sendMessage(_this2, 'apply') // send out that this one is applying .then(function () { return Promise.race([(0, _util.sleep)(waitForAnswerTime), stopCriteriaPromise.then(function () { return Promise.reject(new Error()); })]); - }) // send again in case another instance was just created + }) + // send again in case another instance was just created .then(function () { return _sendMessage(_this2, 'apply'); - }) // let others time to respond + }) + // let others time to respond .then(function () { return Promise.race([(0, _util.sleep)(waitForAnswerTime), stopCriteriaPromise.then(function () { return Promise.reject(new Error()); })]); })["catch"](function () {}).then(function () { _this2.broadcastChannel.removeEventListener('internal', handleMessage); - if (!stopCriteria) { // no stop criteria -> own is leader return beLeader(_this2).then(function () { @@ -183,10 +161,8 @@ LeaderElection.prototype = { return false; } }); - return applyPromise; }; - this._aplQC = this._aplQC + 1; this._aplQ = this._aplQ.then(function () { return applyRun(); @@ -198,82 +174,68 @@ LeaderElection.prototype = { }); }, awaitLeadership: function awaitLeadership() { - if ( - /* _awaitLeadershipPromise */ + if ( /* _awaitLeadershipPromise */ !this._aLP) { this._aLP = _awaitLeadershipOnce(this); } - return this._aLP; }, - set onduplicate(fn) { this._dpL = fn; }, - die: function die() { var _this3 = this; - this._lstns.forEach(function (listener) { return _this3.broadcastChannel.removeEventListener('internal', listener); }); - this._lstns = []; - this._unl.forEach(function (uFn) { return uFn.remove(); }); - this._unl = []; - if (this.isLeader) { this.hasLeader = false; this.isLeader = false; } - this.isDead = true; return _sendMessage(this, 'death'); } }; + /** * @param leaderElector {LeaderElector} */ - function _awaitLeadershipOnce(leaderElector) { if (leaderElector.isLeader) { return _util.PROMISE_RESOLVED_VOID; } - return new Promise(function (res) { var resolved = false; - function finish() { if (resolved) { return; } - resolved = true; leaderElector.broadcastChannel.removeEventListener('internal', whenDeathListener); res(true); - } // try once now - + } + // try once now leaderElector.applyOnce().then(function () { if (leaderElector.isLeader) { finish(); } }); + /** * Try on fallbackInterval * @recursive */ - var tryOnFallBack = function tryOnFallBack() { return (0, _util.sleep)(leaderElector._options.fallbackInterval).then(function () { if (leaderElector.isDead || resolved) { return; } - if (leaderElector.isLeader) { finish(); } else { @@ -287,9 +249,9 @@ function _awaitLeadershipOnce(leaderElector) { } }); }; + tryOnFallBack(); - tryOnFallBack(); // try when other leader dies - + // try when other leader dies var whenDeathListener = function whenDeathListener(msg) { if (msg.context === 'leader' && msg.action === 'death') { leaderElector.hasLeader = false; @@ -300,17 +262,14 @@ function _awaitLeadershipOnce(leaderElector) { }); } }; - leaderElector.broadcastChannel.addEventListener('internal', whenDeathListener); - leaderElector._lstns.push(whenDeathListener); }); } + /** * sends and internal message over the broadcast-channel */ - - function _sendMessage(leaderElector, action) { var msgJson = { context: 'leader', @@ -319,21 +278,17 @@ function _sendMessage(leaderElector, action) { }; return leaderElector.broadcastChannel.postInternal(msgJson); } - function beLeader(leaderElector) { leaderElector.isLeader = true; leaderElector.hasLeader = true; var unloadFn = (0, _unload.add)(function () { return leaderElector.die(); }); - leaderElector._unl.push(unloadFn); - var isLeaderListener = function isLeaderListener(msg) { if (msg.context === 'leader' && msg.action === 'apply') { _sendMessage(leaderElector, 'tell'); } - if (msg.context === 'leader' && msg.action === 'tell' && !leaderElector._dpLC) { /** * another instance is also leader! @@ -344,49 +299,35 @@ function beLeader(leaderElector) { * @link https://github.com/pubkey/broadcast-channel/issues/385 */ leaderElector._dpLC = true; - leaderElector._dpL(); // message the lib user so the app can handle the problem - - _sendMessage(leaderElector, 'tell'); // ensure other leader also knows the problem - } }; leaderElector.broadcastChannel.addEventListener('internal', isLeaderListener); - leaderElector._lstns.push(isLeaderListener); - return _sendMessage(leaderElector, 'tell'); } - function fillOptionsWithDefaults(options, channel) { if (!options) options = {}; options = JSON.parse(JSON.stringify(options)); - if (!options.fallbackInterval) { options.fallbackInterval = 3000; } - if (!options.responseTime) { options.responseTime = channel.method.averageResponseTime(channel.options); } - return options; } - function createLeaderElection(channel, options) { if (channel._leaderElector) { throw new Error('BroadcastChannel already has a leader-elector'); } - options = fillOptionsWithDefaults(options, channel); var elector = new LeaderElection(channel, options); - channel._befC.push(function () { return elector.die(); }); - channel._leaderElector = elector; return elector; } \ No newline at end of file diff --git a/dist/es5node/method-chooser.js b/dist/es5node/method-chooser.js index 4bdab13a..0e616cf7 100644 --- a/dist/es5node/method-chooser.js +++ b/dist/es5node/method-chooser.js @@ -1,61 +1,51 @@ "use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); - var _typeof = require("@babel/runtime/helpers/typeof"); - Object.defineProperty(exports, "__esModule", { value: true }); exports.chooseMethod = chooseMethod; - var _native = _interopRequireDefault(require("./methods/native.js")); - var _indexedDb = _interopRequireDefault(require("./methods/indexed-db.js")); - var _localstorage = _interopRequireDefault(require("./methods/localstorage.js")); - var _simulate = _interopRequireDefault(require("./methods/simulate.js")); - var NodeMethod = _interopRequireWildcard(require("./methods/node.js")); - function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } - function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } - // the line below will be removed from es5/browser builds + // order is important -var METHODS = [_native["default"], // fastest +var METHODS = [_native["default"], +// fastest _indexedDb["default"], _localstorage["default"]]; - function chooseMethod(options) { - var chooseMethods = [].concat(options.methods, METHODS).filter(Boolean); // the line below will be removed from es5/browser builds + var chooseMethods = [].concat(options.methods, METHODS).filter(Boolean); - chooseMethods.push(NodeMethod); // directly chosen + // the line below will be removed from es5/browser builds + chooseMethods.push(NodeMethod); + // directly chosen if (options.type) { if (options.type === 'simulate') { // only use simulate-method if directly chosen return _simulate["default"]; } - var ret = chooseMethods.find(function (m) { return m.type === options.type; }); if (!ret) throw new Error('method-type ' + options.type + ' not found');else return ret; } + /** * if no webworker support is needed, * remove idb from the list so that localstorage is been chosen */ - - if (!options.webWorkerSupport) { chooseMethods = chooseMethods.filter(function (m) { return m.type !== 'idb'; }); } - var useMethod = chooseMethods.find(function (method) { return method.canBeUsed(); }); diff --git a/dist/es5node/methods/indexed-db.js b/dist/es5node/methods/indexed-db.js index dc38ebe8..b3b21767 100644 --- a/dist/es5node/methods/indexed-db.js +++ b/dist/es5node/methods/indexed-db.js @@ -22,13 +22,9 @@ exports.postMessage = postMessage; exports.removeMessagesById = removeMessagesById; exports.type = void 0; exports.writeMessage = writeMessage; - var _util = require("../util.js"); - var _obliviousSet = require("oblivious-set"); - var _options = require("../options.js"); - /** * this method uses indexeddb to store the messages * There is currently no observerAPI for idb @@ -37,58 +33,54 @@ var _options = require("../options.js"); * When working on this, ensure to use these performance optimizations: * @link https://rxdb.info/slow-indexeddb.html */ + var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var DB_PREFIX = 'pubkey.broadcast-channel-0-'; var OBJECT_STORE_ID = 'messages'; + /** * Use relaxed durability for faster performance on all transactions. * @link https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/ */ - var TRANSACTION_SETTINGS = { durability: 'relaxed' }; exports.TRANSACTION_SETTINGS = TRANSACTION_SETTINGS; var type = 'idb'; exports.type = type; - function getIdb() { if (typeof indexedDB !== 'undefined') return indexedDB; - if (typeof window !== 'undefined') { if (typeof window.mozIndexedDB !== 'undefined') return window.mozIndexedDB; if (typeof window.webkitIndexedDB !== 'undefined') return window.webkitIndexedDB; if (typeof window.msIndexedDB !== 'undefined') return window.msIndexedDB; } - return false; } + /** * If possible, we should explicitly commit IndexedDB transactions * for better performance. * @link https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/ */ - - function commitIndexedDBTransaction(tx) { if (tx.commit) { tx.commit(); } } - function createDatabase(channelName) { - var IndexedDB = getIdb(); // create table + var IndexedDB = getIdb(); + // create table var dbName = DB_PREFIX + channelName; + /** * All IndexedDB databases are opened without version * because it is a bit faster, especially on firefox * @link http://nparashuram.com/IndexedDB/perf/#Open%20Database%20with%20version */ - var openRequest = IndexedDB.open(dbName); - openRequest.onupgradeneeded = function (ev) { var db = ev.target.result; db.createObjectStore(OBJECT_STORE_ID, { @@ -96,24 +88,21 @@ function createDatabase(channelName) { autoIncrement: true }); }; - var dbPromise = new Promise(function (res, rej) { openRequest.onerror = function (ev) { return rej(ev); }; - openRequest.onsuccess = function () { res(openRequest.result); }; }); return dbPromise; } + /** * writes the new message to the database * so other readers can find it */ - - function writeMessage(db, readerUuid, messageJson) { var time = new Date().getTime(); var writeObject = { @@ -126,17 +115,14 @@ function writeMessage(db, readerUuid, messageJson) { tx.oncomplete = function () { return res(); }; - tx.onerror = function (ev) { return rej(ev); }; - var objectStore = tx.objectStore(OBJECT_STORE_ID); objectStore.add(writeObject); commitIndexedDBTransaction(tx); }); } - function getAllMessages(db) { var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); @@ -144,10 +130,9 @@ function getAllMessages(db) { return new Promise(function (res) { objectStore.openCursor().onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { - ret.push(cursor.value); //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); - + ret.push(cursor.value); + //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); cursor["continue"](); } else { commitIndexedDBTransaction(tx); @@ -156,31 +141,28 @@ function getAllMessages(db) { }; }); } - function getMessagesHigherThan(db, lastCursorId) { var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); var ret = []; var keyRangeValue = IDBKeyRange.bound(lastCursorId + 1, Infinity); + /** * Optimization shortcut, * if getAll() can be used, do not use a cursor. * @link https://rxdb.info/slow-indexeddb.html */ - if (objectStore.getAll) { var getAllRequest = objectStore.getAll(keyRangeValue); return new Promise(function (res, rej) { getAllRequest.onerror = function (err) { return rej(err); }; - getAllRequest.onsuccess = function (e) { res(e.target.result); }; }); } - function openCursor() { // Occasionally Safari will fail on IDBKeyRange.bound, this // catches that error, having it open the cursor to the first @@ -192,17 +174,13 @@ function getMessagesHigherThan(db, lastCursorId) { return objectStore.openCursor(); } } - return new Promise(function (res, rej) { var openCursorRequest = openCursor(); - openCursorRequest.onerror = function (err) { return rej(err); }; - openCursorRequest.onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { if (cursor.value.id < lastCursorId + 1) { cursor["continue"](lastCursorId + 1); @@ -217,12 +195,10 @@ function getMessagesHigherThan(db, lastCursorId) { }; }); } - function removeMessagesById(channelState, ids) { if (channelState.closed) { return Promise.resolve([]); } - var tx = channelState.db.transaction(OBJECT_STORE_ID, 'readwrite', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); return Promise.all(ids.map(function (id) { @@ -234,7 +210,6 @@ function removeMessagesById(channelState, ids) { }); })); } - function getOldMessages(db, ttl) { var olderThen = new Date().getTime() - ttl; var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS); @@ -243,13 +218,11 @@ function getOldMessages(db, ttl) { return new Promise(function (res) { objectStore.openCursor().onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { var msgObk = cursor.value; - if (msgObk.time < olderThen) { - ret.push(msgObk); //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); - + ret.push(msgObk); + //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); cursor["continue"](); } else { // no more old messages, @@ -263,7 +236,6 @@ function getOldMessages(db, ttl) { }; }); } - function cleanOldMessages(channelState) { return getOldMessages(channelState.db, channelState.options.idb.ttl).then(function (tooOld) { return removeMessagesById(channelState, tooOld.map(function (msg) { @@ -271,7 +243,6 @@ function cleanOldMessages(channelState) { })); }); } - function create(channelName, options) { options = (0, _options.fillOptionsWithDefaults)(options); return createDatabase(channelName).then(function (db) { @@ -281,7 +252,6 @@ function create(channelName, options) { channelName: channelName, options: options, uuid: (0, _util.randomToken)(), - /** * emittedMessagesIds * contains all messages that have been emitted before @@ -294,30 +264,27 @@ function create(channelName, options) { readQueuePromises: [], db: db }; + /** * Handle abrupt closes that do not originate from db.close(). * This could happen, for example, if the underlying storage is * removed or if the user clears the database in the browser's * history preferences. */ - db.onclose = function () { state.closed = true; if (options.idb.onclose) options.idb.onclose(); }; + /** * if service-workers are used, * we have no 'storage'-event if they post a message, * therefore we also have to set an interval */ - - _readLoop(state); - return state; }); } - function _readLoop(state) { if (state.closed) return; readNewMessages(state).then(function () { @@ -326,25 +293,21 @@ function _readLoop(state) { return _readLoop(state); }); } - function _filterMessage(msgObj, state) { if (msgObj.uuid === state.uuid) return false; // send by own - if (state.eMIs.has(msgObj.id)) return false; // already emitted - if (msgObj.data.time < state.messagesCallbackTime) return false; // older then onMessageCallback - return true; } + /** * reads all new messages from the database and emits them */ - - function readNewMessages(state) { // channel already closed - if (state.closed) return _util.PROMISE_RESOLVED_VOID; // if no one is listening, we do not need to scan for new messages + if (state.closed) return _util.PROMISE_RESOLVED_VOID; + // if no one is listening, we do not need to scan for new messages if (!state.messagesCallback) return _util.PROMISE_RESOLVED_VOID; return getMessagesHigherThan(state.db, state.lastCursorId).then(function (newerMessages) { var useMessages = newerMessages @@ -352,21 +315,18 @@ function readNewMessages(state) { * there is a bug in iOS where the msgObj can be undefined some times * so we filter them out * @link https://github.com/pubkey/broadcast-channel/issues/19 - */ - .filter(function (msgObj) { + */.filter(function (msgObj) { return !!msgObj; }).map(function (msgObj) { if (msgObj.id > state.lastCursorId) { state.lastCursorId = msgObj.id; } - return msgObj; }).filter(function (msgObj) { return _filterMessage(msgObj, state); }).sort(function (msgObjA, msgObjB) { return msgObjA.time - msgObjB.time; }); // sort by time - useMessages.forEach(function (msgObj) { if (state.messagesCallback) { state.eMIs.add(msgObj.id); @@ -376,12 +336,10 @@ function readNewMessages(state) { return _util.PROMISE_RESOLVED_VOID; }); } - function close(channelState) { channelState.closed = true; channelState.db.close(); } - function postMessage(channelState, messageJson) { channelState.writeBlockPromise = channelState.writeBlockPromise.then(function () { return writeMessage(channelState.db, channelState.uuid, messageJson); @@ -393,27 +351,21 @@ function postMessage(channelState, messageJson) { }); return channelState.writeBlockPromise; } - function onMessage(channelState, fn, time) { channelState.messagesCallbackTime = time; channelState.messagesCallback = fn; readNewMessages(channelState); } - function canBeUsed() { var idb = getIdb(); - if (!idb) { return false; } - return true; } - function averageResponseTime(options) { return options.idb.fallbackInterval * 2; } - var _default = { create: create, close: close, diff --git a/dist/es5node/methods/localstorage.js b/dist/es5node/methods/localstorage.js index 1f308a9f..0a3d58bb 100644 --- a/dist/es5node/methods/localstorage.js +++ b/dist/es5node/methods/localstorage.js @@ -16,13 +16,9 @@ exports.postMessage = postMessage; exports.removeStorageEventListener = removeStorageEventListener; exports.storageKey = storageKey; exports.type = void 0; - var _obliviousSet = require("oblivious-set"); - var _options = require("../options.js"); - var _util = require("../util.js"); - /** * A localStorage-only method which uses localstorage and its 'storage'-event * This does not work inside of webworkers because they have no access to locastorage @@ -30,41 +26,38 @@ var _util = require("../util.js"); * @link https://caniuse.com/#feat=namevalue-storage * @link https://caniuse.com/#feat=indexeddb */ + var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var KEY_PREFIX = 'pubkey.broadcastChannel-'; var type = 'localstorage'; + /** * copied from crosstab * @link https://github.com/tejacques/crosstab/blob/master/src/crosstab.js#L32 */ - exports.type = type; - function getLocalStorage() { var localStorage; if (typeof window === 'undefined') return null; - try { localStorage = window.localStorage; localStorage = window['ie8-eventlistener/storage'] || window.localStorage; - } catch (e) {// New versions of Firefox throw a Security exception + } catch (e) { + // New versions of Firefox throw a Security exception // if cookies are disabled. See // https://bugzilla.mozilla.org/show_bug.cgi?id=1028153 } - return localStorage; } - function storageKey(channelName) { return KEY_PREFIX + channelName; } + /** * writes the new message to the storage * and fires the storage-event so other readers can find it */ - - function postMessage(channelState, messageJson) { return new Promise(function (res) { (0, _util.sleep)().then(function () { @@ -77,12 +70,12 @@ function postMessage(channelState, messageJson) { }; var value = JSON.stringify(writeObj); getLocalStorage().setItem(key, value); + /** * StorageEvent does not fire the 'storage' event * in the window that changes the state of the local storage. * So we fire it manually */ - var ev = document.createEvent('Event'); ev.initEvent('storage', true, true); ev.key = key; @@ -92,52 +85,42 @@ function postMessage(channelState, messageJson) { }); }); } - function addStorageEventListener(channelName, fn) { var key = storageKey(channelName); - var listener = function listener(ev) { if (ev.key === key) { fn(JSON.parse(ev.newValue)); } }; - window.addEventListener('storage', listener); return listener; } - function removeStorageEventListener(listener) { window.removeEventListener('storage', listener); } - function create(channelName, options) { options = (0, _options.fillOptionsWithDefaults)(options); - if (!canBeUsed()) { throw new Error('BroadcastChannel: localstorage cannot be used'); } - var uuid = (0, _util.randomToken)(); + /** * eMIs * contains all messages that have been emitted before * @type {ObliviousSet} */ - var eMIs = new _obliviousSet.ObliviousSet(options.localstorage.removeTimeout); var state = { channelName: channelName, uuid: uuid, eMIs: eMIs // emittedMessagesIds - }; + state.listener = addStorageEventListener(channelName, function (msgObj) { if (!state.messagesCallback) return; // no listener - if (msgObj.uuid === uuid) return; // own message - if (!msgObj.token || eMIs.has(msgObj.token)) return; // already emitted - if (msgObj.data.time && msgObj.data.time < state.messagesCallbackTime) return; // too old eMIs.add(msgObj.token); @@ -145,20 +128,16 @@ function create(channelName, options) { }); return state; } - function close(channelState) { removeStorageEventListener(channelState.listener); } - function onMessage(channelState, fn, time) { channelState.messagesCallbackTime = time; channelState.messagesCallback = fn; } - function canBeUsed() { var ls = getLocalStorage(); if (!ls) return false; - try { var key = '__broadcastchannel_check'; ls.setItem(key, 'works'); @@ -169,22 +148,17 @@ function canBeUsed() { // https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API#Private_Browsing_Incognito_modes return false; } - return true; } - function averageResponseTime() { var defaultTime = 120; var userAgent = navigator.userAgent.toLowerCase(); - if (userAgent.includes('safari') && !userAgent.includes('chrome')) { // safari is much slower so this time is higher return defaultTime * 2; } - return defaultTime; } - var _default = { create: create, close: close, diff --git a/dist/es5node/methods/native.js b/dist/es5node/methods/native.js index 8118f760..cabcfb3e 100644 --- a/dist/es5node/methods/native.js +++ b/dist/es5node/methods/native.js @@ -11,20 +11,16 @@ exports.microSeconds = exports["default"] = void 0; exports.onMessage = onMessage; exports.postMessage = postMessage; exports.type = void 0; - var _util = require("../util.js"); - var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var type = 'native'; exports.type = type; - function create(channelName) { var state = { messagesCallback: null, bc: new BroadcastChannel(channelName), subFns: [] // subscriberFunctions - }; state.bc.onmessage = function (msg) { @@ -32,15 +28,12 @@ function create(channelName) { state.messagesCallback(msg.data); } }; - return state; } - function close(channelState) { channelState.bc.close(); channelState.subFns = []; } - function postMessage(channelState, messageJson) { try { channelState.bc.postMessage(messageJson, false); @@ -49,31 +42,25 @@ function postMessage(channelState, messageJson) { return Promise.reject(err); } } - function onMessage(channelState, fn) { channelState.messagesCallback = fn; } - function canBeUsed() { if (typeof window === 'undefined') { return false; } - if (typeof BroadcastChannel === 'function') { if (BroadcastChannel._pubkey) { throw new Error('BroadcastChannel: Do not overwrite window.BroadcastChannel with this module, this is not a polyfill'); } - return true; } else { return false; } } - function averageResponseTime() { return 150; } - var _default = { create: create, close: close, diff --git a/dist/es5node/methods/node.js b/dist/es5node/methods/node.js index a3aa3c7e..76d38bc5 100644 --- a/dist/es5node/methods/node.js +++ b/dist/es5node/methods/node.js @@ -1,7 +1,6 @@ "use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); - Object.defineProperty(exports, "__esModule", { value: true }); @@ -35,37 +34,21 @@ exports.socketInfoPath = socketInfoPath; exports.socketPath = socketPath; exports.type = void 0; exports.writeMessage = writeMessage; - var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); - var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); - var _util = _interopRequireDefault(require("util")); - var _fs = _interopRequireDefault(require("fs")); - var _crypto = _interopRequireDefault(require("crypto")); - var _os = _interopRequireDefault(require("os")); - var _events = _interopRequireDefault(require("events")); - var _net = _interopRequireDefault(require("net")); - var _path = _interopRequireDefault(require("path")); - var _rimraf = _interopRequireDefault(require("rimraf")); - var _pQueue = _interopRequireDefault(require("p-queue")); - var _unload = require("unload"); - var _options = require("../options.js"); - var _util2 = require("../util.js"); - var _obliviousSet = require("oblivious-set"); - /** * this method is used in nodejs-environments. * The ipc is handled via sockets and file-writes to the tmp-folder @@ -84,29 +67,18 @@ function cleanPipeName(str) { return str; } } - var mkdir = _util["default"].promisify(_fs["default"].mkdir); - var writeFile = _util["default"].promisify(_fs["default"].writeFile); - var readFile = _util["default"].promisify(_fs["default"].readFile); - var unlink = _util["default"].promisify(_fs["default"].unlink); - var readdir = _util["default"].promisify(_fs["default"].readdir); - var chmod = _util["default"].promisify(_fs["default"].chmod); - var removeDir = _util["default"].promisify(_rimraf["default"]); - var OTHER_INSTANCES = {}; var TMP_FOLDER_NAME = 'pubkey.bc'; - var TMP_FOLDER_BASE = _path["default"].join(_os["default"].tmpdir(), TMP_FOLDER_NAME); - exports.TMP_FOLDER_BASE = TMP_FOLDER_BASE; var getPathsCache = new Map(); - function getPaths(channelName) { if (!getPathsCache.has(channelName)) { /** @@ -116,21 +88,16 @@ function getPaths(channelName) { * in using the same folders for different channels. */ var channelHash = _crypto["default"].createHash('sha256').update(channelName).digest('hex'); + /** * because the length of socket-paths is limited, we use only the first 20 chars * and also start with A to ensure we do not start with a number * @link https://serverfault.com/questions/641347/check-if-a-path-exceeds-maximum-for-unix-domain-socket */ - - var channelFolder = 'A' + channelHash.substring(0, 20); - var channelPathBase = _path["default"].join(TMP_FOLDER_BASE, channelFolder); - var folderPathReaders = _path["default"].join(channelPathBase, 'rdrs'); - var folderPathMessages = _path["default"].join(channelPathBase, 'msgs'); - var ret = { channelBase: channelPathBase, readers: folderPathReaders, @@ -139,16 +106,12 @@ function getPaths(channelName) { getPathsCache.set(channelName, ret); return ret; } - return getPathsCache.get(channelName); } - var ENSURE_BASE_FOLDER_EXISTS_PROMISE = null; - function ensureBaseFolderExists() { return _ensureBaseFolderExists.apply(this, arguments); } - function _ensureBaseFolderExists() { _ensureBaseFolderExists = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee3() { return _regenerator["default"].wrap(function _callee3$(_context3) { @@ -160,9 +123,7 @@ function _ensureBaseFolderExists() { return null; }); } - return _context3.abrupt("return", ENSURE_BASE_FOLDER_EXISTS_PROMISE); - case 2: case "end": return _context3.stop(); @@ -172,7 +133,6 @@ function _ensureBaseFolderExists() { })); return _ensureBaseFolderExists.apply(this, arguments); } - function ensureFoldersExist(_x, _x2) { return _ensureFoldersExist.apply(this, arguments); } @@ -180,8 +140,6 @@ function ensureFoldersExist(_x, _x2) { * removes the tmp-folder * @return {Promise} */ - - function _ensureFoldersExist() { _ensureFoldersExist = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee4(channelName, paths) { var chmodValue; @@ -192,13 +150,11 @@ function _ensureFoldersExist() { paths = paths || getPaths(channelName); _context4.next = 3; return ensureBaseFolderExists(); - case 3: _context4.next = 5; return mkdir(paths.channelBase)["catch"](function () { return null; }); - case 5: _context4.next = 7; return Promise.all([mkdir(paths.readers)["catch"](function () { @@ -206,7 +162,6 @@ function _ensureFoldersExist() { }), mkdir(paths.messages)["catch"](function () { return null; })]); - case 7: // set permissions so other users can use the same channel chmodValue = '777'; @@ -214,7 +169,6 @@ function _ensureFoldersExist() { return Promise.all([chmod(paths.channelBase, chmodValue), chmod(paths.readers, chmodValue), chmod(paths.messages, chmodValue)])["catch"](function () { return null; }); - case 10: case "end": return _context4.stop(); @@ -224,11 +178,9 @@ function _ensureFoldersExist() { })); return _ensureFoldersExist.apply(this, arguments); } - function clearNodeFolder() { return _clearNodeFolder.apply(this, arguments); } - function _clearNodeFolder() { _clearNodeFolder = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee5() { return _regenerator["default"].wrap(function _callee5$(_context5) { @@ -239,18 +191,14 @@ function _clearNodeFolder() { _context5.next = 2; break; } - throw new Error('BroadcastChannel.clearNodeFolder(): path is wrong'); - case 2: ENSURE_BASE_FOLDER_EXISTS_PROMISE = null; _context5.next = 5; return removeDir(TMP_FOLDER_BASE); - case 5: ENSURE_BASE_FOLDER_EXISTS_PROMISE = null; return _context5.abrupt("return", true); - case 7: case "end": return _context5.stop(); @@ -260,29 +208,22 @@ function _clearNodeFolder() { })); return _clearNodeFolder.apply(this, arguments); } - function socketPath(channelName, readerUuid, paths) { paths = paths || getPaths(channelName); - var socketPath = _path["default"].join(paths.readers, readerUuid + '.s'); - return cleanPipeName(socketPath); } - function socketInfoPath(channelName, readerUuid, paths) { paths = paths || getPaths(channelName); - var socketPath = _path["default"].join(paths.readers, readerUuid + '.json'); - return socketPath; } + /** * Because it is not possible to get all socket-files in a folder, * when used under fucking windows, * we have to set a normal file so other readers know our socket exists */ - - function createSocketInfoFile(channelName, readerUuid, paths) { var pathToFile = socketInfoPath(channelName, readerUuid, paths); return writeFile(pathToFile, JSON.stringify({ @@ -291,16 +232,14 @@ function createSocketInfoFile(channelName, readerUuid, paths) { return pathToFile; }); } + /** * returns the amount of channel-folders in the tmp-directory * @return {Promise} */ - - function countChannelFolders() { return _countChannelFolders.apply(this, arguments); } - function _countChannelFolders() { _countChannelFolders = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee6() { var folders; @@ -310,15 +249,12 @@ function _countChannelFolders() { case 0: _context6.next = 2; return ensureBaseFolderExists(); - case 2: _context6.next = 4; return readdir(TMP_FOLDER_BASE); - case 4: folders = _context6.sent; return _context6.abrupt("return", folders.length); - case 6: case "end": return _context6.stop(); @@ -328,7 +264,6 @@ function _countChannelFolders() { })); return _countChannelFolders.apply(this, arguments); } - function connectionError(_x3) { return _connectionError.apply(this, arguments); } @@ -336,8 +271,6 @@ function connectionError(_x3) { * creates the socket-file and subscribes to it * @return {{emitter: EventEmitter, server: any}} */ - - function _connectionError() { _connectionError = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee7(originalError) { var count, addObj, text, newError; @@ -347,28 +280,23 @@ function _connectionError() { case 0: _context7.next = 2; return countChannelFolders(); - case 2: count = _context7.sent; - if (!(count < 30)) { _context7.next = 5; break; } - return _context7.abrupt("return", originalError); - case 5: addObj = {}; Object.entries(originalError).forEach(function (_ref3) { var k = _ref3[0], - v = _ref3[1]; + v = _ref3[1]; return addObj[k] = v; }); text = 'BroadcastChannel.create(): error: ' + 'This might happen if you have created to many channels, ' + 'like when you use BroadcastChannel in unit-tests.' + 'Try using BroadcastChannel.clearNodeFolder() to clear the tmp-folder before each test.' + 'See https://github.com/pubkey/broadcast-channel#clear-tmp-folder'; newError = new Error(text + ': ' + JSON.stringify(addObj, null, 2)); return _context7.abrupt("return", newError); - case 10: case "end": return _context7.stop(); @@ -378,11 +306,9 @@ function _connectionError() { })); return _connectionError.apply(this, arguments); } - function createSocketEventEmitter(_x4, _x5, _x6) { return _createSocketEventEmitter.apply(this, arguments); } - function _createSocketEventEmitter() { _createSocketEventEmitter = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee10(channelName, readerUuid, paths) { var pathToSocket, emitter, server; @@ -409,11 +335,9 @@ function _createSocketEventEmitter() { case 0: _context8.next = 2; return connectionError(err); - case 2: useErr = _context8.sent; reject(useErr); - case 4: case "end": return _context8.stop(); @@ -421,7 +345,6 @@ function _createSocketEventEmitter() { } }, _callee8); })); - return function (_x26) { return _ref4.apply(this, arguments); }; @@ -437,19 +360,15 @@ function _createSocketEventEmitter() { _context9.next = 7; break; } - _context9.next = 3; return connectionError(err); - case 3: useErr = _context9.sent; reject(useErr); _context9.next = 8; break; - case 7: resolve(res); - case 8: case "end": return _context9.stop(); @@ -457,20 +376,17 @@ function _createSocketEventEmitter() { } }, _callee9); })); - return function (_x27, _x28) { return _ref5.apply(this, arguments); }; }()); }); - case 5: return _context10.abrupt("return", { path: pathToSocket, emitter: emitter, server: server }); - case 6: case "end": return _context10.stop(); @@ -480,7 +396,6 @@ function _createSocketEventEmitter() { })); return _createSocketEventEmitter.apply(this, arguments); } - function openClientConnection(_x7, _x8) { return _openClientConnection.apply(this, arguments); } @@ -489,8 +404,6 @@ function openClientConnection(_x7, _x8) { * so other readers can find it * @return {Promise} */ - - function _openClientConnection() { _openClientConnection = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee11(channelName, readerUuid) { var pathToSocket, client; @@ -508,7 +421,6 @@ function _openClientConnection() { return rej(err); }); })); - case 3: case "end": return _context11.stop(); @@ -518,7 +430,6 @@ function _openClientConnection() { })); return _openClientConnection.apply(this, arguments); } - function writeMessage(channelName, readerUuid, messageJson, paths) { paths = paths || getPaths(channelName); var time = microSeconds(); @@ -529,9 +440,7 @@ function writeMessage(channelName, readerUuid, messageJson, paths) { }; var token = (0, _util2.randomToken)(); var fileName = time + '_' + readerUuid + '_' + token + '.json'; - var msgPath = _path["default"].join(paths.messages, fileName); - return writeFile(msgPath, JSON.stringify(writeObject)).then(function () { return { time: time, @@ -541,16 +450,14 @@ function writeMessage(channelName, readerUuid, messageJson, paths) { }; }); } + /** * returns the uuids of all readers * @return {string[]} */ - - function getReadersUuids(_x9, _x10) { return _getReadersUuids.apply(this, arguments); } - function _getReadersUuids() { _getReadersUuids = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee12(channelName, paths) { var readersPath, files; @@ -562,7 +469,6 @@ function _getReadersUuids() { readersPath = paths.readers; _context12.next = 4; return readdir(readersPath); - case 4: files = _context12.sent; return _context12.abrupt("return", files.map(function (file) { @@ -573,7 +479,6 @@ function _getReadersUuids() { .map(function (split) { return split[0]; })); - case 6: case "end": return _context12.stop(); @@ -583,11 +488,9 @@ function _getReadersUuids() { })); return _getReadersUuids.apply(this, arguments); } - function messagePath(_x11, _x12, _x13, _x14) { return _messagePath.apply(this, arguments); } - function _messagePath() { _messagePath = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee13(channelName, time, token, writerUuid) { var fileName, msgPath; @@ -598,7 +501,6 @@ function _messagePath() { fileName = time + '_' + writerUuid + '_' + token + '.json'; msgPath = _path["default"].join(getPaths(channelName).messages, fileName); return _context13.abrupt("return", msgPath); - case 3: case "end": return _context13.stop(); @@ -608,11 +510,9 @@ function _messagePath() { })); return _messagePath.apply(this, arguments); } - function getAllMessages(_x15, _x16) { return _getAllMessages.apply(this, arguments); } - function _getAllMessages() { _getAllMessages = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee14(channelName, paths) { var messagesPath, files; @@ -624,7 +524,6 @@ function _getAllMessages() { messagesPath = paths.messages; _context14.next = 4; return readdir(messagesPath); - case 4: files = _context14.sent; return _context14.abrupt("return", files.map(function (file) { @@ -637,7 +536,6 @@ function _getAllMessages() { token: split[2] }; })); - case 6: case "end": return _context14.stop(); @@ -647,7 +545,6 @@ function _getAllMessages() { })); return _getAllMessages.apply(this, arguments); } - function getSingleMessage(channelName, msgObj, paths) { paths = paths || getPaths(channelName); return { @@ -657,17 +554,14 @@ function getSingleMessage(channelName, msgObj, paths) { token: msgObj.to }; } - function readMessage(messageObj) { return readFile(messageObj.path, 'utf8').then(function (content) { return JSON.parse(content); }); } - function cleanOldMessages(_x17, _x18) { return _cleanOldMessages.apply(this, arguments); } - function _cleanOldMessages() { _cleanOldMessages = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee15(messageObjects, ttl) { var olderThen; @@ -676,7 +570,6 @@ function _cleanOldMessages() { switch (_context15.prev = _context15.next) { case 0: olderThen = microSeconds() - ttl * 1000; // convert ttl to microseconds - _context15.next = 3; return Promise.all(messageObjects.filter(function (obj) { return obj.time < olderThen; @@ -685,7 +578,6 @@ function _cleanOldMessages() { return null; }); })); - case 3: case "end": return _context15.stop(); @@ -695,32 +587,28 @@ function _cleanOldMessages() { })); return _cleanOldMessages.apply(this, arguments); } - var type = 'node'; + /** * creates a new channelState * @return {Promise} */ - exports.type = type; - function create(_x19) { return _create.apply(this, arguments); } - function _create() { _create = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee16(channelName) { var options, - time, - paths, - ensureFolderExistsPromise, - uuid, - state, - _yield$Promise$all, - socketEE, - infoFilePath, - _args16 = arguments; - + time, + paths, + ensureFolderExistsPromise, + uuid, + state, + _yield$Promise$all, + socketEE, + infoFilePath, + _args16 = arguments; return _regenerator["default"].wrap(function _callee16$(_context16) { while (1) { switch (_context16.prev = _context16.next) { @@ -739,7 +627,6 @@ function _create() { paths: paths, // contains all messages that have been emitted before emittedMessagesIds: new _obliviousSet.ObliviousSet(options.node.ttl * 2), - /** * Used to ensure we do not write too many files at once * which could throw an error. @@ -763,18 +650,17 @@ function _create() { OTHER_INSTANCES[channelName].push(state); _context16.next = 11; return ensureFolderExistsPromise; - case 11: _context16.next = 13; return Promise.all([createSocketEventEmitter(channelName, uuid, paths), createSocketInfoFile(channelName, uuid, paths), refreshReaderClients(state)]); - case 13: _yield$Promise$all = _context16.sent; socketEE = _yield$Promise$all[0]; infoFilePath = _yield$Promise$all[1]; state.socketEE = socketEE; - state.infoFilePath = infoFilePath; // when new message comes in, we read it and emit it + state.infoFilePath = infoFilePath; + // when new message comes in, we read it and emit it socketEE.emitter.on('data', function (data) { // if the socket is used fast, it may appear that multiple messages are flushed at once // so we have to split them before @@ -791,7 +677,6 @@ function _create() { }); }); return _context16.abrupt("return", state); - case 20: case "end": return _context16.stop(); @@ -801,27 +686,21 @@ function _create() { })); return _create.apply(this, arguments); } - function _filterMessage(msgObj, state) { if (msgObj.senderUuid === state.uuid) return false; // not send by own - if (state.emittedMessagesIds.has(msgObj.token)) return false; // not already emitted - if (!state.messagesCallback) return false; // no listener - if (msgObj.time < state.messagesCallbackTime) return false; // not older then onMessageCallback - if (msgObj.time < state.time) return false; // msgObj is older then channel state.emittedMessagesIds.add(msgObj.token); return true; } + /** * when the socket pings, so that we now new messages came, * run this */ - - function handleMessagePing(_x20, _x21) { return _handleMessagePing.apply(this, arguments); } @@ -829,8 +708,6 @@ function handleMessagePing(_x20, _x21) { * ensures that the channelState is connected with all other readers * @return {Promise} */ - - function _handleMessagePing() { _handleMessagePing = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee17(state, msgObj) { var messages, useMessages; @@ -842,27 +719,21 @@ function _handleMessagePing() { _context17.next = 2; break; } - return _context17.abrupt("return"); - case 2: if (msgObj) { _context17.next = 8; break; } - _context17.next = 5; return getAllMessages(state.channelName, state.paths); - case 5: messages = _context17.sent; _context17.next = 9; break; - case 8: // get single message messages = [getSingleMessage(state.channelName, msgObj, state.paths)]; - case 9: useMessages = messages.filter(function (msgObj) { return _filterMessage(msgObj, state); @@ -870,14 +741,11 @@ function _handleMessagePing() { return msgObjA.time - msgObjB.time; }); // sort by time // if no listener or message, so not do anything - if (!(!useMessages.length || !state.messagesCallback)) { _context17.next = 12; break; } - return _context17.abrupt("return"); - case 12: _context17.next = 14; return Promise.all(useMessages.map(function (msgObj) { @@ -885,17 +753,14 @@ function _handleMessagePing() { return msgObj.content = content; }); })); - case 14: useMessages.forEach(function (msgObj) { state.emittedMessagesIds.add(msgObj.token); - if (state.messagesCallback) { // emit to subscribers state.messagesCallback(msgObj.content.data); } }); - case 15: case "end": return _context17.stop(); @@ -905,7 +770,6 @@ function _handleMessagePing() { })); return _handleMessagePing.apply(this, arguments); } - function refreshReaderClients(channelState) { return getReadersUuids(channelState.channelName, channelState.paths).then(function (otherReaders) { // remove subscriptions to closed readers @@ -920,18 +784,14 @@ function refreshReaderClients(channelState) { _context.prev = 0; _context.next = 3; return channelState.otherReaderClients[readerUuid].destroy(); - case 3: _context.next = 7; break; - case 5: _context.prev = 5; _context.t0 = _context["catch"](0); - case 7: delete channelState.otherReaderClients[readerUuid]; - case 8: case "end": return _context.stop(); @@ -939,12 +799,12 @@ function refreshReaderClients(channelState) { } }, _callee, null, [[0, 5]]); })); - return function (_x22) { return _ref.apply(this, arguments); }; - }()); // add new readers + }()); + // add new readers return Promise.all(otherReaders.filter(function (readerUuid) { return readerUuid !== channelState.uuid; }) // not own @@ -959,37 +819,29 @@ function refreshReaderClients(channelState) { switch (_context2.prev = _context2.next) { case 0: _context2.prev = 0; - if (!channelState.closed) { _context2.next = 3; break; } - return _context2.abrupt("return"); - case 3: _context2.prev = 3; _context2.next = 6; return openClientConnection(channelState.channelName, readerUuid); - case 6: client = _context2.sent; channelState.otherReaderClients[readerUuid] = client; _context2.next = 12; break; - case 10: _context2.prev = 10; _context2.t0 = _context2["catch"](3); - case 12: _context2.next = 16; break; - case 14: _context2.prev = 14; _context2.t1 = _context2["catch"](0); - case 16: case "end": return _context2.stop(); @@ -997,19 +849,17 @@ function refreshReaderClients(channelState) { } }, _callee2, null, [[0, 14], [3, 10]]); })); - return function (_x23) { return _ref2.apply(this, arguments); }; }())); }); } + /** * post a message to the other readers * @return {Promise} */ - - function postMessage(_x24, _x25) { return _postMessage.apply(this, arguments); } @@ -1019,8 +869,6 @@ function postMessage(_x24, _x25) { * This might not happen often in production * but will speed up things when this module is used in unit-tests. */ - - function _postMessage() { _postMessage = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee19(channelState, messageJson) { var writePromise; @@ -1033,7 +881,6 @@ function _postMessage() { }); channelState.writeBlockPromise = channelState.writeBlockPromise.then( /*#__PURE__*/(0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee18() { var _yield$Promise$all2, msgObj, pingStr, writeToReadersPromise; - return _regenerator["default"].wrap(function _callee18$(_context18) { while (1) { switch (_context18.prev = _context18.next) { @@ -1042,11 +889,9 @@ function _postMessage() { return new Promise(function (res) { return setTimeout(res, 0); }); - case 2: _context18.next = 4; return Promise.all([writePromise, refreshReaderClients(channelState)]); - case 4: _yield$Promise$all2 = _context18.sent; msgObj = _yield$Promise$all2[0]; @@ -1065,16 +910,13 @@ function _postMessage() { * to not waste resources on cleaning up, * only if random-int matches, we clean up old messages */ - if ((0, _util2.randomInt)(0, 20) === 0) { /* await */ getAllMessages(channelState.channelName, channelState.paths).then(function (allMessages) { return cleanOldMessages(allMessages, channelState.options.node.ttl); }); } - return _context18.abrupt("return", writeToReadersPromise); - case 11: case "end": return _context18.stop(); @@ -1083,7 +925,6 @@ function _postMessage() { }, _callee18); }))); return _context19.abrupt("return", channelState.writeBlockPromise); - case 3: case "end": return _context19.stop(); @@ -1093,13 +934,11 @@ function _postMessage() { })); return _postMessage.apply(this, arguments); } - function emitOverFastPath(state, msgObj, messageJson) { if (!state.options.node.useFastPath) { // disabled return; } - var others = OTHER_INSTANCES[state.channelName].filter(function (s) { return s !== state; }); @@ -1114,19 +953,17 @@ function emitOverFastPath(state, msgObj, messageJson) { otherState.messagesCallback(messageJson); }); } - function onMessage(channelState, fn) { var time = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : microSeconds(); channelState.messagesCallbackTime = time; channelState.messagesCallback = fn; handleMessagePing(channelState); } + /** * closes the channel * @return {Promise} */ - - function close(channelState) { if (channelState.closed) return; channelState.closed = true; @@ -1134,35 +971,30 @@ function close(channelState) { OTHER_INSTANCES[channelState.channelName] = OTHER_INSTANCES[channelState.channelName].filter(function (o) { return o !== channelState; }); - if (channelState.removeUnload) { channelState.removeUnload.remove(); } - return new Promise(function (res) { if (channelState.socketEE) channelState.socketEE.emitter.removeAllListeners(); Object.values(channelState.otherReaderClients).forEach(function (client) { return client.destroy(); }); - if (channelState.infoFilePath) { try { _fs["default"].unlinkSync(channelState.infoFilePath); } catch (err) {} } + /** * the server get closed lazy because others might still write on it * and have not found out that the infoFile was deleted */ - - setTimeout(function () { channelState.socketEE.server.close(); res(); }, 200); }); } - function canBeUsed() { if (typeof _fs["default"].mkdir === 'function') { return true; @@ -1170,23 +1002,20 @@ function canBeUsed() { return false; } } + /** * on node we use a relatively height averageResponseTime, * because the file-io might be in use. * Also it is more important that the leader-election is reliable, * then to have a fast election. */ - - function averageResponseTime() { return 200; } - function microSeconds() { // convert nano to micro seconds return parseInt(now() / 1000); } - function now() { return Number(process.hrtime.bigint()); // returns nanoseconds } \ No newline at end of file diff --git a/dist/es5node/methods/simulate.js b/dist/es5node/methods/simulate.js index f0ce13c5..46d5cfde 100644 --- a/dist/es5node/methods/simulate.js +++ b/dist/es5node/methods/simulate.js @@ -11,15 +11,12 @@ exports.microSeconds = exports["default"] = void 0; exports.onMessage = onMessage; exports.postMessage = postMessage; exports.type = void 0; - var _util = require("../util.js"); - var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var type = 'simulate'; exports.type = type; var SIMULATE_CHANNELS = new Set(); - function create(channelName) { var state = { name: channelName, @@ -28,11 +25,9 @@ function create(channelName) { SIMULATE_CHANNELS.add(state); return state; } - function close(channelState) { SIMULATE_CHANNELS["delete"](channelState); } - function postMessage(channelState, messageJson) { return new Promise(function (res) { return setTimeout(function () { @@ -50,19 +45,15 @@ function postMessage(channelState, messageJson) { }, 5); }); } - function onMessage(channelState, fn) { channelState.messagesCallback = fn; } - function canBeUsed() { return true; } - function averageResponseTime() { return 5; } - var _default = { create: create, close: close, diff --git a/dist/es5node/options.js b/dist/es5node/options.js index c8435340..36c0e4e8 100644 --- a/dist/es5node/options.js +++ b/dist/es5node/options.js @@ -4,33 +4,35 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.fillOptionsWithDefaults = fillOptionsWithDefaults; - function fillOptionsWithDefaults() { var originalOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var options = JSON.parse(JSON.stringify(originalOptions)); // main - - if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true; // indexed-db + var options = JSON.parse(JSON.stringify(originalOptions)); - if (!options.idb) options.idb = {}; // after this time the messages get deleted + // main + if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true; + // indexed-db + if (!options.idb) options.idb = {}; + // after this time the messages get deleted if (!options.idb.ttl) options.idb.ttl = 1000 * 45; - if (!options.idb.fallbackInterval) options.idb.fallbackInterval = 150; // handles abrupt db onclose events. - - if (originalOptions.idb && typeof originalOptions.idb.onclose === 'function') options.idb.onclose = originalOptions.idb.onclose; // localstorage + if (!options.idb.fallbackInterval) options.idb.fallbackInterval = 150; + // handles abrupt db onclose events. + if (originalOptions.idb && typeof originalOptions.idb.onclose === 'function') options.idb.onclose = originalOptions.idb.onclose; + // localstorage if (!options.localstorage) options.localstorage = {}; - if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60; // custom methods + if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60; - if (originalOptions.methods) options.methods = originalOptions.methods; // node + // custom methods + if (originalOptions.methods) options.methods = originalOptions.methods; + // node if (!options.node) options.node = {}; if (!options.node.ttl) options.node.ttl = 1000 * 60 * 2; // 2 minutes; - /** * On linux use 'ulimit -Hn' to get the limit of open files. * On ubuntu this was 4096 for me, so we use half of that as maxParallelWrites default. */ - if (!options.node.maxParallelWrites) options.node.maxParallelWrites = 2048; if (typeof options.node.useFastPath === 'undefined') options.node.useFastPath = true; return options; diff --git a/dist/es5node/util.js b/dist/es5node/util.js index bf51ea5e..c63716c9 100644 --- a/dist/es5node/util.js +++ b/dist/es5node/util.js @@ -9,7 +9,6 @@ exports.microSeconds = microSeconds; exports.randomInt = randomInt; exports.randomToken = randomToken; exports.sleep = sleep; - /** * returns true if the given object is a promise */ @@ -20,14 +19,12 @@ function isPromise(obj) { return false; } } - var PROMISE_RESOLVED_FALSE = Promise.resolve(false); exports.PROMISE_RESOLVED_FALSE = PROMISE_RESOLVED_FALSE; var PROMISE_RESOLVED_TRUE = Promise.resolve(true); exports.PROMISE_RESOLVED_TRUE = PROMISE_RESOLVED_TRUE; var PROMISE_RESOLVED_VOID = Promise.resolve(); exports.PROMISE_RESOLVED_VOID = PROMISE_RESOLVED_VOID; - function sleep(time, resolveWith) { if (!time) time = 0; return new Promise(function (res) { @@ -36,21 +33,19 @@ function sleep(time, resolveWith) { }, time); }); } - function randomInt(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } + /** * https://stackoverflow.com/a/8084248 */ - - function randomToken() { return Math.random().toString(36).substring(2); } - var lastMs = 0; var additional = 0; + /** * returns the current time in micro-seconds, * WARNING: This is a pseudo-function @@ -58,10 +53,8 @@ var additional = 0; * This is enough in browsers, and this function will not be used in nodejs. * The main reason for this hack is to ensure that BroadcastChannel behaves equal to production when it is used in fast-running unit tests. */ - function microSeconds() { var ms = new Date().getTime(); - if (ms === lastMs) { additional++; return ms * 1000 + additional; diff --git a/dist/esbrowser/broadcast-channel.js b/dist/esbrowser/broadcast-channel.js index 09502c09..d62e7a95 100644 --- a/dist/esbrowser/broadcast-channel.js +++ b/dist/esbrowser/broadcast-channel.js @@ -1,11 +1,11 @@ import { isPromise, PROMISE_RESOLVED_FALSE, PROMISE_RESOLVED_VOID } from './util.js'; import { chooseMethod } from './method-chooser.js'; import { fillOptionsWithDefaults } from './options.js'; + /** * Contains all open channels, * used in tests to ensure everything is closed. */ - export var OPEN_BROADCAST_CHANNELS = new Set(); var lastId = 0; export var BroadcastChannel = function BroadcastChannel(name, options) { @@ -13,69 +13,67 @@ export var BroadcastChannel = function BroadcastChannel(name, options) { this.id = lastId++; OPEN_BROADCAST_CHANNELS.add(this); this.name = name; - if (ENFORCED_OPTIONS) { options = ENFORCED_OPTIONS; } - this.options = fillOptionsWithDefaults(options); - this.method = chooseMethod(this.options); // isListening + this.method = chooseMethod(this.options); + // isListening this._iL = false; + /** * _onMessageListener * setting onmessage twice, * will overwrite the first listener */ - this._onML = null; + /** * _addEventListeners */ - this._addEL = { message: [], internal: [] }; + /** * Unsend message promises * where the sending is still in progress * @type {Set} */ - this._uMP = new Set(); + /** * _beforeClose * array of promises that will be awaited * before the channel is closed */ - this._befC = []; + /** * _preparePromise */ - this._prepP = null; - _prepareChannel(this); -}; // STATICS +}; + +// STATICS /** * used to identify if someone overwrites * window.BroadcastChannel with this * See methods/native.js */ - BroadcastChannel._pubkey = true; + /** * clears the tmp-folder if is node * @return {Promise} true if has run, false if not node */ - export function clearNodeFolder(options) { options = fillOptionsWithDefaults(options); var method = chooseMethod(options); - if (method.type === 'node') { return method.clearNodeFolder().then(function () { return true; @@ -84,16 +82,17 @@ export function clearNodeFolder(options) { return PROMISE_RESOLVED_FALSE; } } + /** * if set, this method is enforced, * no mather what the options are */ - var ENFORCED_OPTIONS; export function enforceOptions(options) { ENFORCED_OPTIONS = options; -} // PROTOTYPE +} +// PROTOTYPE BroadcastChannel.prototype = { postMessage: function postMessage(msg) { if (this.closed) { @@ -105,87 +104,77 @@ BroadcastChannel.prototype = { */ JSON.stringify(msg)); } - return _post(this, 'message', msg); }, postInternal: function postInternal(msg) { return _post(this, 'internal', msg); }, - set onmessage(fn) { var time = this.method.microSeconds(); var listenObj = { time: time, fn: fn }; - _removeListenerObject(this, 'message', this._onML); - if (fn && typeof fn === 'function') { this._onML = listenObj; - _addListenerObject(this, 'message', listenObj); } else { this._onML = null; } }, - addEventListener: function addEventListener(type, fn) { var time = this.method.microSeconds(); var listenObj = { time: time, fn: fn }; - _addListenerObject(this, type, listenObj); }, removeEventListener: function removeEventListener(type, fn) { var obj = this._addEL[type].find(function (obj) { return obj.fn === fn; }); - _removeListenerObject(this, type, obj); }, close: function close() { var _this = this; - if (this.closed) { return; } - OPEN_BROADCAST_CHANNELS["delete"](this); this.closed = true; var awaitPrepare = this._prepP ? this._prepP : PROMISE_RESOLVED_VOID; this._onML = null; this._addEL.message = []; - return awaitPrepare // wait until all current sending are processed + return awaitPrepare + // wait until all current sending are processed .then(function () { return Promise.all(Array.from(_this._uMP)); - }) // run before-close hooks + }) + // run before-close hooks .then(function () { return Promise.all(_this._befC.map(function (fn) { return fn(); })); - }) // close the channel + }) + // close the channel .then(function () { return _this.method.close(_this._state); }); }, - get type() { return this.method.type; }, - get isClosed() { return this.closed; } - }; + /** * Post a message over the channel * @returns {Promise} that resolved when the message sending is done */ - function _post(broadcastChannel, type, msg) { var time = broadcastChannel.method.microSeconds(); var msgObj = { @@ -195,25 +184,22 @@ function _post(broadcastChannel, type, msg) { }; var awaitPrepare = broadcastChannel._prepP ? broadcastChannel._prepP : PROMISE_RESOLVED_VOID; return awaitPrepare.then(function () { - var sendPromise = broadcastChannel.method.postMessage(broadcastChannel._state, msgObj); // add/remove to unsend messages list + var sendPromise = broadcastChannel.method.postMessage(broadcastChannel._state, msgObj); + // add/remove to unsend messages list broadcastChannel._uMP.add(sendPromise); - sendPromise["catch"]().then(function () { return broadcastChannel._uMP["delete"](sendPromise); }); return sendPromise; }); } - function _prepareChannel(channel) { var maybePromise = channel.method.create(channel.name, channel.options); - if (isPromise(maybePromise)) { channel._prepP = maybePromise; maybePromise.then(function (s) { // used in tests to simulate slow runtime - /*if (channel.options.prepareDelay) { await new Promise(res => setTimeout(res, this.options.prepareDelay)); }*/ @@ -223,30 +209,25 @@ function _prepareChannel(channel) { channel._state = maybePromise; } } - function _hasMessageListeners(channel) { if (channel._addEL.message.length > 0) return true; if (channel._addEL.internal.length > 0) return true; return false; } - function _addListenerObject(channel, type, obj) { channel._addEL[type].push(obj); - _startListening(channel); } - function _removeListenerObject(channel, type, obj) { channel._addEL[type] = channel._addEL[type].filter(function (o) { return o !== obj; }); - _stopListening(channel); } - function _startListening(channel) { if (!channel._iL && _hasMessageListeners(channel)) { // someone is listening, start subscribing + var listenerFn = function listenerFn(msgObj) { channel._addEL[msgObj.type].forEach(function (listenerObject) { /** @@ -260,15 +241,12 @@ function _startListening(channel) { */ var hundredMsInMicro = 100 * 1000; var minMessageTime = listenerObject.time - hundredMsInMicro; - if (msgObj.time >= minMessageTime) { listenerObject.fn(msgObj.data); } }); }; - var time = channel.method.microSeconds(); - if (channel._prepP) { channel._prepP.then(function () { channel._iL = true; @@ -280,7 +258,6 @@ function _startListening(channel) { } } } - function _stopListening(channel) { if (channel._iL && !_hasMessageListeners(channel)) { // noone is listening, stop subscribing diff --git a/dist/esbrowser/browserify.index.js b/dist/esbrowser/browserify.index.js index e500d3df..bd935158 100644 --- a/dist/esbrowser/browserify.index.js +++ b/dist/esbrowser/browserify.index.js @@ -1,5 +1,4 @@ var module = require('./index.es5.js'); - var BroadcastChannel = module.BroadcastChannel; var createLeaderElection = module.createLeaderElection; window['BroadcastChannel2'] = BroadcastChannel; diff --git a/dist/esbrowser/index.es5.js b/dist/esbrowser/index.es5.js index 4d6b827b..60f0b76e 100644 --- a/dist/esbrowser/index.es5.js +++ b/dist/esbrowser/index.es5.js @@ -6,6 +6,7 @@ * but * var BroadcastChannel = require('broadcast-channel'); */ + import { BroadcastChannel, createLeaderElection, clearNodeFolder, enforceOptions, beLeader } from './index.js'; module.exports = { BroadcastChannel: BroadcastChannel, diff --git a/dist/esbrowser/leader-election.js b/dist/esbrowser/leader-election.js index c8c9cbb4..3b2c615c 100644 --- a/dist/esbrowser/leader-election.js +++ b/dist/esbrowser/leader-election.js @@ -1,32 +1,27 @@ import { sleep, randomToken, PROMISE_RESOLVED_VOID, PROMISE_RESOLVED_TRUE } from './util.js'; import { add as unloadAdd } from 'unload'; - var LeaderElection = function LeaderElection(broadcastChannel, options) { var _this = this; - this.broadcastChannel = broadcastChannel; this._options = options; this.isLeader = false; this.hasLeader = false; this.isDead = false; this.token = randomToken(); + /** * Apply Queue, * used to ensure we do not run applyOnce() * in parallel. */ + this._aplQ = PROMISE_RESOLVED_VOID; + // amount of unfinished applyOnce() calls + this._aplQC = 0; - this._aplQ = PROMISE_RESOLVED_VOID; // amount of unfinished applyOnce() calls - - this._aplQC = 0; // things to clean up - + // things to clean up this._unl = []; // _unloads - this._lstns = []; // _listeners - this._dpL = function () {}; // onduplicate listener - - this._dpLC = false; // true when onduplicate called /** @@ -34,55 +29,47 @@ var LeaderElection = function LeaderElection(broadcastChannel, options) { * we still listen to messages to ensure the hasLeader flag * is set correctly. */ - var hasLeaderListener = function hasLeaderListener(msg) { if (msg.context === 'leader') { if (msg.action === 'death') { _this.hasLeader = false; } - if (msg.action === 'tell') { _this.hasLeader = true; } } }; - this.broadcastChannel.addEventListener('internal', hasLeaderListener); - this._lstns.push(hasLeaderListener); }; - LeaderElection.prototype = { /** * Returns true if the instance is leader, * false if not. * @async */ - applyOnce: function applyOnce( // true if the applyOnce() call came from the fallbackInterval cycle + applyOnce: function applyOnce( + // true if the applyOnce() call came from the fallbackInterval cycle isFromFallbackInterval) { var _this2 = this; - if (this.isLeader) { return sleep(0, true); } - if (this.isDead) { return sleep(0, false); } + /** * Already applying more then once, * -> wait for the apply queue to be finished. */ - - if (this._aplQC > 1) { return this._aplQ; } + /** * Add a new apply-run */ - - var applyRun = function applyRun() { /** * Optimization shortcuts. @@ -92,7 +79,6 @@ LeaderElection.prototype = { if (_this2.isLeader) { return PROMISE_RESOLVED_TRUE; } - var stopCriteria = false; var stopCriteriaPromiseResolve; /** @@ -101,7 +87,6 @@ LeaderElection.prototype = { * have to await the responseTime when it is already clear * that the election failed. */ - var stopCriteriaPromise = new Promise(function (res) { stopCriteriaPromiseResolve = function stopCriteriaPromiseResolve() { stopCriteria = true; @@ -109,11 +94,9 @@ LeaderElection.prototype = { }; }); var recieved = []; - var handleMessage = function handleMessage(msg) { if (msg.context === 'leader' && msg.token != _this2.token) { recieved.push(msg); - if (msg.action === 'apply') { // other is applying if (msg.token > _this2.token) { @@ -124,7 +107,6 @@ LeaderElection.prototype = { stopCriteriaPromiseResolve(); } } - if (msg.action === 'tell') { // other is already leader stopCriteriaPromiseResolve(); @@ -132,8 +114,8 @@ LeaderElection.prototype = { } } }; - _this2.broadcastChannel.addEventListener('internal', handleMessage); + /** * If the applyOnce() call came from the fallbackInterval, * we can assume that the election runs in the background and @@ -144,26 +126,24 @@ LeaderElection.prototype = { * But also it takes longer which is not a problem because we anyway * run in the background. */ - - var waitForAnswerTime = isFromFallbackInterval ? _this2._options.responseTime * 4 : _this2._options.responseTime; - var applyPromise = _sendMessage(_this2, 'apply') // send out that this one is applying .then(function () { return Promise.race([sleep(waitForAnswerTime), stopCriteriaPromise.then(function () { return Promise.reject(new Error()); })]); - }) // send again in case another instance was just created + }) + // send again in case another instance was just created .then(function () { return _sendMessage(_this2, 'apply'); - }) // let others time to respond + }) + // let others time to respond .then(function () { return Promise.race([sleep(waitForAnswerTime), stopCriteriaPromise.then(function () { return Promise.reject(new Error()); })]); })["catch"](function () {}).then(function () { _this2.broadcastChannel.removeEventListener('internal', handleMessage); - if (!stopCriteria) { // no stop criteria -> own is leader return beLeader(_this2).then(function () { @@ -174,10 +154,8 @@ LeaderElection.prototype = { return false; } }); - return applyPromise; }; - this._aplQC = this._aplQC + 1; this._aplQ = this._aplQ.then(function () { return applyRun(); @@ -189,82 +167,68 @@ LeaderElection.prototype = { }); }, awaitLeadership: function awaitLeadership() { - if ( - /* _awaitLeadershipPromise */ + if ( /* _awaitLeadershipPromise */ !this._aLP) { this._aLP = _awaitLeadershipOnce(this); } - return this._aLP; }, - set onduplicate(fn) { this._dpL = fn; }, - die: function die() { var _this3 = this; - this._lstns.forEach(function (listener) { return _this3.broadcastChannel.removeEventListener('internal', listener); }); - this._lstns = []; - this._unl.forEach(function (uFn) { return uFn.remove(); }); - this._unl = []; - if (this.isLeader) { this.hasLeader = false; this.isLeader = false; } - this.isDead = true; return _sendMessage(this, 'death'); } }; + /** * @param leaderElector {LeaderElector} */ - function _awaitLeadershipOnce(leaderElector) { if (leaderElector.isLeader) { return PROMISE_RESOLVED_VOID; } - return new Promise(function (res) { var resolved = false; - function finish() { if (resolved) { return; } - resolved = true; leaderElector.broadcastChannel.removeEventListener('internal', whenDeathListener); res(true); - } // try once now - + } + // try once now leaderElector.applyOnce().then(function () { if (leaderElector.isLeader) { finish(); } }); + /** * Try on fallbackInterval * @recursive */ - var tryOnFallBack = function tryOnFallBack() { return sleep(leaderElector._options.fallbackInterval).then(function () { if (leaderElector.isDead || resolved) { return; } - if (leaderElector.isLeader) { finish(); } else { @@ -278,9 +242,9 @@ function _awaitLeadershipOnce(leaderElector) { } }); }; + tryOnFallBack(); - tryOnFallBack(); // try when other leader dies - + // try when other leader dies var whenDeathListener = function whenDeathListener(msg) { if (msg.context === 'leader' && msg.action === 'death') { leaderElector.hasLeader = false; @@ -291,17 +255,14 @@ function _awaitLeadershipOnce(leaderElector) { }); } }; - leaderElector.broadcastChannel.addEventListener('internal', whenDeathListener); - leaderElector._lstns.push(whenDeathListener); }); } + /** * sends and internal message over the broadcast-channel */ - - function _sendMessage(leaderElector, action) { var msgJson = { context: 'leader', @@ -310,21 +271,17 @@ function _sendMessage(leaderElector, action) { }; return leaderElector.broadcastChannel.postInternal(msgJson); } - export function beLeader(leaderElector) { leaderElector.isLeader = true; leaderElector.hasLeader = true; var unloadFn = unloadAdd(function () { return leaderElector.die(); }); - leaderElector._unl.push(unloadFn); - var isLeaderListener = function isLeaderListener(msg) { if (msg.context === 'leader' && msg.action === 'apply') { _sendMessage(leaderElector, 'tell'); } - if (msg.context === 'leader' && msg.action === 'tell' && !leaderElector._dpLC) { /** * another instance is also leader! @@ -335,49 +292,35 @@ export function beLeader(leaderElector) { * @link https://github.com/pubkey/broadcast-channel/issues/385 */ leaderElector._dpLC = true; - leaderElector._dpL(); // message the lib user so the app can handle the problem - - _sendMessage(leaderElector, 'tell'); // ensure other leader also knows the problem - } }; leaderElector.broadcastChannel.addEventListener('internal', isLeaderListener); - leaderElector._lstns.push(isLeaderListener); - return _sendMessage(leaderElector, 'tell'); } - function fillOptionsWithDefaults(options, channel) { if (!options) options = {}; options = JSON.parse(JSON.stringify(options)); - if (!options.fallbackInterval) { options.fallbackInterval = 3000; } - if (!options.responseTime) { options.responseTime = channel.method.averageResponseTime(channel.options); } - return options; } - export function createLeaderElection(channel, options) { if (channel._leaderElector) { throw new Error('BroadcastChannel already has a leader-elector'); } - options = fillOptionsWithDefaults(options, channel); var elector = new LeaderElection(channel, options); - channel._befC.push(function () { return elector.die(); }); - channel._leaderElector = elector; return elector; } \ No newline at end of file diff --git a/dist/esbrowser/method-chooser.js b/dist/esbrowser/method-chooser.js index 4ab50593..74f4bda3 100644 --- a/dist/esbrowser/method-chooser.js +++ b/dist/esbrowser/method-chooser.js @@ -1,38 +1,39 @@ import NativeMethod from './methods/native.js'; import IndexeDbMethod from './methods/indexed-db.js'; import LocalstorageMethod from './methods/localstorage.js'; -import SimulateMethod from './methods/simulate.js'; // the line below will be removed from es5/browser builds +import SimulateMethod from './methods/simulate.js'; +// the line below will be removed from es5/browser builds - -var METHODS = [NativeMethod, // fastest +// order is important +var METHODS = [NativeMethod, +// fastest IndexeDbMethod, LocalstorageMethod]; export function chooseMethod(options) { - var chooseMethods = [].concat(options.methods, METHODS).filter(Boolean); // the line below will be removed from es5/browser builds + var chooseMethods = [].concat(options.methods, METHODS).filter(Boolean); + // the line below will be removed from es5/browser builds + // directly chosen if (options.type) { if (options.type === 'simulate') { // only use simulate-method if directly chosen return SimulateMethod; } - var ret = chooseMethods.find(function (m) { return m.type === options.type; }); if (!ret) throw new Error('method-type ' + options.type + ' not found');else return ret; } + /** * if no webworker support is needed, * remove idb from the list so that localstorage is been chosen */ - - if (!options.webWorkerSupport) { chooseMethods = chooseMethods.filter(function (m) { return m.type !== 'idb'; }); } - var useMethod = chooseMethods.find(function (method) { return method.canBeUsed(); }); diff --git a/dist/esbrowser/methods/indexed-db.js b/dist/esbrowser/methods/indexed-db.js index 75f983c0..24727d1a 100644 --- a/dist/esbrowser/methods/indexed-db.js +++ b/dist/esbrowser/methods/indexed-db.js @@ -6,55 +6,54 @@ * When working on this, ensure to use these performance optimizations: * @link https://rxdb.info/slow-indexeddb.html */ + import { sleep, randomInt, randomToken, microSeconds as micro, PROMISE_RESOLVED_VOID } from '../util.js'; export var microSeconds = micro; import { ObliviousSet } from 'oblivious-set'; import { fillOptionsWithDefaults } from '../options.js'; var DB_PREFIX = 'pubkey.broadcast-channel-0-'; var OBJECT_STORE_ID = 'messages'; + /** * Use relaxed durability for faster performance on all transactions. * @link https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/ */ - export var TRANSACTION_SETTINGS = { durability: 'relaxed' }; export var type = 'idb'; export function getIdb() { if (typeof indexedDB !== 'undefined') return indexedDB; - if (typeof window !== 'undefined') { if (typeof window.mozIndexedDB !== 'undefined') return window.mozIndexedDB; if (typeof window.webkitIndexedDB !== 'undefined') return window.webkitIndexedDB; if (typeof window.msIndexedDB !== 'undefined') return window.msIndexedDB; } - return false; } + /** * If possible, we should explicitly commit IndexedDB transactions * for better performance. * @link https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/ */ - export function commitIndexedDBTransaction(tx) { if (tx.commit) { tx.commit(); } } export function createDatabase(channelName) { - var IndexedDB = getIdb(); // create table + var IndexedDB = getIdb(); + // create table var dbName = DB_PREFIX + channelName; + /** * All IndexedDB databases are opened without version * because it is a bit faster, especially on firefox * @link http://nparashuram.com/IndexedDB/perf/#Open%20Database%20with%20version */ - var openRequest = IndexedDB.open(dbName); - openRequest.onupgradeneeded = function (ev) { var db = ev.target.result; db.createObjectStore(OBJECT_STORE_ID, { @@ -62,23 +61,21 @@ export function createDatabase(channelName) { autoIncrement: true }); }; - var dbPromise = new Promise(function (res, rej) { openRequest.onerror = function (ev) { return rej(ev); }; - openRequest.onsuccess = function () { res(openRequest.result); }; }); return dbPromise; } + /** * writes the new message to the database * so other readers can find it */ - export function writeMessage(db, readerUuid, messageJson) { var time = new Date().getTime(); var writeObject = { @@ -91,11 +88,9 @@ export function writeMessage(db, readerUuid, messageJson) { tx.oncomplete = function () { return res(); }; - tx.onerror = function (ev) { return rej(ev); }; - var objectStore = tx.objectStore(OBJECT_STORE_ID); objectStore.add(writeObject); commitIndexedDBTransaction(tx); @@ -108,10 +103,9 @@ export function getAllMessages(db) { return new Promise(function (res) { objectStore.openCursor().onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { - ret.push(cursor.value); //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); - + ret.push(cursor.value); + //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); cursor["continue"](); } else { commitIndexedDBTransaction(tx); @@ -125,25 +119,23 @@ export function getMessagesHigherThan(db, lastCursorId) { var objectStore = tx.objectStore(OBJECT_STORE_ID); var ret = []; var keyRangeValue = IDBKeyRange.bound(lastCursorId + 1, Infinity); + /** * Optimization shortcut, * if getAll() can be used, do not use a cursor. * @link https://rxdb.info/slow-indexeddb.html */ - if (objectStore.getAll) { var getAllRequest = objectStore.getAll(keyRangeValue); return new Promise(function (res, rej) { getAllRequest.onerror = function (err) { return rej(err); }; - getAllRequest.onsuccess = function (e) { res(e.target.result); }; }); } - function openCursor() { // Occasionally Safari will fail on IDBKeyRange.bound, this // catches that error, having it open the cursor to the first @@ -155,17 +147,13 @@ export function getMessagesHigherThan(db, lastCursorId) { return objectStore.openCursor(); } } - return new Promise(function (res, rej) { var openCursorRequest = openCursor(); - openCursorRequest.onerror = function (err) { return rej(err); }; - openCursorRequest.onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { if (cursor.value.id < lastCursorId + 1) { cursor["continue"](lastCursorId + 1); @@ -184,7 +172,6 @@ export function removeMessagesById(channelState, ids) { if (channelState.closed) { return Promise.resolve([]); } - var tx = channelState.db.transaction(OBJECT_STORE_ID, 'readwrite', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); return Promise.all(ids.map(function (id) { @@ -204,13 +191,11 @@ export function getOldMessages(db, ttl) { return new Promise(function (res) { objectStore.openCursor().onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { var msgObk = cursor.value; - if (msgObk.time < olderThen) { - ret.push(msgObk); //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); - + ret.push(msgObk); + //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); cursor["continue"](); } else { // no more old messages, @@ -240,7 +225,6 @@ export function create(channelName, options) { channelName: channelName, options: options, uuid: randomToken(), - /** * emittedMessagesIds * contains all messages that have been emitted before @@ -253,30 +237,27 @@ export function create(channelName, options) { readQueuePromises: [], db: db }; + /** * Handle abrupt closes that do not originate from db.close(). * This could happen, for example, if the underlying storage is * removed or if the user clears the database in the browser's * history preferences. */ - db.onclose = function () { state.closed = true; if (options.idb.onclose) options.idb.onclose(); }; + /** * if service-workers are used, * we have no 'storage'-event if they post a message, * therefore we also have to set an interval */ - - _readLoop(state); - return state; }); } - function _readLoop(state) { if (state.closed) return; readNewMessages(state).then(function () { @@ -285,25 +266,21 @@ function _readLoop(state) { return _readLoop(state); }); } - function _filterMessage(msgObj, state) { if (msgObj.uuid === state.uuid) return false; // send by own - if (state.eMIs.has(msgObj.id)) return false; // already emitted - if (msgObj.data.time < state.messagesCallbackTime) return false; // older then onMessageCallback - return true; } + /** * reads all new messages from the database and emits them */ - - function readNewMessages(state) { // channel already closed - if (state.closed) return PROMISE_RESOLVED_VOID; // if no one is listening, we do not need to scan for new messages + if (state.closed) return PROMISE_RESOLVED_VOID; + // if no one is listening, we do not need to scan for new messages if (!state.messagesCallback) return PROMISE_RESOLVED_VOID; return getMessagesHigherThan(state.db, state.lastCursorId).then(function (newerMessages) { var useMessages = newerMessages @@ -311,21 +288,18 @@ function readNewMessages(state) { * there is a bug in iOS where the msgObj can be undefined some times * so we filter them out * @link https://github.com/pubkey/broadcast-channel/issues/19 - */ - .filter(function (msgObj) { + */.filter(function (msgObj) { return !!msgObj; }).map(function (msgObj) { if (msgObj.id > state.lastCursorId) { state.lastCursorId = msgObj.id; } - return msgObj; }).filter(function (msgObj) { return _filterMessage(msgObj, state); }).sort(function (msgObjA, msgObjB) { return msgObjA.time - msgObjB.time; }); // sort by time - useMessages.forEach(function (msgObj) { if (state.messagesCallback) { state.eMIs.add(msgObj.id); @@ -335,7 +309,6 @@ function readNewMessages(state) { return PROMISE_RESOLVED_VOID; }); } - export function close(channelState) { channelState.closed = true; channelState.db.close(); @@ -358,11 +331,9 @@ export function onMessage(channelState, fn, time) { } export function canBeUsed() { var idb = getIdb(); - if (!idb) { return false; } - return true; } export function averageResponseTime(options) { diff --git a/dist/esbrowser/methods/localstorage.js b/dist/esbrowser/methods/localstorage.js index 5a4adcc6..a0bb80e5 100644 --- a/dist/esbrowser/methods/localstorage.js +++ b/dist/esbrowser/methods/localstorage.js @@ -5,39 +5,39 @@ * @link https://caniuse.com/#feat=namevalue-storage * @link https://caniuse.com/#feat=indexeddb */ + import { ObliviousSet } from 'oblivious-set'; import { fillOptionsWithDefaults } from '../options.js'; import { sleep, randomToken, microSeconds as micro } from '../util.js'; export var microSeconds = micro; var KEY_PREFIX = 'pubkey.broadcastChannel-'; export var type = 'localstorage'; + /** * copied from crosstab * @link https://github.com/tejacques/crosstab/blob/master/src/crosstab.js#L32 */ - export function getLocalStorage() { var localStorage; if (typeof window === 'undefined') return null; - try { localStorage = window.localStorage; localStorage = window['ie8-eventlistener/storage'] || window.localStorage; - } catch (e) {// New versions of Firefox throw a Security exception + } catch (e) { + // New versions of Firefox throw a Security exception // if cookies are disabled. See // https://bugzilla.mozilla.org/show_bug.cgi?id=1028153 } - return localStorage; } export function storageKey(channelName) { return KEY_PREFIX + channelName; } + /** * writes the new message to the storage * and fires the storage-event so other readers can find it */ - export function postMessage(channelState, messageJson) { return new Promise(function (res) { sleep().then(function () { @@ -50,12 +50,12 @@ export function postMessage(channelState, messageJson) { }; var value = JSON.stringify(writeObj); getLocalStorage().setItem(key, value); + /** * StorageEvent does not fire the 'storage' event * in the window that changes the state of the local storage. * So we fire it manually */ - var ev = document.createEvent('Event'); ev.initEvent('storage', true, true); ev.key = key; @@ -67,13 +67,11 @@ export function postMessage(channelState, messageJson) { } export function addStorageEventListener(channelName, fn) { var key = storageKey(channelName); - var listener = function listener(ev) { if (ev.key === key) { fn(JSON.parse(ev.newValue)); } }; - window.addEventListener('storage', listener); return listener; } @@ -82,32 +80,27 @@ export function removeStorageEventListener(listener) { } export function create(channelName, options) { options = fillOptionsWithDefaults(options); - if (!canBeUsed()) { throw new Error('BroadcastChannel: localstorage cannot be used'); } - var uuid = randomToken(); + /** * eMIs * contains all messages that have been emitted before * @type {ObliviousSet} */ - var eMIs = new ObliviousSet(options.localstorage.removeTimeout); var state = { channelName: channelName, uuid: uuid, eMIs: eMIs // emittedMessagesIds - }; + state.listener = addStorageEventListener(channelName, function (msgObj) { if (!state.messagesCallback) return; // no listener - if (msgObj.uuid === uuid) return; // own message - if (!msgObj.token || eMIs.has(msgObj.token)) return; // already emitted - if (msgObj.data.time && msgObj.data.time < state.messagesCallbackTime) return; // too old eMIs.add(msgObj.token); @@ -125,7 +118,6 @@ export function onMessage(channelState, fn, time) { export function canBeUsed() { var ls = getLocalStorage(); if (!ls) return false; - try { var key = '__broadcastchannel_check'; ls.setItem(key, 'works'); @@ -136,18 +128,15 @@ export function canBeUsed() { // https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API#Private_Browsing_Incognito_modes return false; } - return true; } export function averageResponseTime() { var defaultTime = 120; var userAgent = navigator.userAgent.toLowerCase(); - if (userAgent.includes('safari') && !userAgent.includes('chrome')) { // safari is much slower so this time is higher return defaultTime * 2; } - return defaultTime; } export default { diff --git a/dist/esbrowser/methods/native.js b/dist/esbrowser/methods/native.js index 6841c45b..6e45b272 100644 --- a/dist/esbrowser/methods/native.js +++ b/dist/esbrowser/methods/native.js @@ -6,7 +6,6 @@ export function create(channelName) { messagesCallback: null, bc: new BroadcastChannel(channelName), subFns: [] // subscriberFunctions - }; state.bc.onmessage = function (msg) { @@ -14,7 +13,6 @@ export function create(channelName) { state.messagesCallback(msg.data); } }; - return state; } export function close(channelState) { @@ -36,12 +34,10 @@ export function canBeUsed() { if (typeof window === 'undefined') { return false; } - if (typeof BroadcastChannel === 'function') { if (BroadcastChannel._pubkey) { throw new Error('BroadcastChannel: Do not overwrite window.BroadcastChannel with this module, this is not a polyfill'); } - return true; } else { return false; diff --git a/dist/esbrowser/methods/node.js b/dist/esbrowser/methods/node.js index 5b30135d..0f823fad 100644 --- a/dist/esbrowser/methods/node.js +++ b/dist/esbrowser/methods/node.js @@ -1,10 +1,10 @@ import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator"; import _regeneratorRuntime from "@babel/runtime/regenerator"; - /** * this method is used in nodejs-environments. * The ipc is handled via sockets and file-writes to the tmp-folder */ + import util from 'util'; import fs from 'fs'; import crypto from 'crypto'; @@ -18,11 +18,11 @@ import { add as unloadAdd } from 'unload'; import { fillOptionsWithDefaults } from '../options.js'; import { randomInt, randomToken, PROMISE_RESOLVED_VOID } from '../util.js'; import { ObliviousSet } from 'oblivious-set'; + /** * windows sucks, so we have handle windows-type of socket-paths * @link https://gist.github.com/domenic/2790533#gistcomment-331356 */ - export function cleanPipeName(str) { if (process.platform === 'win32' && !str.startsWith('\\\\.\\pipe\\')) { str = str.replace(/^\//, ''); @@ -52,12 +52,12 @@ export function getPaths(channelName) { * in using the same folders for different channels. */ var channelHash = crypto.createHash('sha256').update(channelName).digest('hex'); + /** * because the length of socket-paths is limited, we use only the first 20 chars * and also start with A to ensure we do not start with a number * @link https://serverfault.com/questions/641347/check-if-a-path-exceeds-maximum-for-unix-domain-socket */ - var channelFolder = 'A' + channelHash.substring(0, 20); var channelPathBase = path.join(TMP_FOLDER_BASE, channelFolder); var folderPathReaders = path.join(channelPathBase, 'rdrs'); @@ -70,15 +70,12 @@ export function getPaths(channelName) { getPathsCache.set(channelName, ret); return ret; } - return getPathsCache.get(channelName); } var ENSURE_BASE_FOLDER_EXISTS_PROMISE = null; - function ensureBaseFolderExists() { return _ensureBaseFolderExists.apply(this, arguments); } - function _ensureBaseFolderExists() { _ensureBaseFolderExists = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3() { return _regeneratorRuntime.wrap(function _callee3$(_context3) { @@ -90,9 +87,7 @@ function _ensureBaseFolderExists() { return null; }); } - return _context3.abrupt("return", ENSURE_BASE_FOLDER_EXISTS_PROMISE); - case 2: case "end": return _context3.stop(); @@ -102,15 +97,14 @@ function _ensureBaseFolderExists() { })); return _ensureBaseFolderExists.apply(this, arguments); } - export function ensureFoldersExist(_x, _x2) { return _ensureFoldersExist.apply(this, arguments); } + /** * removes the tmp-folder * @return {Promise} */ - function _ensureFoldersExist() { _ensureFoldersExist = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4(channelName, paths) { var chmodValue; @@ -121,13 +115,11 @@ function _ensureFoldersExist() { paths = paths || getPaths(channelName); _context4.next = 3; return ensureBaseFolderExists(); - case 3: _context4.next = 5; return mkdir(paths.channelBase)["catch"](function () { return null; }); - case 5: _context4.next = 7; return Promise.all([mkdir(paths.readers)["catch"](function () { @@ -135,7 +127,6 @@ function _ensureFoldersExist() { }), mkdir(paths.messages)["catch"](function () { return null; })]); - case 7: // set permissions so other users can use the same channel chmodValue = '777'; @@ -143,7 +134,6 @@ function _ensureFoldersExist() { return Promise.all([chmod(paths.channelBase, chmodValue), chmod(paths.readers, chmodValue), chmod(paths.messages, chmodValue)])["catch"](function () { return null; }); - case 10: case "end": return _context4.stop(); @@ -153,11 +143,9 @@ function _ensureFoldersExist() { })); return _ensureFoldersExist.apply(this, arguments); } - export function clearNodeFolder() { return _clearNodeFolder.apply(this, arguments); } - function _clearNodeFolder() { _clearNodeFolder = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5() { return _regeneratorRuntime.wrap(function _callee5$(_context5) { @@ -168,18 +156,14 @@ function _clearNodeFolder() { _context5.next = 2; break; } - throw new Error('BroadcastChannel.clearNodeFolder(): path is wrong'); - case 2: ENSURE_BASE_FOLDER_EXISTS_PROMISE = null; _context5.next = 5; return removeDir(TMP_FOLDER_BASE); - case 5: ENSURE_BASE_FOLDER_EXISTS_PROMISE = null; return _context5.abrupt("return", true); - case 7: case "end": return _context5.stop(); @@ -189,7 +173,6 @@ function _clearNodeFolder() { })); return _clearNodeFolder.apply(this, arguments); } - export function socketPath(channelName, readerUuid, paths) { paths = paths || getPaths(channelName); var socketPath = path.join(paths.readers, readerUuid + '.s'); @@ -200,12 +183,12 @@ export function socketInfoPath(channelName, readerUuid, paths) { var socketPath = path.join(paths.readers, readerUuid + '.json'); return socketPath; } + /** * Because it is not possible to get all socket-files in a folder, * when used under fucking windows, * we have to set a normal file so other readers know our socket exists */ - export function createSocketInfoFile(channelName, readerUuid, paths) { var pathToFile = socketInfoPath(channelName, readerUuid, paths); return writeFile(pathToFile, JSON.stringify({ @@ -214,15 +197,14 @@ export function createSocketInfoFile(channelName, readerUuid, paths) { return pathToFile; }); } + /** * returns the amount of channel-folders in the tmp-directory * @return {Promise} */ - export function countChannelFolders() { return _countChannelFolders.apply(this, arguments); } - function _countChannelFolders() { _countChannelFolders = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee6() { var folders; @@ -232,15 +214,12 @@ function _countChannelFolders() { case 0: _context6.next = 2; return ensureBaseFolderExists(); - case 2: _context6.next = 4; return readdir(TMP_FOLDER_BASE); - case 4: folders = _context6.sent; return _context6.abrupt("return", folders.length); - case 6: case "end": return _context6.stop(); @@ -250,7 +229,6 @@ function _countChannelFolders() { })); return _countChannelFolders.apply(this, arguments); } - function connectionError(_x3) { return _connectionError.apply(this, arguments); } @@ -258,8 +236,6 @@ function connectionError(_x3) { * creates the socket-file and subscribes to it * @return {{emitter: EventEmitter, server: any}} */ - - function _connectionError() { _connectionError = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee7(originalError) { var count, addObj, text, newError; @@ -269,28 +245,23 @@ function _connectionError() { case 0: _context7.next = 2; return countChannelFolders(); - case 2: count = _context7.sent; - if (!(count < 30)) { _context7.next = 5; break; } - return _context7.abrupt("return", originalError); - case 5: addObj = {}; Object.entries(originalError).forEach(function (_ref3) { var k = _ref3[0], - v = _ref3[1]; + v = _ref3[1]; return addObj[k] = v; }); text = 'BroadcastChannel.create(): error: ' + 'This might happen if you have created to many channels, ' + 'like when you use BroadcastChannel in unit-tests.' + 'Try using BroadcastChannel.clearNodeFolder() to clear the tmp-folder before each test.' + 'See https://github.com/pubkey/broadcast-channel#clear-tmp-folder'; newError = new Error(text + ': ' + JSON.stringify(addObj, null, 2)); return _context7.abrupt("return", newError); - case 10: case "end": return _context7.stop(); @@ -300,11 +271,9 @@ function _connectionError() { })); return _connectionError.apply(this, arguments); } - export function createSocketEventEmitter(_x4, _x5, _x6) { return _createSocketEventEmitter.apply(this, arguments); } - function _createSocketEventEmitter() { _createSocketEventEmitter = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee10(channelName, readerUuid, paths) { var pathToSocket, emitter, server; @@ -331,11 +300,9 @@ function _createSocketEventEmitter() { case 0: _context8.next = 2; return connectionError(err); - case 2: useErr = _context8.sent; reject(useErr); - case 4: case "end": return _context8.stop(); @@ -343,7 +310,6 @@ function _createSocketEventEmitter() { } }, _callee8); })); - return function (_x26) { return _ref4.apply(this, arguments); }; @@ -359,19 +325,15 @@ function _createSocketEventEmitter() { _context9.next = 7; break; } - _context9.next = 3; return connectionError(err); - case 3: useErr = _context9.sent; reject(useErr); _context9.next = 8; break; - case 7: resolve(res); - case 8: case "end": return _context9.stop(); @@ -379,20 +341,17 @@ function _createSocketEventEmitter() { } }, _callee9); })); - return function (_x27, _x28) { return _ref5.apply(this, arguments); }; }()); }); - case 5: return _context10.abrupt("return", { path: pathToSocket, emitter: emitter, server: server }); - case 6: case "end": return _context10.stop(); @@ -402,16 +361,15 @@ function _createSocketEventEmitter() { })); return _createSocketEventEmitter.apply(this, arguments); } - export function openClientConnection(_x7, _x8) { return _openClientConnection.apply(this, arguments); } + /** * writes the new message to the file-system * so other readers can find it * @return {Promise} */ - function _openClientConnection() { _openClientConnection = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee11(channelName, readerUuid) { var pathToSocket, client; @@ -429,7 +387,6 @@ function _openClientConnection() { return rej(err); }); })); - case 3: case "end": return _context11.stop(); @@ -439,7 +396,6 @@ function _openClientConnection() { })); return _openClientConnection.apply(this, arguments); } - export function writeMessage(channelName, readerUuid, messageJson, paths) { paths = paths || getPaths(channelName); var time = microSeconds(); @@ -460,15 +416,14 @@ export function writeMessage(channelName, readerUuid, messageJson, paths) { }; }); } + /** * returns the uuids of all readers * @return {string[]} */ - export function getReadersUuids(_x9, _x10) { return _getReadersUuids.apply(this, arguments); } - function _getReadersUuids() { _getReadersUuids = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee12(channelName, paths) { var readersPath, files; @@ -480,7 +435,6 @@ function _getReadersUuids() { readersPath = paths.readers; _context12.next = 4; return readdir(readersPath); - case 4: files = _context12.sent; return _context12.abrupt("return", files.map(function (file) { @@ -491,7 +445,6 @@ function _getReadersUuids() { .map(function (split) { return split[0]; })); - case 6: case "end": return _context12.stop(); @@ -501,11 +454,9 @@ function _getReadersUuids() { })); return _getReadersUuids.apply(this, arguments); } - export function messagePath(_x11, _x12, _x13, _x14) { return _messagePath.apply(this, arguments); } - function _messagePath() { _messagePath = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee13(channelName, time, token, writerUuid) { var fileName, msgPath; @@ -516,7 +467,6 @@ function _messagePath() { fileName = time + '_' + writerUuid + '_' + token + '.json'; msgPath = path.join(getPaths(channelName).messages, fileName); return _context13.abrupt("return", msgPath); - case 3: case "end": return _context13.stop(); @@ -526,11 +476,9 @@ function _messagePath() { })); return _messagePath.apply(this, arguments); } - export function getAllMessages(_x15, _x16) { return _getAllMessages.apply(this, arguments); } - function _getAllMessages() { _getAllMessages = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee14(channelName, paths) { var messagesPath, files; @@ -542,7 +490,6 @@ function _getAllMessages() { messagesPath = paths.messages; _context14.next = 4; return readdir(messagesPath); - case 4: files = _context14.sent; return _context14.abrupt("return", files.map(function (file) { @@ -555,7 +502,6 @@ function _getAllMessages() { token: split[2] }; })); - case 6: case "end": return _context14.stop(); @@ -565,7 +511,6 @@ function _getAllMessages() { })); return _getAllMessages.apply(this, arguments); } - export function getSingleMessage(channelName, msgObj, paths) { paths = paths || getPaths(channelName); return { @@ -583,7 +528,6 @@ export function readMessage(messageObj) { export function cleanOldMessages(_x17, _x18) { return _cleanOldMessages.apply(this, arguments); } - function _cleanOldMessages() { _cleanOldMessages = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee15(messageObjects, ttl) { var olderThen; @@ -592,7 +536,6 @@ function _cleanOldMessages() { switch (_context15.prev = _context15.next) { case 0: olderThen = microSeconds() - ttl * 1000; // convert ttl to microseconds - _context15.next = 3; return Promise.all(messageObjects.filter(function (obj) { return obj.time < olderThen; @@ -601,7 +544,6 @@ function _cleanOldMessages() { return null; }); })); - case 3: case "end": return _context15.stop(); @@ -611,30 +553,27 @@ function _cleanOldMessages() { })); return _cleanOldMessages.apply(this, arguments); } - export var type = 'node'; + /** * creates a new channelState * @return {Promise} */ - export function create(_x19) { return _create.apply(this, arguments); } - function _create() { _create = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee16(channelName) { var options, - time, - paths, - ensureFolderExistsPromise, - uuid, - state, - _yield$Promise$all, - socketEE, - infoFilePath, - _args16 = arguments; - + time, + paths, + ensureFolderExistsPromise, + uuid, + state, + _yield$Promise$all, + socketEE, + infoFilePath, + _args16 = arguments; return _regeneratorRuntime.wrap(function _callee16$(_context16) { while (1) { switch (_context16.prev = _context16.next) { @@ -653,7 +592,6 @@ function _create() { paths: paths, // contains all messages that have been emitted before emittedMessagesIds: new ObliviousSet(options.node.ttl * 2), - /** * Used to ensure we do not write too many files at once * which could throw an error. @@ -677,18 +615,17 @@ function _create() { OTHER_INSTANCES[channelName].push(state); _context16.next = 11; return ensureFolderExistsPromise; - case 11: _context16.next = 13; return Promise.all([createSocketEventEmitter(channelName, uuid, paths), createSocketInfoFile(channelName, uuid, paths), refreshReaderClients(state)]); - case 13: _yield$Promise$all = _context16.sent; socketEE = _yield$Promise$all[0]; infoFilePath = _yield$Promise$all[1]; state.socketEE = socketEE; - state.infoFilePath = infoFilePath; // when new message comes in, we read it and emit it + state.infoFilePath = infoFilePath; + // when new message comes in, we read it and emit it socketEE.emitter.on('data', function (data) { // if the socket is used fast, it may appear that multiple messages are flushed at once // so we have to split them before @@ -705,7 +642,6 @@ function _create() { }); }); return _context16.abrupt("return", state); - case 20: case "end": return _context16.stop(); @@ -715,34 +651,29 @@ function _create() { })); return _create.apply(this, arguments); } - export function _filterMessage(msgObj, state) { if (msgObj.senderUuid === state.uuid) return false; // not send by own - if (state.emittedMessagesIds.has(msgObj.token)) return false; // not already emitted - if (!state.messagesCallback) return false; // no listener - if (msgObj.time < state.messagesCallbackTime) return false; // not older then onMessageCallback - if (msgObj.time < state.time) return false; // msgObj is older then channel state.emittedMessagesIds.add(msgObj.token); return true; } + /** * when the socket pings, so that we now new messages came, * run this */ - export function handleMessagePing(_x20, _x21) { return _handleMessagePing.apply(this, arguments); } + /** * ensures that the channelState is connected with all other readers * @return {Promise} */ - function _handleMessagePing() { _handleMessagePing = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee17(state, msgObj) { var messages, useMessages; @@ -754,27 +685,21 @@ function _handleMessagePing() { _context17.next = 2; break; } - return _context17.abrupt("return"); - case 2: if (msgObj) { _context17.next = 8; break; } - _context17.next = 5; return getAllMessages(state.channelName, state.paths); - case 5: messages = _context17.sent; _context17.next = 9; break; - case 8: // get single message messages = [getSingleMessage(state.channelName, msgObj, state.paths)]; - case 9: useMessages = messages.filter(function (msgObj) { return _filterMessage(msgObj, state); @@ -782,14 +707,11 @@ function _handleMessagePing() { return msgObjA.time - msgObjB.time; }); // sort by time // if no listener or message, so not do anything - if (!(!useMessages.length || !state.messagesCallback)) { _context17.next = 12; break; } - return _context17.abrupt("return"); - case 12: _context17.next = 14; return Promise.all(useMessages.map(function (msgObj) { @@ -797,17 +719,14 @@ function _handleMessagePing() { return msgObj.content = content; }); })); - case 14: useMessages.forEach(function (msgObj) { state.emittedMessagesIds.add(msgObj.token); - if (state.messagesCallback) { // emit to subscribers state.messagesCallback(msgObj.content.data); } }); - case 15: case "end": return _context17.stop(); @@ -817,7 +736,6 @@ function _handleMessagePing() { })); return _handleMessagePing.apply(this, arguments); } - export function refreshReaderClients(channelState) { return getReadersUuids(channelState.channelName, channelState.paths).then(function (otherReaders) { // remove subscriptions to closed readers @@ -832,18 +750,14 @@ export function refreshReaderClients(channelState) { _context.prev = 0; _context.next = 3; return channelState.otherReaderClients[readerUuid].destroy(); - case 3: _context.next = 7; break; - case 5: _context.prev = 5; _context.t0 = _context["catch"](0); - case 7: delete channelState.otherReaderClients[readerUuid]; - case 8: case "end": return _context.stop(); @@ -851,12 +765,12 @@ export function refreshReaderClients(channelState) { } }, _callee, null, [[0, 5]]); })); - return function (_x22) { return _ref.apply(this, arguments); }; - }()); // add new readers + }()); + // add new readers return Promise.all(otherReaders.filter(function (readerUuid) { return readerUuid !== channelState.uuid; }) // not own @@ -871,37 +785,29 @@ export function refreshReaderClients(channelState) { switch (_context2.prev = _context2.next) { case 0: _context2.prev = 0; - if (!channelState.closed) { _context2.next = 3; break; } - return _context2.abrupt("return"); - case 3: _context2.prev = 3; _context2.next = 6; return openClientConnection(channelState.channelName, readerUuid); - case 6: client = _context2.sent; channelState.otherReaderClients[readerUuid] = client; _context2.next = 12; break; - case 10: _context2.prev = 10; _context2.t0 = _context2["catch"](3); - case 12: _context2.next = 16; break; - case 14: _context2.prev = 14; _context2.t1 = _context2["catch"](0); - case 16: case "end": return _context2.stop(); @@ -909,28 +815,27 @@ export function refreshReaderClients(channelState) { } }, _callee2, null, [[0, 14], [3, 10]]); })); - return function (_x23) { return _ref2.apply(this, arguments); }; }())); }); } + /** * post a message to the other readers * @return {Promise} */ - export function postMessage(_x24, _x25) { return _postMessage.apply(this, arguments); } + /** * When multiple BroadcastChannels with the same name * are created in a single node-process, we can access them directly and emit messages. * This might not happen often in production * but will speed up things when this module is used in unit-tests. */ - function _postMessage() { _postMessage = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee19(channelState, messageJson) { var writePromise; @@ -943,7 +848,6 @@ function _postMessage() { }); channelState.writeBlockPromise = channelState.writeBlockPromise.then( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee18() { var _yield$Promise$all2, msgObj, pingStr, writeToReadersPromise; - return _regeneratorRuntime.wrap(function _callee18$(_context18) { while (1) { switch (_context18.prev = _context18.next) { @@ -952,11 +856,9 @@ function _postMessage() { return new Promise(function (res) { return setTimeout(res, 0); }); - case 2: _context18.next = 4; return Promise.all([writePromise, refreshReaderClients(channelState)]); - case 4: _yield$Promise$all2 = _context18.sent; msgObj = _yield$Promise$all2[0]; @@ -975,16 +877,13 @@ function _postMessage() { * to not waste resources on cleaning up, * only if random-int matches, we clean up old messages */ - if (randomInt(0, 20) === 0) { /* await */ getAllMessages(channelState.channelName, channelState.paths).then(function (allMessages) { return cleanOldMessages(allMessages, channelState.options.node.ttl); }); } - return _context18.abrupt("return", writeToReadersPromise); - case 11: case "end": return _context18.stop(); @@ -993,7 +892,6 @@ function _postMessage() { }, _callee18); }))); return _context19.abrupt("return", channelState.writeBlockPromise); - case 3: case "end": return _context19.stop(); @@ -1003,13 +901,11 @@ function _postMessage() { })); return _postMessage.apply(this, arguments); } - export function emitOverFastPath(state, msgObj, messageJson) { if (!state.options.node.useFastPath) { // disabled return; } - var others = OTHER_INSTANCES[state.channelName].filter(function (s) { return s !== state; }); @@ -1030,11 +926,11 @@ export function onMessage(channelState, fn) { channelState.messagesCallback = fn; handleMessagePing(channelState); } + /** * closes the channel * @return {Promise} */ - export function close(channelState) { if (channelState.closed) return; channelState.closed = true; @@ -1042,28 +938,24 @@ export function close(channelState) { OTHER_INSTANCES[channelState.channelName] = OTHER_INSTANCES[channelState.channelName].filter(function (o) { return o !== channelState; }); - if (channelState.removeUnload) { channelState.removeUnload.remove(); } - return new Promise(function (res) { if (channelState.socketEE) channelState.socketEE.emitter.removeAllListeners(); Object.values(channelState.otherReaderClients).forEach(function (client) { return client.destroy(); }); - if (channelState.infoFilePath) { try { fs.unlinkSync(channelState.infoFilePath); } catch (err) {} } + /** * the server get closed lazy because others might still write on it * and have not found out that the infoFile was deleted */ - - setTimeout(function () { channelState.socketEE.server.close(); res(); @@ -1077,13 +969,13 @@ export function canBeUsed() { return false; } } + /** * on node we use a relatively height averageResponseTime, * because the file-io might be in use. * Also it is more important that the leader-election is reliable, * then to have a fast election. */ - export function averageResponseTime() { return 200; } @@ -1091,7 +983,6 @@ export function microSeconds() { // convert nano to micro seconds return parseInt(now() / 1000); } - function now() { return Number(process.hrtime.bigint()); // returns nanoseconds } \ No newline at end of file diff --git a/dist/esbrowser/options.js b/dist/esbrowser/options.js index fbcc5244..86c7fae3 100644 --- a/dist/esbrowser/options.js +++ b/dist/esbrowser/options.js @@ -1,29 +1,32 @@ export function fillOptionsWithDefaults() { var originalOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var options = JSON.parse(JSON.stringify(originalOptions)); // main + var options = JSON.parse(JSON.stringify(originalOptions)); - if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true; // indexed-db - - if (!options.idb) options.idb = {}; // after this time the messages get deleted + // main + if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true; + // indexed-db + if (!options.idb) options.idb = {}; + // after this time the messages get deleted if (!options.idb.ttl) options.idb.ttl = 1000 * 45; - if (!options.idb.fallbackInterval) options.idb.fallbackInterval = 150; // handles abrupt db onclose events. - - if (originalOptions.idb && typeof originalOptions.idb.onclose === 'function') options.idb.onclose = originalOptions.idb.onclose; // localstorage + if (!options.idb.fallbackInterval) options.idb.fallbackInterval = 150; + // handles abrupt db onclose events. + if (originalOptions.idb && typeof originalOptions.idb.onclose === 'function') options.idb.onclose = originalOptions.idb.onclose; + // localstorage if (!options.localstorage) options.localstorage = {}; - if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60; // custom methods + if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60; - if (originalOptions.methods) options.methods = originalOptions.methods; // node + // custom methods + if (originalOptions.methods) options.methods = originalOptions.methods; + // node if (!options.node) options.node = {}; if (!options.node.ttl) options.node.ttl = 1000 * 60 * 2; // 2 minutes; - /** * On linux use 'ulimit -Hn' to get the limit of open files. * On ubuntu this was 4096 for me, so we use half of that as maxParallelWrites default. */ - if (!options.node.maxParallelWrites) options.node.maxParallelWrites = 2048; if (typeof options.node.useFastPath === 'undefined') options.node.useFastPath = true; return options; diff --git a/dist/esbrowser/util.js b/dist/esbrowser/util.js index 99b7e529..6c9b9b6e 100644 --- a/dist/esbrowser/util.js +++ b/dist/esbrowser/util.js @@ -22,15 +22,16 @@ export function sleep(time, resolveWith) { export function randomInt(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } + /** * https://stackoverflow.com/a/8084248 */ - export function randomToken() { return Math.random().toString(36).substring(2); } var lastMs = 0; var additional = 0; + /** * returns the current time in micro-seconds, * WARNING: This is a pseudo-function @@ -38,10 +39,8 @@ var additional = 0; * This is enough in browsers, and this function will not be used in nodejs. * The main reason for this hack is to ensure that BroadcastChannel behaves equal to production when it is used in fast-running unit tests. */ - export function microSeconds() { var ms = new Date().getTime(); - if (ms === lastMs) { additional++; return ms * 1000 + additional; diff --git a/dist/esnode/broadcast-channel.js b/dist/esnode/broadcast-channel.js index 09502c09..d62e7a95 100644 --- a/dist/esnode/broadcast-channel.js +++ b/dist/esnode/broadcast-channel.js @@ -1,11 +1,11 @@ import { isPromise, PROMISE_RESOLVED_FALSE, PROMISE_RESOLVED_VOID } from './util.js'; import { chooseMethod } from './method-chooser.js'; import { fillOptionsWithDefaults } from './options.js'; + /** * Contains all open channels, * used in tests to ensure everything is closed. */ - export var OPEN_BROADCAST_CHANNELS = new Set(); var lastId = 0; export var BroadcastChannel = function BroadcastChannel(name, options) { @@ -13,69 +13,67 @@ export var BroadcastChannel = function BroadcastChannel(name, options) { this.id = lastId++; OPEN_BROADCAST_CHANNELS.add(this); this.name = name; - if (ENFORCED_OPTIONS) { options = ENFORCED_OPTIONS; } - this.options = fillOptionsWithDefaults(options); - this.method = chooseMethod(this.options); // isListening + this.method = chooseMethod(this.options); + // isListening this._iL = false; + /** * _onMessageListener * setting onmessage twice, * will overwrite the first listener */ - this._onML = null; + /** * _addEventListeners */ - this._addEL = { message: [], internal: [] }; + /** * Unsend message promises * where the sending is still in progress * @type {Set} */ - this._uMP = new Set(); + /** * _beforeClose * array of promises that will be awaited * before the channel is closed */ - this._befC = []; + /** * _preparePromise */ - this._prepP = null; - _prepareChannel(this); -}; // STATICS +}; + +// STATICS /** * used to identify if someone overwrites * window.BroadcastChannel with this * See methods/native.js */ - BroadcastChannel._pubkey = true; + /** * clears the tmp-folder if is node * @return {Promise} true if has run, false if not node */ - export function clearNodeFolder(options) { options = fillOptionsWithDefaults(options); var method = chooseMethod(options); - if (method.type === 'node') { return method.clearNodeFolder().then(function () { return true; @@ -84,16 +82,17 @@ export function clearNodeFolder(options) { return PROMISE_RESOLVED_FALSE; } } + /** * if set, this method is enforced, * no mather what the options are */ - var ENFORCED_OPTIONS; export function enforceOptions(options) { ENFORCED_OPTIONS = options; -} // PROTOTYPE +} +// PROTOTYPE BroadcastChannel.prototype = { postMessage: function postMessage(msg) { if (this.closed) { @@ -105,87 +104,77 @@ BroadcastChannel.prototype = { */ JSON.stringify(msg)); } - return _post(this, 'message', msg); }, postInternal: function postInternal(msg) { return _post(this, 'internal', msg); }, - set onmessage(fn) { var time = this.method.microSeconds(); var listenObj = { time: time, fn: fn }; - _removeListenerObject(this, 'message', this._onML); - if (fn && typeof fn === 'function') { this._onML = listenObj; - _addListenerObject(this, 'message', listenObj); } else { this._onML = null; } }, - addEventListener: function addEventListener(type, fn) { var time = this.method.microSeconds(); var listenObj = { time: time, fn: fn }; - _addListenerObject(this, type, listenObj); }, removeEventListener: function removeEventListener(type, fn) { var obj = this._addEL[type].find(function (obj) { return obj.fn === fn; }); - _removeListenerObject(this, type, obj); }, close: function close() { var _this = this; - if (this.closed) { return; } - OPEN_BROADCAST_CHANNELS["delete"](this); this.closed = true; var awaitPrepare = this._prepP ? this._prepP : PROMISE_RESOLVED_VOID; this._onML = null; this._addEL.message = []; - return awaitPrepare // wait until all current sending are processed + return awaitPrepare + // wait until all current sending are processed .then(function () { return Promise.all(Array.from(_this._uMP)); - }) // run before-close hooks + }) + // run before-close hooks .then(function () { return Promise.all(_this._befC.map(function (fn) { return fn(); })); - }) // close the channel + }) + // close the channel .then(function () { return _this.method.close(_this._state); }); }, - get type() { return this.method.type; }, - get isClosed() { return this.closed; } - }; + /** * Post a message over the channel * @returns {Promise} that resolved when the message sending is done */ - function _post(broadcastChannel, type, msg) { var time = broadcastChannel.method.microSeconds(); var msgObj = { @@ -195,25 +184,22 @@ function _post(broadcastChannel, type, msg) { }; var awaitPrepare = broadcastChannel._prepP ? broadcastChannel._prepP : PROMISE_RESOLVED_VOID; return awaitPrepare.then(function () { - var sendPromise = broadcastChannel.method.postMessage(broadcastChannel._state, msgObj); // add/remove to unsend messages list + var sendPromise = broadcastChannel.method.postMessage(broadcastChannel._state, msgObj); + // add/remove to unsend messages list broadcastChannel._uMP.add(sendPromise); - sendPromise["catch"]().then(function () { return broadcastChannel._uMP["delete"](sendPromise); }); return sendPromise; }); } - function _prepareChannel(channel) { var maybePromise = channel.method.create(channel.name, channel.options); - if (isPromise(maybePromise)) { channel._prepP = maybePromise; maybePromise.then(function (s) { // used in tests to simulate slow runtime - /*if (channel.options.prepareDelay) { await new Promise(res => setTimeout(res, this.options.prepareDelay)); }*/ @@ -223,30 +209,25 @@ function _prepareChannel(channel) { channel._state = maybePromise; } } - function _hasMessageListeners(channel) { if (channel._addEL.message.length > 0) return true; if (channel._addEL.internal.length > 0) return true; return false; } - function _addListenerObject(channel, type, obj) { channel._addEL[type].push(obj); - _startListening(channel); } - function _removeListenerObject(channel, type, obj) { channel._addEL[type] = channel._addEL[type].filter(function (o) { return o !== obj; }); - _stopListening(channel); } - function _startListening(channel) { if (!channel._iL && _hasMessageListeners(channel)) { // someone is listening, start subscribing + var listenerFn = function listenerFn(msgObj) { channel._addEL[msgObj.type].forEach(function (listenerObject) { /** @@ -260,15 +241,12 @@ function _startListening(channel) { */ var hundredMsInMicro = 100 * 1000; var minMessageTime = listenerObject.time - hundredMsInMicro; - if (msgObj.time >= minMessageTime) { listenerObject.fn(msgObj.data); } }); }; - var time = channel.method.microSeconds(); - if (channel._prepP) { channel._prepP.then(function () { channel._iL = true; @@ -280,7 +258,6 @@ function _startListening(channel) { } } } - function _stopListening(channel) { if (channel._iL && !_hasMessageListeners(channel)) { // noone is listening, stop subscribing diff --git a/dist/esnode/browserify.index.js b/dist/esnode/browserify.index.js index e500d3df..bd935158 100644 --- a/dist/esnode/browserify.index.js +++ b/dist/esnode/browserify.index.js @@ -1,5 +1,4 @@ var module = require('./index.es5.js'); - var BroadcastChannel = module.BroadcastChannel; var createLeaderElection = module.createLeaderElection; window['BroadcastChannel2'] = BroadcastChannel; diff --git a/dist/esnode/index.es5.js b/dist/esnode/index.es5.js index 4d6b827b..60f0b76e 100644 --- a/dist/esnode/index.es5.js +++ b/dist/esnode/index.es5.js @@ -6,6 +6,7 @@ * but * var BroadcastChannel = require('broadcast-channel'); */ + import { BroadcastChannel, createLeaderElection, clearNodeFolder, enforceOptions, beLeader } from './index.js'; module.exports = { BroadcastChannel: BroadcastChannel, diff --git a/dist/esnode/leader-election.js b/dist/esnode/leader-election.js index c8c9cbb4..3b2c615c 100644 --- a/dist/esnode/leader-election.js +++ b/dist/esnode/leader-election.js @@ -1,32 +1,27 @@ import { sleep, randomToken, PROMISE_RESOLVED_VOID, PROMISE_RESOLVED_TRUE } from './util.js'; import { add as unloadAdd } from 'unload'; - var LeaderElection = function LeaderElection(broadcastChannel, options) { var _this = this; - this.broadcastChannel = broadcastChannel; this._options = options; this.isLeader = false; this.hasLeader = false; this.isDead = false; this.token = randomToken(); + /** * Apply Queue, * used to ensure we do not run applyOnce() * in parallel. */ + this._aplQ = PROMISE_RESOLVED_VOID; + // amount of unfinished applyOnce() calls + this._aplQC = 0; - this._aplQ = PROMISE_RESOLVED_VOID; // amount of unfinished applyOnce() calls - - this._aplQC = 0; // things to clean up - + // things to clean up this._unl = []; // _unloads - this._lstns = []; // _listeners - this._dpL = function () {}; // onduplicate listener - - this._dpLC = false; // true when onduplicate called /** @@ -34,55 +29,47 @@ var LeaderElection = function LeaderElection(broadcastChannel, options) { * we still listen to messages to ensure the hasLeader flag * is set correctly. */ - var hasLeaderListener = function hasLeaderListener(msg) { if (msg.context === 'leader') { if (msg.action === 'death') { _this.hasLeader = false; } - if (msg.action === 'tell') { _this.hasLeader = true; } } }; - this.broadcastChannel.addEventListener('internal', hasLeaderListener); - this._lstns.push(hasLeaderListener); }; - LeaderElection.prototype = { /** * Returns true if the instance is leader, * false if not. * @async */ - applyOnce: function applyOnce( // true if the applyOnce() call came from the fallbackInterval cycle + applyOnce: function applyOnce( + // true if the applyOnce() call came from the fallbackInterval cycle isFromFallbackInterval) { var _this2 = this; - if (this.isLeader) { return sleep(0, true); } - if (this.isDead) { return sleep(0, false); } + /** * Already applying more then once, * -> wait for the apply queue to be finished. */ - - if (this._aplQC > 1) { return this._aplQ; } + /** * Add a new apply-run */ - - var applyRun = function applyRun() { /** * Optimization shortcuts. @@ -92,7 +79,6 @@ LeaderElection.prototype = { if (_this2.isLeader) { return PROMISE_RESOLVED_TRUE; } - var stopCriteria = false; var stopCriteriaPromiseResolve; /** @@ -101,7 +87,6 @@ LeaderElection.prototype = { * have to await the responseTime when it is already clear * that the election failed. */ - var stopCriteriaPromise = new Promise(function (res) { stopCriteriaPromiseResolve = function stopCriteriaPromiseResolve() { stopCriteria = true; @@ -109,11 +94,9 @@ LeaderElection.prototype = { }; }); var recieved = []; - var handleMessage = function handleMessage(msg) { if (msg.context === 'leader' && msg.token != _this2.token) { recieved.push(msg); - if (msg.action === 'apply') { // other is applying if (msg.token > _this2.token) { @@ -124,7 +107,6 @@ LeaderElection.prototype = { stopCriteriaPromiseResolve(); } } - if (msg.action === 'tell') { // other is already leader stopCriteriaPromiseResolve(); @@ -132,8 +114,8 @@ LeaderElection.prototype = { } } }; - _this2.broadcastChannel.addEventListener('internal', handleMessage); + /** * If the applyOnce() call came from the fallbackInterval, * we can assume that the election runs in the background and @@ -144,26 +126,24 @@ LeaderElection.prototype = { * But also it takes longer which is not a problem because we anyway * run in the background. */ - - var waitForAnswerTime = isFromFallbackInterval ? _this2._options.responseTime * 4 : _this2._options.responseTime; - var applyPromise = _sendMessage(_this2, 'apply') // send out that this one is applying .then(function () { return Promise.race([sleep(waitForAnswerTime), stopCriteriaPromise.then(function () { return Promise.reject(new Error()); })]); - }) // send again in case another instance was just created + }) + // send again in case another instance was just created .then(function () { return _sendMessage(_this2, 'apply'); - }) // let others time to respond + }) + // let others time to respond .then(function () { return Promise.race([sleep(waitForAnswerTime), stopCriteriaPromise.then(function () { return Promise.reject(new Error()); })]); })["catch"](function () {}).then(function () { _this2.broadcastChannel.removeEventListener('internal', handleMessage); - if (!stopCriteria) { // no stop criteria -> own is leader return beLeader(_this2).then(function () { @@ -174,10 +154,8 @@ LeaderElection.prototype = { return false; } }); - return applyPromise; }; - this._aplQC = this._aplQC + 1; this._aplQ = this._aplQ.then(function () { return applyRun(); @@ -189,82 +167,68 @@ LeaderElection.prototype = { }); }, awaitLeadership: function awaitLeadership() { - if ( - /* _awaitLeadershipPromise */ + if ( /* _awaitLeadershipPromise */ !this._aLP) { this._aLP = _awaitLeadershipOnce(this); } - return this._aLP; }, - set onduplicate(fn) { this._dpL = fn; }, - die: function die() { var _this3 = this; - this._lstns.forEach(function (listener) { return _this3.broadcastChannel.removeEventListener('internal', listener); }); - this._lstns = []; - this._unl.forEach(function (uFn) { return uFn.remove(); }); - this._unl = []; - if (this.isLeader) { this.hasLeader = false; this.isLeader = false; } - this.isDead = true; return _sendMessage(this, 'death'); } }; + /** * @param leaderElector {LeaderElector} */ - function _awaitLeadershipOnce(leaderElector) { if (leaderElector.isLeader) { return PROMISE_RESOLVED_VOID; } - return new Promise(function (res) { var resolved = false; - function finish() { if (resolved) { return; } - resolved = true; leaderElector.broadcastChannel.removeEventListener('internal', whenDeathListener); res(true); - } // try once now - + } + // try once now leaderElector.applyOnce().then(function () { if (leaderElector.isLeader) { finish(); } }); + /** * Try on fallbackInterval * @recursive */ - var tryOnFallBack = function tryOnFallBack() { return sleep(leaderElector._options.fallbackInterval).then(function () { if (leaderElector.isDead || resolved) { return; } - if (leaderElector.isLeader) { finish(); } else { @@ -278,9 +242,9 @@ function _awaitLeadershipOnce(leaderElector) { } }); }; + tryOnFallBack(); - tryOnFallBack(); // try when other leader dies - + // try when other leader dies var whenDeathListener = function whenDeathListener(msg) { if (msg.context === 'leader' && msg.action === 'death') { leaderElector.hasLeader = false; @@ -291,17 +255,14 @@ function _awaitLeadershipOnce(leaderElector) { }); } }; - leaderElector.broadcastChannel.addEventListener('internal', whenDeathListener); - leaderElector._lstns.push(whenDeathListener); }); } + /** * sends and internal message over the broadcast-channel */ - - function _sendMessage(leaderElector, action) { var msgJson = { context: 'leader', @@ -310,21 +271,17 @@ function _sendMessage(leaderElector, action) { }; return leaderElector.broadcastChannel.postInternal(msgJson); } - export function beLeader(leaderElector) { leaderElector.isLeader = true; leaderElector.hasLeader = true; var unloadFn = unloadAdd(function () { return leaderElector.die(); }); - leaderElector._unl.push(unloadFn); - var isLeaderListener = function isLeaderListener(msg) { if (msg.context === 'leader' && msg.action === 'apply') { _sendMessage(leaderElector, 'tell'); } - if (msg.context === 'leader' && msg.action === 'tell' && !leaderElector._dpLC) { /** * another instance is also leader! @@ -335,49 +292,35 @@ export function beLeader(leaderElector) { * @link https://github.com/pubkey/broadcast-channel/issues/385 */ leaderElector._dpLC = true; - leaderElector._dpL(); // message the lib user so the app can handle the problem - - _sendMessage(leaderElector, 'tell'); // ensure other leader also knows the problem - } }; leaderElector.broadcastChannel.addEventListener('internal', isLeaderListener); - leaderElector._lstns.push(isLeaderListener); - return _sendMessage(leaderElector, 'tell'); } - function fillOptionsWithDefaults(options, channel) { if (!options) options = {}; options = JSON.parse(JSON.stringify(options)); - if (!options.fallbackInterval) { options.fallbackInterval = 3000; } - if (!options.responseTime) { options.responseTime = channel.method.averageResponseTime(channel.options); } - return options; } - export function createLeaderElection(channel, options) { if (channel._leaderElector) { throw new Error('BroadcastChannel already has a leader-elector'); } - options = fillOptionsWithDefaults(options, channel); var elector = new LeaderElection(channel, options); - channel._befC.push(function () { return elector.die(); }); - channel._leaderElector = elector; return elector; } \ No newline at end of file diff --git a/dist/esnode/method-chooser.js b/dist/esnode/method-chooser.js index cb4f81cd..aa522d66 100644 --- a/dist/esnode/method-chooser.js +++ b/dist/esnode/method-chooser.js @@ -1,40 +1,41 @@ import NativeMethod from './methods/native.js'; import IndexeDbMethod from './methods/indexed-db.js'; import LocalstorageMethod from './methods/localstorage.js'; -import SimulateMethod from './methods/simulate.js'; // the line below will be removed from es5/browser builds +import SimulateMethod from './methods/simulate.js'; +// the line below will be removed from es5/browser builds +import * as NodeMethod from './methods/node.js'; -import * as NodeMethod from './methods/node.js'; // order is important - -var METHODS = [NativeMethod, // fastest +// order is important +var METHODS = [NativeMethod, +// fastest IndexeDbMethod, LocalstorageMethod]; export function chooseMethod(options) { - var chooseMethods = [].concat(options.methods, METHODS).filter(Boolean); // the line below will be removed from es5/browser builds + var chooseMethods = [].concat(options.methods, METHODS).filter(Boolean); - chooseMethods.push(NodeMethod); // directly chosen + // the line below will be removed from es5/browser builds + chooseMethods.push(NodeMethod); + // directly chosen if (options.type) { if (options.type === 'simulate') { // only use simulate-method if directly chosen return SimulateMethod; } - var ret = chooseMethods.find(function (m) { return m.type === options.type; }); if (!ret) throw new Error('method-type ' + options.type + ' not found');else return ret; } + /** * if no webworker support is needed, * remove idb from the list so that localstorage is been chosen */ - - if (!options.webWorkerSupport) { chooseMethods = chooseMethods.filter(function (m) { return m.type !== 'idb'; }); } - var useMethod = chooseMethods.find(function (method) { return method.canBeUsed(); }); diff --git a/dist/esnode/methods/indexed-db.js b/dist/esnode/methods/indexed-db.js index 75f983c0..24727d1a 100644 --- a/dist/esnode/methods/indexed-db.js +++ b/dist/esnode/methods/indexed-db.js @@ -6,55 +6,54 @@ * When working on this, ensure to use these performance optimizations: * @link https://rxdb.info/slow-indexeddb.html */ + import { sleep, randomInt, randomToken, microSeconds as micro, PROMISE_RESOLVED_VOID } from '../util.js'; export var microSeconds = micro; import { ObliviousSet } from 'oblivious-set'; import { fillOptionsWithDefaults } from '../options.js'; var DB_PREFIX = 'pubkey.broadcast-channel-0-'; var OBJECT_STORE_ID = 'messages'; + /** * Use relaxed durability for faster performance on all transactions. * @link https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/ */ - export var TRANSACTION_SETTINGS = { durability: 'relaxed' }; export var type = 'idb'; export function getIdb() { if (typeof indexedDB !== 'undefined') return indexedDB; - if (typeof window !== 'undefined') { if (typeof window.mozIndexedDB !== 'undefined') return window.mozIndexedDB; if (typeof window.webkitIndexedDB !== 'undefined') return window.webkitIndexedDB; if (typeof window.msIndexedDB !== 'undefined') return window.msIndexedDB; } - return false; } + /** * If possible, we should explicitly commit IndexedDB transactions * for better performance. * @link https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/ */ - export function commitIndexedDBTransaction(tx) { if (tx.commit) { tx.commit(); } } export function createDatabase(channelName) { - var IndexedDB = getIdb(); // create table + var IndexedDB = getIdb(); + // create table var dbName = DB_PREFIX + channelName; + /** * All IndexedDB databases are opened without version * because it is a bit faster, especially on firefox * @link http://nparashuram.com/IndexedDB/perf/#Open%20Database%20with%20version */ - var openRequest = IndexedDB.open(dbName); - openRequest.onupgradeneeded = function (ev) { var db = ev.target.result; db.createObjectStore(OBJECT_STORE_ID, { @@ -62,23 +61,21 @@ export function createDatabase(channelName) { autoIncrement: true }); }; - var dbPromise = new Promise(function (res, rej) { openRequest.onerror = function (ev) { return rej(ev); }; - openRequest.onsuccess = function () { res(openRequest.result); }; }); return dbPromise; } + /** * writes the new message to the database * so other readers can find it */ - export function writeMessage(db, readerUuid, messageJson) { var time = new Date().getTime(); var writeObject = { @@ -91,11 +88,9 @@ export function writeMessage(db, readerUuid, messageJson) { tx.oncomplete = function () { return res(); }; - tx.onerror = function (ev) { return rej(ev); }; - var objectStore = tx.objectStore(OBJECT_STORE_ID); objectStore.add(writeObject); commitIndexedDBTransaction(tx); @@ -108,10 +103,9 @@ export function getAllMessages(db) { return new Promise(function (res) { objectStore.openCursor().onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { - ret.push(cursor.value); //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); - + ret.push(cursor.value); + //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); cursor["continue"](); } else { commitIndexedDBTransaction(tx); @@ -125,25 +119,23 @@ export function getMessagesHigherThan(db, lastCursorId) { var objectStore = tx.objectStore(OBJECT_STORE_ID); var ret = []; var keyRangeValue = IDBKeyRange.bound(lastCursorId + 1, Infinity); + /** * Optimization shortcut, * if getAll() can be used, do not use a cursor. * @link https://rxdb.info/slow-indexeddb.html */ - if (objectStore.getAll) { var getAllRequest = objectStore.getAll(keyRangeValue); return new Promise(function (res, rej) { getAllRequest.onerror = function (err) { return rej(err); }; - getAllRequest.onsuccess = function (e) { res(e.target.result); }; }); } - function openCursor() { // Occasionally Safari will fail on IDBKeyRange.bound, this // catches that error, having it open the cursor to the first @@ -155,17 +147,13 @@ export function getMessagesHigherThan(db, lastCursorId) { return objectStore.openCursor(); } } - return new Promise(function (res, rej) { var openCursorRequest = openCursor(); - openCursorRequest.onerror = function (err) { return rej(err); }; - openCursorRequest.onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { if (cursor.value.id < lastCursorId + 1) { cursor["continue"](lastCursorId + 1); @@ -184,7 +172,6 @@ export function removeMessagesById(channelState, ids) { if (channelState.closed) { return Promise.resolve([]); } - var tx = channelState.db.transaction(OBJECT_STORE_ID, 'readwrite', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); return Promise.all(ids.map(function (id) { @@ -204,13 +191,11 @@ export function getOldMessages(db, ttl) { return new Promise(function (res) { objectStore.openCursor().onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { var msgObk = cursor.value; - if (msgObk.time < olderThen) { - ret.push(msgObk); //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); - + ret.push(msgObk); + //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); cursor["continue"](); } else { // no more old messages, @@ -240,7 +225,6 @@ export function create(channelName, options) { channelName: channelName, options: options, uuid: randomToken(), - /** * emittedMessagesIds * contains all messages that have been emitted before @@ -253,30 +237,27 @@ export function create(channelName, options) { readQueuePromises: [], db: db }; + /** * Handle abrupt closes that do not originate from db.close(). * This could happen, for example, if the underlying storage is * removed or if the user clears the database in the browser's * history preferences. */ - db.onclose = function () { state.closed = true; if (options.idb.onclose) options.idb.onclose(); }; + /** * if service-workers are used, * we have no 'storage'-event if they post a message, * therefore we also have to set an interval */ - - _readLoop(state); - return state; }); } - function _readLoop(state) { if (state.closed) return; readNewMessages(state).then(function () { @@ -285,25 +266,21 @@ function _readLoop(state) { return _readLoop(state); }); } - function _filterMessage(msgObj, state) { if (msgObj.uuid === state.uuid) return false; // send by own - if (state.eMIs.has(msgObj.id)) return false; // already emitted - if (msgObj.data.time < state.messagesCallbackTime) return false; // older then onMessageCallback - return true; } + /** * reads all new messages from the database and emits them */ - - function readNewMessages(state) { // channel already closed - if (state.closed) return PROMISE_RESOLVED_VOID; // if no one is listening, we do not need to scan for new messages + if (state.closed) return PROMISE_RESOLVED_VOID; + // if no one is listening, we do not need to scan for new messages if (!state.messagesCallback) return PROMISE_RESOLVED_VOID; return getMessagesHigherThan(state.db, state.lastCursorId).then(function (newerMessages) { var useMessages = newerMessages @@ -311,21 +288,18 @@ function readNewMessages(state) { * there is a bug in iOS where the msgObj can be undefined some times * so we filter them out * @link https://github.com/pubkey/broadcast-channel/issues/19 - */ - .filter(function (msgObj) { + */.filter(function (msgObj) { return !!msgObj; }).map(function (msgObj) { if (msgObj.id > state.lastCursorId) { state.lastCursorId = msgObj.id; } - return msgObj; }).filter(function (msgObj) { return _filterMessage(msgObj, state); }).sort(function (msgObjA, msgObjB) { return msgObjA.time - msgObjB.time; }); // sort by time - useMessages.forEach(function (msgObj) { if (state.messagesCallback) { state.eMIs.add(msgObj.id); @@ -335,7 +309,6 @@ function readNewMessages(state) { return PROMISE_RESOLVED_VOID; }); } - export function close(channelState) { channelState.closed = true; channelState.db.close(); @@ -358,11 +331,9 @@ export function onMessage(channelState, fn, time) { } export function canBeUsed() { var idb = getIdb(); - if (!idb) { return false; } - return true; } export function averageResponseTime(options) { diff --git a/dist/esnode/methods/localstorage.js b/dist/esnode/methods/localstorage.js index 5a4adcc6..a0bb80e5 100644 --- a/dist/esnode/methods/localstorage.js +++ b/dist/esnode/methods/localstorage.js @@ -5,39 +5,39 @@ * @link https://caniuse.com/#feat=namevalue-storage * @link https://caniuse.com/#feat=indexeddb */ + import { ObliviousSet } from 'oblivious-set'; import { fillOptionsWithDefaults } from '../options.js'; import { sleep, randomToken, microSeconds as micro } from '../util.js'; export var microSeconds = micro; var KEY_PREFIX = 'pubkey.broadcastChannel-'; export var type = 'localstorage'; + /** * copied from crosstab * @link https://github.com/tejacques/crosstab/blob/master/src/crosstab.js#L32 */ - export function getLocalStorage() { var localStorage; if (typeof window === 'undefined') return null; - try { localStorage = window.localStorage; localStorage = window['ie8-eventlistener/storage'] || window.localStorage; - } catch (e) {// New versions of Firefox throw a Security exception + } catch (e) { + // New versions of Firefox throw a Security exception // if cookies are disabled. See // https://bugzilla.mozilla.org/show_bug.cgi?id=1028153 } - return localStorage; } export function storageKey(channelName) { return KEY_PREFIX + channelName; } + /** * writes the new message to the storage * and fires the storage-event so other readers can find it */ - export function postMessage(channelState, messageJson) { return new Promise(function (res) { sleep().then(function () { @@ -50,12 +50,12 @@ export function postMessage(channelState, messageJson) { }; var value = JSON.stringify(writeObj); getLocalStorage().setItem(key, value); + /** * StorageEvent does not fire the 'storage' event * in the window that changes the state of the local storage. * So we fire it manually */ - var ev = document.createEvent('Event'); ev.initEvent('storage', true, true); ev.key = key; @@ -67,13 +67,11 @@ export function postMessage(channelState, messageJson) { } export function addStorageEventListener(channelName, fn) { var key = storageKey(channelName); - var listener = function listener(ev) { if (ev.key === key) { fn(JSON.parse(ev.newValue)); } }; - window.addEventListener('storage', listener); return listener; } @@ -82,32 +80,27 @@ export function removeStorageEventListener(listener) { } export function create(channelName, options) { options = fillOptionsWithDefaults(options); - if (!canBeUsed()) { throw new Error('BroadcastChannel: localstorage cannot be used'); } - var uuid = randomToken(); + /** * eMIs * contains all messages that have been emitted before * @type {ObliviousSet} */ - var eMIs = new ObliviousSet(options.localstorage.removeTimeout); var state = { channelName: channelName, uuid: uuid, eMIs: eMIs // emittedMessagesIds - }; + state.listener = addStorageEventListener(channelName, function (msgObj) { if (!state.messagesCallback) return; // no listener - if (msgObj.uuid === uuid) return; // own message - if (!msgObj.token || eMIs.has(msgObj.token)) return; // already emitted - if (msgObj.data.time && msgObj.data.time < state.messagesCallbackTime) return; // too old eMIs.add(msgObj.token); @@ -125,7 +118,6 @@ export function onMessage(channelState, fn, time) { export function canBeUsed() { var ls = getLocalStorage(); if (!ls) return false; - try { var key = '__broadcastchannel_check'; ls.setItem(key, 'works'); @@ -136,18 +128,15 @@ export function canBeUsed() { // https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API#Private_Browsing_Incognito_modes return false; } - return true; } export function averageResponseTime() { var defaultTime = 120; var userAgent = navigator.userAgent.toLowerCase(); - if (userAgent.includes('safari') && !userAgent.includes('chrome')) { // safari is much slower so this time is higher return defaultTime * 2; } - return defaultTime; } export default { diff --git a/dist/esnode/methods/native.js b/dist/esnode/methods/native.js index 6841c45b..6e45b272 100644 --- a/dist/esnode/methods/native.js +++ b/dist/esnode/methods/native.js @@ -6,7 +6,6 @@ export function create(channelName) { messagesCallback: null, bc: new BroadcastChannel(channelName), subFns: [] // subscriberFunctions - }; state.bc.onmessage = function (msg) { @@ -14,7 +13,6 @@ export function create(channelName) { state.messagesCallback(msg.data); } }; - return state; } export function close(channelState) { @@ -36,12 +34,10 @@ export function canBeUsed() { if (typeof window === 'undefined') { return false; } - if (typeof BroadcastChannel === 'function') { if (BroadcastChannel._pubkey) { throw new Error('BroadcastChannel: Do not overwrite window.BroadcastChannel with this module, this is not a polyfill'); } - return true; } else { return false; diff --git a/dist/esnode/methods/node.js b/dist/esnode/methods/node.js index 5b30135d..0f823fad 100644 --- a/dist/esnode/methods/node.js +++ b/dist/esnode/methods/node.js @@ -1,10 +1,10 @@ import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator"; import _regeneratorRuntime from "@babel/runtime/regenerator"; - /** * this method is used in nodejs-environments. * The ipc is handled via sockets and file-writes to the tmp-folder */ + import util from 'util'; import fs from 'fs'; import crypto from 'crypto'; @@ -18,11 +18,11 @@ import { add as unloadAdd } from 'unload'; import { fillOptionsWithDefaults } from '../options.js'; import { randomInt, randomToken, PROMISE_RESOLVED_VOID } from '../util.js'; import { ObliviousSet } from 'oblivious-set'; + /** * windows sucks, so we have handle windows-type of socket-paths * @link https://gist.github.com/domenic/2790533#gistcomment-331356 */ - export function cleanPipeName(str) { if (process.platform === 'win32' && !str.startsWith('\\\\.\\pipe\\')) { str = str.replace(/^\//, ''); @@ -52,12 +52,12 @@ export function getPaths(channelName) { * in using the same folders for different channels. */ var channelHash = crypto.createHash('sha256').update(channelName).digest('hex'); + /** * because the length of socket-paths is limited, we use only the first 20 chars * and also start with A to ensure we do not start with a number * @link https://serverfault.com/questions/641347/check-if-a-path-exceeds-maximum-for-unix-domain-socket */ - var channelFolder = 'A' + channelHash.substring(0, 20); var channelPathBase = path.join(TMP_FOLDER_BASE, channelFolder); var folderPathReaders = path.join(channelPathBase, 'rdrs'); @@ -70,15 +70,12 @@ export function getPaths(channelName) { getPathsCache.set(channelName, ret); return ret; } - return getPathsCache.get(channelName); } var ENSURE_BASE_FOLDER_EXISTS_PROMISE = null; - function ensureBaseFolderExists() { return _ensureBaseFolderExists.apply(this, arguments); } - function _ensureBaseFolderExists() { _ensureBaseFolderExists = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3() { return _regeneratorRuntime.wrap(function _callee3$(_context3) { @@ -90,9 +87,7 @@ function _ensureBaseFolderExists() { return null; }); } - return _context3.abrupt("return", ENSURE_BASE_FOLDER_EXISTS_PROMISE); - case 2: case "end": return _context3.stop(); @@ -102,15 +97,14 @@ function _ensureBaseFolderExists() { })); return _ensureBaseFolderExists.apply(this, arguments); } - export function ensureFoldersExist(_x, _x2) { return _ensureFoldersExist.apply(this, arguments); } + /** * removes the tmp-folder * @return {Promise} */ - function _ensureFoldersExist() { _ensureFoldersExist = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4(channelName, paths) { var chmodValue; @@ -121,13 +115,11 @@ function _ensureFoldersExist() { paths = paths || getPaths(channelName); _context4.next = 3; return ensureBaseFolderExists(); - case 3: _context4.next = 5; return mkdir(paths.channelBase)["catch"](function () { return null; }); - case 5: _context4.next = 7; return Promise.all([mkdir(paths.readers)["catch"](function () { @@ -135,7 +127,6 @@ function _ensureFoldersExist() { }), mkdir(paths.messages)["catch"](function () { return null; })]); - case 7: // set permissions so other users can use the same channel chmodValue = '777'; @@ -143,7 +134,6 @@ function _ensureFoldersExist() { return Promise.all([chmod(paths.channelBase, chmodValue), chmod(paths.readers, chmodValue), chmod(paths.messages, chmodValue)])["catch"](function () { return null; }); - case 10: case "end": return _context4.stop(); @@ -153,11 +143,9 @@ function _ensureFoldersExist() { })); return _ensureFoldersExist.apply(this, arguments); } - export function clearNodeFolder() { return _clearNodeFolder.apply(this, arguments); } - function _clearNodeFolder() { _clearNodeFolder = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5() { return _regeneratorRuntime.wrap(function _callee5$(_context5) { @@ -168,18 +156,14 @@ function _clearNodeFolder() { _context5.next = 2; break; } - throw new Error('BroadcastChannel.clearNodeFolder(): path is wrong'); - case 2: ENSURE_BASE_FOLDER_EXISTS_PROMISE = null; _context5.next = 5; return removeDir(TMP_FOLDER_BASE); - case 5: ENSURE_BASE_FOLDER_EXISTS_PROMISE = null; return _context5.abrupt("return", true); - case 7: case "end": return _context5.stop(); @@ -189,7 +173,6 @@ function _clearNodeFolder() { })); return _clearNodeFolder.apply(this, arguments); } - export function socketPath(channelName, readerUuid, paths) { paths = paths || getPaths(channelName); var socketPath = path.join(paths.readers, readerUuid + '.s'); @@ -200,12 +183,12 @@ export function socketInfoPath(channelName, readerUuid, paths) { var socketPath = path.join(paths.readers, readerUuid + '.json'); return socketPath; } + /** * Because it is not possible to get all socket-files in a folder, * when used under fucking windows, * we have to set a normal file so other readers know our socket exists */ - export function createSocketInfoFile(channelName, readerUuid, paths) { var pathToFile = socketInfoPath(channelName, readerUuid, paths); return writeFile(pathToFile, JSON.stringify({ @@ -214,15 +197,14 @@ export function createSocketInfoFile(channelName, readerUuid, paths) { return pathToFile; }); } + /** * returns the amount of channel-folders in the tmp-directory * @return {Promise} */ - export function countChannelFolders() { return _countChannelFolders.apply(this, arguments); } - function _countChannelFolders() { _countChannelFolders = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee6() { var folders; @@ -232,15 +214,12 @@ function _countChannelFolders() { case 0: _context6.next = 2; return ensureBaseFolderExists(); - case 2: _context6.next = 4; return readdir(TMP_FOLDER_BASE); - case 4: folders = _context6.sent; return _context6.abrupt("return", folders.length); - case 6: case "end": return _context6.stop(); @@ -250,7 +229,6 @@ function _countChannelFolders() { })); return _countChannelFolders.apply(this, arguments); } - function connectionError(_x3) { return _connectionError.apply(this, arguments); } @@ -258,8 +236,6 @@ function connectionError(_x3) { * creates the socket-file and subscribes to it * @return {{emitter: EventEmitter, server: any}} */ - - function _connectionError() { _connectionError = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee7(originalError) { var count, addObj, text, newError; @@ -269,28 +245,23 @@ function _connectionError() { case 0: _context7.next = 2; return countChannelFolders(); - case 2: count = _context7.sent; - if (!(count < 30)) { _context7.next = 5; break; } - return _context7.abrupt("return", originalError); - case 5: addObj = {}; Object.entries(originalError).forEach(function (_ref3) { var k = _ref3[0], - v = _ref3[1]; + v = _ref3[1]; return addObj[k] = v; }); text = 'BroadcastChannel.create(): error: ' + 'This might happen if you have created to many channels, ' + 'like when you use BroadcastChannel in unit-tests.' + 'Try using BroadcastChannel.clearNodeFolder() to clear the tmp-folder before each test.' + 'See https://github.com/pubkey/broadcast-channel#clear-tmp-folder'; newError = new Error(text + ': ' + JSON.stringify(addObj, null, 2)); return _context7.abrupt("return", newError); - case 10: case "end": return _context7.stop(); @@ -300,11 +271,9 @@ function _connectionError() { })); return _connectionError.apply(this, arguments); } - export function createSocketEventEmitter(_x4, _x5, _x6) { return _createSocketEventEmitter.apply(this, arguments); } - function _createSocketEventEmitter() { _createSocketEventEmitter = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee10(channelName, readerUuid, paths) { var pathToSocket, emitter, server; @@ -331,11 +300,9 @@ function _createSocketEventEmitter() { case 0: _context8.next = 2; return connectionError(err); - case 2: useErr = _context8.sent; reject(useErr); - case 4: case "end": return _context8.stop(); @@ -343,7 +310,6 @@ function _createSocketEventEmitter() { } }, _callee8); })); - return function (_x26) { return _ref4.apply(this, arguments); }; @@ -359,19 +325,15 @@ function _createSocketEventEmitter() { _context9.next = 7; break; } - _context9.next = 3; return connectionError(err); - case 3: useErr = _context9.sent; reject(useErr); _context9.next = 8; break; - case 7: resolve(res); - case 8: case "end": return _context9.stop(); @@ -379,20 +341,17 @@ function _createSocketEventEmitter() { } }, _callee9); })); - return function (_x27, _x28) { return _ref5.apply(this, arguments); }; }()); }); - case 5: return _context10.abrupt("return", { path: pathToSocket, emitter: emitter, server: server }); - case 6: case "end": return _context10.stop(); @@ -402,16 +361,15 @@ function _createSocketEventEmitter() { })); return _createSocketEventEmitter.apply(this, arguments); } - export function openClientConnection(_x7, _x8) { return _openClientConnection.apply(this, arguments); } + /** * writes the new message to the file-system * so other readers can find it * @return {Promise} */ - function _openClientConnection() { _openClientConnection = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee11(channelName, readerUuid) { var pathToSocket, client; @@ -429,7 +387,6 @@ function _openClientConnection() { return rej(err); }); })); - case 3: case "end": return _context11.stop(); @@ -439,7 +396,6 @@ function _openClientConnection() { })); return _openClientConnection.apply(this, arguments); } - export function writeMessage(channelName, readerUuid, messageJson, paths) { paths = paths || getPaths(channelName); var time = microSeconds(); @@ -460,15 +416,14 @@ export function writeMessage(channelName, readerUuid, messageJson, paths) { }; }); } + /** * returns the uuids of all readers * @return {string[]} */ - export function getReadersUuids(_x9, _x10) { return _getReadersUuids.apply(this, arguments); } - function _getReadersUuids() { _getReadersUuids = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee12(channelName, paths) { var readersPath, files; @@ -480,7 +435,6 @@ function _getReadersUuids() { readersPath = paths.readers; _context12.next = 4; return readdir(readersPath); - case 4: files = _context12.sent; return _context12.abrupt("return", files.map(function (file) { @@ -491,7 +445,6 @@ function _getReadersUuids() { .map(function (split) { return split[0]; })); - case 6: case "end": return _context12.stop(); @@ -501,11 +454,9 @@ function _getReadersUuids() { })); return _getReadersUuids.apply(this, arguments); } - export function messagePath(_x11, _x12, _x13, _x14) { return _messagePath.apply(this, arguments); } - function _messagePath() { _messagePath = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee13(channelName, time, token, writerUuid) { var fileName, msgPath; @@ -516,7 +467,6 @@ function _messagePath() { fileName = time + '_' + writerUuid + '_' + token + '.json'; msgPath = path.join(getPaths(channelName).messages, fileName); return _context13.abrupt("return", msgPath); - case 3: case "end": return _context13.stop(); @@ -526,11 +476,9 @@ function _messagePath() { })); return _messagePath.apply(this, arguments); } - export function getAllMessages(_x15, _x16) { return _getAllMessages.apply(this, arguments); } - function _getAllMessages() { _getAllMessages = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee14(channelName, paths) { var messagesPath, files; @@ -542,7 +490,6 @@ function _getAllMessages() { messagesPath = paths.messages; _context14.next = 4; return readdir(messagesPath); - case 4: files = _context14.sent; return _context14.abrupt("return", files.map(function (file) { @@ -555,7 +502,6 @@ function _getAllMessages() { token: split[2] }; })); - case 6: case "end": return _context14.stop(); @@ -565,7 +511,6 @@ function _getAllMessages() { })); return _getAllMessages.apply(this, arguments); } - export function getSingleMessage(channelName, msgObj, paths) { paths = paths || getPaths(channelName); return { @@ -583,7 +528,6 @@ export function readMessage(messageObj) { export function cleanOldMessages(_x17, _x18) { return _cleanOldMessages.apply(this, arguments); } - function _cleanOldMessages() { _cleanOldMessages = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee15(messageObjects, ttl) { var olderThen; @@ -592,7 +536,6 @@ function _cleanOldMessages() { switch (_context15.prev = _context15.next) { case 0: olderThen = microSeconds() - ttl * 1000; // convert ttl to microseconds - _context15.next = 3; return Promise.all(messageObjects.filter(function (obj) { return obj.time < olderThen; @@ -601,7 +544,6 @@ function _cleanOldMessages() { return null; }); })); - case 3: case "end": return _context15.stop(); @@ -611,30 +553,27 @@ function _cleanOldMessages() { })); return _cleanOldMessages.apply(this, arguments); } - export var type = 'node'; + /** * creates a new channelState * @return {Promise} */ - export function create(_x19) { return _create.apply(this, arguments); } - function _create() { _create = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee16(channelName) { var options, - time, - paths, - ensureFolderExistsPromise, - uuid, - state, - _yield$Promise$all, - socketEE, - infoFilePath, - _args16 = arguments; - + time, + paths, + ensureFolderExistsPromise, + uuid, + state, + _yield$Promise$all, + socketEE, + infoFilePath, + _args16 = arguments; return _regeneratorRuntime.wrap(function _callee16$(_context16) { while (1) { switch (_context16.prev = _context16.next) { @@ -653,7 +592,6 @@ function _create() { paths: paths, // contains all messages that have been emitted before emittedMessagesIds: new ObliviousSet(options.node.ttl * 2), - /** * Used to ensure we do not write too many files at once * which could throw an error. @@ -677,18 +615,17 @@ function _create() { OTHER_INSTANCES[channelName].push(state); _context16.next = 11; return ensureFolderExistsPromise; - case 11: _context16.next = 13; return Promise.all([createSocketEventEmitter(channelName, uuid, paths), createSocketInfoFile(channelName, uuid, paths), refreshReaderClients(state)]); - case 13: _yield$Promise$all = _context16.sent; socketEE = _yield$Promise$all[0]; infoFilePath = _yield$Promise$all[1]; state.socketEE = socketEE; - state.infoFilePath = infoFilePath; // when new message comes in, we read it and emit it + state.infoFilePath = infoFilePath; + // when new message comes in, we read it and emit it socketEE.emitter.on('data', function (data) { // if the socket is used fast, it may appear that multiple messages are flushed at once // so we have to split them before @@ -705,7 +642,6 @@ function _create() { }); }); return _context16.abrupt("return", state); - case 20: case "end": return _context16.stop(); @@ -715,34 +651,29 @@ function _create() { })); return _create.apply(this, arguments); } - export function _filterMessage(msgObj, state) { if (msgObj.senderUuid === state.uuid) return false; // not send by own - if (state.emittedMessagesIds.has(msgObj.token)) return false; // not already emitted - if (!state.messagesCallback) return false; // no listener - if (msgObj.time < state.messagesCallbackTime) return false; // not older then onMessageCallback - if (msgObj.time < state.time) return false; // msgObj is older then channel state.emittedMessagesIds.add(msgObj.token); return true; } + /** * when the socket pings, so that we now new messages came, * run this */ - export function handleMessagePing(_x20, _x21) { return _handleMessagePing.apply(this, arguments); } + /** * ensures that the channelState is connected with all other readers * @return {Promise} */ - function _handleMessagePing() { _handleMessagePing = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee17(state, msgObj) { var messages, useMessages; @@ -754,27 +685,21 @@ function _handleMessagePing() { _context17.next = 2; break; } - return _context17.abrupt("return"); - case 2: if (msgObj) { _context17.next = 8; break; } - _context17.next = 5; return getAllMessages(state.channelName, state.paths); - case 5: messages = _context17.sent; _context17.next = 9; break; - case 8: // get single message messages = [getSingleMessage(state.channelName, msgObj, state.paths)]; - case 9: useMessages = messages.filter(function (msgObj) { return _filterMessage(msgObj, state); @@ -782,14 +707,11 @@ function _handleMessagePing() { return msgObjA.time - msgObjB.time; }); // sort by time // if no listener or message, so not do anything - if (!(!useMessages.length || !state.messagesCallback)) { _context17.next = 12; break; } - return _context17.abrupt("return"); - case 12: _context17.next = 14; return Promise.all(useMessages.map(function (msgObj) { @@ -797,17 +719,14 @@ function _handleMessagePing() { return msgObj.content = content; }); })); - case 14: useMessages.forEach(function (msgObj) { state.emittedMessagesIds.add(msgObj.token); - if (state.messagesCallback) { // emit to subscribers state.messagesCallback(msgObj.content.data); } }); - case 15: case "end": return _context17.stop(); @@ -817,7 +736,6 @@ function _handleMessagePing() { })); return _handleMessagePing.apply(this, arguments); } - export function refreshReaderClients(channelState) { return getReadersUuids(channelState.channelName, channelState.paths).then(function (otherReaders) { // remove subscriptions to closed readers @@ -832,18 +750,14 @@ export function refreshReaderClients(channelState) { _context.prev = 0; _context.next = 3; return channelState.otherReaderClients[readerUuid].destroy(); - case 3: _context.next = 7; break; - case 5: _context.prev = 5; _context.t0 = _context["catch"](0); - case 7: delete channelState.otherReaderClients[readerUuid]; - case 8: case "end": return _context.stop(); @@ -851,12 +765,12 @@ export function refreshReaderClients(channelState) { } }, _callee, null, [[0, 5]]); })); - return function (_x22) { return _ref.apply(this, arguments); }; - }()); // add new readers + }()); + // add new readers return Promise.all(otherReaders.filter(function (readerUuid) { return readerUuid !== channelState.uuid; }) // not own @@ -871,37 +785,29 @@ export function refreshReaderClients(channelState) { switch (_context2.prev = _context2.next) { case 0: _context2.prev = 0; - if (!channelState.closed) { _context2.next = 3; break; } - return _context2.abrupt("return"); - case 3: _context2.prev = 3; _context2.next = 6; return openClientConnection(channelState.channelName, readerUuid); - case 6: client = _context2.sent; channelState.otherReaderClients[readerUuid] = client; _context2.next = 12; break; - case 10: _context2.prev = 10; _context2.t0 = _context2["catch"](3); - case 12: _context2.next = 16; break; - case 14: _context2.prev = 14; _context2.t1 = _context2["catch"](0); - case 16: case "end": return _context2.stop(); @@ -909,28 +815,27 @@ export function refreshReaderClients(channelState) { } }, _callee2, null, [[0, 14], [3, 10]]); })); - return function (_x23) { return _ref2.apply(this, arguments); }; }())); }); } + /** * post a message to the other readers * @return {Promise} */ - export function postMessage(_x24, _x25) { return _postMessage.apply(this, arguments); } + /** * When multiple BroadcastChannels with the same name * are created in a single node-process, we can access them directly and emit messages. * This might not happen often in production * but will speed up things when this module is used in unit-tests. */ - function _postMessage() { _postMessage = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee19(channelState, messageJson) { var writePromise; @@ -943,7 +848,6 @@ function _postMessage() { }); channelState.writeBlockPromise = channelState.writeBlockPromise.then( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee18() { var _yield$Promise$all2, msgObj, pingStr, writeToReadersPromise; - return _regeneratorRuntime.wrap(function _callee18$(_context18) { while (1) { switch (_context18.prev = _context18.next) { @@ -952,11 +856,9 @@ function _postMessage() { return new Promise(function (res) { return setTimeout(res, 0); }); - case 2: _context18.next = 4; return Promise.all([writePromise, refreshReaderClients(channelState)]); - case 4: _yield$Promise$all2 = _context18.sent; msgObj = _yield$Promise$all2[0]; @@ -975,16 +877,13 @@ function _postMessage() { * to not waste resources on cleaning up, * only if random-int matches, we clean up old messages */ - if (randomInt(0, 20) === 0) { /* await */ getAllMessages(channelState.channelName, channelState.paths).then(function (allMessages) { return cleanOldMessages(allMessages, channelState.options.node.ttl); }); } - return _context18.abrupt("return", writeToReadersPromise); - case 11: case "end": return _context18.stop(); @@ -993,7 +892,6 @@ function _postMessage() { }, _callee18); }))); return _context19.abrupt("return", channelState.writeBlockPromise); - case 3: case "end": return _context19.stop(); @@ -1003,13 +901,11 @@ function _postMessage() { })); return _postMessage.apply(this, arguments); } - export function emitOverFastPath(state, msgObj, messageJson) { if (!state.options.node.useFastPath) { // disabled return; } - var others = OTHER_INSTANCES[state.channelName].filter(function (s) { return s !== state; }); @@ -1030,11 +926,11 @@ export function onMessage(channelState, fn) { channelState.messagesCallback = fn; handleMessagePing(channelState); } + /** * closes the channel * @return {Promise} */ - export function close(channelState) { if (channelState.closed) return; channelState.closed = true; @@ -1042,28 +938,24 @@ export function close(channelState) { OTHER_INSTANCES[channelState.channelName] = OTHER_INSTANCES[channelState.channelName].filter(function (o) { return o !== channelState; }); - if (channelState.removeUnload) { channelState.removeUnload.remove(); } - return new Promise(function (res) { if (channelState.socketEE) channelState.socketEE.emitter.removeAllListeners(); Object.values(channelState.otherReaderClients).forEach(function (client) { return client.destroy(); }); - if (channelState.infoFilePath) { try { fs.unlinkSync(channelState.infoFilePath); } catch (err) {} } + /** * the server get closed lazy because others might still write on it * and have not found out that the infoFile was deleted */ - - setTimeout(function () { channelState.socketEE.server.close(); res(); @@ -1077,13 +969,13 @@ export function canBeUsed() { return false; } } + /** * on node we use a relatively height averageResponseTime, * because the file-io might be in use. * Also it is more important that the leader-election is reliable, * then to have a fast election. */ - export function averageResponseTime() { return 200; } @@ -1091,7 +983,6 @@ export function microSeconds() { // convert nano to micro seconds return parseInt(now() / 1000); } - function now() { return Number(process.hrtime.bigint()); // returns nanoseconds } \ No newline at end of file diff --git a/dist/esnode/options.js b/dist/esnode/options.js index fbcc5244..86c7fae3 100644 --- a/dist/esnode/options.js +++ b/dist/esnode/options.js @@ -1,29 +1,32 @@ export function fillOptionsWithDefaults() { var originalOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var options = JSON.parse(JSON.stringify(originalOptions)); // main + var options = JSON.parse(JSON.stringify(originalOptions)); - if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true; // indexed-db - - if (!options.idb) options.idb = {}; // after this time the messages get deleted + // main + if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true; + // indexed-db + if (!options.idb) options.idb = {}; + // after this time the messages get deleted if (!options.idb.ttl) options.idb.ttl = 1000 * 45; - if (!options.idb.fallbackInterval) options.idb.fallbackInterval = 150; // handles abrupt db onclose events. - - if (originalOptions.idb && typeof originalOptions.idb.onclose === 'function') options.idb.onclose = originalOptions.idb.onclose; // localstorage + if (!options.idb.fallbackInterval) options.idb.fallbackInterval = 150; + // handles abrupt db onclose events. + if (originalOptions.idb && typeof originalOptions.idb.onclose === 'function') options.idb.onclose = originalOptions.idb.onclose; + // localstorage if (!options.localstorage) options.localstorage = {}; - if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60; // custom methods + if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60; - if (originalOptions.methods) options.methods = originalOptions.methods; // node + // custom methods + if (originalOptions.methods) options.methods = originalOptions.methods; + // node if (!options.node) options.node = {}; if (!options.node.ttl) options.node.ttl = 1000 * 60 * 2; // 2 minutes; - /** * On linux use 'ulimit -Hn' to get the limit of open files. * On ubuntu this was 4096 for me, so we use half of that as maxParallelWrites default. */ - if (!options.node.maxParallelWrites) options.node.maxParallelWrites = 2048; if (typeof options.node.useFastPath === 'undefined') options.node.useFastPath = true; return options; diff --git a/dist/esnode/util.js b/dist/esnode/util.js index 99b7e529..6c9b9b6e 100644 --- a/dist/esnode/util.js +++ b/dist/esnode/util.js @@ -22,15 +22,16 @@ export function sleep(time, resolveWith) { export function randomInt(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } + /** * https://stackoverflow.com/a/8084248 */ - export function randomToken() { return Math.random().toString(36).substring(2); } var lastMs = 0; var additional = 0; + /** * returns the current time in micro-seconds, * WARNING: This is a pseudo-function @@ -38,10 +39,8 @@ var additional = 0; * This is enough in browsers, and this function will not be used in nodejs. * The main reason for this hack is to ensure that BroadcastChannel behaves equal to production when it is used in fast-running unit tests. */ - export function microSeconds() { var ms = new Date().getTime(); - if (ms === lastMs) { additional++; return ms * 1000 + additional; diff --git a/dist/lib/broadcast-channel.js b/dist/lib/broadcast-channel.js index 8f3fc9bf..d8162620 100644 --- a/dist/lib/broadcast-channel.js +++ b/dist/lib/broadcast-channel.js @@ -6,13 +6,9 @@ Object.defineProperty(exports, "__esModule", { exports.OPEN_BROADCAST_CHANNELS = exports.BroadcastChannel = void 0; exports.clearNodeFolder = clearNodeFolder; exports.enforceOptions = enforceOptions; - var _util = require("./util.js"); - var _methodChooser = require("./method-chooser.js"); - var _options = require("./options.js"); - /** * Contains all open channels, * used in tests to ensure everything is closed. @@ -20,77 +16,73 @@ var _options = require("./options.js"); var OPEN_BROADCAST_CHANNELS = new Set(); exports.OPEN_BROADCAST_CHANNELS = OPEN_BROADCAST_CHANNELS; var lastId = 0; - var BroadcastChannel = function BroadcastChannel(name, options) { // identifier of the channel to debug stuff this.id = lastId++; OPEN_BROADCAST_CHANNELS.add(this); this.name = name; - if (ENFORCED_OPTIONS) { options = ENFORCED_OPTIONS; } - this.options = (0, _options.fillOptionsWithDefaults)(options); - this.method = (0, _methodChooser.chooseMethod)(this.options); // isListening + this.method = (0, _methodChooser.chooseMethod)(this.options); + // isListening this._iL = false; + /** * _onMessageListener * setting onmessage twice, * will overwrite the first listener */ - this._onML = null; + /** * _addEventListeners */ - this._addEL = { message: [], internal: [] }; + /** * Unsend message promises * where the sending is still in progress * @type {Set} */ - this._uMP = new Set(); + /** * _beforeClose * array of promises that will be awaited * before the channel is closed */ - this._befC = []; + /** * _preparePromise */ - this._prepP = null; - _prepareChannel(this); -}; // STATICS +}; + +// STATICS /** * used to identify if someone overwrites * window.BroadcastChannel with this * See methods/native.js */ - - exports.BroadcastChannel = BroadcastChannel; BroadcastChannel._pubkey = true; + /** * clears the tmp-folder if is node * @return {Promise} true if has run, false if not node */ - function clearNodeFolder(options) { options = (0, _options.fillOptionsWithDefaults)(options); var method = (0, _methodChooser.chooseMethod)(options); - if (method.type === 'node') { return method.clearNodeFolder().then(function () { return true; @@ -99,19 +91,17 @@ function clearNodeFolder(options) { return _util.PROMISE_RESOLVED_FALSE; } } + /** * if set, this method is enforced, * no mather what the options are */ - - var ENFORCED_OPTIONS; - function enforceOptions(options) { ENFORCED_OPTIONS = options; -} // PROTOTYPE - +} +// PROTOTYPE BroadcastChannel.prototype = { postMessage: function postMessage(msg) { if (this.closed) { @@ -123,87 +113,77 @@ BroadcastChannel.prototype = { */ JSON.stringify(msg)); } - return _post(this, 'message', msg); }, postInternal: function postInternal(msg) { return _post(this, 'internal', msg); }, - set onmessage(fn) { var time = this.method.microSeconds(); var listenObj = { time: time, fn: fn }; - _removeListenerObject(this, 'message', this._onML); - if (fn && typeof fn === 'function') { this._onML = listenObj; - _addListenerObject(this, 'message', listenObj); } else { this._onML = null; } }, - addEventListener: function addEventListener(type, fn) { var time = this.method.microSeconds(); var listenObj = { time: time, fn: fn }; - _addListenerObject(this, type, listenObj); }, removeEventListener: function removeEventListener(type, fn) { var obj = this._addEL[type].find(function (obj) { return obj.fn === fn; }); - _removeListenerObject(this, type, obj); }, close: function close() { var _this = this; - if (this.closed) { return; } - OPEN_BROADCAST_CHANNELS["delete"](this); this.closed = true; var awaitPrepare = this._prepP ? this._prepP : _util.PROMISE_RESOLVED_VOID; this._onML = null; this._addEL.message = []; - return awaitPrepare // wait until all current sending are processed + return awaitPrepare + // wait until all current sending are processed .then(function () { return Promise.all(Array.from(_this._uMP)); - }) // run before-close hooks + }) + // run before-close hooks .then(function () { return Promise.all(_this._befC.map(function (fn) { return fn(); })); - }) // close the channel + }) + // close the channel .then(function () { return _this.method.close(_this._state); }); }, - get type() { return this.method.type; }, - get isClosed() { return this.closed; } - }; + /** * Post a message over the channel * @returns {Promise} that resolved when the message sending is done */ - function _post(broadcastChannel, type, msg) { var time = broadcastChannel.method.microSeconds(); var msgObj = { @@ -213,25 +193,22 @@ function _post(broadcastChannel, type, msg) { }; var awaitPrepare = broadcastChannel._prepP ? broadcastChannel._prepP : _util.PROMISE_RESOLVED_VOID; return awaitPrepare.then(function () { - var sendPromise = broadcastChannel.method.postMessage(broadcastChannel._state, msgObj); // add/remove to unsend messages list + var sendPromise = broadcastChannel.method.postMessage(broadcastChannel._state, msgObj); + // add/remove to unsend messages list broadcastChannel._uMP.add(sendPromise); - sendPromise["catch"]().then(function () { return broadcastChannel._uMP["delete"](sendPromise); }); return sendPromise; }); } - function _prepareChannel(channel) { var maybePromise = channel.method.create(channel.name, channel.options); - if ((0, _util.isPromise)(maybePromise)) { channel._prepP = maybePromise; maybePromise.then(function (s) { // used in tests to simulate slow runtime - /*if (channel.options.prepareDelay) { await new Promise(res => setTimeout(res, this.options.prepareDelay)); }*/ @@ -241,30 +218,25 @@ function _prepareChannel(channel) { channel._state = maybePromise; } } - function _hasMessageListeners(channel) { if (channel._addEL.message.length > 0) return true; if (channel._addEL.internal.length > 0) return true; return false; } - function _addListenerObject(channel, type, obj) { channel._addEL[type].push(obj); - _startListening(channel); } - function _removeListenerObject(channel, type, obj) { channel._addEL[type] = channel._addEL[type].filter(function (o) { return o !== obj; }); - _stopListening(channel); } - function _startListening(channel) { if (!channel._iL && _hasMessageListeners(channel)) { // someone is listening, start subscribing + var listenerFn = function listenerFn(msgObj) { channel._addEL[msgObj.type].forEach(function (listenerObject) { /** @@ -278,15 +250,12 @@ function _startListening(channel) { */ var hundredMsInMicro = 100 * 1000; var minMessageTime = listenerObject.time - hundredMsInMicro; - if (msgObj.time >= minMessageTime) { listenerObject.fn(msgObj.data); } }); }; - var time = channel.method.microSeconds(); - if (channel._prepP) { channel._prepP.then(function () { channel._iL = true; @@ -298,7 +267,6 @@ function _startListening(channel) { } } } - function _stopListening(channel) { if (channel._iL && !_hasMessageListeners(channel)) { // noone is listening, stop subscribing diff --git a/dist/lib/browser.js b/dist/lib/browser.js index bdb4fdcb..f912820e 100644 --- a/dist/lib/browser.js +++ b/dist/lib/browser.js @@ -7,13 +7,9 @@ Object.defineProperty(exports, "__esModule", { exports.OPEN_BROADCAST_CHANNELS = exports.BroadcastChannel = void 0; exports.clearNodeFolder = clearNodeFolder; exports.enforceOptions = enforceOptions; - var _util = require("./util.js"); - var _methodChooser = require("./method-chooser.js"); - var _options = require("./options.js"); - /** * Contains all open channels, * used in tests to ensure everything is closed. @@ -21,77 +17,73 @@ var _options = require("./options.js"); var OPEN_BROADCAST_CHANNELS = new Set(); exports.OPEN_BROADCAST_CHANNELS = OPEN_BROADCAST_CHANNELS; var lastId = 0; - var BroadcastChannel = function BroadcastChannel(name, options) { // identifier of the channel to debug stuff this.id = lastId++; OPEN_BROADCAST_CHANNELS.add(this); this.name = name; - if (ENFORCED_OPTIONS) { options = ENFORCED_OPTIONS; } - this.options = (0, _options.fillOptionsWithDefaults)(options); - this.method = (0, _methodChooser.chooseMethod)(this.options); // isListening + this.method = (0, _methodChooser.chooseMethod)(this.options); + // isListening this._iL = false; + /** * _onMessageListener * setting onmessage twice, * will overwrite the first listener */ - this._onML = null; + /** * _addEventListeners */ - this._addEL = { message: [], internal: [] }; + /** * Unsend message promises * where the sending is still in progress * @type {Set} */ - this._uMP = new Set(); + /** * _beforeClose * array of promises that will be awaited * before the channel is closed */ - this._befC = []; + /** * _preparePromise */ - this._prepP = null; - _prepareChannel(this); -}; // STATICS +}; + +// STATICS /** * used to identify if someone overwrites * window.BroadcastChannel with this * See methods/native.js */ - - exports.BroadcastChannel = BroadcastChannel; BroadcastChannel._pubkey = true; + /** * clears the tmp-folder if is node * @return {Promise} true if has run, false if not node */ - function clearNodeFolder(options) { options = (0, _options.fillOptionsWithDefaults)(options); var method = (0, _methodChooser.chooseMethod)(options); - if (method.type === 'node') { return method.clearNodeFolder().then(function () { return true; @@ -100,19 +92,17 @@ function clearNodeFolder(options) { return _util.PROMISE_RESOLVED_FALSE; } } + /** * if set, this method is enforced, * no mather what the options are */ - - var ENFORCED_OPTIONS; - function enforceOptions(options) { ENFORCED_OPTIONS = options; -} // PROTOTYPE - +} +// PROTOTYPE BroadcastChannel.prototype = { postMessage: function postMessage(msg) { if (this.closed) { @@ -124,87 +114,77 @@ BroadcastChannel.prototype = { */ JSON.stringify(msg)); } - return _post(this, 'message', msg); }, postInternal: function postInternal(msg) { return _post(this, 'internal', msg); }, - set onmessage(fn) { var time = this.method.microSeconds(); var listenObj = { time: time, fn: fn }; - _removeListenerObject(this, 'message', this._onML); - if (fn && typeof fn === 'function') { this._onML = listenObj; - _addListenerObject(this, 'message', listenObj); } else { this._onML = null; } }, - addEventListener: function addEventListener(type, fn) { var time = this.method.microSeconds(); var listenObj = { time: time, fn: fn }; - _addListenerObject(this, type, listenObj); }, removeEventListener: function removeEventListener(type, fn) { var obj = this._addEL[type].find(function (obj) { return obj.fn === fn; }); - _removeListenerObject(this, type, obj); }, close: function close() { var _this = this; - if (this.closed) { return; } - OPEN_BROADCAST_CHANNELS["delete"](this); this.closed = true; var awaitPrepare = this._prepP ? this._prepP : _util.PROMISE_RESOLVED_VOID; this._onML = null; this._addEL.message = []; - return awaitPrepare // wait until all current sending are processed + return awaitPrepare + // wait until all current sending are processed .then(function () { return Promise.all(Array.from(_this._uMP)); - }) // run before-close hooks + }) + // run before-close hooks .then(function () { return Promise.all(_this._befC.map(function (fn) { return fn(); })); - }) // close the channel + }) + // close the channel .then(function () { return _this.method.close(_this._state); }); }, - get type() { return this.method.type; }, - get isClosed() { return this.closed; } - }; + /** * Post a message over the channel * @returns {Promise} that resolved when the message sending is done */ - function _post(broadcastChannel, type, msg) { var time = broadcastChannel.method.microSeconds(); var msgObj = { @@ -214,25 +194,22 @@ function _post(broadcastChannel, type, msg) { }; var awaitPrepare = broadcastChannel._prepP ? broadcastChannel._prepP : _util.PROMISE_RESOLVED_VOID; return awaitPrepare.then(function () { - var sendPromise = broadcastChannel.method.postMessage(broadcastChannel._state, msgObj); // add/remove to unsend messages list + var sendPromise = broadcastChannel.method.postMessage(broadcastChannel._state, msgObj); + // add/remove to unsend messages list broadcastChannel._uMP.add(sendPromise); - sendPromise["catch"]().then(function () { return broadcastChannel._uMP["delete"](sendPromise); }); return sendPromise; }); } - function _prepareChannel(channel) { var maybePromise = channel.method.create(channel.name, channel.options); - if ((0, _util.isPromise)(maybePromise)) { channel._prepP = maybePromise; maybePromise.then(function (s) { // used in tests to simulate slow runtime - /*if (channel.options.prepareDelay) { await new Promise(res => setTimeout(res, this.options.prepareDelay)); }*/ @@ -242,30 +219,25 @@ function _prepareChannel(channel) { channel._state = maybePromise; } } - function _hasMessageListeners(channel) { if (channel._addEL.message.length > 0) return true; if (channel._addEL.internal.length > 0) return true; return false; } - function _addListenerObject(channel, type, obj) { channel._addEL[type].push(obj); - _startListening(channel); } - function _removeListenerObject(channel, type, obj) { channel._addEL[type] = channel._addEL[type].filter(function (o) { return o !== obj; }); - _stopListening(channel); } - function _startListening(channel) { if (!channel._iL && _hasMessageListeners(channel)) { // someone is listening, start subscribing + var listenerFn = function listenerFn(msgObj) { channel._addEL[msgObj.type].forEach(function (listenerObject) { /** @@ -279,15 +251,12 @@ function _startListening(channel) { */ var hundredMsInMicro = 100 * 1000; var minMessageTime = listenerObject.time - hundredMsInMicro; - if (msgObj.time >= minMessageTime) { listenerObject.fn(msgObj.data); } }); }; - var time = channel.method.microSeconds(); - if (channel._prepP) { channel._prepP.then(function () { channel._iL = true; @@ -299,7 +268,6 @@ function _startListening(channel) { } } } - function _stopListening(channel) { if (channel._iL && !_hasMessageListeners(channel)) { // noone is listening, stop subscribing @@ -312,7 +280,6 @@ function _stopListening(channel) { "use strict"; var _module = require('./index.es5.js'); - var BroadcastChannel = _module.BroadcastChannel; var createLeaderElection = _module.createLeaderElection; window['BroadcastChannel2'] = BroadcastChannel; @@ -321,7 +288,6 @@ window['createLeaderElection'] = createLeaderElection; "use strict"; var _index = require("./index.js"); - /** * because babel can only export on default-attribute, * we use this for the non-module-build @@ -330,6 +296,7 @@ var _index = require("./index.js"); * but * var BroadcastChannel = require('broadcast-channel'); */ + module.exports = { BroadcastChannel: _index.BroadcastChannel, createLeaderElection: _index.createLeaderElection, @@ -379,9 +346,7 @@ Object.defineProperty(exports, "enforceOptions", { return _broadcastChannel.enforceOptions; } }); - var _broadcastChannel = require("./broadcast-channel.js"); - var _leaderElection = require("./leader-election.js"); },{"./broadcast-channel.js":1,"./leader-election.js":5}],5:[function(require,module,exports){ "use strict"; @@ -391,37 +356,30 @@ Object.defineProperty(exports, "__esModule", { }); exports.beLeader = beLeader; exports.createLeaderElection = createLeaderElection; - var _util = require("./util.js"); - var _unload = require("unload"); - var LeaderElection = function LeaderElection(broadcastChannel, options) { var _this = this; - this.broadcastChannel = broadcastChannel; this._options = options; this.isLeader = false; this.hasLeader = false; this.isDead = false; this.token = (0, _util.randomToken)(); + /** * Apply Queue, * used to ensure we do not run applyOnce() * in parallel. */ + this._aplQ = _util.PROMISE_RESOLVED_VOID; + // amount of unfinished applyOnce() calls + this._aplQC = 0; - this._aplQ = _util.PROMISE_RESOLVED_VOID; // amount of unfinished applyOnce() calls - - this._aplQC = 0; // things to clean up - + // things to clean up this._unl = []; // _unloads - this._lstns = []; // _listeners - this._dpL = function () {}; // onduplicate listener - - this._dpLC = false; // true when onduplicate called /** @@ -429,55 +387,47 @@ var LeaderElection = function LeaderElection(broadcastChannel, options) { * we still listen to messages to ensure the hasLeader flag * is set correctly. */ - var hasLeaderListener = function hasLeaderListener(msg) { if (msg.context === 'leader') { if (msg.action === 'death') { _this.hasLeader = false; } - if (msg.action === 'tell') { _this.hasLeader = true; } } }; - this.broadcastChannel.addEventListener('internal', hasLeaderListener); - this._lstns.push(hasLeaderListener); }; - LeaderElection.prototype = { /** * Returns true if the instance is leader, * false if not. * @async */ - applyOnce: function applyOnce( // true if the applyOnce() call came from the fallbackInterval cycle + applyOnce: function applyOnce( + // true if the applyOnce() call came from the fallbackInterval cycle isFromFallbackInterval) { var _this2 = this; - if (this.isLeader) { return (0, _util.sleep)(0, true); } - if (this.isDead) { return (0, _util.sleep)(0, false); } + /** * Already applying more then once, * -> wait for the apply queue to be finished. */ - - if (this._aplQC > 1) { return this._aplQ; } + /** * Add a new apply-run */ - - var applyRun = function applyRun() { /** * Optimization shortcuts. @@ -487,7 +437,6 @@ LeaderElection.prototype = { if (_this2.isLeader) { return _util.PROMISE_RESOLVED_TRUE; } - var stopCriteria = false; var stopCriteriaPromiseResolve; /** @@ -496,7 +445,6 @@ LeaderElection.prototype = { * have to await the responseTime when it is already clear * that the election failed. */ - var stopCriteriaPromise = new Promise(function (res) { stopCriteriaPromiseResolve = function stopCriteriaPromiseResolve() { stopCriteria = true; @@ -504,11 +452,9 @@ LeaderElection.prototype = { }; }); var recieved = []; - var handleMessage = function handleMessage(msg) { if (msg.context === 'leader' && msg.token != _this2.token) { recieved.push(msg); - if (msg.action === 'apply') { // other is applying if (msg.token > _this2.token) { @@ -519,7 +465,6 @@ LeaderElection.prototype = { stopCriteriaPromiseResolve(); } } - if (msg.action === 'tell') { // other is already leader stopCriteriaPromiseResolve(); @@ -527,8 +472,8 @@ LeaderElection.prototype = { } } }; - _this2.broadcastChannel.addEventListener('internal', handleMessage); + /** * If the applyOnce() call came from the fallbackInterval, * we can assume that the election runs in the background and @@ -539,26 +484,24 @@ LeaderElection.prototype = { * But also it takes longer which is not a problem because we anyway * run in the background. */ - - var waitForAnswerTime = isFromFallbackInterval ? _this2._options.responseTime * 4 : _this2._options.responseTime; - var applyPromise = _sendMessage(_this2, 'apply') // send out that this one is applying .then(function () { return Promise.race([(0, _util.sleep)(waitForAnswerTime), stopCriteriaPromise.then(function () { return Promise.reject(new Error()); })]); - }) // send again in case another instance was just created + }) + // send again in case another instance was just created .then(function () { return _sendMessage(_this2, 'apply'); - }) // let others time to respond + }) + // let others time to respond .then(function () { return Promise.race([(0, _util.sleep)(waitForAnswerTime), stopCriteriaPromise.then(function () { return Promise.reject(new Error()); })]); })["catch"](function () {}).then(function () { _this2.broadcastChannel.removeEventListener('internal', handleMessage); - if (!stopCriteria) { // no stop criteria -> own is leader return beLeader(_this2).then(function () { @@ -569,10 +512,8 @@ LeaderElection.prototype = { return false; } }); - return applyPromise; }; - this._aplQC = this._aplQC + 1; this._aplQ = this._aplQ.then(function () { return applyRun(); @@ -584,82 +525,68 @@ LeaderElection.prototype = { }); }, awaitLeadership: function awaitLeadership() { - if ( - /* _awaitLeadershipPromise */ + if ( /* _awaitLeadershipPromise */ !this._aLP) { this._aLP = _awaitLeadershipOnce(this); } - return this._aLP; }, - set onduplicate(fn) { this._dpL = fn; }, - die: function die() { var _this3 = this; - this._lstns.forEach(function (listener) { return _this3.broadcastChannel.removeEventListener('internal', listener); }); - this._lstns = []; - this._unl.forEach(function (uFn) { return uFn.remove(); }); - this._unl = []; - if (this.isLeader) { this.hasLeader = false; this.isLeader = false; } - this.isDead = true; return _sendMessage(this, 'death'); } }; + /** * @param leaderElector {LeaderElector} */ - function _awaitLeadershipOnce(leaderElector) { if (leaderElector.isLeader) { return _util.PROMISE_RESOLVED_VOID; } - return new Promise(function (res) { var resolved = false; - function finish() { if (resolved) { return; } - resolved = true; leaderElector.broadcastChannel.removeEventListener('internal', whenDeathListener); res(true); - } // try once now - + } + // try once now leaderElector.applyOnce().then(function () { if (leaderElector.isLeader) { finish(); } }); + /** * Try on fallbackInterval * @recursive */ - var tryOnFallBack = function tryOnFallBack() { return (0, _util.sleep)(leaderElector._options.fallbackInterval).then(function () { if (leaderElector.isDead || resolved) { return; } - if (leaderElector.isLeader) { finish(); } else { @@ -673,9 +600,9 @@ function _awaitLeadershipOnce(leaderElector) { } }); }; + tryOnFallBack(); - tryOnFallBack(); // try when other leader dies - + // try when other leader dies var whenDeathListener = function whenDeathListener(msg) { if (msg.context === 'leader' && msg.action === 'death') { leaderElector.hasLeader = false; @@ -686,17 +613,14 @@ function _awaitLeadershipOnce(leaderElector) { }); } }; - leaderElector.broadcastChannel.addEventListener('internal', whenDeathListener); - leaderElector._lstns.push(whenDeathListener); }); } + /** * sends and internal message over the broadcast-channel */ - - function _sendMessage(leaderElector, action) { var msgJson = { context: 'leader', @@ -705,21 +629,17 @@ function _sendMessage(leaderElector, action) { }; return leaderElector.broadcastChannel.postInternal(msgJson); } - function beLeader(leaderElector) { leaderElector.isLeader = true; leaderElector.hasLeader = true; var unloadFn = (0, _unload.add)(function () { return leaderElector.die(); }); - leaderElector._unl.push(unloadFn); - var isLeaderListener = function isLeaderListener(msg) { if (msg.context === 'leader' && msg.action === 'apply') { _sendMessage(leaderElector, 'tell'); } - if (msg.context === 'leader' && msg.action === 'tell' && !leaderElector._dpLC) { /** * another instance is also leader! @@ -730,49 +650,35 @@ function beLeader(leaderElector) { * @link https://github.com/pubkey/broadcast-channel/issues/385 */ leaderElector._dpLC = true; - leaderElector._dpL(); // message the lib user so the app can handle the problem - - _sendMessage(leaderElector, 'tell'); // ensure other leader also knows the problem - } }; leaderElector.broadcastChannel.addEventListener('internal', isLeaderListener); - leaderElector._lstns.push(isLeaderListener); - return _sendMessage(leaderElector, 'tell'); } - function fillOptionsWithDefaults(options, channel) { if (!options) options = {}; options = JSON.parse(JSON.stringify(options)); - if (!options.fallbackInterval) { options.fallbackInterval = 3000; } - if (!options.responseTime) { options.responseTime = channel.method.averageResponseTime(channel.options); } - return options; } - function createLeaderElection(channel, options) { if (channel._leaderElector) { throw new Error('BroadcastChannel already has a leader-elector'); } - options = fillOptionsWithDefaults(options, channel); var elector = new LeaderElection(channel, options); - channel._befC.push(function () { return elector.die(); }); - channel._leaderElector = elector; return elector; } @@ -780,59 +686,49 @@ function createLeaderElection(channel, options) { "use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); - var _typeof = require("@babel/runtime/helpers/typeof"); - Object.defineProperty(exports, "__esModule", { value: true }); exports.chooseMethod = chooseMethod; - var _native = _interopRequireDefault(require("./methods/native.js")); - var _indexedDb = _interopRequireDefault(require("./methods/indexed-db.js")); - var _localstorage = _interopRequireDefault(require("./methods/localstorage.js")); - var _simulate = _interopRequireDefault(require("./methods/simulate.js")); - - function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } - function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } - // the line below will be removed from es5/browser builds + // order is important -var METHODS = [_native["default"], // fastest +var METHODS = [_native["default"], +// fastest _indexedDb["default"], _localstorage["default"]]; - function chooseMethod(options) { - var chooseMethods = [].concat(options.methods, METHODS).filter(Boolean); // the line below will be removed from es5/browser builds + var chooseMethods = [].concat(options.methods, METHODS).filter(Boolean); + // the line below will be removed from es5/browser builds + // directly chosen if (options.type) { if (options.type === 'simulate') { // only use simulate-method if directly chosen return _simulate["default"]; } - var ret = chooseMethods.find(function (m) { return m.type === options.type; }); if (!ret) throw new Error('method-type ' + options.type + ' not found');else return ret; } + /** * if no webworker support is needed, * remove idb from the list so that localstorage is been chosen */ - - if (!options.webWorkerSupport) { chooseMethods = chooseMethods.filter(function (m) { return m.type !== 'idb'; }); } - var useMethod = chooseMethods.find(function (method) { return method.canBeUsed(); }); @@ -865,13 +761,9 @@ exports.postMessage = postMessage; exports.removeMessagesById = removeMessagesById; exports.type = void 0; exports.writeMessage = writeMessage; - var _util = require("../util.js"); - var _obliviousSet = require("oblivious-set"); - var _options = require("../options.js"); - /** * this method uses indexeddb to store the messages * There is currently no observerAPI for idb @@ -880,58 +772,54 @@ var _options = require("../options.js"); * When working on this, ensure to use these performance optimizations: * @link https://rxdb.info/slow-indexeddb.html */ + var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var DB_PREFIX = 'pubkey.broadcast-channel-0-'; var OBJECT_STORE_ID = 'messages'; + /** * Use relaxed durability for faster performance on all transactions. * @link https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/ */ - var TRANSACTION_SETTINGS = { durability: 'relaxed' }; exports.TRANSACTION_SETTINGS = TRANSACTION_SETTINGS; var type = 'idb'; exports.type = type; - function getIdb() { if (typeof indexedDB !== 'undefined') return indexedDB; - if (typeof window !== 'undefined') { if (typeof window.mozIndexedDB !== 'undefined') return window.mozIndexedDB; if (typeof window.webkitIndexedDB !== 'undefined') return window.webkitIndexedDB; if (typeof window.msIndexedDB !== 'undefined') return window.msIndexedDB; } - return false; } + /** * If possible, we should explicitly commit IndexedDB transactions * for better performance. * @link https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/ */ - - function commitIndexedDBTransaction(tx) { if (tx.commit) { tx.commit(); } } - function createDatabase(channelName) { - var IndexedDB = getIdb(); // create table + var IndexedDB = getIdb(); + // create table var dbName = DB_PREFIX + channelName; + /** * All IndexedDB databases are opened without version * because it is a bit faster, especially on firefox * @link http://nparashuram.com/IndexedDB/perf/#Open%20Database%20with%20version */ - var openRequest = IndexedDB.open(dbName); - openRequest.onupgradeneeded = function (ev) { var db = ev.target.result; db.createObjectStore(OBJECT_STORE_ID, { @@ -939,24 +827,21 @@ function createDatabase(channelName) { autoIncrement: true }); }; - var dbPromise = new Promise(function (res, rej) { openRequest.onerror = function (ev) { return rej(ev); }; - openRequest.onsuccess = function () { res(openRequest.result); }; }); return dbPromise; } + /** * writes the new message to the database * so other readers can find it */ - - function writeMessage(db, readerUuid, messageJson) { var time = new Date().getTime(); var writeObject = { @@ -969,17 +854,14 @@ function writeMessage(db, readerUuid, messageJson) { tx.oncomplete = function () { return res(); }; - tx.onerror = function (ev) { return rej(ev); }; - var objectStore = tx.objectStore(OBJECT_STORE_ID); objectStore.add(writeObject); commitIndexedDBTransaction(tx); }); } - function getAllMessages(db) { var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); @@ -987,10 +869,9 @@ function getAllMessages(db) { return new Promise(function (res) { objectStore.openCursor().onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { - ret.push(cursor.value); //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); - + ret.push(cursor.value); + //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); cursor["continue"](); } else { commitIndexedDBTransaction(tx); @@ -999,31 +880,28 @@ function getAllMessages(db) { }; }); } - function getMessagesHigherThan(db, lastCursorId) { var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); var ret = []; var keyRangeValue = IDBKeyRange.bound(lastCursorId + 1, Infinity); + /** * Optimization shortcut, * if getAll() can be used, do not use a cursor. * @link https://rxdb.info/slow-indexeddb.html */ - if (objectStore.getAll) { var getAllRequest = objectStore.getAll(keyRangeValue); return new Promise(function (res, rej) { getAllRequest.onerror = function (err) { return rej(err); }; - getAllRequest.onsuccess = function (e) { res(e.target.result); }; }); } - function openCursor() { // Occasionally Safari will fail on IDBKeyRange.bound, this // catches that error, having it open the cursor to the first @@ -1035,17 +913,13 @@ function getMessagesHigherThan(db, lastCursorId) { return objectStore.openCursor(); } } - return new Promise(function (res, rej) { var openCursorRequest = openCursor(); - openCursorRequest.onerror = function (err) { return rej(err); }; - openCursorRequest.onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { if (cursor.value.id < lastCursorId + 1) { cursor["continue"](lastCursorId + 1); @@ -1060,12 +934,10 @@ function getMessagesHigherThan(db, lastCursorId) { }; }); } - function removeMessagesById(channelState, ids) { if (channelState.closed) { return Promise.resolve([]); } - var tx = channelState.db.transaction(OBJECT_STORE_ID, 'readwrite', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); return Promise.all(ids.map(function (id) { @@ -1077,7 +949,6 @@ function removeMessagesById(channelState, ids) { }); })); } - function getOldMessages(db, ttl) { var olderThen = new Date().getTime() - ttl; var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS); @@ -1086,13 +957,11 @@ function getOldMessages(db, ttl) { return new Promise(function (res) { objectStore.openCursor().onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { var msgObk = cursor.value; - if (msgObk.time < olderThen) { - ret.push(msgObk); //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); - + ret.push(msgObk); + //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); cursor["continue"](); } else { // no more old messages, @@ -1106,7 +975,6 @@ function getOldMessages(db, ttl) { }; }); } - function cleanOldMessages(channelState) { return getOldMessages(channelState.db, channelState.options.idb.ttl).then(function (tooOld) { return removeMessagesById(channelState, tooOld.map(function (msg) { @@ -1114,7 +982,6 @@ function cleanOldMessages(channelState) { })); }); } - function create(channelName, options) { options = (0, _options.fillOptionsWithDefaults)(options); return createDatabase(channelName).then(function (db) { @@ -1124,7 +991,6 @@ function create(channelName, options) { channelName: channelName, options: options, uuid: (0, _util.randomToken)(), - /** * emittedMessagesIds * contains all messages that have been emitted before @@ -1137,30 +1003,27 @@ function create(channelName, options) { readQueuePromises: [], db: db }; + /** * Handle abrupt closes that do not originate from db.close(). * This could happen, for example, if the underlying storage is * removed or if the user clears the database in the browser's * history preferences. */ - db.onclose = function () { state.closed = true; if (options.idb.onclose) options.idb.onclose(); }; + /** * if service-workers are used, * we have no 'storage'-event if they post a message, * therefore we also have to set an interval */ - - _readLoop(state); - return state; }); } - function _readLoop(state) { if (state.closed) return; readNewMessages(state).then(function () { @@ -1169,25 +1032,21 @@ function _readLoop(state) { return _readLoop(state); }); } - function _filterMessage(msgObj, state) { if (msgObj.uuid === state.uuid) return false; // send by own - if (state.eMIs.has(msgObj.id)) return false; // already emitted - if (msgObj.data.time < state.messagesCallbackTime) return false; // older then onMessageCallback - return true; } + /** * reads all new messages from the database and emits them */ - - function readNewMessages(state) { // channel already closed - if (state.closed) return _util.PROMISE_RESOLVED_VOID; // if no one is listening, we do not need to scan for new messages + if (state.closed) return _util.PROMISE_RESOLVED_VOID; + // if no one is listening, we do not need to scan for new messages if (!state.messagesCallback) return _util.PROMISE_RESOLVED_VOID; return getMessagesHigherThan(state.db, state.lastCursorId).then(function (newerMessages) { var useMessages = newerMessages @@ -1195,21 +1054,18 @@ function readNewMessages(state) { * there is a bug in iOS where the msgObj can be undefined some times * so we filter them out * @link https://github.com/pubkey/broadcast-channel/issues/19 - */ - .filter(function (msgObj) { + */.filter(function (msgObj) { return !!msgObj; }).map(function (msgObj) { if (msgObj.id > state.lastCursorId) { state.lastCursorId = msgObj.id; } - return msgObj; }).filter(function (msgObj) { return _filterMessage(msgObj, state); }).sort(function (msgObjA, msgObjB) { return msgObjA.time - msgObjB.time; }); // sort by time - useMessages.forEach(function (msgObj) { if (state.messagesCallback) { state.eMIs.add(msgObj.id); @@ -1219,12 +1075,10 @@ function readNewMessages(state) { return _util.PROMISE_RESOLVED_VOID; }); } - function close(channelState) { channelState.closed = true; channelState.db.close(); } - function postMessage(channelState, messageJson) { channelState.writeBlockPromise = channelState.writeBlockPromise.then(function () { return writeMessage(channelState.db, channelState.uuid, messageJson); @@ -1236,27 +1090,21 @@ function postMessage(channelState, messageJson) { }); return channelState.writeBlockPromise; } - function onMessage(channelState, fn, time) { channelState.messagesCallbackTime = time; channelState.messagesCallback = fn; readNewMessages(channelState); } - function canBeUsed() { var idb = getIdb(); - if (!idb) { return false; } - return true; } - function averageResponseTime(options) { return options.idb.fallbackInterval * 2; } - var _default = { create: create, close: close, @@ -1287,13 +1135,9 @@ exports.postMessage = postMessage; exports.removeStorageEventListener = removeStorageEventListener; exports.storageKey = storageKey; exports.type = void 0; - var _obliviousSet = require("oblivious-set"); - var _options = require("../options.js"); - var _util = require("../util.js"); - /** * A localStorage-only method which uses localstorage and its 'storage'-event * This does not work inside of webworkers because they have no access to locastorage @@ -1301,41 +1145,38 @@ var _util = require("../util.js"); * @link https://caniuse.com/#feat=namevalue-storage * @link https://caniuse.com/#feat=indexeddb */ + var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var KEY_PREFIX = 'pubkey.broadcastChannel-'; var type = 'localstorage'; + /** * copied from crosstab * @link https://github.com/tejacques/crosstab/blob/master/src/crosstab.js#L32 */ - exports.type = type; - function getLocalStorage() { var localStorage; if (typeof window === 'undefined') return null; - try { localStorage = window.localStorage; localStorage = window['ie8-eventlistener/storage'] || window.localStorage; - } catch (e) {// New versions of Firefox throw a Security exception + } catch (e) { + // New versions of Firefox throw a Security exception // if cookies are disabled. See // https://bugzilla.mozilla.org/show_bug.cgi?id=1028153 } - return localStorage; } - function storageKey(channelName) { return KEY_PREFIX + channelName; } + /** * writes the new message to the storage * and fires the storage-event so other readers can find it */ - - function postMessage(channelState, messageJson) { return new Promise(function (res) { (0, _util.sleep)().then(function () { @@ -1348,12 +1189,12 @@ function postMessage(channelState, messageJson) { }; var value = JSON.stringify(writeObj); getLocalStorage().setItem(key, value); + /** * StorageEvent does not fire the 'storage' event * in the window that changes the state of the local storage. * So we fire it manually */ - var ev = document.createEvent('Event'); ev.initEvent('storage', true, true); ev.key = key; @@ -1363,52 +1204,42 @@ function postMessage(channelState, messageJson) { }); }); } - function addStorageEventListener(channelName, fn) { var key = storageKey(channelName); - var listener = function listener(ev) { if (ev.key === key) { fn(JSON.parse(ev.newValue)); } }; - window.addEventListener('storage', listener); return listener; } - function removeStorageEventListener(listener) { window.removeEventListener('storage', listener); } - function create(channelName, options) { options = (0, _options.fillOptionsWithDefaults)(options); - if (!canBeUsed()) { throw new Error('BroadcastChannel: localstorage cannot be used'); } - var uuid = (0, _util.randomToken)(); + /** * eMIs * contains all messages that have been emitted before * @type {ObliviousSet} */ - var eMIs = new _obliviousSet.ObliviousSet(options.localstorage.removeTimeout); var state = { channelName: channelName, uuid: uuid, eMIs: eMIs // emittedMessagesIds - }; + state.listener = addStorageEventListener(channelName, function (msgObj) { if (!state.messagesCallback) return; // no listener - if (msgObj.uuid === uuid) return; // own message - if (!msgObj.token || eMIs.has(msgObj.token)) return; // already emitted - if (msgObj.data.time && msgObj.data.time < state.messagesCallbackTime) return; // too old eMIs.add(msgObj.token); @@ -1416,20 +1247,16 @@ function create(channelName, options) { }); return state; } - function close(channelState) { removeStorageEventListener(channelState.listener); } - function onMessage(channelState, fn, time) { channelState.messagesCallbackTime = time; channelState.messagesCallback = fn; } - function canBeUsed() { var ls = getLocalStorage(); if (!ls) return false; - try { var key = '__broadcastchannel_check'; ls.setItem(key, 'works'); @@ -1440,22 +1267,17 @@ function canBeUsed() { // https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API#Private_Browsing_Incognito_modes return false; } - return true; } - function averageResponseTime() { var defaultTime = 120; var userAgent = navigator.userAgent.toLowerCase(); - if (userAgent.includes('safari') && !userAgent.includes('chrome')) { // safari is much slower so this time is higher return defaultTime * 2; } - return defaultTime; } - var _default = { create: create, close: close, @@ -1481,20 +1303,16 @@ exports.microSeconds = exports["default"] = void 0; exports.onMessage = onMessage; exports.postMessage = postMessage; exports.type = void 0; - var _util = require("../util.js"); - var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var type = 'native'; exports.type = type; - function create(channelName) { var state = { messagesCallback: null, bc: new BroadcastChannel(channelName), subFns: [] // subscriberFunctions - }; state.bc.onmessage = function (msg) { @@ -1502,15 +1320,12 @@ function create(channelName) { state.messagesCallback(msg.data); } }; - return state; } - function close(channelState) { channelState.bc.close(); channelState.subFns = []; } - function postMessage(channelState, messageJson) { try { channelState.bc.postMessage(messageJson, false); @@ -1519,31 +1334,25 @@ function postMessage(channelState, messageJson) { return Promise.reject(err); } } - function onMessage(channelState, fn) { channelState.messagesCallback = fn; } - function canBeUsed() { if (typeof window === 'undefined') { return false; } - if (typeof BroadcastChannel === 'function') { if (BroadcastChannel._pubkey) { throw new Error('BroadcastChannel: Do not overwrite window.BroadcastChannel with this module, this is not a polyfill'); } - return true; } else { return false; } } - function averageResponseTime() { return 150; } - var _default = { create: create, close: close, @@ -1569,15 +1378,12 @@ exports.microSeconds = exports["default"] = void 0; exports.onMessage = onMessage; exports.postMessage = postMessage; exports.type = void 0; - var _util = require("../util.js"); - var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var type = 'simulate'; exports.type = type; var SIMULATE_CHANNELS = new Set(); - function create(channelName) { var state = { name: channelName, @@ -1586,11 +1392,9 @@ function create(channelName) { SIMULATE_CHANNELS.add(state); return state; } - function close(channelState) { SIMULATE_CHANNELS["delete"](channelState); } - function postMessage(channelState, messageJson) { return new Promise(function (res) { return setTimeout(function () { @@ -1608,19 +1412,15 @@ function postMessage(channelState, messageJson) { }, 5); }); } - function onMessage(channelState, fn) { channelState.messagesCallback = fn; } - function canBeUsed() { return true; } - function averageResponseTime() { return 5; } - var _default = { create: create, close: close, @@ -1639,33 +1439,35 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.fillOptionsWithDefaults = fillOptionsWithDefaults; - function fillOptionsWithDefaults() { var originalOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var options = JSON.parse(JSON.stringify(originalOptions)); // main - - if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true; // indexed-db + var options = JSON.parse(JSON.stringify(originalOptions)); - if (!options.idb) options.idb = {}; // after this time the messages get deleted + // main + if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true; + // indexed-db + if (!options.idb) options.idb = {}; + // after this time the messages get deleted if (!options.idb.ttl) options.idb.ttl = 1000 * 45; - if (!options.idb.fallbackInterval) options.idb.fallbackInterval = 150; // handles abrupt db onclose events. - - if (originalOptions.idb && typeof originalOptions.idb.onclose === 'function') options.idb.onclose = originalOptions.idb.onclose; // localstorage + if (!options.idb.fallbackInterval) options.idb.fallbackInterval = 150; + // handles abrupt db onclose events. + if (originalOptions.idb && typeof originalOptions.idb.onclose === 'function') options.idb.onclose = originalOptions.idb.onclose; + // localstorage if (!options.localstorage) options.localstorage = {}; - if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60; // custom methods + if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60; - if (originalOptions.methods) options.methods = originalOptions.methods; // node + // custom methods + if (originalOptions.methods) options.methods = originalOptions.methods; + // node if (!options.node) options.node = {}; if (!options.node.ttl) options.node.ttl = 1000 * 60 * 2; // 2 minutes; - /** * On linux use 'ulimit -Hn' to get the limit of open files. * On ubuntu this was 4096 for me, so we use half of that as maxParallelWrites default. */ - if (!options.node.maxParallelWrites) options.node.maxParallelWrites = 2048; if (typeof options.node.useFastPath === 'undefined') options.node.useFastPath = true; return options; @@ -1682,7 +1484,6 @@ exports.microSeconds = microSeconds; exports.randomInt = randomInt; exports.randomToken = randomToken; exports.sleep = sleep; - /** * returns true if the given object is a promise */ @@ -1693,14 +1494,12 @@ function isPromise(obj) { return false; } } - var PROMISE_RESOLVED_FALSE = Promise.resolve(false); exports.PROMISE_RESOLVED_FALSE = PROMISE_RESOLVED_FALSE; var PROMISE_RESOLVED_TRUE = Promise.resolve(true); exports.PROMISE_RESOLVED_TRUE = PROMISE_RESOLVED_TRUE; var PROMISE_RESOLVED_VOID = Promise.resolve(); exports.PROMISE_RESOLVED_VOID = PROMISE_RESOLVED_VOID; - function sleep(time, resolveWith) { if (!time) time = 0; return new Promise(function (res) { @@ -1709,21 +1508,19 @@ function sleep(time, resolveWith) { }, time); }); } - function randomInt(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } + /** * https://stackoverflow.com/a/8084248 */ - - function randomToken() { return Math.random().toString(36).substring(2); } - var lastMs = 0; var additional = 0; + /** * returns the current time in micro-seconds, * WARNING: This is a pseudo-function @@ -1731,10 +1528,8 @@ var additional = 0; * This is enough in browsers, and this function will not be used in nodejs. * The main reason for this hack is to ensure that BroadcastChannel behaves equal to production when it is used in fast-running unit tests. */ - function microSeconds() { var ms = new Date().getTime(); - if (ms === lastMs) { additional++; return ms * 1000 + additional; @@ -1750,7 +1545,6 @@ function _interopRequireDefault(obj) { "default": obj }; } - module.exports = _interopRequireDefault, module.exports.__esModule = true, module.exports["default"] = module.exports; },{}],14:[function(require,module,exports){ function _typeof(obj) { @@ -1762,7 +1556,6 @@ function _typeof(obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, module.exports.__esModule = true, module.exports["default"] = module.exports), _typeof(obj); } - module.exports = _typeof, module.exports.__esModule = true, module.exports["default"] = module.exports; },{}],15:[function(require,module,exports){ diff --git a/dist/lib/browser.min.js b/dist/lib/browser.min.js index 0890b041..479b0a6e 100644 --- a/dist/lib/browser.min.js +++ b/dist/lib/browser.min.js @@ -1 +1 @@ -!function o(r,i,s){function a(t,e){if(!i[t]){if(!r[t]){var n="function"==typeof require&&require;if(!e&&n)return n(t,!0);if(u)return u(t,!0);throw(e=new Error("Cannot find module '"+t+"'")).code="MODULE_NOT_FOUND",e}n=i[t]={exports:{}},r[t][0].call(n.exports,function(e){return a(r[t][1][e]||e)},n,n.exports,o,r,i,s)}return i[t].exports}for(var u="function"==typeof require&&require,e=0;e=t&&e.fn(n.data)})},r=i.method.microSeconds(),i._prepP?i._prepP.then(function(){i._iL=!0,i.method.onMessage(i._state,o,r)}):(i._iL=!0,i.method.onMessage(i._state,o,r)))}function h(e,t,n){e._addEL[t]=e._addEL[t].filter(function(e){return e!==n});t=e;t._iL&&!l(t)&&(t._iL=!1,e=t.method.microSeconds(),t.method.onMessage(t._state,null,e))}(n.BroadcastChannel=o)._pubkey=!0,o.prototype={postMessage:function(e){if(this.closed)throw new Error("BroadcastChannel.postMessage(): Cannot post message after channel has closed "+JSON.stringify(e));return d(this,"message",e)},postInternal:function(e){return d(this,"internal",e)},set onmessage(e){var t={time:this.method.microSeconds(),fn:e};h(this,"message",this._onML),e&&"function"==typeof e?(this._onML=t,f(this,"message",t)):this._onML=null},addEventListener:function(e,t){var n=this.method.microSeconds();f(this,e,{time:n,fn:t})},removeEventListener:function(e,t){var n=this._addEL[e].find(function(e){return e.fn===t});h(this,e,n)},close:function(){var e,t=this;if(!this.closed)return u.delete(this),this.closed=!0,e=this._prepP||i.PROMISE_RESOLVED_VOID,this._onML=null,this._addEL.message=[],e.then(function(){return Promise.all(Array.from(t._uMP))}).then(function(){return Promise.all(t._befC.map(function(e){return e()}))}).then(function(){return t.method.close(t._state)})},get type(){return this.method.type},get isClosed(){return this.closed}}},{"./method-chooser.js":6,"./options.js":11,"./util.js":12}],2:[function(e,t,n){"use strict";var e=e("./index.es5.js"),o=e.BroadcastChannel,e=e.createLeaderElection;window.BroadcastChannel2=o,window.createLeaderElection=e},{"./index.es5.js":3}],3:[function(e,t,n){"use strict";e=e("./index.js");t.exports={BroadcastChannel:e.BroadcastChannel,createLeaderElection:e.createLeaderElection,clearNodeFolder:e.clearNodeFolder,enforceOptions:e.enforceOptions,beLeader:e.beLeader}},{"./index.js":4}],4:[function(e,t,n){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),Object.defineProperty(n,"BroadcastChannel",{enumerable:!0,get:function(){return o.BroadcastChannel}}),Object.defineProperty(n,"OPEN_BROADCAST_CHANNELS",{enumerable:!0,get:function(){return o.OPEN_BROADCAST_CHANNELS}}),Object.defineProperty(n,"beLeader",{enumerable:!0,get:function(){return r.beLeader}}),Object.defineProperty(n,"clearNodeFolder",{enumerable:!0,get:function(){return o.clearNodeFolder}}),Object.defineProperty(n,"createLeaderElection",{enumerable:!0,get:function(){return r.createLeaderElection}}),Object.defineProperty(n,"enforceOptions",{enumerable:!0,get:function(){return o.enforceOptions}});var o=e("./broadcast-channel.js"),r=e("./leader-election.js")},{"./broadcast-channel.js":1,"./leader-election.js":5}],5:[function(e,t,n){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.beLeader=d,n.createLeaderElection=function(e,t){if(e._leaderElector)throw new Error("BroadcastChannel already has a leader-elector");t=function(e,t){e=e||{};(e=JSON.parse(JSON.stringify(e))).fallbackInterval||(e.fallbackInterval=3e3);e.responseTime||(e.responseTime=t.method.averageResponseTime(t.options));return e}(t,e);var n=new r(e,t);return e._befC.push(function(){return n.die()}),e._leaderElector=n};var u=e("./util.js"),o=e("unload"),r=function(e,t){function n(e){"leader"===e.context&&("death"===e.action&&(o.hasLeader=!1),"tell"===e.action&&(o.hasLeader=!0))}var o=this;this.broadcastChannel=e,this._options=t,this.isLeader=!1,this.hasLeader=!1,this.isDead=!1,this.token=(0,u.randomToken)(),this._aplQ=u.PROMISE_RESOLVED_VOID,this._aplQC=0,this._unl=[],this._lstns=[],this._dpL=function(){},this._dpLC=!1;this.broadcastChannel.addEventListener("internal",n),this._lstns.push(n)};function c(e,t){t={context:"leader",action:t,token:e.token};return e.broadcastChannel.postInternal(t)}function d(t){t.isLeader=!0,t.hasLeader=!0;function e(e){"leader"===e.context&&"apply"===e.action&&c(t,"tell"),"leader"!==e.context||"tell"!==e.action||t._dpLC||(t._dpLC=!0,t._dpL(),c(t,"tell"))}var n=(0,o.add)(function(){return t.die()});t._unl.push(n);return t.broadcastChannel.addEventListener("internal",e),t._lstns.push(e),c(t,"tell")}r.prototype={applyOnce:function(s){var a=this;return this.isLeader?(0,u.sleep)(0,!0):this.isDead?(0,u.sleep)(0,!1):1a.token&&n(),"tell"===e.action&&(n(),a.hasLeader=!0))}),i=s?4*a._options.responseTime:a._options.responseTime,c(a,"apply").then(function(){return Promise.race([(0,u.sleep)(i),e.then(function(){return Promise.reject(new Error)})])}).then(function(){return c(a,"apply")}).then(function(){return Promise.race([(0,u.sleep)(i),e.then(function(){return Promise.reject(new Error)})])}).catch(function(){}).then(function(){return a.broadcastChannel.removeEventListener("internal",r),!t&&d(a).then(function(){return!0})}));var t,n,e,o,r,i}).then(function(){a._aplQC=a._aplQC-1}),this._aplQ.then(function(){return a.isLeader}))},awaitLeadership:function(){return this._aLP||(this._aLP=function(r){if(r.isLeader)return u.PROMISE_RESOLVED_VOID;return new Promise(function(e){var t=!1;function n(){t||(t=!0,r.broadcastChannel.removeEventListener("internal",o),e(!0))}r.applyOnce().then(function(){r.isLeader&&n()});(function e(){return(0,u.sleep)(r._options.fallbackInterval).then(function(){if(!r.isDead&&!t)return r.isLeader?void n():r.applyOnce(!0).then(function(){(r.isLeader?n:e)()})})})();var o=function(e){"leader"===e.context&&"death"===e.action&&(r.hasLeader=!1,r.applyOnce().then(function(){r.isLeader&&n()}))};r.broadcastChannel.addEventListener("internal",o),r._lstns.push(o)})}(this)),this._aLP},set onduplicate(e){this._dpL=e},die:function(){var t=this;return this._lstns.forEach(function(e){return t.broadcastChannel.removeEventListener("internal",e)}),this._lstns=[],this._unl.forEach(function(e){return e.remove()}),this._unl=[],this.isLeader&&(this.hasLeader=!1,this.isLeader=!1),this.isDead=!0,c(this,"death")}}},{"./util.js":12,unload:19}],6:[function(e,t,n){"use strict";var o=e("@babel/runtime/helpers/interopRequireDefault"),n=(e("@babel/runtime/helpers/typeof"),Object.defineProperty(n,"__esModule",{value:!0}),n.chooseMethod=function(t){var e=[].concat(t.methods,a).filter(Boolean);if(t.type){if("simulate"===t.type)return s.default;var n=e.find(function(e){return e.type===t.type});if(n)return n;throw new Error("method-type "+t.type+" not found")}t.webWorkerSupport||(e=e.filter(function(e){return"idb"!==e.type}));n=e.find(function(e){return e.canBeUsed()});{if(n)return n;throw new Error("No useable method found in "+JSON.stringify(a.map(function(e){return e.type})))}},o(e("./methods/native.js"))),r=o(e("./methods/indexed-db.js")),i=o(e("./methods/localstorage.js")),s=o(e("./methods/simulate.js"));var a=[n.default,r.default,i.default]},{"./methods/indexed-db.js":7,"./methods/localstorage.js":8,"./methods/native.js":9,"./methods/simulate.js":10,"@babel/runtime/helpers/interopRequireDefault":13,"@babel/runtime/helpers/typeof":14}],7:[function(e,t,n){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.TRANSACTION_SETTINGS=void 0,n.averageResponseTime=w,n.canBeUsed=S,n.cleanOldMessages=v,n.close=g,n.commitIndexedDBTransaction=l,n.create=b,n.createDatabase=u,n.default=void 0,n.getAllMessages=function(e){var n=e.transaction(c,"readonly",d),o=n.objectStore(c),r=[];return new Promise(function(t){o.openCursor().onsuccess=function(e){e=e.target.result;e?(r.push(e.value),e.continue()):(l(n),t(r))}})},n.getIdb=a,n.getMessagesHigherThan=h,n.getOldMessages=m,n.microSeconds=void 0,n.onMessage=y,n.postMessage=E,n.removeMessagesById=p,n.type=void 0,n.writeMessage=f;var r=e("../util.js"),i=e("oblivious-set"),s=e("../options.js"),e=r.microSeconds,o=(n.microSeconds=e,"pubkey.broadcast-channel-0-"),c="messages",d={durability:"relaxed"};n.TRANSACTION_SETTINGS=d;function a(){if("undefined"!=typeof indexedDB)return indexedDB;if("undefined"!=typeof window){if(void 0!==window.mozIndexedDB)return window.mozIndexedDB;if(void 0!==window.webkitIndexedDB)return window.webkitIndexedDB;if(void 0!==window.msIndexedDB)return window.msIndexedDB}return!1}function l(e){e.commit&&e.commit()}function u(e){var n=a().open(o+e);return n.onupgradeneeded=function(e){e.target.result.createObjectStore(c,{keyPath:"id",autoIncrement:!0})},new Promise(function(e,t){n.onerror=function(e){return t(e)},n.onsuccess=function(){e(n.result)}})}function f(e,t,n){var o={uuid:t,time:(new Date).getTime(),data:n},r=e.transaction([c],"readwrite",d);return new Promise(function(e,t){r.oncomplete=function(){return e()},r.onerror=function(e){return t(e)},r.objectStore(c).add(o),l(r)})}function h(e,o){var r,i=e.transaction(c,"readonly",d),s=i.objectStore(c),a=[],u=IDBKeyRange.bound(o+1,1/0);return s.getAll?(r=s.getAll(u),new Promise(function(t,n){r.onerror=function(e){return n(e)},r.onsuccess=function(e){t(e.target.result)}})):new Promise(function(t,n){var e=function(){try{return u=IDBKeyRange.bound(o+1,1/0),s.openCursor(u)}catch(e){return s.openCursor()}}();e.onerror=function(e){return n(e)},e.onsuccess=function(e){e=e.target.result;e?e.value.idn.lastCursorId&&(n.lastCursorId=e.id),e}).filter(function(e){return t=n,(e=e).uuid!==t.uuid&&(!t.eMIs.has(e.id)&&!(e.data.time=t&&e.fn(n.data)})},r=i.method.microSeconds(),i._prepP?i._prepP.then(function(){i._iL=!0,i.method.onMessage(i._state,o,r)}):(i._iL=!0,i.method.onMessage(i._state,o,r)))}function h(e,t,n){e._addEL[t]=e._addEL[t].filter(function(e){return e!==n});t=e;t._iL&&!l(t)&&(t._iL=!1,e=t.method.microSeconds(),t.method.onMessage(t._state,null,e))}(n.BroadcastChannel=o)._pubkey=!0,o.prototype={postMessage:function(e){if(this.closed)throw new Error("BroadcastChannel.postMessage(): Cannot post message after channel has closed "+JSON.stringify(e));return d(this,"message",e)},postInternal:function(e){return d(this,"internal",e)},set onmessage(e){var t={time:this.method.microSeconds(),fn:e};h(this,"message",this._onML),e&&"function"==typeof e?(this._onML=t,f(this,"message",t)):this._onML=null},addEventListener:function(e,t){var n=this.method.microSeconds();f(this,e,{time:n,fn:t})},removeEventListener:function(e,t){var n=this._addEL[e].find(function(e){return e.fn===t});h(this,e,n)},close:function(){var e,t=this;if(!this.closed)return u.delete(this),this.closed=!0,e=this._prepP||i.PROMISE_RESOLVED_VOID,this._onML=null,this._addEL.message=[],e.then(function(){return Promise.all(Array.from(t._uMP))}).then(function(){return Promise.all(t._befC.map(function(e){return e()}))}).then(function(){return t.method.close(t._state)})},get type(){return this.method.type},get isClosed(){return this.closed}}},{"./method-chooser.js":6,"./options.js":11,"./util.js":12}],2:[function(e,t,n){"use strict";var e=e("./index.es5.js"),o=e.BroadcastChannel,e=e.createLeaderElection;window.BroadcastChannel2=o,window.createLeaderElection=e},{"./index.es5.js":3}],3:[function(e,t,n){"use strict";e=e("./index.js");t.exports={BroadcastChannel:e.BroadcastChannel,createLeaderElection:e.createLeaderElection,clearNodeFolder:e.clearNodeFolder,enforceOptions:e.enforceOptions,beLeader:e.beLeader}},{"./index.js":4}],4:[function(e,t,n){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),Object.defineProperty(n,"BroadcastChannel",{enumerable:!0,get:function(){return o.BroadcastChannel}}),Object.defineProperty(n,"OPEN_BROADCAST_CHANNELS",{enumerable:!0,get:function(){return o.OPEN_BROADCAST_CHANNELS}}),Object.defineProperty(n,"beLeader",{enumerable:!0,get:function(){return r.beLeader}}),Object.defineProperty(n,"clearNodeFolder",{enumerable:!0,get:function(){return o.clearNodeFolder}}),Object.defineProperty(n,"createLeaderElection",{enumerable:!0,get:function(){return r.createLeaderElection}}),Object.defineProperty(n,"enforceOptions",{enumerable:!0,get:function(){return o.enforceOptions}});var o=e("./broadcast-channel.js"),r=e("./leader-election.js")},{"./broadcast-channel.js":1,"./leader-election.js":5}],5:[function(e,t,n){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.beLeader=d,n.createLeaderElection=function(e,t){if(e._leaderElector)throw new Error("BroadcastChannel already has a leader-elector");t=function(e,t){e=e||{};(e=JSON.parse(JSON.stringify(e))).fallbackInterval||(e.fallbackInterval=3e3);e.responseTime||(e.responseTime=t.method.averageResponseTime(t.options));return e}(t,e);var n=new r(e,t);return e._befC.push(function(){return n.die()}),e._leaderElector=n};var u=e("./util.js"),o=e("unload"),r=function(e,t){function n(e){"leader"===e.context&&("death"===e.action&&(o.hasLeader=!1),"tell"===e.action)&&(o.hasLeader=!0)}var o=this;this.broadcastChannel=e,this._options=t,this.isLeader=!1,this.hasLeader=!1,this.isDead=!1,this.token=(0,u.randomToken)(),this._aplQ=u.PROMISE_RESOLVED_VOID,this._aplQC=0,this._unl=[],this._lstns=[],this._dpL=function(){},this._dpLC=!1;this.broadcastChannel.addEventListener("internal",n),this._lstns.push(n)};function c(e,t){t={context:"leader",action:t,token:e.token};return e.broadcastChannel.postInternal(t)}function d(t){t.isLeader=!0,t.hasLeader=!0;function e(e){"leader"===e.context&&"apply"===e.action&&c(t,"tell"),"leader"!==e.context||"tell"!==e.action||t._dpLC||(t._dpLC=!0,t._dpL(),c(t,"tell"))}var n=(0,o.add)(function(){return t.die()});t._unl.push(n);return t.broadcastChannel.addEventListener("internal",e),t._lstns.push(e),c(t,"tell")}r.prototype={applyOnce:function(s){var a=this;return this.isLeader?(0,u.sleep)(0,!0):this.isDead?(0,u.sleep)(0,!1):1a.token&&n(),"tell"===e.action)&&(n(),a.hasLeader=!0)}),i=s?4*a._options.responseTime:a._options.responseTime,c(a,"apply").then(function(){return Promise.race([(0,u.sleep)(i),e.then(function(){return Promise.reject(new Error)})])}).then(function(){return c(a,"apply")}).then(function(){return Promise.race([(0,u.sleep)(i),e.then(function(){return Promise.reject(new Error)})])}).catch(function(){}).then(function(){return a.broadcastChannel.removeEventListener("internal",r),!t&&d(a).then(function(){return!0})}));var t,n,e,o,r,i}).then(function(){a._aplQC=a._aplQC-1}),this._aplQ.then(function(){return a.isLeader}))},awaitLeadership:function(){return this._aLP||(this._aLP=function(r){if(r.isLeader)return u.PROMISE_RESOLVED_VOID;return new Promise(function(e){var t=!1;function n(){t||(t=!0,r.broadcastChannel.removeEventListener("internal",o),e(!0))}r.applyOnce().then(function(){r.isLeader&&n()});(function e(){return(0,u.sleep)(r._options.fallbackInterval).then(function(){if(!r.isDead&&!t)return r.isLeader?void n():r.applyOnce(!0).then(function(){(r.isLeader?n:e)()})})})();var o=function(e){"leader"===e.context&&"death"===e.action&&(r.hasLeader=!1,r.applyOnce().then(function(){r.isLeader&&n()}))};r.broadcastChannel.addEventListener("internal",o),r._lstns.push(o)})}(this)),this._aLP},set onduplicate(e){this._dpL=e},die:function(){var t=this;return this._lstns.forEach(function(e){return t.broadcastChannel.removeEventListener("internal",e)}),this._lstns=[],this._unl.forEach(function(e){return e.remove()}),this._unl=[],this.isLeader&&(this.hasLeader=!1,this.isLeader=!1),this.isDead=!0,c(this,"death")}}},{"./util.js":12,unload:19}],6:[function(e,t,n){"use strict";var o=e("@babel/runtime/helpers/interopRequireDefault"),n=(e("@babel/runtime/helpers/typeof"),Object.defineProperty(n,"__esModule",{value:!0}),n.chooseMethod=function(t){var e=[].concat(t.methods,a).filter(Boolean);if(t.type){if("simulate"===t.type)return s.default;var n=e.find(function(e){return e.type===t.type});if(n)return n;throw new Error("method-type "+t.type+" not found")}t.webWorkerSupport||(e=e.filter(function(e){return"idb"!==e.type}));n=e.find(function(e){return e.canBeUsed()});{if(n)return n;throw new Error("No useable method found in "+JSON.stringify(a.map(function(e){return e.type})))}},o(e("./methods/native.js"))),r=o(e("./methods/indexed-db.js")),i=o(e("./methods/localstorage.js")),s=o(e("./methods/simulate.js"));var a=[n.default,r.default,i.default]},{"./methods/indexed-db.js":7,"./methods/localstorage.js":8,"./methods/native.js":9,"./methods/simulate.js":10,"@babel/runtime/helpers/interopRequireDefault":13,"@babel/runtime/helpers/typeof":14}],7:[function(e,t,n){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.TRANSACTION_SETTINGS=void 0,n.averageResponseTime=w,n.canBeUsed=S,n.cleanOldMessages=v,n.close=g,n.commitIndexedDBTransaction=l,n.create=b,n.createDatabase=u,n.default=void 0,n.getAllMessages=function(e){var n=e.transaction(c,"readonly",d),o=n.objectStore(c),r=[];return new Promise(function(t){o.openCursor().onsuccess=function(e){e=e.target.result;e?(r.push(e.value),e.continue()):(l(n),t(r))}})},n.getIdb=a,n.getMessagesHigherThan=h,n.getOldMessages=m,n.microSeconds=void 0,n.onMessage=y,n.postMessage=E,n.removeMessagesById=p,n.type=void 0,n.writeMessage=f;var r=e("../util.js"),i=e("oblivious-set"),s=e("../options.js"),e=r.microSeconds,o=(n.microSeconds=e,"pubkey.broadcast-channel-0-"),c="messages",d={durability:"relaxed"};n.TRANSACTION_SETTINGS=d;function a(){if("undefined"!=typeof indexedDB)return indexedDB;if("undefined"!=typeof window){if(void 0!==window.mozIndexedDB)return window.mozIndexedDB;if(void 0!==window.webkitIndexedDB)return window.webkitIndexedDB;if(void 0!==window.msIndexedDB)return window.msIndexedDB}return!1}function l(e){e.commit&&e.commit()}function u(e){var n=a().open(o+e);return n.onupgradeneeded=function(e){e.target.result.createObjectStore(c,{keyPath:"id",autoIncrement:!0})},new Promise(function(e,t){n.onerror=function(e){return t(e)},n.onsuccess=function(){e(n.result)}})}function f(e,t,n){var o={uuid:t,time:(new Date).getTime(),data:n},r=e.transaction([c],"readwrite",d);return new Promise(function(e,t){r.oncomplete=function(){return e()},r.onerror=function(e){return t(e)},r.objectStore(c).add(o),l(r)})}function h(e,o){var r,i=e.transaction(c,"readonly",d),s=i.objectStore(c),a=[],u=IDBKeyRange.bound(o+1,1/0);return s.getAll?(r=s.getAll(u),new Promise(function(t,n){r.onerror=function(e){return n(e)},r.onsuccess=function(e){t(e.target.result)}})):new Promise(function(t,n){var e=function(){try{return u=IDBKeyRange.bound(o+1,1/0),s.openCursor(u)}catch(e){return s.openCursor()}}();e.onerror=function(e){return n(e)},e.onsuccess=function(e){e=e.target.result;e?e.value.idn.lastCursorId&&(n.lastCursorId=e.id),e}).filter(function(e){return t=n,(e=e).uuid!==t.uuid&&!(t.eMIs.has(e.id)||e.data.time wait for the apply queue to be finished. */ - - if (this._aplQC > 1) { return this._aplQ; } + /** * Add a new apply-run */ - - var applyRun = function applyRun() { /** * Optimization shortcuts. @@ -101,7 +86,6 @@ LeaderElection.prototype = { if (_this2.isLeader) { return _util.PROMISE_RESOLVED_TRUE; } - var stopCriteria = false; var stopCriteriaPromiseResolve; /** @@ -110,7 +94,6 @@ LeaderElection.prototype = { * have to await the responseTime when it is already clear * that the election failed. */ - var stopCriteriaPromise = new Promise(function (res) { stopCriteriaPromiseResolve = function stopCriteriaPromiseResolve() { stopCriteria = true; @@ -118,11 +101,9 @@ LeaderElection.prototype = { }; }); var recieved = []; - var handleMessage = function handleMessage(msg) { if (msg.context === 'leader' && msg.token != _this2.token) { recieved.push(msg); - if (msg.action === 'apply') { // other is applying if (msg.token > _this2.token) { @@ -133,7 +114,6 @@ LeaderElection.prototype = { stopCriteriaPromiseResolve(); } } - if (msg.action === 'tell') { // other is already leader stopCriteriaPromiseResolve(); @@ -141,8 +121,8 @@ LeaderElection.prototype = { } } }; - _this2.broadcastChannel.addEventListener('internal', handleMessage); + /** * If the applyOnce() call came from the fallbackInterval, * we can assume that the election runs in the background and @@ -153,26 +133,24 @@ LeaderElection.prototype = { * But also it takes longer which is not a problem because we anyway * run in the background. */ - - var waitForAnswerTime = isFromFallbackInterval ? _this2._options.responseTime * 4 : _this2._options.responseTime; - var applyPromise = _sendMessage(_this2, 'apply') // send out that this one is applying .then(function () { return Promise.race([(0, _util.sleep)(waitForAnswerTime), stopCriteriaPromise.then(function () { return Promise.reject(new Error()); })]); - }) // send again in case another instance was just created + }) + // send again in case another instance was just created .then(function () { return _sendMessage(_this2, 'apply'); - }) // let others time to respond + }) + // let others time to respond .then(function () { return Promise.race([(0, _util.sleep)(waitForAnswerTime), stopCriteriaPromise.then(function () { return Promise.reject(new Error()); })]); })["catch"](function () {}).then(function () { _this2.broadcastChannel.removeEventListener('internal', handleMessage); - if (!stopCriteria) { // no stop criteria -> own is leader return beLeader(_this2).then(function () { @@ -183,10 +161,8 @@ LeaderElection.prototype = { return false; } }); - return applyPromise; }; - this._aplQC = this._aplQC + 1; this._aplQ = this._aplQ.then(function () { return applyRun(); @@ -198,82 +174,68 @@ LeaderElection.prototype = { }); }, awaitLeadership: function awaitLeadership() { - if ( - /* _awaitLeadershipPromise */ + if ( /* _awaitLeadershipPromise */ !this._aLP) { this._aLP = _awaitLeadershipOnce(this); } - return this._aLP; }, - set onduplicate(fn) { this._dpL = fn; }, - die: function die() { var _this3 = this; - this._lstns.forEach(function (listener) { return _this3.broadcastChannel.removeEventListener('internal', listener); }); - this._lstns = []; - this._unl.forEach(function (uFn) { return uFn.remove(); }); - this._unl = []; - if (this.isLeader) { this.hasLeader = false; this.isLeader = false; } - this.isDead = true; return _sendMessage(this, 'death'); } }; + /** * @param leaderElector {LeaderElector} */ - function _awaitLeadershipOnce(leaderElector) { if (leaderElector.isLeader) { return _util.PROMISE_RESOLVED_VOID; } - return new Promise(function (res) { var resolved = false; - function finish() { if (resolved) { return; } - resolved = true; leaderElector.broadcastChannel.removeEventListener('internal', whenDeathListener); res(true); - } // try once now - + } + // try once now leaderElector.applyOnce().then(function () { if (leaderElector.isLeader) { finish(); } }); + /** * Try on fallbackInterval * @recursive */ - var tryOnFallBack = function tryOnFallBack() { return (0, _util.sleep)(leaderElector._options.fallbackInterval).then(function () { if (leaderElector.isDead || resolved) { return; } - if (leaderElector.isLeader) { finish(); } else { @@ -287,9 +249,9 @@ function _awaitLeadershipOnce(leaderElector) { } }); }; + tryOnFallBack(); - tryOnFallBack(); // try when other leader dies - + // try when other leader dies var whenDeathListener = function whenDeathListener(msg) { if (msg.context === 'leader' && msg.action === 'death') { leaderElector.hasLeader = false; @@ -300,17 +262,14 @@ function _awaitLeadershipOnce(leaderElector) { }); } }; - leaderElector.broadcastChannel.addEventListener('internal', whenDeathListener); - leaderElector._lstns.push(whenDeathListener); }); } + /** * sends and internal message over the broadcast-channel */ - - function _sendMessage(leaderElector, action) { var msgJson = { context: 'leader', @@ -319,21 +278,17 @@ function _sendMessage(leaderElector, action) { }; return leaderElector.broadcastChannel.postInternal(msgJson); } - function beLeader(leaderElector) { leaderElector.isLeader = true; leaderElector.hasLeader = true; var unloadFn = (0, _unload.add)(function () { return leaderElector.die(); }); - leaderElector._unl.push(unloadFn); - var isLeaderListener = function isLeaderListener(msg) { if (msg.context === 'leader' && msg.action === 'apply') { _sendMessage(leaderElector, 'tell'); } - if (msg.context === 'leader' && msg.action === 'tell' && !leaderElector._dpLC) { /** * another instance is also leader! @@ -344,49 +299,35 @@ function beLeader(leaderElector) { * @link https://github.com/pubkey/broadcast-channel/issues/385 */ leaderElector._dpLC = true; - leaderElector._dpL(); // message the lib user so the app can handle the problem - - _sendMessage(leaderElector, 'tell'); // ensure other leader also knows the problem - } }; leaderElector.broadcastChannel.addEventListener('internal', isLeaderListener); - leaderElector._lstns.push(isLeaderListener); - return _sendMessage(leaderElector, 'tell'); } - function fillOptionsWithDefaults(options, channel) { if (!options) options = {}; options = JSON.parse(JSON.stringify(options)); - if (!options.fallbackInterval) { options.fallbackInterval = 3000; } - if (!options.responseTime) { options.responseTime = channel.method.averageResponseTime(channel.options); } - return options; } - function createLeaderElection(channel, options) { if (channel._leaderElector) { throw new Error('BroadcastChannel already has a leader-elector'); } - options = fillOptionsWithDefaults(options, channel); var elector = new LeaderElection(channel, options); - channel._befC.push(function () { return elector.die(); }); - channel._leaderElector = elector; return elector; } \ No newline at end of file diff --git a/dist/lib/method-chooser.js b/dist/lib/method-chooser.js index 26cd096f..6b7dd32f 100644 --- a/dist/lib/method-chooser.js +++ b/dist/lib/method-chooser.js @@ -1,59 +1,49 @@ "use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); - var _typeof = require("@babel/runtime/helpers/typeof"); - Object.defineProperty(exports, "__esModule", { value: true }); exports.chooseMethod = chooseMethod; - var _native = _interopRequireDefault(require("./methods/native.js")); - var _indexedDb = _interopRequireDefault(require("./methods/indexed-db.js")); - var _localstorage = _interopRequireDefault(require("./methods/localstorage.js")); - var _simulate = _interopRequireDefault(require("./methods/simulate.js")); - - function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } - function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } - // the line below will be removed from es5/browser builds + // order is important -var METHODS = [_native["default"], // fastest +var METHODS = [_native["default"], +// fastest _indexedDb["default"], _localstorage["default"]]; - function chooseMethod(options) { - var chooseMethods = [].concat(options.methods, METHODS).filter(Boolean); // the line below will be removed from es5/browser builds + var chooseMethods = [].concat(options.methods, METHODS).filter(Boolean); + // the line below will be removed from es5/browser builds + // directly chosen if (options.type) { if (options.type === 'simulate') { // only use simulate-method if directly chosen return _simulate["default"]; } - var ret = chooseMethods.find(function (m) { return m.type === options.type; }); if (!ret) throw new Error('method-type ' + options.type + ' not found');else return ret; } + /** * if no webworker support is needed, * remove idb from the list so that localstorage is been chosen */ - - if (!options.webWorkerSupport) { chooseMethods = chooseMethods.filter(function (m) { return m.type !== 'idb'; }); } - var useMethod = chooseMethods.find(function (method) { return method.canBeUsed(); }); diff --git a/dist/lib/methods/indexed-db.js b/dist/lib/methods/indexed-db.js index dc38ebe8..b3b21767 100644 --- a/dist/lib/methods/indexed-db.js +++ b/dist/lib/methods/indexed-db.js @@ -22,13 +22,9 @@ exports.postMessage = postMessage; exports.removeMessagesById = removeMessagesById; exports.type = void 0; exports.writeMessage = writeMessage; - var _util = require("../util.js"); - var _obliviousSet = require("oblivious-set"); - var _options = require("../options.js"); - /** * this method uses indexeddb to store the messages * There is currently no observerAPI for idb @@ -37,58 +33,54 @@ var _options = require("../options.js"); * When working on this, ensure to use these performance optimizations: * @link https://rxdb.info/slow-indexeddb.html */ + var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var DB_PREFIX = 'pubkey.broadcast-channel-0-'; var OBJECT_STORE_ID = 'messages'; + /** * Use relaxed durability for faster performance on all transactions. * @link https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/ */ - var TRANSACTION_SETTINGS = { durability: 'relaxed' }; exports.TRANSACTION_SETTINGS = TRANSACTION_SETTINGS; var type = 'idb'; exports.type = type; - function getIdb() { if (typeof indexedDB !== 'undefined') return indexedDB; - if (typeof window !== 'undefined') { if (typeof window.mozIndexedDB !== 'undefined') return window.mozIndexedDB; if (typeof window.webkitIndexedDB !== 'undefined') return window.webkitIndexedDB; if (typeof window.msIndexedDB !== 'undefined') return window.msIndexedDB; } - return false; } + /** * If possible, we should explicitly commit IndexedDB transactions * for better performance. * @link https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/ */ - - function commitIndexedDBTransaction(tx) { if (tx.commit) { tx.commit(); } } - function createDatabase(channelName) { - var IndexedDB = getIdb(); // create table + var IndexedDB = getIdb(); + // create table var dbName = DB_PREFIX + channelName; + /** * All IndexedDB databases are opened without version * because it is a bit faster, especially on firefox * @link http://nparashuram.com/IndexedDB/perf/#Open%20Database%20with%20version */ - var openRequest = IndexedDB.open(dbName); - openRequest.onupgradeneeded = function (ev) { var db = ev.target.result; db.createObjectStore(OBJECT_STORE_ID, { @@ -96,24 +88,21 @@ function createDatabase(channelName) { autoIncrement: true }); }; - var dbPromise = new Promise(function (res, rej) { openRequest.onerror = function (ev) { return rej(ev); }; - openRequest.onsuccess = function () { res(openRequest.result); }; }); return dbPromise; } + /** * writes the new message to the database * so other readers can find it */ - - function writeMessage(db, readerUuid, messageJson) { var time = new Date().getTime(); var writeObject = { @@ -126,17 +115,14 @@ function writeMessage(db, readerUuid, messageJson) { tx.oncomplete = function () { return res(); }; - tx.onerror = function (ev) { return rej(ev); }; - var objectStore = tx.objectStore(OBJECT_STORE_ID); objectStore.add(writeObject); commitIndexedDBTransaction(tx); }); } - function getAllMessages(db) { var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); @@ -144,10 +130,9 @@ function getAllMessages(db) { return new Promise(function (res) { objectStore.openCursor().onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { - ret.push(cursor.value); //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); - + ret.push(cursor.value); + //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); cursor["continue"](); } else { commitIndexedDBTransaction(tx); @@ -156,31 +141,28 @@ function getAllMessages(db) { }; }); } - function getMessagesHigherThan(db, lastCursorId) { var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); var ret = []; var keyRangeValue = IDBKeyRange.bound(lastCursorId + 1, Infinity); + /** * Optimization shortcut, * if getAll() can be used, do not use a cursor. * @link https://rxdb.info/slow-indexeddb.html */ - if (objectStore.getAll) { var getAllRequest = objectStore.getAll(keyRangeValue); return new Promise(function (res, rej) { getAllRequest.onerror = function (err) { return rej(err); }; - getAllRequest.onsuccess = function (e) { res(e.target.result); }; }); } - function openCursor() { // Occasionally Safari will fail on IDBKeyRange.bound, this // catches that error, having it open the cursor to the first @@ -192,17 +174,13 @@ function getMessagesHigherThan(db, lastCursorId) { return objectStore.openCursor(); } } - return new Promise(function (res, rej) { var openCursorRequest = openCursor(); - openCursorRequest.onerror = function (err) { return rej(err); }; - openCursorRequest.onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { if (cursor.value.id < lastCursorId + 1) { cursor["continue"](lastCursorId + 1); @@ -217,12 +195,10 @@ function getMessagesHigherThan(db, lastCursorId) { }; }); } - function removeMessagesById(channelState, ids) { if (channelState.closed) { return Promise.resolve([]); } - var tx = channelState.db.transaction(OBJECT_STORE_ID, 'readwrite', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); return Promise.all(ids.map(function (id) { @@ -234,7 +210,6 @@ function removeMessagesById(channelState, ids) { }); })); } - function getOldMessages(db, ttl) { var olderThen = new Date().getTime() - ttl; var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS); @@ -243,13 +218,11 @@ function getOldMessages(db, ttl) { return new Promise(function (res) { objectStore.openCursor().onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { var msgObk = cursor.value; - if (msgObk.time < olderThen) { - ret.push(msgObk); //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); - + ret.push(msgObk); + //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); cursor["continue"](); } else { // no more old messages, @@ -263,7 +236,6 @@ function getOldMessages(db, ttl) { }; }); } - function cleanOldMessages(channelState) { return getOldMessages(channelState.db, channelState.options.idb.ttl).then(function (tooOld) { return removeMessagesById(channelState, tooOld.map(function (msg) { @@ -271,7 +243,6 @@ function cleanOldMessages(channelState) { })); }); } - function create(channelName, options) { options = (0, _options.fillOptionsWithDefaults)(options); return createDatabase(channelName).then(function (db) { @@ -281,7 +252,6 @@ function create(channelName, options) { channelName: channelName, options: options, uuid: (0, _util.randomToken)(), - /** * emittedMessagesIds * contains all messages that have been emitted before @@ -294,30 +264,27 @@ function create(channelName, options) { readQueuePromises: [], db: db }; + /** * Handle abrupt closes that do not originate from db.close(). * This could happen, for example, if the underlying storage is * removed or if the user clears the database in the browser's * history preferences. */ - db.onclose = function () { state.closed = true; if (options.idb.onclose) options.idb.onclose(); }; + /** * if service-workers are used, * we have no 'storage'-event if they post a message, * therefore we also have to set an interval */ - - _readLoop(state); - return state; }); } - function _readLoop(state) { if (state.closed) return; readNewMessages(state).then(function () { @@ -326,25 +293,21 @@ function _readLoop(state) { return _readLoop(state); }); } - function _filterMessage(msgObj, state) { if (msgObj.uuid === state.uuid) return false; // send by own - if (state.eMIs.has(msgObj.id)) return false; // already emitted - if (msgObj.data.time < state.messagesCallbackTime) return false; // older then onMessageCallback - return true; } + /** * reads all new messages from the database and emits them */ - - function readNewMessages(state) { // channel already closed - if (state.closed) return _util.PROMISE_RESOLVED_VOID; // if no one is listening, we do not need to scan for new messages + if (state.closed) return _util.PROMISE_RESOLVED_VOID; + // if no one is listening, we do not need to scan for new messages if (!state.messagesCallback) return _util.PROMISE_RESOLVED_VOID; return getMessagesHigherThan(state.db, state.lastCursorId).then(function (newerMessages) { var useMessages = newerMessages @@ -352,21 +315,18 @@ function readNewMessages(state) { * there is a bug in iOS where the msgObj can be undefined some times * so we filter them out * @link https://github.com/pubkey/broadcast-channel/issues/19 - */ - .filter(function (msgObj) { + */.filter(function (msgObj) { return !!msgObj; }).map(function (msgObj) { if (msgObj.id > state.lastCursorId) { state.lastCursorId = msgObj.id; } - return msgObj; }).filter(function (msgObj) { return _filterMessage(msgObj, state); }).sort(function (msgObjA, msgObjB) { return msgObjA.time - msgObjB.time; }); // sort by time - useMessages.forEach(function (msgObj) { if (state.messagesCallback) { state.eMIs.add(msgObj.id); @@ -376,12 +336,10 @@ function readNewMessages(state) { return _util.PROMISE_RESOLVED_VOID; }); } - function close(channelState) { channelState.closed = true; channelState.db.close(); } - function postMessage(channelState, messageJson) { channelState.writeBlockPromise = channelState.writeBlockPromise.then(function () { return writeMessage(channelState.db, channelState.uuid, messageJson); @@ -393,27 +351,21 @@ function postMessage(channelState, messageJson) { }); return channelState.writeBlockPromise; } - function onMessage(channelState, fn, time) { channelState.messagesCallbackTime = time; channelState.messagesCallback = fn; readNewMessages(channelState); } - function canBeUsed() { var idb = getIdb(); - if (!idb) { return false; } - return true; } - function averageResponseTime(options) { return options.idb.fallbackInterval * 2; } - var _default = { create: create, close: close, diff --git a/dist/lib/methods/localstorage.js b/dist/lib/methods/localstorage.js index 1f308a9f..0a3d58bb 100644 --- a/dist/lib/methods/localstorage.js +++ b/dist/lib/methods/localstorage.js @@ -16,13 +16,9 @@ exports.postMessage = postMessage; exports.removeStorageEventListener = removeStorageEventListener; exports.storageKey = storageKey; exports.type = void 0; - var _obliviousSet = require("oblivious-set"); - var _options = require("../options.js"); - var _util = require("../util.js"); - /** * A localStorage-only method which uses localstorage and its 'storage'-event * This does not work inside of webworkers because they have no access to locastorage @@ -30,41 +26,38 @@ var _util = require("../util.js"); * @link https://caniuse.com/#feat=namevalue-storage * @link https://caniuse.com/#feat=indexeddb */ + var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var KEY_PREFIX = 'pubkey.broadcastChannel-'; var type = 'localstorage'; + /** * copied from crosstab * @link https://github.com/tejacques/crosstab/blob/master/src/crosstab.js#L32 */ - exports.type = type; - function getLocalStorage() { var localStorage; if (typeof window === 'undefined') return null; - try { localStorage = window.localStorage; localStorage = window['ie8-eventlistener/storage'] || window.localStorage; - } catch (e) {// New versions of Firefox throw a Security exception + } catch (e) { + // New versions of Firefox throw a Security exception // if cookies are disabled. See // https://bugzilla.mozilla.org/show_bug.cgi?id=1028153 } - return localStorage; } - function storageKey(channelName) { return KEY_PREFIX + channelName; } + /** * writes the new message to the storage * and fires the storage-event so other readers can find it */ - - function postMessage(channelState, messageJson) { return new Promise(function (res) { (0, _util.sleep)().then(function () { @@ -77,12 +70,12 @@ function postMessage(channelState, messageJson) { }; var value = JSON.stringify(writeObj); getLocalStorage().setItem(key, value); + /** * StorageEvent does not fire the 'storage' event * in the window that changes the state of the local storage. * So we fire it manually */ - var ev = document.createEvent('Event'); ev.initEvent('storage', true, true); ev.key = key; @@ -92,52 +85,42 @@ function postMessage(channelState, messageJson) { }); }); } - function addStorageEventListener(channelName, fn) { var key = storageKey(channelName); - var listener = function listener(ev) { if (ev.key === key) { fn(JSON.parse(ev.newValue)); } }; - window.addEventListener('storage', listener); return listener; } - function removeStorageEventListener(listener) { window.removeEventListener('storage', listener); } - function create(channelName, options) { options = (0, _options.fillOptionsWithDefaults)(options); - if (!canBeUsed()) { throw new Error('BroadcastChannel: localstorage cannot be used'); } - var uuid = (0, _util.randomToken)(); + /** * eMIs * contains all messages that have been emitted before * @type {ObliviousSet} */ - var eMIs = new _obliviousSet.ObliviousSet(options.localstorage.removeTimeout); var state = { channelName: channelName, uuid: uuid, eMIs: eMIs // emittedMessagesIds - }; + state.listener = addStorageEventListener(channelName, function (msgObj) { if (!state.messagesCallback) return; // no listener - if (msgObj.uuid === uuid) return; // own message - if (!msgObj.token || eMIs.has(msgObj.token)) return; // already emitted - if (msgObj.data.time && msgObj.data.time < state.messagesCallbackTime) return; // too old eMIs.add(msgObj.token); @@ -145,20 +128,16 @@ function create(channelName, options) { }); return state; } - function close(channelState) { removeStorageEventListener(channelState.listener); } - function onMessage(channelState, fn, time) { channelState.messagesCallbackTime = time; channelState.messagesCallback = fn; } - function canBeUsed() { var ls = getLocalStorage(); if (!ls) return false; - try { var key = '__broadcastchannel_check'; ls.setItem(key, 'works'); @@ -169,22 +148,17 @@ function canBeUsed() { // https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API#Private_Browsing_Incognito_modes return false; } - return true; } - function averageResponseTime() { var defaultTime = 120; var userAgent = navigator.userAgent.toLowerCase(); - if (userAgent.includes('safari') && !userAgent.includes('chrome')) { // safari is much slower so this time is higher return defaultTime * 2; } - return defaultTime; } - var _default = { create: create, close: close, diff --git a/dist/lib/methods/native.js b/dist/lib/methods/native.js index 8118f760..cabcfb3e 100644 --- a/dist/lib/methods/native.js +++ b/dist/lib/methods/native.js @@ -11,20 +11,16 @@ exports.microSeconds = exports["default"] = void 0; exports.onMessage = onMessage; exports.postMessage = postMessage; exports.type = void 0; - var _util = require("../util.js"); - var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var type = 'native'; exports.type = type; - function create(channelName) { var state = { messagesCallback: null, bc: new BroadcastChannel(channelName), subFns: [] // subscriberFunctions - }; state.bc.onmessage = function (msg) { @@ -32,15 +28,12 @@ function create(channelName) { state.messagesCallback(msg.data); } }; - return state; } - function close(channelState) { channelState.bc.close(); channelState.subFns = []; } - function postMessage(channelState, messageJson) { try { channelState.bc.postMessage(messageJson, false); @@ -49,31 +42,25 @@ function postMessage(channelState, messageJson) { return Promise.reject(err); } } - function onMessage(channelState, fn) { channelState.messagesCallback = fn; } - function canBeUsed() { if (typeof window === 'undefined') { return false; } - if (typeof BroadcastChannel === 'function') { if (BroadcastChannel._pubkey) { throw new Error('BroadcastChannel: Do not overwrite window.BroadcastChannel with this module, this is not a polyfill'); } - return true; } else { return false; } } - function averageResponseTime() { return 150; } - var _default = { create: create, close: close, diff --git a/dist/lib/methods/node.js b/dist/lib/methods/node.js index a3aa3c7e..76d38bc5 100644 --- a/dist/lib/methods/node.js +++ b/dist/lib/methods/node.js @@ -1,7 +1,6 @@ "use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); - Object.defineProperty(exports, "__esModule", { value: true }); @@ -35,37 +34,21 @@ exports.socketInfoPath = socketInfoPath; exports.socketPath = socketPath; exports.type = void 0; exports.writeMessage = writeMessage; - var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); - var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); - var _util = _interopRequireDefault(require("util")); - var _fs = _interopRequireDefault(require("fs")); - var _crypto = _interopRequireDefault(require("crypto")); - var _os = _interopRequireDefault(require("os")); - var _events = _interopRequireDefault(require("events")); - var _net = _interopRequireDefault(require("net")); - var _path = _interopRequireDefault(require("path")); - var _rimraf = _interopRequireDefault(require("rimraf")); - var _pQueue = _interopRequireDefault(require("p-queue")); - var _unload = require("unload"); - var _options = require("../options.js"); - var _util2 = require("../util.js"); - var _obliviousSet = require("oblivious-set"); - /** * this method is used in nodejs-environments. * The ipc is handled via sockets and file-writes to the tmp-folder @@ -84,29 +67,18 @@ function cleanPipeName(str) { return str; } } - var mkdir = _util["default"].promisify(_fs["default"].mkdir); - var writeFile = _util["default"].promisify(_fs["default"].writeFile); - var readFile = _util["default"].promisify(_fs["default"].readFile); - var unlink = _util["default"].promisify(_fs["default"].unlink); - var readdir = _util["default"].promisify(_fs["default"].readdir); - var chmod = _util["default"].promisify(_fs["default"].chmod); - var removeDir = _util["default"].promisify(_rimraf["default"]); - var OTHER_INSTANCES = {}; var TMP_FOLDER_NAME = 'pubkey.bc'; - var TMP_FOLDER_BASE = _path["default"].join(_os["default"].tmpdir(), TMP_FOLDER_NAME); - exports.TMP_FOLDER_BASE = TMP_FOLDER_BASE; var getPathsCache = new Map(); - function getPaths(channelName) { if (!getPathsCache.has(channelName)) { /** @@ -116,21 +88,16 @@ function getPaths(channelName) { * in using the same folders for different channels. */ var channelHash = _crypto["default"].createHash('sha256').update(channelName).digest('hex'); + /** * because the length of socket-paths is limited, we use only the first 20 chars * and also start with A to ensure we do not start with a number * @link https://serverfault.com/questions/641347/check-if-a-path-exceeds-maximum-for-unix-domain-socket */ - - var channelFolder = 'A' + channelHash.substring(0, 20); - var channelPathBase = _path["default"].join(TMP_FOLDER_BASE, channelFolder); - var folderPathReaders = _path["default"].join(channelPathBase, 'rdrs'); - var folderPathMessages = _path["default"].join(channelPathBase, 'msgs'); - var ret = { channelBase: channelPathBase, readers: folderPathReaders, @@ -139,16 +106,12 @@ function getPaths(channelName) { getPathsCache.set(channelName, ret); return ret; } - return getPathsCache.get(channelName); } - var ENSURE_BASE_FOLDER_EXISTS_PROMISE = null; - function ensureBaseFolderExists() { return _ensureBaseFolderExists.apply(this, arguments); } - function _ensureBaseFolderExists() { _ensureBaseFolderExists = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee3() { return _regenerator["default"].wrap(function _callee3$(_context3) { @@ -160,9 +123,7 @@ function _ensureBaseFolderExists() { return null; }); } - return _context3.abrupt("return", ENSURE_BASE_FOLDER_EXISTS_PROMISE); - case 2: case "end": return _context3.stop(); @@ -172,7 +133,6 @@ function _ensureBaseFolderExists() { })); return _ensureBaseFolderExists.apply(this, arguments); } - function ensureFoldersExist(_x, _x2) { return _ensureFoldersExist.apply(this, arguments); } @@ -180,8 +140,6 @@ function ensureFoldersExist(_x, _x2) { * removes the tmp-folder * @return {Promise} */ - - function _ensureFoldersExist() { _ensureFoldersExist = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee4(channelName, paths) { var chmodValue; @@ -192,13 +150,11 @@ function _ensureFoldersExist() { paths = paths || getPaths(channelName); _context4.next = 3; return ensureBaseFolderExists(); - case 3: _context4.next = 5; return mkdir(paths.channelBase)["catch"](function () { return null; }); - case 5: _context4.next = 7; return Promise.all([mkdir(paths.readers)["catch"](function () { @@ -206,7 +162,6 @@ function _ensureFoldersExist() { }), mkdir(paths.messages)["catch"](function () { return null; })]); - case 7: // set permissions so other users can use the same channel chmodValue = '777'; @@ -214,7 +169,6 @@ function _ensureFoldersExist() { return Promise.all([chmod(paths.channelBase, chmodValue), chmod(paths.readers, chmodValue), chmod(paths.messages, chmodValue)])["catch"](function () { return null; }); - case 10: case "end": return _context4.stop(); @@ -224,11 +178,9 @@ function _ensureFoldersExist() { })); return _ensureFoldersExist.apply(this, arguments); } - function clearNodeFolder() { return _clearNodeFolder.apply(this, arguments); } - function _clearNodeFolder() { _clearNodeFolder = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee5() { return _regenerator["default"].wrap(function _callee5$(_context5) { @@ -239,18 +191,14 @@ function _clearNodeFolder() { _context5.next = 2; break; } - throw new Error('BroadcastChannel.clearNodeFolder(): path is wrong'); - case 2: ENSURE_BASE_FOLDER_EXISTS_PROMISE = null; _context5.next = 5; return removeDir(TMP_FOLDER_BASE); - case 5: ENSURE_BASE_FOLDER_EXISTS_PROMISE = null; return _context5.abrupt("return", true); - case 7: case "end": return _context5.stop(); @@ -260,29 +208,22 @@ function _clearNodeFolder() { })); return _clearNodeFolder.apply(this, arguments); } - function socketPath(channelName, readerUuid, paths) { paths = paths || getPaths(channelName); - var socketPath = _path["default"].join(paths.readers, readerUuid + '.s'); - return cleanPipeName(socketPath); } - function socketInfoPath(channelName, readerUuid, paths) { paths = paths || getPaths(channelName); - var socketPath = _path["default"].join(paths.readers, readerUuid + '.json'); - return socketPath; } + /** * Because it is not possible to get all socket-files in a folder, * when used under fucking windows, * we have to set a normal file so other readers know our socket exists */ - - function createSocketInfoFile(channelName, readerUuid, paths) { var pathToFile = socketInfoPath(channelName, readerUuid, paths); return writeFile(pathToFile, JSON.stringify({ @@ -291,16 +232,14 @@ function createSocketInfoFile(channelName, readerUuid, paths) { return pathToFile; }); } + /** * returns the amount of channel-folders in the tmp-directory * @return {Promise} */ - - function countChannelFolders() { return _countChannelFolders.apply(this, arguments); } - function _countChannelFolders() { _countChannelFolders = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee6() { var folders; @@ -310,15 +249,12 @@ function _countChannelFolders() { case 0: _context6.next = 2; return ensureBaseFolderExists(); - case 2: _context6.next = 4; return readdir(TMP_FOLDER_BASE); - case 4: folders = _context6.sent; return _context6.abrupt("return", folders.length); - case 6: case "end": return _context6.stop(); @@ -328,7 +264,6 @@ function _countChannelFolders() { })); return _countChannelFolders.apply(this, arguments); } - function connectionError(_x3) { return _connectionError.apply(this, arguments); } @@ -336,8 +271,6 @@ function connectionError(_x3) { * creates the socket-file and subscribes to it * @return {{emitter: EventEmitter, server: any}} */ - - function _connectionError() { _connectionError = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee7(originalError) { var count, addObj, text, newError; @@ -347,28 +280,23 @@ function _connectionError() { case 0: _context7.next = 2; return countChannelFolders(); - case 2: count = _context7.sent; - if (!(count < 30)) { _context7.next = 5; break; } - return _context7.abrupt("return", originalError); - case 5: addObj = {}; Object.entries(originalError).forEach(function (_ref3) { var k = _ref3[0], - v = _ref3[1]; + v = _ref3[1]; return addObj[k] = v; }); text = 'BroadcastChannel.create(): error: ' + 'This might happen if you have created to many channels, ' + 'like when you use BroadcastChannel in unit-tests.' + 'Try using BroadcastChannel.clearNodeFolder() to clear the tmp-folder before each test.' + 'See https://github.com/pubkey/broadcast-channel#clear-tmp-folder'; newError = new Error(text + ': ' + JSON.stringify(addObj, null, 2)); return _context7.abrupt("return", newError); - case 10: case "end": return _context7.stop(); @@ -378,11 +306,9 @@ function _connectionError() { })); return _connectionError.apply(this, arguments); } - function createSocketEventEmitter(_x4, _x5, _x6) { return _createSocketEventEmitter.apply(this, arguments); } - function _createSocketEventEmitter() { _createSocketEventEmitter = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee10(channelName, readerUuid, paths) { var pathToSocket, emitter, server; @@ -409,11 +335,9 @@ function _createSocketEventEmitter() { case 0: _context8.next = 2; return connectionError(err); - case 2: useErr = _context8.sent; reject(useErr); - case 4: case "end": return _context8.stop(); @@ -421,7 +345,6 @@ function _createSocketEventEmitter() { } }, _callee8); })); - return function (_x26) { return _ref4.apply(this, arguments); }; @@ -437,19 +360,15 @@ function _createSocketEventEmitter() { _context9.next = 7; break; } - _context9.next = 3; return connectionError(err); - case 3: useErr = _context9.sent; reject(useErr); _context9.next = 8; break; - case 7: resolve(res); - case 8: case "end": return _context9.stop(); @@ -457,20 +376,17 @@ function _createSocketEventEmitter() { } }, _callee9); })); - return function (_x27, _x28) { return _ref5.apply(this, arguments); }; }()); }); - case 5: return _context10.abrupt("return", { path: pathToSocket, emitter: emitter, server: server }); - case 6: case "end": return _context10.stop(); @@ -480,7 +396,6 @@ function _createSocketEventEmitter() { })); return _createSocketEventEmitter.apply(this, arguments); } - function openClientConnection(_x7, _x8) { return _openClientConnection.apply(this, arguments); } @@ -489,8 +404,6 @@ function openClientConnection(_x7, _x8) { * so other readers can find it * @return {Promise} */ - - function _openClientConnection() { _openClientConnection = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee11(channelName, readerUuid) { var pathToSocket, client; @@ -508,7 +421,6 @@ function _openClientConnection() { return rej(err); }); })); - case 3: case "end": return _context11.stop(); @@ -518,7 +430,6 @@ function _openClientConnection() { })); return _openClientConnection.apply(this, arguments); } - function writeMessage(channelName, readerUuid, messageJson, paths) { paths = paths || getPaths(channelName); var time = microSeconds(); @@ -529,9 +440,7 @@ function writeMessage(channelName, readerUuid, messageJson, paths) { }; var token = (0, _util2.randomToken)(); var fileName = time + '_' + readerUuid + '_' + token + '.json'; - var msgPath = _path["default"].join(paths.messages, fileName); - return writeFile(msgPath, JSON.stringify(writeObject)).then(function () { return { time: time, @@ -541,16 +450,14 @@ function writeMessage(channelName, readerUuid, messageJson, paths) { }; }); } + /** * returns the uuids of all readers * @return {string[]} */ - - function getReadersUuids(_x9, _x10) { return _getReadersUuids.apply(this, arguments); } - function _getReadersUuids() { _getReadersUuids = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee12(channelName, paths) { var readersPath, files; @@ -562,7 +469,6 @@ function _getReadersUuids() { readersPath = paths.readers; _context12.next = 4; return readdir(readersPath); - case 4: files = _context12.sent; return _context12.abrupt("return", files.map(function (file) { @@ -573,7 +479,6 @@ function _getReadersUuids() { .map(function (split) { return split[0]; })); - case 6: case "end": return _context12.stop(); @@ -583,11 +488,9 @@ function _getReadersUuids() { })); return _getReadersUuids.apply(this, arguments); } - function messagePath(_x11, _x12, _x13, _x14) { return _messagePath.apply(this, arguments); } - function _messagePath() { _messagePath = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee13(channelName, time, token, writerUuid) { var fileName, msgPath; @@ -598,7 +501,6 @@ function _messagePath() { fileName = time + '_' + writerUuid + '_' + token + '.json'; msgPath = _path["default"].join(getPaths(channelName).messages, fileName); return _context13.abrupt("return", msgPath); - case 3: case "end": return _context13.stop(); @@ -608,11 +510,9 @@ function _messagePath() { })); return _messagePath.apply(this, arguments); } - function getAllMessages(_x15, _x16) { return _getAllMessages.apply(this, arguments); } - function _getAllMessages() { _getAllMessages = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee14(channelName, paths) { var messagesPath, files; @@ -624,7 +524,6 @@ function _getAllMessages() { messagesPath = paths.messages; _context14.next = 4; return readdir(messagesPath); - case 4: files = _context14.sent; return _context14.abrupt("return", files.map(function (file) { @@ -637,7 +536,6 @@ function _getAllMessages() { token: split[2] }; })); - case 6: case "end": return _context14.stop(); @@ -647,7 +545,6 @@ function _getAllMessages() { })); return _getAllMessages.apply(this, arguments); } - function getSingleMessage(channelName, msgObj, paths) { paths = paths || getPaths(channelName); return { @@ -657,17 +554,14 @@ function getSingleMessage(channelName, msgObj, paths) { token: msgObj.to }; } - function readMessage(messageObj) { return readFile(messageObj.path, 'utf8').then(function (content) { return JSON.parse(content); }); } - function cleanOldMessages(_x17, _x18) { return _cleanOldMessages.apply(this, arguments); } - function _cleanOldMessages() { _cleanOldMessages = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee15(messageObjects, ttl) { var olderThen; @@ -676,7 +570,6 @@ function _cleanOldMessages() { switch (_context15.prev = _context15.next) { case 0: olderThen = microSeconds() - ttl * 1000; // convert ttl to microseconds - _context15.next = 3; return Promise.all(messageObjects.filter(function (obj) { return obj.time < olderThen; @@ -685,7 +578,6 @@ function _cleanOldMessages() { return null; }); })); - case 3: case "end": return _context15.stop(); @@ -695,32 +587,28 @@ function _cleanOldMessages() { })); return _cleanOldMessages.apply(this, arguments); } - var type = 'node'; + /** * creates a new channelState * @return {Promise} */ - exports.type = type; - function create(_x19) { return _create.apply(this, arguments); } - function _create() { _create = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee16(channelName) { var options, - time, - paths, - ensureFolderExistsPromise, - uuid, - state, - _yield$Promise$all, - socketEE, - infoFilePath, - _args16 = arguments; - + time, + paths, + ensureFolderExistsPromise, + uuid, + state, + _yield$Promise$all, + socketEE, + infoFilePath, + _args16 = arguments; return _regenerator["default"].wrap(function _callee16$(_context16) { while (1) { switch (_context16.prev = _context16.next) { @@ -739,7 +627,6 @@ function _create() { paths: paths, // contains all messages that have been emitted before emittedMessagesIds: new _obliviousSet.ObliviousSet(options.node.ttl * 2), - /** * Used to ensure we do not write too many files at once * which could throw an error. @@ -763,18 +650,17 @@ function _create() { OTHER_INSTANCES[channelName].push(state); _context16.next = 11; return ensureFolderExistsPromise; - case 11: _context16.next = 13; return Promise.all([createSocketEventEmitter(channelName, uuid, paths), createSocketInfoFile(channelName, uuid, paths), refreshReaderClients(state)]); - case 13: _yield$Promise$all = _context16.sent; socketEE = _yield$Promise$all[0]; infoFilePath = _yield$Promise$all[1]; state.socketEE = socketEE; - state.infoFilePath = infoFilePath; // when new message comes in, we read it and emit it + state.infoFilePath = infoFilePath; + // when new message comes in, we read it and emit it socketEE.emitter.on('data', function (data) { // if the socket is used fast, it may appear that multiple messages are flushed at once // so we have to split them before @@ -791,7 +677,6 @@ function _create() { }); }); return _context16.abrupt("return", state); - case 20: case "end": return _context16.stop(); @@ -801,27 +686,21 @@ function _create() { })); return _create.apply(this, arguments); } - function _filterMessage(msgObj, state) { if (msgObj.senderUuid === state.uuid) return false; // not send by own - if (state.emittedMessagesIds.has(msgObj.token)) return false; // not already emitted - if (!state.messagesCallback) return false; // no listener - if (msgObj.time < state.messagesCallbackTime) return false; // not older then onMessageCallback - if (msgObj.time < state.time) return false; // msgObj is older then channel state.emittedMessagesIds.add(msgObj.token); return true; } + /** * when the socket pings, so that we now new messages came, * run this */ - - function handleMessagePing(_x20, _x21) { return _handleMessagePing.apply(this, arguments); } @@ -829,8 +708,6 @@ function handleMessagePing(_x20, _x21) { * ensures that the channelState is connected with all other readers * @return {Promise} */ - - function _handleMessagePing() { _handleMessagePing = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee17(state, msgObj) { var messages, useMessages; @@ -842,27 +719,21 @@ function _handleMessagePing() { _context17.next = 2; break; } - return _context17.abrupt("return"); - case 2: if (msgObj) { _context17.next = 8; break; } - _context17.next = 5; return getAllMessages(state.channelName, state.paths); - case 5: messages = _context17.sent; _context17.next = 9; break; - case 8: // get single message messages = [getSingleMessage(state.channelName, msgObj, state.paths)]; - case 9: useMessages = messages.filter(function (msgObj) { return _filterMessage(msgObj, state); @@ -870,14 +741,11 @@ function _handleMessagePing() { return msgObjA.time - msgObjB.time; }); // sort by time // if no listener or message, so not do anything - if (!(!useMessages.length || !state.messagesCallback)) { _context17.next = 12; break; } - return _context17.abrupt("return"); - case 12: _context17.next = 14; return Promise.all(useMessages.map(function (msgObj) { @@ -885,17 +753,14 @@ function _handleMessagePing() { return msgObj.content = content; }); })); - case 14: useMessages.forEach(function (msgObj) { state.emittedMessagesIds.add(msgObj.token); - if (state.messagesCallback) { // emit to subscribers state.messagesCallback(msgObj.content.data); } }); - case 15: case "end": return _context17.stop(); @@ -905,7 +770,6 @@ function _handleMessagePing() { })); return _handleMessagePing.apply(this, arguments); } - function refreshReaderClients(channelState) { return getReadersUuids(channelState.channelName, channelState.paths).then(function (otherReaders) { // remove subscriptions to closed readers @@ -920,18 +784,14 @@ function refreshReaderClients(channelState) { _context.prev = 0; _context.next = 3; return channelState.otherReaderClients[readerUuid].destroy(); - case 3: _context.next = 7; break; - case 5: _context.prev = 5; _context.t0 = _context["catch"](0); - case 7: delete channelState.otherReaderClients[readerUuid]; - case 8: case "end": return _context.stop(); @@ -939,12 +799,12 @@ function refreshReaderClients(channelState) { } }, _callee, null, [[0, 5]]); })); - return function (_x22) { return _ref.apply(this, arguments); }; - }()); // add new readers + }()); + // add new readers return Promise.all(otherReaders.filter(function (readerUuid) { return readerUuid !== channelState.uuid; }) // not own @@ -959,37 +819,29 @@ function refreshReaderClients(channelState) { switch (_context2.prev = _context2.next) { case 0: _context2.prev = 0; - if (!channelState.closed) { _context2.next = 3; break; } - return _context2.abrupt("return"); - case 3: _context2.prev = 3; _context2.next = 6; return openClientConnection(channelState.channelName, readerUuid); - case 6: client = _context2.sent; channelState.otherReaderClients[readerUuid] = client; _context2.next = 12; break; - case 10: _context2.prev = 10; _context2.t0 = _context2["catch"](3); - case 12: _context2.next = 16; break; - case 14: _context2.prev = 14; _context2.t1 = _context2["catch"](0); - case 16: case "end": return _context2.stop(); @@ -997,19 +849,17 @@ function refreshReaderClients(channelState) { } }, _callee2, null, [[0, 14], [3, 10]]); })); - return function (_x23) { return _ref2.apply(this, arguments); }; }())); }); } + /** * post a message to the other readers * @return {Promise} */ - - function postMessage(_x24, _x25) { return _postMessage.apply(this, arguments); } @@ -1019,8 +869,6 @@ function postMessage(_x24, _x25) { * This might not happen often in production * but will speed up things when this module is used in unit-tests. */ - - function _postMessage() { _postMessage = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee19(channelState, messageJson) { var writePromise; @@ -1033,7 +881,6 @@ function _postMessage() { }); channelState.writeBlockPromise = channelState.writeBlockPromise.then( /*#__PURE__*/(0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee18() { var _yield$Promise$all2, msgObj, pingStr, writeToReadersPromise; - return _regenerator["default"].wrap(function _callee18$(_context18) { while (1) { switch (_context18.prev = _context18.next) { @@ -1042,11 +889,9 @@ function _postMessage() { return new Promise(function (res) { return setTimeout(res, 0); }); - case 2: _context18.next = 4; return Promise.all([writePromise, refreshReaderClients(channelState)]); - case 4: _yield$Promise$all2 = _context18.sent; msgObj = _yield$Promise$all2[0]; @@ -1065,16 +910,13 @@ function _postMessage() { * to not waste resources on cleaning up, * only if random-int matches, we clean up old messages */ - if ((0, _util2.randomInt)(0, 20) === 0) { /* await */ getAllMessages(channelState.channelName, channelState.paths).then(function (allMessages) { return cleanOldMessages(allMessages, channelState.options.node.ttl); }); } - return _context18.abrupt("return", writeToReadersPromise); - case 11: case "end": return _context18.stop(); @@ -1083,7 +925,6 @@ function _postMessage() { }, _callee18); }))); return _context19.abrupt("return", channelState.writeBlockPromise); - case 3: case "end": return _context19.stop(); @@ -1093,13 +934,11 @@ function _postMessage() { })); return _postMessage.apply(this, arguments); } - function emitOverFastPath(state, msgObj, messageJson) { if (!state.options.node.useFastPath) { // disabled return; } - var others = OTHER_INSTANCES[state.channelName].filter(function (s) { return s !== state; }); @@ -1114,19 +953,17 @@ function emitOverFastPath(state, msgObj, messageJson) { otherState.messagesCallback(messageJson); }); } - function onMessage(channelState, fn) { var time = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : microSeconds(); channelState.messagesCallbackTime = time; channelState.messagesCallback = fn; handleMessagePing(channelState); } + /** * closes the channel * @return {Promise} */ - - function close(channelState) { if (channelState.closed) return; channelState.closed = true; @@ -1134,35 +971,30 @@ function close(channelState) { OTHER_INSTANCES[channelState.channelName] = OTHER_INSTANCES[channelState.channelName].filter(function (o) { return o !== channelState; }); - if (channelState.removeUnload) { channelState.removeUnload.remove(); } - return new Promise(function (res) { if (channelState.socketEE) channelState.socketEE.emitter.removeAllListeners(); Object.values(channelState.otherReaderClients).forEach(function (client) { return client.destroy(); }); - if (channelState.infoFilePath) { try { _fs["default"].unlinkSync(channelState.infoFilePath); } catch (err) {} } + /** * the server get closed lazy because others might still write on it * and have not found out that the infoFile was deleted */ - - setTimeout(function () { channelState.socketEE.server.close(); res(); }, 200); }); } - function canBeUsed() { if (typeof _fs["default"].mkdir === 'function') { return true; @@ -1170,23 +1002,20 @@ function canBeUsed() { return false; } } + /** * on node we use a relatively height averageResponseTime, * because the file-io might be in use. * Also it is more important that the leader-election is reliable, * then to have a fast election. */ - - function averageResponseTime() { return 200; } - function microSeconds() { // convert nano to micro seconds return parseInt(now() / 1000); } - function now() { return Number(process.hrtime.bigint()); // returns nanoseconds } \ No newline at end of file diff --git a/dist/lib/methods/simulate.js b/dist/lib/methods/simulate.js index f0ce13c5..46d5cfde 100644 --- a/dist/lib/methods/simulate.js +++ b/dist/lib/methods/simulate.js @@ -11,15 +11,12 @@ exports.microSeconds = exports["default"] = void 0; exports.onMessage = onMessage; exports.postMessage = postMessage; exports.type = void 0; - var _util = require("../util.js"); - var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var type = 'simulate'; exports.type = type; var SIMULATE_CHANNELS = new Set(); - function create(channelName) { var state = { name: channelName, @@ -28,11 +25,9 @@ function create(channelName) { SIMULATE_CHANNELS.add(state); return state; } - function close(channelState) { SIMULATE_CHANNELS["delete"](channelState); } - function postMessage(channelState, messageJson) { return new Promise(function (res) { return setTimeout(function () { @@ -50,19 +45,15 @@ function postMessage(channelState, messageJson) { }, 5); }); } - function onMessage(channelState, fn) { channelState.messagesCallback = fn; } - function canBeUsed() { return true; } - function averageResponseTime() { return 5; } - var _default = { create: create, close: close, diff --git a/dist/lib/options.js b/dist/lib/options.js index c8435340..36c0e4e8 100644 --- a/dist/lib/options.js +++ b/dist/lib/options.js @@ -4,33 +4,35 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.fillOptionsWithDefaults = fillOptionsWithDefaults; - function fillOptionsWithDefaults() { var originalOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var options = JSON.parse(JSON.stringify(originalOptions)); // main - - if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true; // indexed-db + var options = JSON.parse(JSON.stringify(originalOptions)); - if (!options.idb) options.idb = {}; // after this time the messages get deleted + // main + if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true; + // indexed-db + if (!options.idb) options.idb = {}; + // after this time the messages get deleted if (!options.idb.ttl) options.idb.ttl = 1000 * 45; - if (!options.idb.fallbackInterval) options.idb.fallbackInterval = 150; // handles abrupt db onclose events. - - if (originalOptions.idb && typeof originalOptions.idb.onclose === 'function') options.idb.onclose = originalOptions.idb.onclose; // localstorage + if (!options.idb.fallbackInterval) options.idb.fallbackInterval = 150; + // handles abrupt db onclose events. + if (originalOptions.idb && typeof originalOptions.idb.onclose === 'function') options.idb.onclose = originalOptions.idb.onclose; + // localstorage if (!options.localstorage) options.localstorage = {}; - if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60; // custom methods + if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60; - if (originalOptions.methods) options.methods = originalOptions.methods; // node + // custom methods + if (originalOptions.methods) options.methods = originalOptions.methods; + // node if (!options.node) options.node = {}; if (!options.node.ttl) options.node.ttl = 1000 * 60 * 2; // 2 minutes; - /** * On linux use 'ulimit -Hn' to get the limit of open files. * On ubuntu this was 4096 for me, so we use half of that as maxParallelWrites default. */ - if (!options.node.maxParallelWrites) options.node.maxParallelWrites = 2048; if (typeof options.node.useFastPath === 'undefined') options.node.useFastPath = true; return options; diff --git a/dist/lib/util.js b/dist/lib/util.js index bf51ea5e..c63716c9 100644 --- a/dist/lib/util.js +++ b/dist/lib/util.js @@ -9,7 +9,6 @@ exports.microSeconds = microSeconds; exports.randomInt = randomInt; exports.randomToken = randomToken; exports.sleep = sleep; - /** * returns true if the given object is a promise */ @@ -20,14 +19,12 @@ function isPromise(obj) { return false; } } - var PROMISE_RESOLVED_FALSE = Promise.resolve(false); exports.PROMISE_RESOLVED_FALSE = PROMISE_RESOLVED_FALSE; var PROMISE_RESOLVED_TRUE = Promise.resolve(true); exports.PROMISE_RESOLVED_TRUE = PROMISE_RESOLVED_TRUE; var PROMISE_RESOLVED_VOID = Promise.resolve(); exports.PROMISE_RESOLVED_VOID = PROMISE_RESOLVED_VOID; - function sleep(time, resolveWith) { if (!time) time = 0; return new Promise(function (res) { @@ -36,21 +33,19 @@ function sleep(time, resolveWith) { }, time); }); } - function randomInt(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } + /** * https://stackoverflow.com/a/8084248 */ - - function randomToken() { return Math.random().toString(36).substring(2); } - var lastMs = 0; var additional = 0; + /** * returns the current time in micro-seconds, * WARNING: This is a pseudo-function @@ -58,10 +53,8 @@ var additional = 0; * This is enough in browsers, and this function will not be used in nodejs. * The main reason for this hack is to ensure that BroadcastChannel behaves equal to production when it is used in fast-running unit tests. */ - function microSeconds() { var ms = new Date().getTime(); - if (ms === lastMs) { additional++; return ms * 1000 + additional; diff --git a/docs/e2e.js b/docs/e2e.js index 71a6a640..19884333 100644 --- a/docs/e2e.js +++ b/docs/e2e.js @@ -7,13 +7,9 @@ Object.defineProperty(exports, "__esModule", { exports.OPEN_BROADCAST_CHANNELS = exports.BroadcastChannel = void 0; exports.clearNodeFolder = clearNodeFolder; exports.enforceOptions = enforceOptions; - var _util = require("./util.js"); - var _methodChooser = require("./method-chooser.js"); - var _options = require("./options.js"); - /** * Contains all open channels, * used in tests to ensure everything is closed. @@ -21,77 +17,73 @@ var _options = require("./options.js"); var OPEN_BROADCAST_CHANNELS = new Set(); exports.OPEN_BROADCAST_CHANNELS = OPEN_BROADCAST_CHANNELS; var lastId = 0; - var BroadcastChannel = function BroadcastChannel(name, options) { // identifier of the channel to debug stuff this.id = lastId++; OPEN_BROADCAST_CHANNELS.add(this); this.name = name; - if (ENFORCED_OPTIONS) { options = ENFORCED_OPTIONS; } - this.options = (0, _options.fillOptionsWithDefaults)(options); - this.method = (0, _methodChooser.chooseMethod)(this.options); // isListening + this.method = (0, _methodChooser.chooseMethod)(this.options); + // isListening this._iL = false; + /** * _onMessageListener * setting onmessage twice, * will overwrite the first listener */ - this._onML = null; + /** * _addEventListeners */ - this._addEL = { message: [], internal: [] }; + /** * Unsend message promises * where the sending is still in progress * @type {Set} */ - this._uMP = new Set(); + /** * _beforeClose * array of promises that will be awaited * before the channel is closed */ - this._befC = []; + /** * _preparePromise */ - this._prepP = null; - _prepareChannel(this); -}; // STATICS +}; + +// STATICS /** * used to identify if someone overwrites * window.BroadcastChannel with this * See methods/native.js */ - - exports.BroadcastChannel = BroadcastChannel; BroadcastChannel._pubkey = true; + /** * clears the tmp-folder if is node * @return {Promise} true if has run, false if not node */ - function clearNodeFolder(options) { options = (0, _options.fillOptionsWithDefaults)(options); var method = (0, _methodChooser.chooseMethod)(options); - if (method.type === 'node') { return method.clearNodeFolder().then(function () { return true; @@ -100,19 +92,17 @@ function clearNodeFolder(options) { return _util.PROMISE_RESOLVED_FALSE; } } + /** * if set, this method is enforced, * no mather what the options are */ - - var ENFORCED_OPTIONS; - function enforceOptions(options) { ENFORCED_OPTIONS = options; -} // PROTOTYPE - +} +// PROTOTYPE BroadcastChannel.prototype = { postMessage: function postMessage(msg) { if (this.closed) { @@ -124,87 +114,77 @@ BroadcastChannel.prototype = { */ JSON.stringify(msg)); } - return _post(this, 'message', msg); }, postInternal: function postInternal(msg) { return _post(this, 'internal', msg); }, - set onmessage(fn) { var time = this.method.microSeconds(); var listenObj = { time: time, fn: fn }; - _removeListenerObject(this, 'message', this._onML); - if (fn && typeof fn === 'function') { this._onML = listenObj; - _addListenerObject(this, 'message', listenObj); } else { this._onML = null; } }, - addEventListener: function addEventListener(type, fn) { var time = this.method.microSeconds(); var listenObj = { time: time, fn: fn }; - _addListenerObject(this, type, listenObj); }, removeEventListener: function removeEventListener(type, fn) { var obj = this._addEL[type].find(function (obj) { return obj.fn === fn; }); - _removeListenerObject(this, type, obj); }, close: function close() { var _this = this; - if (this.closed) { return; } - OPEN_BROADCAST_CHANNELS["delete"](this); this.closed = true; var awaitPrepare = this._prepP ? this._prepP : _util.PROMISE_RESOLVED_VOID; this._onML = null; this._addEL.message = []; - return awaitPrepare // wait until all current sending are processed + return awaitPrepare + // wait until all current sending are processed .then(function () { return Promise.all(Array.from(_this._uMP)); - }) // run before-close hooks + }) + // run before-close hooks .then(function () { return Promise.all(_this._befC.map(function (fn) { return fn(); })); - }) // close the channel + }) + // close the channel .then(function () { return _this.method.close(_this._state); }); }, - get type() { return this.method.type; }, - get isClosed() { return this.closed; } - }; + /** * Post a message over the channel * @returns {Promise} that resolved when the message sending is done */ - function _post(broadcastChannel, type, msg) { var time = broadcastChannel.method.microSeconds(); var msgObj = { @@ -214,25 +194,22 @@ function _post(broadcastChannel, type, msg) { }; var awaitPrepare = broadcastChannel._prepP ? broadcastChannel._prepP : _util.PROMISE_RESOLVED_VOID; return awaitPrepare.then(function () { - var sendPromise = broadcastChannel.method.postMessage(broadcastChannel._state, msgObj); // add/remove to unsend messages list + var sendPromise = broadcastChannel.method.postMessage(broadcastChannel._state, msgObj); + // add/remove to unsend messages list broadcastChannel._uMP.add(sendPromise); - sendPromise["catch"]().then(function () { return broadcastChannel._uMP["delete"](sendPromise); }); return sendPromise; }); } - function _prepareChannel(channel) { var maybePromise = channel.method.create(channel.name, channel.options); - if ((0, _util.isPromise)(maybePromise)) { channel._prepP = maybePromise; maybePromise.then(function (s) { // used in tests to simulate slow runtime - /*if (channel.options.prepareDelay) { await new Promise(res => setTimeout(res, this.options.prepareDelay)); }*/ @@ -242,30 +219,25 @@ function _prepareChannel(channel) { channel._state = maybePromise; } } - function _hasMessageListeners(channel) { if (channel._addEL.message.length > 0) return true; if (channel._addEL.internal.length > 0) return true; return false; } - function _addListenerObject(channel, type, obj) { channel._addEL[type].push(obj); - _startListening(channel); } - function _removeListenerObject(channel, type, obj) { channel._addEL[type] = channel._addEL[type].filter(function (o) { return o !== obj; }); - _stopListening(channel); } - function _startListening(channel) { if (!channel._iL && _hasMessageListeners(channel)) { // someone is listening, start subscribing + var listenerFn = function listenerFn(msgObj) { channel._addEL[msgObj.type].forEach(function (listenerObject) { /** @@ -279,15 +251,12 @@ function _startListening(channel) { */ var hundredMsInMicro = 100 * 1000; var minMessageTime = listenerObject.time - hundredMsInMicro; - if (msgObj.time >= minMessageTime) { listenerObject.fn(msgObj.data); } }); }; - var time = channel.method.microSeconds(); - if (channel._prepP) { channel._prepP.then(function () { channel._iL = true; @@ -299,7 +268,6 @@ function _startListening(channel) { } } } - function _stopListening(channel) { if (channel._iL && !_hasMessageListeners(channel)) { // noone is listening, stop subscribing @@ -312,7 +280,6 @@ function _stopListening(channel) { "use strict"; var _index = require("./index.js"); - /** * because babel can only export on default-attribute, * we use this for the non-module-build @@ -321,6 +288,7 @@ var _index = require("./index.js"); * but * var BroadcastChannel = require('broadcast-channel'); */ + module.exports = { BroadcastChannel: _index.BroadcastChannel, createLeaderElection: _index.createLeaderElection, @@ -370,9 +338,7 @@ Object.defineProperty(exports, "enforceOptions", { return _broadcastChannel.enforceOptions; } }); - var _broadcastChannel = require("./broadcast-channel.js"); - var _leaderElection = require("./leader-election.js"); },{"./broadcast-channel.js":1,"./leader-election.js":4}],4:[function(require,module,exports){ "use strict"; @@ -382,37 +348,30 @@ Object.defineProperty(exports, "__esModule", { }); exports.beLeader = beLeader; exports.createLeaderElection = createLeaderElection; - var _util = require("./util.js"); - var _unload = require("unload"); - var LeaderElection = function LeaderElection(broadcastChannel, options) { var _this = this; - this.broadcastChannel = broadcastChannel; this._options = options; this.isLeader = false; this.hasLeader = false; this.isDead = false; this.token = (0, _util.randomToken)(); + /** * Apply Queue, * used to ensure we do not run applyOnce() * in parallel. */ + this._aplQ = _util.PROMISE_RESOLVED_VOID; + // amount of unfinished applyOnce() calls + this._aplQC = 0; - this._aplQ = _util.PROMISE_RESOLVED_VOID; // amount of unfinished applyOnce() calls - - this._aplQC = 0; // things to clean up - + // things to clean up this._unl = []; // _unloads - this._lstns = []; // _listeners - this._dpL = function () {}; // onduplicate listener - - this._dpLC = false; // true when onduplicate called /** @@ -420,55 +379,47 @@ var LeaderElection = function LeaderElection(broadcastChannel, options) { * we still listen to messages to ensure the hasLeader flag * is set correctly. */ - var hasLeaderListener = function hasLeaderListener(msg) { if (msg.context === 'leader') { if (msg.action === 'death') { _this.hasLeader = false; } - if (msg.action === 'tell') { _this.hasLeader = true; } } }; - this.broadcastChannel.addEventListener('internal', hasLeaderListener); - this._lstns.push(hasLeaderListener); }; - LeaderElection.prototype = { /** * Returns true if the instance is leader, * false if not. * @async */ - applyOnce: function applyOnce( // true if the applyOnce() call came from the fallbackInterval cycle + applyOnce: function applyOnce( + // true if the applyOnce() call came from the fallbackInterval cycle isFromFallbackInterval) { var _this2 = this; - if (this.isLeader) { return (0, _util.sleep)(0, true); } - if (this.isDead) { return (0, _util.sleep)(0, false); } + /** * Already applying more then once, * -> wait for the apply queue to be finished. */ - - if (this._aplQC > 1) { return this._aplQ; } + /** * Add a new apply-run */ - - var applyRun = function applyRun() { /** * Optimization shortcuts. @@ -478,7 +429,6 @@ LeaderElection.prototype = { if (_this2.isLeader) { return _util.PROMISE_RESOLVED_TRUE; } - var stopCriteria = false; var stopCriteriaPromiseResolve; /** @@ -487,7 +437,6 @@ LeaderElection.prototype = { * have to await the responseTime when it is already clear * that the election failed. */ - var stopCriteriaPromise = new Promise(function (res) { stopCriteriaPromiseResolve = function stopCriteriaPromiseResolve() { stopCriteria = true; @@ -495,11 +444,9 @@ LeaderElection.prototype = { }; }); var recieved = []; - var handleMessage = function handleMessage(msg) { if (msg.context === 'leader' && msg.token != _this2.token) { recieved.push(msg); - if (msg.action === 'apply') { // other is applying if (msg.token > _this2.token) { @@ -510,7 +457,6 @@ LeaderElection.prototype = { stopCriteriaPromiseResolve(); } } - if (msg.action === 'tell') { // other is already leader stopCriteriaPromiseResolve(); @@ -518,8 +464,8 @@ LeaderElection.prototype = { } } }; - _this2.broadcastChannel.addEventListener('internal', handleMessage); + /** * If the applyOnce() call came from the fallbackInterval, * we can assume that the election runs in the background and @@ -530,26 +476,24 @@ LeaderElection.prototype = { * But also it takes longer which is not a problem because we anyway * run in the background. */ - - var waitForAnswerTime = isFromFallbackInterval ? _this2._options.responseTime * 4 : _this2._options.responseTime; - var applyPromise = _sendMessage(_this2, 'apply') // send out that this one is applying .then(function () { return Promise.race([(0, _util.sleep)(waitForAnswerTime), stopCriteriaPromise.then(function () { return Promise.reject(new Error()); })]); - }) // send again in case another instance was just created + }) + // send again in case another instance was just created .then(function () { return _sendMessage(_this2, 'apply'); - }) // let others time to respond + }) + // let others time to respond .then(function () { return Promise.race([(0, _util.sleep)(waitForAnswerTime), stopCriteriaPromise.then(function () { return Promise.reject(new Error()); })]); })["catch"](function () {}).then(function () { _this2.broadcastChannel.removeEventListener('internal', handleMessage); - if (!stopCriteria) { // no stop criteria -> own is leader return beLeader(_this2).then(function () { @@ -560,10 +504,8 @@ LeaderElection.prototype = { return false; } }); - return applyPromise; }; - this._aplQC = this._aplQC + 1; this._aplQ = this._aplQ.then(function () { return applyRun(); @@ -575,82 +517,68 @@ LeaderElection.prototype = { }); }, awaitLeadership: function awaitLeadership() { - if ( - /* _awaitLeadershipPromise */ + if ( /* _awaitLeadershipPromise */ !this._aLP) { this._aLP = _awaitLeadershipOnce(this); } - return this._aLP; }, - set onduplicate(fn) { this._dpL = fn; }, - die: function die() { var _this3 = this; - this._lstns.forEach(function (listener) { return _this3.broadcastChannel.removeEventListener('internal', listener); }); - this._lstns = []; - this._unl.forEach(function (uFn) { return uFn.remove(); }); - this._unl = []; - if (this.isLeader) { this.hasLeader = false; this.isLeader = false; } - this.isDead = true; return _sendMessage(this, 'death'); } }; + /** * @param leaderElector {LeaderElector} */ - function _awaitLeadershipOnce(leaderElector) { if (leaderElector.isLeader) { return _util.PROMISE_RESOLVED_VOID; } - return new Promise(function (res) { var resolved = false; - function finish() { if (resolved) { return; } - resolved = true; leaderElector.broadcastChannel.removeEventListener('internal', whenDeathListener); res(true); - } // try once now - + } + // try once now leaderElector.applyOnce().then(function () { if (leaderElector.isLeader) { finish(); } }); + /** * Try on fallbackInterval * @recursive */ - var tryOnFallBack = function tryOnFallBack() { return (0, _util.sleep)(leaderElector._options.fallbackInterval).then(function () { if (leaderElector.isDead || resolved) { return; } - if (leaderElector.isLeader) { finish(); } else { @@ -664,9 +592,9 @@ function _awaitLeadershipOnce(leaderElector) { } }); }; + tryOnFallBack(); - tryOnFallBack(); // try when other leader dies - + // try when other leader dies var whenDeathListener = function whenDeathListener(msg) { if (msg.context === 'leader' && msg.action === 'death') { leaderElector.hasLeader = false; @@ -677,17 +605,14 @@ function _awaitLeadershipOnce(leaderElector) { }); } }; - leaderElector.broadcastChannel.addEventListener('internal', whenDeathListener); - leaderElector._lstns.push(whenDeathListener); }); } + /** * sends and internal message over the broadcast-channel */ - - function _sendMessage(leaderElector, action) { var msgJson = { context: 'leader', @@ -696,21 +621,17 @@ function _sendMessage(leaderElector, action) { }; return leaderElector.broadcastChannel.postInternal(msgJson); } - function beLeader(leaderElector) { leaderElector.isLeader = true; leaderElector.hasLeader = true; var unloadFn = (0, _unload.add)(function () { return leaderElector.die(); }); - leaderElector._unl.push(unloadFn); - var isLeaderListener = function isLeaderListener(msg) { if (msg.context === 'leader' && msg.action === 'apply') { _sendMessage(leaderElector, 'tell'); } - if (msg.context === 'leader' && msg.action === 'tell' && !leaderElector._dpLC) { /** * another instance is also leader! @@ -721,49 +642,35 @@ function beLeader(leaderElector) { * @link https://github.com/pubkey/broadcast-channel/issues/385 */ leaderElector._dpLC = true; - leaderElector._dpL(); // message the lib user so the app can handle the problem - - _sendMessage(leaderElector, 'tell'); // ensure other leader also knows the problem - } }; leaderElector.broadcastChannel.addEventListener('internal', isLeaderListener); - leaderElector._lstns.push(isLeaderListener); - return _sendMessage(leaderElector, 'tell'); } - function fillOptionsWithDefaults(options, channel) { if (!options) options = {}; options = JSON.parse(JSON.stringify(options)); - if (!options.fallbackInterval) { options.fallbackInterval = 3000; } - if (!options.responseTime) { options.responseTime = channel.method.averageResponseTime(channel.options); } - return options; } - function createLeaderElection(channel, options) { if (channel._leaderElector) { throw new Error('BroadcastChannel already has a leader-elector'); } - options = fillOptionsWithDefaults(options, channel); var elector = new LeaderElection(channel, options); - channel._befC.push(function () { return elector.die(); }); - channel._leaderElector = elector; return elector; } @@ -771,59 +678,49 @@ function createLeaderElection(channel, options) { "use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); - var _typeof = require("@babel/runtime/helpers/typeof"); - Object.defineProperty(exports, "__esModule", { value: true }); exports.chooseMethod = chooseMethod; - var _native = _interopRequireDefault(require("./methods/native.js")); - var _indexedDb = _interopRequireDefault(require("./methods/indexed-db.js")); - var _localstorage = _interopRequireDefault(require("./methods/localstorage.js")); - var _simulate = _interopRequireDefault(require("./methods/simulate.js")); - - function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } - function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } - // the line below will be removed from es5/browser builds + // order is important -var METHODS = [_native["default"], // fastest +var METHODS = [_native["default"], +// fastest _indexedDb["default"], _localstorage["default"]]; - function chooseMethod(options) { - var chooseMethods = [].concat(options.methods, METHODS).filter(Boolean); // the line below will be removed from es5/browser builds + var chooseMethods = [].concat(options.methods, METHODS).filter(Boolean); + // the line below will be removed from es5/browser builds + // directly chosen if (options.type) { if (options.type === 'simulate') { // only use simulate-method if directly chosen return _simulate["default"]; } - var ret = chooseMethods.find(function (m) { return m.type === options.type; }); if (!ret) throw new Error('method-type ' + options.type + ' not found');else return ret; } + /** * if no webworker support is needed, * remove idb from the list so that localstorage is been chosen */ - - if (!options.webWorkerSupport) { chooseMethods = chooseMethods.filter(function (m) { return m.type !== 'idb'; }); } - var useMethod = chooseMethods.find(function (method) { return method.canBeUsed(); }); @@ -856,13 +753,9 @@ exports.postMessage = postMessage; exports.removeMessagesById = removeMessagesById; exports.type = void 0; exports.writeMessage = writeMessage; - var _util = require("../util.js"); - var _obliviousSet = require("oblivious-set"); - var _options = require("../options.js"); - /** * this method uses indexeddb to store the messages * There is currently no observerAPI for idb @@ -871,58 +764,54 @@ var _options = require("../options.js"); * When working on this, ensure to use these performance optimizations: * @link https://rxdb.info/slow-indexeddb.html */ + var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var DB_PREFIX = 'pubkey.broadcast-channel-0-'; var OBJECT_STORE_ID = 'messages'; + /** * Use relaxed durability for faster performance on all transactions. * @link https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/ */ - var TRANSACTION_SETTINGS = { durability: 'relaxed' }; exports.TRANSACTION_SETTINGS = TRANSACTION_SETTINGS; var type = 'idb'; exports.type = type; - function getIdb() { if (typeof indexedDB !== 'undefined') return indexedDB; - if (typeof window !== 'undefined') { if (typeof window.mozIndexedDB !== 'undefined') return window.mozIndexedDB; if (typeof window.webkitIndexedDB !== 'undefined') return window.webkitIndexedDB; if (typeof window.msIndexedDB !== 'undefined') return window.msIndexedDB; } - return false; } + /** * If possible, we should explicitly commit IndexedDB transactions * for better performance. * @link https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/ */ - - function commitIndexedDBTransaction(tx) { if (tx.commit) { tx.commit(); } } - function createDatabase(channelName) { - var IndexedDB = getIdb(); // create table + var IndexedDB = getIdb(); + // create table var dbName = DB_PREFIX + channelName; + /** * All IndexedDB databases are opened without version * because it is a bit faster, especially on firefox * @link http://nparashuram.com/IndexedDB/perf/#Open%20Database%20with%20version */ - var openRequest = IndexedDB.open(dbName); - openRequest.onupgradeneeded = function (ev) { var db = ev.target.result; db.createObjectStore(OBJECT_STORE_ID, { @@ -930,24 +819,21 @@ function createDatabase(channelName) { autoIncrement: true }); }; - var dbPromise = new Promise(function (res, rej) { openRequest.onerror = function (ev) { return rej(ev); }; - openRequest.onsuccess = function () { res(openRequest.result); }; }); return dbPromise; } + /** * writes the new message to the database * so other readers can find it */ - - function writeMessage(db, readerUuid, messageJson) { var time = new Date().getTime(); var writeObject = { @@ -960,17 +846,14 @@ function writeMessage(db, readerUuid, messageJson) { tx.oncomplete = function () { return res(); }; - tx.onerror = function (ev) { return rej(ev); }; - var objectStore = tx.objectStore(OBJECT_STORE_ID); objectStore.add(writeObject); commitIndexedDBTransaction(tx); }); } - function getAllMessages(db) { var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); @@ -978,10 +861,9 @@ function getAllMessages(db) { return new Promise(function (res) { objectStore.openCursor().onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { - ret.push(cursor.value); //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); - + ret.push(cursor.value); + //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); cursor["continue"](); } else { commitIndexedDBTransaction(tx); @@ -990,31 +872,28 @@ function getAllMessages(db) { }; }); } - function getMessagesHigherThan(db, lastCursorId) { var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); var ret = []; var keyRangeValue = IDBKeyRange.bound(lastCursorId + 1, Infinity); + /** * Optimization shortcut, * if getAll() can be used, do not use a cursor. * @link https://rxdb.info/slow-indexeddb.html */ - if (objectStore.getAll) { var getAllRequest = objectStore.getAll(keyRangeValue); return new Promise(function (res, rej) { getAllRequest.onerror = function (err) { return rej(err); }; - getAllRequest.onsuccess = function (e) { res(e.target.result); }; }); } - function openCursor() { // Occasionally Safari will fail on IDBKeyRange.bound, this // catches that error, having it open the cursor to the first @@ -1026,17 +905,13 @@ function getMessagesHigherThan(db, lastCursorId) { return objectStore.openCursor(); } } - return new Promise(function (res, rej) { var openCursorRequest = openCursor(); - openCursorRequest.onerror = function (err) { return rej(err); }; - openCursorRequest.onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { if (cursor.value.id < lastCursorId + 1) { cursor["continue"](lastCursorId + 1); @@ -1051,12 +926,10 @@ function getMessagesHigherThan(db, lastCursorId) { }; }); } - function removeMessagesById(channelState, ids) { if (channelState.closed) { return Promise.resolve([]); } - var tx = channelState.db.transaction(OBJECT_STORE_ID, 'readwrite', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); return Promise.all(ids.map(function (id) { @@ -1068,7 +941,6 @@ function removeMessagesById(channelState, ids) { }); })); } - function getOldMessages(db, ttl) { var olderThen = new Date().getTime() - ttl; var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS); @@ -1077,13 +949,11 @@ function getOldMessages(db, ttl) { return new Promise(function (res) { objectStore.openCursor().onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { var msgObk = cursor.value; - if (msgObk.time < olderThen) { - ret.push(msgObk); //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); - + ret.push(msgObk); + //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); cursor["continue"](); } else { // no more old messages, @@ -1097,7 +967,6 @@ function getOldMessages(db, ttl) { }; }); } - function cleanOldMessages(channelState) { return getOldMessages(channelState.db, channelState.options.idb.ttl).then(function (tooOld) { return removeMessagesById(channelState, tooOld.map(function (msg) { @@ -1105,7 +974,6 @@ function cleanOldMessages(channelState) { })); }); } - function create(channelName, options) { options = (0, _options.fillOptionsWithDefaults)(options); return createDatabase(channelName).then(function (db) { @@ -1115,7 +983,6 @@ function create(channelName, options) { channelName: channelName, options: options, uuid: (0, _util.randomToken)(), - /** * emittedMessagesIds * contains all messages that have been emitted before @@ -1128,30 +995,27 @@ function create(channelName, options) { readQueuePromises: [], db: db }; + /** * Handle abrupt closes that do not originate from db.close(). * This could happen, for example, if the underlying storage is * removed or if the user clears the database in the browser's * history preferences. */ - db.onclose = function () { state.closed = true; if (options.idb.onclose) options.idb.onclose(); }; + /** * if service-workers are used, * we have no 'storage'-event if they post a message, * therefore we also have to set an interval */ - - _readLoop(state); - return state; }); } - function _readLoop(state) { if (state.closed) return; readNewMessages(state).then(function () { @@ -1160,25 +1024,21 @@ function _readLoop(state) { return _readLoop(state); }); } - function _filterMessage(msgObj, state) { if (msgObj.uuid === state.uuid) return false; // send by own - if (state.eMIs.has(msgObj.id)) return false; // already emitted - if (msgObj.data.time < state.messagesCallbackTime) return false; // older then onMessageCallback - return true; } + /** * reads all new messages from the database and emits them */ - - function readNewMessages(state) { // channel already closed - if (state.closed) return _util.PROMISE_RESOLVED_VOID; // if no one is listening, we do not need to scan for new messages + if (state.closed) return _util.PROMISE_RESOLVED_VOID; + // if no one is listening, we do not need to scan for new messages if (!state.messagesCallback) return _util.PROMISE_RESOLVED_VOID; return getMessagesHigherThan(state.db, state.lastCursorId).then(function (newerMessages) { var useMessages = newerMessages @@ -1186,21 +1046,18 @@ function readNewMessages(state) { * there is a bug in iOS where the msgObj can be undefined some times * so we filter them out * @link https://github.com/pubkey/broadcast-channel/issues/19 - */ - .filter(function (msgObj) { + */.filter(function (msgObj) { return !!msgObj; }).map(function (msgObj) { if (msgObj.id > state.lastCursorId) { state.lastCursorId = msgObj.id; } - return msgObj; }).filter(function (msgObj) { return _filterMessage(msgObj, state); }).sort(function (msgObjA, msgObjB) { return msgObjA.time - msgObjB.time; }); // sort by time - useMessages.forEach(function (msgObj) { if (state.messagesCallback) { state.eMIs.add(msgObj.id); @@ -1210,12 +1067,10 @@ function readNewMessages(state) { return _util.PROMISE_RESOLVED_VOID; }); } - function close(channelState) { channelState.closed = true; channelState.db.close(); } - function postMessage(channelState, messageJson) { channelState.writeBlockPromise = channelState.writeBlockPromise.then(function () { return writeMessage(channelState.db, channelState.uuid, messageJson); @@ -1227,27 +1082,21 @@ function postMessage(channelState, messageJson) { }); return channelState.writeBlockPromise; } - function onMessage(channelState, fn, time) { channelState.messagesCallbackTime = time; channelState.messagesCallback = fn; readNewMessages(channelState); } - function canBeUsed() { var idb = getIdb(); - if (!idb) { return false; } - return true; } - function averageResponseTime(options) { return options.idb.fallbackInterval * 2; } - var _default = { create: create, close: close, @@ -1278,13 +1127,9 @@ exports.postMessage = postMessage; exports.removeStorageEventListener = removeStorageEventListener; exports.storageKey = storageKey; exports.type = void 0; - var _obliviousSet = require("oblivious-set"); - var _options = require("../options.js"); - var _util = require("../util.js"); - /** * A localStorage-only method which uses localstorage and its 'storage'-event * This does not work inside of webworkers because they have no access to locastorage @@ -1292,41 +1137,38 @@ var _util = require("../util.js"); * @link https://caniuse.com/#feat=namevalue-storage * @link https://caniuse.com/#feat=indexeddb */ + var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var KEY_PREFIX = 'pubkey.broadcastChannel-'; var type = 'localstorage'; + /** * copied from crosstab * @link https://github.com/tejacques/crosstab/blob/master/src/crosstab.js#L32 */ - exports.type = type; - function getLocalStorage() { var localStorage; if (typeof window === 'undefined') return null; - try { localStorage = window.localStorage; localStorage = window['ie8-eventlistener/storage'] || window.localStorage; - } catch (e) {// New versions of Firefox throw a Security exception + } catch (e) { + // New versions of Firefox throw a Security exception // if cookies are disabled. See // https://bugzilla.mozilla.org/show_bug.cgi?id=1028153 } - return localStorage; } - function storageKey(channelName) { return KEY_PREFIX + channelName; } + /** * writes the new message to the storage * and fires the storage-event so other readers can find it */ - - function postMessage(channelState, messageJson) { return new Promise(function (res) { (0, _util.sleep)().then(function () { @@ -1339,12 +1181,12 @@ function postMessage(channelState, messageJson) { }; var value = JSON.stringify(writeObj); getLocalStorage().setItem(key, value); + /** * StorageEvent does not fire the 'storage' event * in the window that changes the state of the local storage. * So we fire it manually */ - var ev = document.createEvent('Event'); ev.initEvent('storage', true, true); ev.key = key; @@ -1354,52 +1196,42 @@ function postMessage(channelState, messageJson) { }); }); } - function addStorageEventListener(channelName, fn) { var key = storageKey(channelName); - var listener = function listener(ev) { if (ev.key === key) { fn(JSON.parse(ev.newValue)); } }; - window.addEventListener('storage', listener); return listener; } - function removeStorageEventListener(listener) { window.removeEventListener('storage', listener); } - function create(channelName, options) { options = (0, _options.fillOptionsWithDefaults)(options); - if (!canBeUsed()) { throw new Error('BroadcastChannel: localstorage cannot be used'); } - var uuid = (0, _util.randomToken)(); + /** * eMIs * contains all messages that have been emitted before * @type {ObliviousSet} */ - var eMIs = new _obliviousSet.ObliviousSet(options.localstorage.removeTimeout); var state = { channelName: channelName, uuid: uuid, eMIs: eMIs // emittedMessagesIds - }; + state.listener = addStorageEventListener(channelName, function (msgObj) { if (!state.messagesCallback) return; // no listener - if (msgObj.uuid === uuid) return; // own message - if (!msgObj.token || eMIs.has(msgObj.token)) return; // already emitted - if (msgObj.data.time && msgObj.data.time < state.messagesCallbackTime) return; // too old eMIs.add(msgObj.token); @@ -1407,20 +1239,16 @@ function create(channelName, options) { }); return state; } - function close(channelState) { removeStorageEventListener(channelState.listener); } - function onMessage(channelState, fn, time) { channelState.messagesCallbackTime = time; channelState.messagesCallback = fn; } - function canBeUsed() { var ls = getLocalStorage(); if (!ls) return false; - try { var key = '__broadcastchannel_check'; ls.setItem(key, 'works'); @@ -1431,22 +1259,17 @@ function canBeUsed() { // https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API#Private_Browsing_Incognito_modes return false; } - return true; } - function averageResponseTime() { var defaultTime = 120; var userAgent = navigator.userAgent.toLowerCase(); - if (userAgent.includes('safari') && !userAgent.includes('chrome')) { // safari is much slower so this time is higher return defaultTime * 2; } - return defaultTime; } - var _default = { create: create, close: close, @@ -1472,20 +1295,16 @@ exports.microSeconds = exports["default"] = void 0; exports.onMessage = onMessage; exports.postMessage = postMessage; exports.type = void 0; - var _util = require("../util.js"); - var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var type = 'native'; exports.type = type; - function create(channelName) { var state = { messagesCallback: null, bc: new BroadcastChannel(channelName), subFns: [] // subscriberFunctions - }; state.bc.onmessage = function (msg) { @@ -1493,15 +1312,12 @@ function create(channelName) { state.messagesCallback(msg.data); } }; - return state; } - function close(channelState) { channelState.bc.close(); channelState.subFns = []; } - function postMessage(channelState, messageJson) { try { channelState.bc.postMessage(messageJson, false); @@ -1510,31 +1326,25 @@ function postMessage(channelState, messageJson) { return Promise.reject(err); } } - function onMessage(channelState, fn) { channelState.messagesCallback = fn; } - function canBeUsed() { if (typeof window === 'undefined') { return false; } - if (typeof BroadcastChannel === 'function') { if (BroadcastChannel._pubkey) { throw new Error('BroadcastChannel: Do not overwrite window.BroadcastChannel with this module, this is not a polyfill'); } - return true; } else { return false; } } - function averageResponseTime() { return 150; } - var _default = { create: create, close: close, @@ -1560,15 +1370,12 @@ exports.microSeconds = exports["default"] = void 0; exports.onMessage = onMessage; exports.postMessage = postMessage; exports.type = void 0; - var _util = require("../util.js"); - var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var type = 'simulate'; exports.type = type; var SIMULATE_CHANNELS = new Set(); - function create(channelName) { var state = { name: channelName, @@ -1577,11 +1384,9 @@ function create(channelName) { SIMULATE_CHANNELS.add(state); return state; } - function close(channelState) { SIMULATE_CHANNELS["delete"](channelState); } - function postMessage(channelState, messageJson) { return new Promise(function (res) { return setTimeout(function () { @@ -1599,19 +1404,15 @@ function postMessage(channelState, messageJson) { }, 5); }); } - function onMessage(channelState, fn) { channelState.messagesCallback = fn; } - function canBeUsed() { return true; } - function averageResponseTime() { return 5; } - var _default = { create: create, close: close, @@ -1630,33 +1431,35 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.fillOptionsWithDefaults = fillOptionsWithDefaults; - function fillOptionsWithDefaults() { var originalOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var options = JSON.parse(JSON.stringify(originalOptions)); // main + var options = JSON.parse(JSON.stringify(originalOptions)); - if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true; // indexed-db - - if (!options.idb) options.idb = {}; // after this time the messages get deleted + // main + if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true; + // indexed-db + if (!options.idb) options.idb = {}; + // after this time the messages get deleted if (!options.idb.ttl) options.idb.ttl = 1000 * 45; - if (!options.idb.fallbackInterval) options.idb.fallbackInterval = 150; // handles abrupt db onclose events. - - if (originalOptions.idb && typeof originalOptions.idb.onclose === 'function') options.idb.onclose = originalOptions.idb.onclose; // localstorage + if (!options.idb.fallbackInterval) options.idb.fallbackInterval = 150; + // handles abrupt db onclose events. + if (originalOptions.idb && typeof originalOptions.idb.onclose === 'function') options.idb.onclose = originalOptions.idb.onclose; + // localstorage if (!options.localstorage) options.localstorage = {}; - if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60; // custom methods + if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60; - if (originalOptions.methods) options.methods = originalOptions.methods; // node + // custom methods + if (originalOptions.methods) options.methods = originalOptions.methods; + // node if (!options.node) options.node = {}; if (!options.node.ttl) options.node.ttl = 1000 * 60 * 2; // 2 minutes; - /** * On linux use 'ulimit -Hn' to get the limit of open files. * On ubuntu this was 4096 for me, so we use half of that as maxParallelWrites default. */ - if (!options.node.maxParallelWrites) options.node.maxParallelWrites = 2048; if (typeof options.node.useFastPath === 'undefined') options.node.useFastPath = true; return options; @@ -1673,7 +1476,6 @@ exports.microSeconds = microSeconds; exports.randomInt = randomInt; exports.randomToken = randomToken; exports.sleep = sleep; - /** * returns true if the given object is a promise */ @@ -1684,14 +1486,12 @@ function isPromise(obj) { return false; } } - var PROMISE_RESOLVED_FALSE = Promise.resolve(false); exports.PROMISE_RESOLVED_FALSE = PROMISE_RESOLVED_FALSE; var PROMISE_RESOLVED_TRUE = Promise.resolve(true); exports.PROMISE_RESOLVED_TRUE = PROMISE_RESOLVED_TRUE; var PROMISE_RESOLVED_VOID = Promise.resolve(); exports.PROMISE_RESOLVED_VOID = PROMISE_RESOLVED_VOID; - function sleep(time, resolveWith) { if (!time) time = 0; return new Promise(function (res) { @@ -1700,21 +1500,19 @@ function sleep(time, resolveWith) { }, time); }); } - function randomInt(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } + /** * https://stackoverflow.com/a/8084248 */ - - function randomToken() { return Math.random().toString(36).substring(2); } - var lastMs = 0; var additional = 0; + /** * returns the current time in micro-seconds, * WARNING: This is a pseudo-function @@ -1722,10 +1520,8 @@ var additional = 0; * This is enough in browsers, and this function will not be used in nodejs. * The main reason for this hack is to ensure that BroadcastChannel behaves equal to production when it is used in fast-running unit tests. */ - function microSeconds() { var ms = new Date().getTime(); - if (ms === lastMs) { additional++; return ms * 1000 + additional; @@ -1788,34 +1584,28 @@ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { reject(error); return; } - if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } - function _asyncToGenerator(fn) { return function () { var self = this, - args = arguments; + args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); - function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } - function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } - _next(undefined); }); }; } - module.exports = _asyncToGenerator, module.exports.__esModule = true, module.exports["default"] = module.exports; },{}],15:[function(require,module,exports){ function _interopRequireDefault(obj) { @@ -1823,26 +1613,24 @@ function _interopRequireDefault(obj) { "default": obj }; } - module.exports = _interopRequireDefault, module.exports.__esModule = true, module.exports["default"] = module.exports; },{}],16:[function(require,module,exports){ var _typeof = require("./typeof.js")["default"]; - function _regeneratorRuntime() { - "use strict"; - /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ - + "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ module.exports = _regeneratorRuntime = function _regeneratorRuntime() { return exports; }, module.exports.__esModule = true, module.exports["default"] = module.exports; var exports = {}, - Op = Object.prototype, - hasOwn = Op.hasOwnProperty, - $Symbol = "function" == typeof Symbol ? Symbol : {}, - iteratorSymbol = $Symbol.iterator || "@@iterator", - asyncIteratorSymbol = $Symbol.asyncIterator || "@@asyncIterator", - toStringTagSymbol = $Symbol.toStringTag || "@@toStringTag"; - + Op = Object.prototype, + hasOwn = Op.hasOwnProperty, + defineProperty = Object.defineProperty || function (obj, key, desc) { + obj[key] = desc.value; + }, + $Symbol = "function" == typeof Symbol ? Symbol : {}, + iteratorSymbol = $Symbol.iterator || "@@iterator", + asyncIteratorSymbol = $Symbol.asyncIterator || "@@asyncIterator", + toStringTagSymbol = $Symbol.toStringTag || "@@toStringTag"; function define(obj, key, value) { return Object.defineProperty(obj, key, { value: value, @@ -1851,7 +1639,6 @@ function _regeneratorRuntime() { writable: !0 }), obj[key]; } - try { define({}, ""); } catch (err) { @@ -1859,54 +1646,14 @@ function _regeneratorRuntime() { return obj[key] = value; }; } - function wrap(innerFn, outerFn, self, tryLocsList) { var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator, - generator = Object.create(protoGenerator.prototype), - context = new Context(tryLocsList || []); - return generator._invoke = function (innerFn, self, context) { - var state = "suspendedStart"; - return function (method, arg) { - if ("executing" === state) throw new Error("Generator is already running"); - - if ("completed" === state) { - if ("throw" === method) throw arg; - return doneResult(); - } - - for (context.method = method, context.arg = arg;;) { - var delegate = context.delegate; - - if (delegate) { - var delegateResult = maybeInvokeDelegate(delegate, context); - - if (delegateResult) { - if (delegateResult === ContinueSentinel) continue; - return delegateResult; - } - } - - if ("next" === context.method) context.sent = context._sent = context.arg;else if ("throw" === context.method) { - if ("suspendedStart" === state) throw state = "completed", context.arg; - context.dispatchException(context.arg); - } else "return" === context.method && context.abrupt("return", context.arg); - state = "executing"; - var record = tryCatch(innerFn, self, context); - - if ("normal" === record.type) { - if (state = context.done ? "completed" : "suspendedYield", record.arg === ContinueSentinel) continue; - return { - value: record.arg, - done: context.done - }; - } - - "throw" === record.type && (state = "completed", context.method = "throw", context.arg = record.arg); - } - }; - }(innerFn, self, context), generator; + generator = Object.create(protoGenerator.prototype), + context = new Context(tryLocsList || []); + return defineProperty(generator, "_invoke", { + value: makeInvokeMethod(innerFn, self, context) + }), generator; } - function tryCatch(fn, obj, arg) { try { return { @@ -1920,25 +1667,19 @@ function _regeneratorRuntime() { }; } } - exports.wrap = wrap; var ContinueSentinel = {}; - function Generator() {} - function GeneratorFunction() {} - function GeneratorFunctionPrototype() {} - var IteratorPrototype = {}; define(IteratorPrototype, iteratorSymbol, function () { return this; }); var getProto = Object.getPrototypeOf, - NativeIteratorPrototype = getProto && getProto(getProto(values([]))); + NativeIteratorPrototype = getProto && getProto(getProto(values([]))); NativeIteratorPrototype && NativeIteratorPrototype !== Op && hasOwn.call(NativeIteratorPrototype, iteratorSymbol) && (IteratorPrototype = NativeIteratorPrototype); var Gp = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(IteratorPrototype); - function defineIteratorMethods(prototype) { ["next", "throw", "return"].forEach(function (method) { define(prototype, method, function (arg) { @@ -1946,14 +1687,12 @@ function _regeneratorRuntime() { }); }); } - function AsyncIterator(generator, PromiseImpl) { function invoke(method, arg, resolve, reject) { var record = tryCatch(generator[method], generator, arg); - if ("throw" !== record.type) { var result = record.arg, - value = result.value; + value = result.value; return value && "object" == _typeof(value) && hasOwn.call(value, "__await") ? PromiseImpl.resolve(value.__await).then(function (value) { invoke("next", value, resolve, reject); }, function (err) { @@ -1964,92 +1703,116 @@ function _regeneratorRuntime() { return invoke("throw", error, resolve, reject); }); } - reject(record.arg); } - var previousPromise; - - this._invoke = function (method, arg) { - function callInvokeWithMethodAndArg() { - return new PromiseImpl(function (resolve, reject) { - invoke(method, arg, resolve, reject); - }); + defineProperty(this, "_invoke", { + value: function value(method, arg) { + function callInvokeWithMethodAndArg() { + return new PromiseImpl(function (resolve, reject) { + invoke(method, arg, resolve, reject); + }); + } + return previousPromise = previousPromise ? previousPromise.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); + } + }); + } + function makeInvokeMethod(innerFn, self, context) { + var state = "suspendedStart"; + return function (method, arg) { + if ("executing" === state) throw new Error("Generator is already running"); + if ("completed" === state) { + if ("throw" === method) throw arg; + return doneResult(); + } + for (context.method = method, context.arg = arg;;) { + var delegate = context.delegate; + if (delegate) { + var delegateResult = maybeInvokeDelegate(delegate, context); + if (delegateResult) { + if (delegateResult === ContinueSentinel) continue; + return delegateResult; + } + } + if ("next" === context.method) context.sent = context._sent = context.arg;else if ("throw" === context.method) { + if ("suspendedStart" === state) throw state = "completed", context.arg; + context.dispatchException(context.arg); + } else "return" === context.method && context.abrupt("return", context.arg); + state = "executing"; + var record = tryCatch(innerFn, self, context); + if ("normal" === record.type) { + if (state = context.done ? "completed" : "suspendedYield", record.arg === ContinueSentinel) continue; + return { + value: record.arg, + done: context.done + }; + } + "throw" === record.type && (state = "completed", context.method = "throw", context.arg = record.arg); } - - return previousPromise = previousPromise ? previousPromise.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); }; } - function maybeInvokeDelegate(delegate, context) { var method = delegate.iterator[context.method]; - if (undefined === method) { if (context.delegate = null, "throw" === context.method) { if (delegate.iterator["return"] && (context.method = "return", context.arg = undefined, maybeInvokeDelegate(delegate, context), "throw" === context.method)) return ContinueSentinel; context.method = "throw", context.arg = new TypeError("The iterator does not provide a 'throw' method"); } - return ContinueSentinel; } - var record = tryCatch(method, delegate.iterator, context.arg); if ("throw" === record.type) return context.method = "throw", context.arg = record.arg, context.delegate = null, ContinueSentinel; var info = record.arg; return info ? info.done ? (context[delegate.resultName] = info.value, context.next = delegate.nextLoc, "return" !== context.method && (context.method = "next", context.arg = undefined), context.delegate = null, ContinueSentinel) : info : (context.method = "throw", context.arg = new TypeError("iterator result is not an object"), context.delegate = null, ContinueSentinel); } - function pushTryEntry(locs) { var entry = { tryLoc: locs[0] }; 1 in locs && (entry.catchLoc = locs[1]), 2 in locs && (entry.finallyLoc = locs[2], entry.afterLoc = locs[3]), this.tryEntries.push(entry); } - function resetTryEntry(entry) { var record = entry.completion || {}; record.type = "normal", delete record.arg, entry.completion = record; } - function Context(tryLocsList) { this.tryEntries = [{ tryLoc: "root" }], tryLocsList.forEach(pushTryEntry, this), this.reset(!0); } - function values(iterable) { if (iterable) { var iteratorMethod = iterable[iteratorSymbol]; if (iteratorMethod) return iteratorMethod.call(iterable); if ("function" == typeof iterable.next) return iterable; - if (!isNaN(iterable.length)) { var i = -1, - next = function next() { - for (; ++i < iterable.length;) { - if (hasOwn.call(iterable, i)) return next.value = iterable[i], next.done = !1, next; - } - - return next.value = undefined, next.done = !0, next; - }; - + next = function next() { + for (; ++i < iterable.length;) { + if (hasOwn.call(iterable, i)) return next.value = iterable[i], next.done = !1, next; + } + return next.value = undefined, next.done = !0, next; + }; return next.next = next; } } - return { next: doneResult }; } - function doneResult() { return { value: undefined, done: !0 }; } - - return GeneratorFunction.prototype = GeneratorFunctionPrototype, define(Gp, "constructor", GeneratorFunctionPrototype), define(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, toStringTagSymbol, "GeneratorFunction"), exports.isGeneratorFunction = function (genFun) { + return GeneratorFunction.prototype = GeneratorFunctionPrototype, defineProperty(Gp, "constructor", { + value: GeneratorFunctionPrototype, + configurable: !0 + }), defineProperty(GeneratorFunctionPrototype, "constructor", { + value: GeneratorFunction, + configurable: !0 + }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, toStringTagSymbol, "GeneratorFunction"), exports.isGeneratorFunction = function (genFun) { var ctor = "function" == typeof genFun && genFun.constructor; return !!ctor && (ctor === GeneratorFunction || "GeneratorFunction" === (ctor.displayName || ctor.name)); }, exports.mark = function (genFun) { @@ -2070,19 +1833,17 @@ function _regeneratorRuntime() { return this; }), define(Gp, "toString", function () { return "[object Generator]"; - }), exports.keys = function (object) { - var keys = []; - + }), exports.keys = function (val) { + var object = Object(val), + keys = []; for (var key in object) { keys.push(key); } - return keys.reverse(), function next() { for (; keys.length;) { var key = keys.pop(); if (key in object) return next.value = key, next.done = !1, next; } - return next.done = !0, next; }; }, exports.values = values, Context.prototype = { @@ -2101,20 +1862,16 @@ function _regeneratorRuntime() { dispatchException: function dispatchException(exception) { if (this.done) throw exception; var context = this; - function handle(loc, caught) { return record.type = "throw", record.arg = exception, context.next = loc, caught && (context.method = "next", context.arg = undefined), !!caught; } - for (var i = this.tryEntries.length - 1; i >= 0; --i) { var entry = this.tryEntries[i], - record = entry.completion; + record = entry.completion; if ("root" === entry.tryLoc) return handle("end"); - if (entry.tryLoc <= this.prev) { var hasCatch = hasOwn.call(entry, "catchLoc"), - hasFinally = hasOwn.call(entry, "finallyLoc"); - + hasFinally = hasOwn.call(entry, "finallyLoc"); if (hasCatch && hasFinally) { if (this.prev < entry.catchLoc) return handle(entry.catchLoc, !0); if (this.prev < entry.finallyLoc) return handle(entry.finallyLoc); @@ -2130,13 +1887,11 @@ function _regeneratorRuntime() { abrupt: function abrupt(type, arg) { for (var i = this.tryEntries.length - 1; i >= 0; --i) { var entry = this.tryEntries[i]; - if (entry.tryLoc <= this.prev && hasOwn.call(entry, "finallyLoc") && this.prev < entry.finallyLoc) { var finallyEntry = entry; break; } } - finallyEntry && ("break" === type || "continue" === type) && finallyEntry.tryLoc <= arg && arg <= finallyEntry.finallyLoc && (finallyEntry = null); var record = finallyEntry ? finallyEntry.completion : {}; return record.type = type, record.arg = arg, finallyEntry ? (this.method = "next", this.next = finallyEntry.finallyLoc, ContinueSentinel) : this.complete(record); @@ -2154,19 +1909,15 @@ function _regeneratorRuntime() { "catch": function _catch(tryLoc) { for (var i = this.tryEntries.length - 1; i >= 0; --i) { var entry = this.tryEntries[i]; - if (entry.tryLoc === tryLoc) { var record = entry.completion; - if ("throw" === record.type) { var thrown = record.arg; resetTryEntry(entry); } - return thrown; } } - throw new Error("illegal catch attempt"); }, delegateYield: function delegateYield(iterable, resultName, nextLoc) { @@ -2178,7 +1929,6 @@ function _regeneratorRuntime() { } }, exports; } - module.exports = _regeneratorRuntime, module.exports.__esModule = true, module.exports["default"] = module.exports; },{"./typeof.js":17}],17:[function(require,module,exports){ function _typeof(obj) { @@ -2190,7 +1940,6 @@ function _typeof(obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, module.exports.__esModule = true, module.exports["default"] = module.exports), _typeof(obj); } - module.exports = _typeof, module.exports.__esModule = true, module.exports["default"] = module.exports; },{}],18:[function(require,module,exports){ // TODO(Babel 8): Remove this file. @@ -11875,6 +11624,7 @@ var runtime = (function (exports) { var Op = Object.prototype; var hasOwn = Op.hasOwnProperty; + var defineProperty = Object.defineProperty || function (obj, key, desc) { obj[key] = desc.value; }; var undefined; // More compressible than void 0. var $Symbol = typeof Symbol === "function" ? Symbol : {}; var iteratorSymbol = $Symbol.iterator || "@@iterator"; @@ -11907,7 +11657,7 @@ var runtime = (function (exports) { // The ._invoke method unifies the implementations of the .next, // .throw, and .return methods. - generator._invoke = makeInvokeMethod(innerFn, self, context); + defineProperty(generator, "_invoke", { value: makeInvokeMethod(innerFn, self, context) }); return generator; } @@ -11968,8 +11718,12 @@ var runtime = (function (exports) { var Gp = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(IteratorPrototype); GeneratorFunction.prototype = GeneratorFunctionPrototype; - define(Gp, "constructor", GeneratorFunctionPrototype); - define(GeneratorFunctionPrototype, "constructor", GeneratorFunction); + defineProperty(Gp, "constructor", { value: GeneratorFunctionPrototype, configurable: true }); + defineProperty( + GeneratorFunctionPrototype, + "constructor", + { value: GeneratorFunction, configurable: true } + ); GeneratorFunction.displayName = define( GeneratorFunctionPrototype, toStringTagSymbol, @@ -12079,7 +11833,7 @@ var runtime = (function (exports) { // Define the unified helper method that is used to implement .next, // .throw, and .return (see defineIteratorMethods). - this._invoke = enqueue; + defineProperty(this, "_invoke", { value: enqueue }); } defineIteratorMethods(AsyncIterator.prototype); @@ -12317,7 +12071,8 @@ var runtime = (function (exports) { this.reset(true); } - exports.keys = function(object) { + exports.keys = function(val) { + var object = Object(val); var keys = []; for (var key in object) { keys.push(key); @@ -12727,28 +12482,20 @@ function getSize() { "use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); - var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); - var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); - var _util = require("./util.js"); - /* eslint-disable */ - /** * used in docs/e2e.html */ require('@babel/polyfill'); - var _require = require('../../'), - BroadcastChannel = _require.BroadcastChannel; - + BroadcastChannel = _require.BroadcastChannel; var _require2 = require('async-test-util'), - wait = _require2.wait, - randomNumber = _require2.randomNumber, - randomBoolean = _require2.randomBoolean; - + wait = _require2.wait, + randomNumber = _require2.randomNumber, + randomBoolean = _require2.randomBoolean; function run() { console.log('run()'); console.log('navigator.userAgent: ' + navigator.userAgent); @@ -12756,21 +12503,20 @@ function run() { if (!methodType || methodType === '' || methodType === 'default') methodType = undefined; console.log('methodType: ' + methodType); var autoStart = (0, _util.getParameterByName)('autoStart'); - console.log('autoStart: ' + autoStart); // set select-input + console.log('autoStart: ' + autoStart); + // set select-input var selectEl = document.getElementById('method-type-select'); - selectEl.onchange = function (ev) { var newValue = selectEl.value; var newUrl = location.origin + location.pathname + '?methodType=' + newValue; location = newUrl; }; - if (methodType) { selectEl.value = methodType; - } // do not increase this too much because it will cause a timeout in the CI - + } + // do not increase this too much because it will cause a timeout in the CI var TEST_MESSAGES = 25; var body = document.getElementById('body'); var msgContainer = document.getElementById('messages'); @@ -12785,23 +12531,21 @@ function run() { type: methodType }); document.getElementById('method').innerHTML = channel.type; + /** * to measure the speed, we: * 1. send message * 2. wait until iframe and worker answers * 3. repeat from 1. for TEST_MESSAGES times */ - var messagesSend = 0; var answerPool = {}; var useWorker = false; - function gotAllAnswers(answerPool) { if (!answerPool.iframe) return false; if (useWorker && !answerPool.worker) return false; return true; } - window.startBroadcastChannel = /*#__PURE__*/(0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee() { var rand, worker; return _regenerator["default"].wrap(function _callee$(_context) { @@ -12811,12 +12555,10 @@ function run() { console.log('window.startBroadcastChannel()'); stateContainer.innerHTML = 'running..'; rand = new Date().getTime(); - channel.onmessage = function (msg) { answerPool[msg.from] = msg; var textnode = document.createTextNode(JSON.stringify(msg) + '
'); msgContainer.appendChild(textnode); - if (gotAllAnswers(answerPool)) { answerPool = {}; // reset @@ -12839,9 +12581,9 @@ function run() { }); } } - }; // load iframe - + }; + // load iframe iframeEl.src = './iframe.html?channelName=' + channel.name + '&methodType=' + channel.type + '&t=' + rand; _context.next = 7; return new Promise(function (res) { @@ -12849,22 +12591,19 @@ function run() { return res(); }; }); - case 7: - console.log('main: Iframe has loaded'); // spawn web-worker if possible + console.log('main: Iframe has loaded'); + // spawn web-worker if possible if (!(channel.type !== 'localstorage' && typeof window.Worker === 'function')) { _context.next = 14; break; } - useWorker = true; worker = new Worker('worker.js?t=' + rand); - worker.onerror = function (event) { console.error('worker: ' + event.message + " (" + event.filename + ":" + event.lineno + ")"); }; - _context.next = 14; return new Promise(function (res) { worker.addEventListener('message', function (e) { @@ -12882,7 +12621,6 @@ function run() { } }); }); - case 14: console.log('========== START SENDING MESSAGES ' + channel.type); startTime = new Date().getTime(); @@ -12891,15 +12629,15 @@ function run() { step: 0 }); console.log('main: message send (0)'); - case 18: case "end": return _context.stop(); } } }, _callee); - })); // LEADER-ELECTION + })); + // LEADER-ELECTION window.startLeaderElection = /*#__PURE__*/(0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee2() { var FRAMES_COUNT, rand, frameSrc, leaderIframes, leaderFramesCache, amountTime; return _regenerator["default"].wrap(function _callee2$(_context2) { @@ -12912,14 +12650,12 @@ function run() { rand = new Date().getTime(); frameSrc = './leader-iframe.html?channelName=' + channel.name + '&methodType=' + channel.type + '&t=' + rand; leaderIframes = document.getElementById('leader-iframes'); // create iframes - leaderFramesCache = new Array(FRAMES_COUNT).fill(0).map(function () { var ifrm = document.createElement('iframe'); ifrm.setAttribute('src', frameSrc); leaderIframes.appendChild(ifrm); return ifrm; }); // wait until all iframes have loaded - _context2.next = 9; return Promise.all(leaderFramesCache.map(function (iframe) { return new Promise(function (res) { @@ -12928,34 +12664,29 @@ function run() { }; }); })); - case 9: startTime = new Date().getTime(); + /** * remove the leader-iframe until no iframe is left */ - case 10: if (!(leaderFramesCache.length > 0)) { _context2.next = 16; break; } - _context2.next = 13; return removeLeaderIframe(leaderFramesCache); - case 13: leaderFramesCache = _context2.sent; _context2.next = 10; break; - case 16: // done body.style.backgroundColor = 'green'; stateContainer.innerHTML = 'SUCCESS'; amountTime = new Date().getTime() - startTime; document.getElementById('time-amount').innerHTML = amountTime + 'ms'; - case 20: case "end": return _context2.stop(); @@ -12963,7 +12694,6 @@ function run() { } }, _callee2); })); - var removeLeaderIframe = /*#__PURE__*/function () { var _ref3 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee3(leaderFramesCache) { var leaders; @@ -12975,29 +12705,24 @@ function run() { var boxText = frame.contentDocument.getElementById('box').innerHTML; return boxText === 'Leader'; }); - if (!(leaders.length === 0)) { _context3.next = 3; break; } - return _context3.abrupt("return", new Promise(function (res) { return setTimeout(function () { res(leaderFramesCache); }, 50); })); - case 3: if (leaders.length > 1) { console.error('LeaderElection: There is more then one leader!'); - } // remove iframe - - + } + // remove iframe leaders[0].parentNode.removeChild(leaders[0]); return _context3.abrupt("return", leaderFramesCache.filter(function (f) { return f !== leaders[0]; })); - case 6: case "end": return _context3.stop(); @@ -13005,13 +12730,12 @@ function run() { } }, _callee3); })); - return function removeLeaderIframe(_x) { return _ref3.apply(this, arguments); }; - }(); // Worker test - + }(); + // Worker test window.startWorkerTest = /*#__PURE__*/(0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee5() { var worker, t, perRun, k, done, amountTime; return _regenerator["default"].wrap(function _callee5$(_context5) { @@ -13019,20 +12743,18 @@ function run() { switch (_context5.prev = _context5.next) { case 0: console.log('window.startWorkerTest()'); - stateContainer.innerHTML = 'running..'; // spawn web-worker + stateContainer.innerHTML = 'running..'; + // spawn web-worker if (!(channel.type !== 'localstorage' && typeof window.Worker === 'function')) { _context5.next = 8; break; } - useWorker = true; worker = new Worker('worker.js?t=' + new Date().getTime()); - worker.onerror = function (event) { console.error('worker: ' + event.message + " (" + event.filename + ":" + event.lineno + ")"); }; - _context5.next = 8; return new Promise(function (res) { worker.addEventListener('message', function (e) { @@ -13050,7 +12772,6 @@ function run() { } }); }); - case 8: console.log('========== START SENDING MESSAGES ' + channel.type); startTime = new Date().getTime(); @@ -13058,13 +12779,11 @@ function run() { perRun = 100; k = 0; done = 0; - case 14: if (!(t > 0)) { _context5.next = 20; break; } - t--; _context5.next = 18; return Promise.all(new Array(perRun).fill(0).map( /*#__PURE__*/(0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee4() { @@ -13077,10 +12796,8 @@ function run() { _context4.next = 3; break; } - _context4.next = 3; return wait(randomNumber(10, 150)); - case 3: msgId = 'worker-test-' + startTime + '-' + k++; waitForResponsePromise = new Promise(function (res) { @@ -13094,7 +12811,6 @@ function run() { res(); } }; - channel.addEventListener('message', listener); }); channel.postMessage({ @@ -13113,7 +12829,6 @@ function run() { throw new Error(errorMessage); } }); - case 8: case "end": return _context4.stop(); @@ -13121,17 +12836,14 @@ function run() { } }, _callee4); })))); - case 18: _context5.next = 14; break; - case 20: body.style.backgroundColor = 'green'; stateContainer.innerHTML = 'SUCCESS'; amountTime = new Date().getTime() - startTime; document.getElementById('time-amount').innerHTML = amountTime + 'ms'; - case 24: case "end": return _context5.stop(); @@ -13139,14 +12851,11 @@ function run() { } }, _callee5); })); - if (autoStart && autoStart !== '') { window[autoStart](); } } - ; - try { run(); } catch (error) { @@ -13160,8 +12869,8 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.getParameterByName = getParameterByName; - /* eslint no-useless-escape: "off" */ + // https://stackoverflow.com/a/901144/3443137 function getParameterByName(name, url) { if (!url) url = window.location.href; diff --git a/docs/iframe.js b/docs/iframe.js index 3dc97c16..c1dff4a7 100644 --- a/docs/iframe.js +++ b/docs/iframe.js @@ -7,13 +7,9 @@ Object.defineProperty(exports, "__esModule", { exports.OPEN_BROADCAST_CHANNELS = exports.BroadcastChannel = void 0; exports.clearNodeFolder = clearNodeFolder; exports.enforceOptions = enforceOptions; - var _util = require("./util.js"); - var _methodChooser = require("./method-chooser.js"); - var _options = require("./options.js"); - /** * Contains all open channels, * used in tests to ensure everything is closed. @@ -21,77 +17,73 @@ var _options = require("./options.js"); var OPEN_BROADCAST_CHANNELS = new Set(); exports.OPEN_BROADCAST_CHANNELS = OPEN_BROADCAST_CHANNELS; var lastId = 0; - var BroadcastChannel = function BroadcastChannel(name, options) { // identifier of the channel to debug stuff this.id = lastId++; OPEN_BROADCAST_CHANNELS.add(this); this.name = name; - if (ENFORCED_OPTIONS) { options = ENFORCED_OPTIONS; } - this.options = (0, _options.fillOptionsWithDefaults)(options); - this.method = (0, _methodChooser.chooseMethod)(this.options); // isListening + this.method = (0, _methodChooser.chooseMethod)(this.options); + // isListening this._iL = false; + /** * _onMessageListener * setting onmessage twice, * will overwrite the first listener */ - this._onML = null; + /** * _addEventListeners */ - this._addEL = { message: [], internal: [] }; + /** * Unsend message promises * where the sending is still in progress * @type {Set} */ - this._uMP = new Set(); + /** * _beforeClose * array of promises that will be awaited * before the channel is closed */ - this._befC = []; + /** * _preparePromise */ - this._prepP = null; - _prepareChannel(this); -}; // STATICS +}; + +// STATICS /** * used to identify if someone overwrites * window.BroadcastChannel with this * See methods/native.js */ - - exports.BroadcastChannel = BroadcastChannel; BroadcastChannel._pubkey = true; + /** * clears the tmp-folder if is node * @return {Promise} true if has run, false if not node */ - function clearNodeFolder(options) { options = (0, _options.fillOptionsWithDefaults)(options); var method = (0, _methodChooser.chooseMethod)(options); - if (method.type === 'node') { return method.clearNodeFolder().then(function () { return true; @@ -100,19 +92,17 @@ function clearNodeFolder(options) { return _util.PROMISE_RESOLVED_FALSE; } } + /** * if set, this method is enforced, * no mather what the options are */ - - var ENFORCED_OPTIONS; - function enforceOptions(options) { ENFORCED_OPTIONS = options; -} // PROTOTYPE - +} +// PROTOTYPE BroadcastChannel.prototype = { postMessage: function postMessage(msg) { if (this.closed) { @@ -124,87 +114,77 @@ BroadcastChannel.prototype = { */ JSON.stringify(msg)); } - return _post(this, 'message', msg); }, postInternal: function postInternal(msg) { return _post(this, 'internal', msg); }, - set onmessage(fn) { var time = this.method.microSeconds(); var listenObj = { time: time, fn: fn }; - _removeListenerObject(this, 'message', this._onML); - if (fn && typeof fn === 'function') { this._onML = listenObj; - _addListenerObject(this, 'message', listenObj); } else { this._onML = null; } }, - addEventListener: function addEventListener(type, fn) { var time = this.method.microSeconds(); var listenObj = { time: time, fn: fn }; - _addListenerObject(this, type, listenObj); }, removeEventListener: function removeEventListener(type, fn) { var obj = this._addEL[type].find(function (obj) { return obj.fn === fn; }); - _removeListenerObject(this, type, obj); }, close: function close() { var _this = this; - if (this.closed) { return; } - OPEN_BROADCAST_CHANNELS["delete"](this); this.closed = true; var awaitPrepare = this._prepP ? this._prepP : _util.PROMISE_RESOLVED_VOID; this._onML = null; this._addEL.message = []; - return awaitPrepare // wait until all current sending are processed + return awaitPrepare + // wait until all current sending are processed .then(function () { return Promise.all(Array.from(_this._uMP)); - }) // run before-close hooks + }) + // run before-close hooks .then(function () { return Promise.all(_this._befC.map(function (fn) { return fn(); })); - }) // close the channel + }) + // close the channel .then(function () { return _this.method.close(_this._state); }); }, - get type() { return this.method.type; }, - get isClosed() { return this.closed; } - }; + /** * Post a message over the channel * @returns {Promise} that resolved when the message sending is done */ - function _post(broadcastChannel, type, msg) { var time = broadcastChannel.method.microSeconds(); var msgObj = { @@ -214,25 +194,22 @@ function _post(broadcastChannel, type, msg) { }; var awaitPrepare = broadcastChannel._prepP ? broadcastChannel._prepP : _util.PROMISE_RESOLVED_VOID; return awaitPrepare.then(function () { - var sendPromise = broadcastChannel.method.postMessage(broadcastChannel._state, msgObj); // add/remove to unsend messages list + var sendPromise = broadcastChannel.method.postMessage(broadcastChannel._state, msgObj); + // add/remove to unsend messages list broadcastChannel._uMP.add(sendPromise); - sendPromise["catch"]().then(function () { return broadcastChannel._uMP["delete"](sendPromise); }); return sendPromise; }); } - function _prepareChannel(channel) { var maybePromise = channel.method.create(channel.name, channel.options); - if ((0, _util.isPromise)(maybePromise)) { channel._prepP = maybePromise; maybePromise.then(function (s) { // used in tests to simulate slow runtime - /*if (channel.options.prepareDelay) { await new Promise(res => setTimeout(res, this.options.prepareDelay)); }*/ @@ -242,30 +219,25 @@ function _prepareChannel(channel) { channel._state = maybePromise; } } - function _hasMessageListeners(channel) { if (channel._addEL.message.length > 0) return true; if (channel._addEL.internal.length > 0) return true; return false; } - function _addListenerObject(channel, type, obj) { channel._addEL[type].push(obj); - _startListening(channel); } - function _removeListenerObject(channel, type, obj) { channel._addEL[type] = channel._addEL[type].filter(function (o) { return o !== obj; }); - _stopListening(channel); } - function _startListening(channel) { if (!channel._iL && _hasMessageListeners(channel)) { // someone is listening, start subscribing + var listenerFn = function listenerFn(msgObj) { channel._addEL[msgObj.type].forEach(function (listenerObject) { /** @@ -279,15 +251,12 @@ function _startListening(channel) { */ var hundredMsInMicro = 100 * 1000; var minMessageTime = listenerObject.time - hundredMsInMicro; - if (msgObj.time >= minMessageTime) { listenerObject.fn(msgObj.data); } }); }; - var time = channel.method.microSeconds(); - if (channel._prepP) { channel._prepP.then(function () { channel._iL = true; @@ -299,7 +268,6 @@ function _startListening(channel) { } } } - function _stopListening(channel) { if (channel._iL && !_hasMessageListeners(channel)) { // noone is listening, stop subscribing @@ -312,7 +280,6 @@ function _stopListening(channel) { "use strict"; var _index = require("./index.js"); - /** * because babel can only export on default-attribute, * we use this for the non-module-build @@ -321,6 +288,7 @@ var _index = require("./index.js"); * but * var BroadcastChannel = require('broadcast-channel'); */ + module.exports = { BroadcastChannel: _index.BroadcastChannel, createLeaderElection: _index.createLeaderElection, @@ -370,9 +338,7 @@ Object.defineProperty(exports, "enforceOptions", { return _broadcastChannel.enforceOptions; } }); - var _broadcastChannel = require("./broadcast-channel.js"); - var _leaderElection = require("./leader-election.js"); },{"./broadcast-channel.js":1,"./leader-election.js":4}],4:[function(require,module,exports){ "use strict"; @@ -382,37 +348,30 @@ Object.defineProperty(exports, "__esModule", { }); exports.beLeader = beLeader; exports.createLeaderElection = createLeaderElection; - var _util = require("./util.js"); - var _unload = require("unload"); - var LeaderElection = function LeaderElection(broadcastChannel, options) { var _this = this; - this.broadcastChannel = broadcastChannel; this._options = options; this.isLeader = false; this.hasLeader = false; this.isDead = false; this.token = (0, _util.randomToken)(); + /** * Apply Queue, * used to ensure we do not run applyOnce() * in parallel. */ + this._aplQ = _util.PROMISE_RESOLVED_VOID; + // amount of unfinished applyOnce() calls + this._aplQC = 0; - this._aplQ = _util.PROMISE_RESOLVED_VOID; // amount of unfinished applyOnce() calls - - this._aplQC = 0; // things to clean up - + // things to clean up this._unl = []; // _unloads - this._lstns = []; // _listeners - this._dpL = function () {}; // onduplicate listener - - this._dpLC = false; // true when onduplicate called /** @@ -420,55 +379,47 @@ var LeaderElection = function LeaderElection(broadcastChannel, options) { * we still listen to messages to ensure the hasLeader flag * is set correctly. */ - var hasLeaderListener = function hasLeaderListener(msg) { if (msg.context === 'leader') { if (msg.action === 'death') { _this.hasLeader = false; } - if (msg.action === 'tell') { _this.hasLeader = true; } } }; - this.broadcastChannel.addEventListener('internal', hasLeaderListener); - this._lstns.push(hasLeaderListener); }; - LeaderElection.prototype = { /** * Returns true if the instance is leader, * false if not. * @async */ - applyOnce: function applyOnce( // true if the applyOnce() call came from the fallbackInterval cycle + applyOnce: function applyOnce( + // true if the applyOnce() call came from the fallbackInterval cycle isFromFallbackInterval) { var _this2 = this; - if (this.isLeader) { return (0, _util.sleep)(0, true); } - if (this.isDead) { return (0, _util.sleep)(0, false); } + /** * Already applying more then once, * -> wait for the apply queue to be finished. */ - - if (this._aplQC > 1) { return this._aplQ; } + /** * Add a new apply-run */ - - var applyRun = function applyRun() { /** * Optimization shortcuts. @@ -478,7 +429,6 @@ LeaderElection.prototype = { if (_this2.isLeader) { return _util.PROMISE_RESOLVED_TRUE; } - var stopCriteria = false; var stopCriteriaPromiseResolve; /** @@ -487,7 +437,6 @@ LeaderElection.prototype = { * have to await the responseTime when it is already clear * that the election failed. */ - var stopCriteriaPromise = new Promise(function (res) { stopCriteriaPromiseResolve = function stopCriteriaPromiseResolve() { stopCriteria = true; @@ -495,11 +444,9 @@ LeaderElection.prototype = { }; }); var recieved = []; - var handleMessage = function handleMessage(msg) { if (msg.context === 'leader' && msg.token != _this2.token) { recieved.push(msg); - if (msg.action === 'apply') { // other is applying if (msg.token > _this2.token) { @@ -510,7 +457,6 @@ LeaderElection.prototype = { stopCriteriaPromiseResolve(); } } - if (msg.action === 'tell') { // other is already leader stopCriteriaPromiseResolve(); @@ -518,8 +464,8 @@ LeaderElection.prototype = { } } }; - _this2.broadcastChannel.addEventListener('internal', handleMessage); + /** * If the applyOnce() call came from the fallbackInterval, * we can assume that the election runs in the background and @@ -530,26 +476,24 @@ LeaderElection.prototype = { * But also it takes longer which is not a problem because we anyway * run in the background. */ - - var waitForAnswerTime = isFromFallbackInterval ? _this2._options.responseTime * 4 : _this2._options.responseTime; - var applyPromise = _sendMessage(_this2, 'apply') // send out that this one is applying .then(function () { return Promise.race([(0, _util.sleep)(waitForAnswerTime), stopCriteriaPromise.then(function () { return Promise.reject(new Error()); })]); - }) // send again in case another instance was just created + }) + // send again in case another instance was just created .then(function () { return _sendMessage(_this2, 'apply'); - }) // let others time to respond + }) + // let others time to respond .then(function () { return Promise.race([(0, _util.sleep)(waitForAnswerTime), stopCriteriaPromise.then(function () { return Promise.reject(new Error()); })]); })["catch"](function () {}).then(function () { _this2.broadcastChannel.removeEventListener('internal', handleMessage); - if (!stopCriteria) { // no stop criteria -> own is leader return beLeader(_this2).then(function () { @@ -560,10 +504,8 @@ LeaderElection.prototype = { return false; } }); - return applyPromise; }; - this._aplQC = this._aplQC + 1; this._aplQ = this._aplQ.then(function () { return applyRun(); @@ -575,82 +517,68 @@ LeaderElection.prototype = { }); }, awaitLeadership: function awaitLeadership() { - if ( - /* _awaitLeadershipPromise */ + if ( /* _awaitLeadershipPromise */ !this._aLP) { this._aLP = _awaitLeadershipOnce(this); } - return this._aLP; }, - set onduplicate(fn) { this._dpL = fn; }, - die: function die() { var _this3 = this; - this._lstns.forEach(function (listener) { return _this3.broadcastChannel.removeEventListener('internal', listener); }); - this._lstns = []; - this._unl.forEach(function (uFn) { return uFn.remove(); }); - this._unl = []; - if (this.isLeader) { this.hasLeader = false; this.isLeader = false; } - this.isDead = true; return _sendMessage(this, 'death'); } }; + /** * @param leaderElector {LeaderElector} */ - function _awaitLeadershipOnce(leaderElector) { if (leaderElector.isLeader) { return _util.PROMISE_RESOLVED_VOID; } - return new Promise(function (res) { var resolved = false; - function finish() { if (resolved) { return; } - resolved = true; leaderElector.broadcastChannel.removeEventListener('internal', whenDeathListener); res(true); - } // try once now - + } + // try once now leaderElector.applyOnce().then(function () { if (leaderElector.isLeader) { finish(); } }); + /** * Try on fallbackInterval * @recursive */ - var tryOnFallBack = function tryOnFallBack() { return (0, _util.sleep)(leaderElector._options.fallbackInterval).then(function () { if (leaderElector.isDead || resolved) { return; } - if (leaderElector.isLeader) { finish(); } else { @@ -664,9 +592,9 @@ function _awaitLeadershipOnce(leaderElector) { } }); }; + tryOnFallBack(); - tryOnFallBack(); // try when other leader dies - + // try when other leader dies var whenDeathListener = function whenDeathListener(msg) { if (msg.context === 'leader' && msg.action === 'death') { leaderElector.hasLeader = false; @@ -677,17 +605,14 @@ function _awaitLeadershipOnce(leaderElector) { }); } }; - leaderElector.broadcastChannel.addEventListener('internal', whenDeathListener); - leaderElector._lstns.push(whenDeathListener); }); } + /** * sends and internal message over the broadcast-channel */ - - function _sendMessage(leaderElector, action) { var msgJson = { context: 'leader', @@ -696,21 +621,17 @@ function _sendMessage(leaderElector, action) { }; return leaderElector.broadcastChannel.postInternal(msgJson); } - function beLeader(leaderElector) { leaderElector.isLeader = true; leaderElector.hasLeader = true; var unloadFn = (0, _unload.add)(function () { return leaderElector.die(); }); - leaderElector._unl.push(unloadFn); - var isLeaderListener = function isLeaderListener(msg) { if (msg.context === 'leader' && msg.action === 'apply') { _sendMessage(leaderElector, 'tell'); } - if (msg.context === 'leader' && msg.action === 'tell' && !leaderElector._dpLC) { /** * another instance is also leader! @@ -721,49 +642,35 @@ function beLeader(leaderElector) { * @link https://github.com/pubkey/broadcast-channel/issues/385 */ leaderElector._dpLC = true; - leaderElector._dpL(); // message the lib user so the app can handle the problem - - _sendMessage(leaderElector, 'tell'); // ensure other leader also knows the problem - } }; leaderElector.broadcastChannel.addEventListener('internal', isLeaderListener); - leaderElector._lstns.push(isLeaderListener); - return _sendMessage(leaderElector, 'tell'); } - function fillOptionsWithDefaults(options, channel) { if (!options) options = {}; options = JSON.parse(JSON.stringify(options)); - if (!options.fallbackInterval) { options.fallbackInterval = 3000; } - if (!options.responseTime) { options.responseTime = channel.method.averageResponseTime(channel.options); } - return options; } - function createLeaderElection(channel, options) { if (channel._leaderElector) { throw new Error('BroadcastChannel already has a leader-elector'); } - options = fillOptionsWithDefaults(options, channel); var elector = new LeaderElection(channel, options); - channel._befC.push(function () { return elector.die(); }); - channel._leaderElector = elector; return elector; } @@ -771,59 +678,49 @@ function createLeaderElection(channel, options) { "use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); - var _typeof = require("@babel/runtime/helpers/typeof"); - Object.defineProperty(exports, "__esModule", { value: true }); exports.chooseMethod = chooseMethod; - var _native = _interopRequireDefault(require("./methods/native.js")); - var _indexedDb = _interopRequireDefault(require("./methods/indexed-db.js")); - var _localstorage = _interopRequireDefault(require("./methods/localstorage.js")); - var _simulate = _interopRequireDefault(require("./methods/simulate.js")); - - function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } - function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } - // the line below will be removed from es5/browser builds + // order is important -var METHODS = [_native["default"], // fastest +var METHODS = [_native["default"], +// fastest _indexedDb["default"], _localstorage["default"]]; - function chooseMethod(options) { - var chooseMethods = [].concat(options.methods, METHODS).filter(Boolean); // the line below will be removed from es5/browser builds + var chooseMethods = [].concat(options.methods, METHODS).filter(Boolean); + // the line below will be removed from es5/browser builds + // directly chosen if (options.type) { if (options.type === 'simulate') { // only use simulate-method if directly chosen return _simulate["default"]; } - var ret = chooseMethods.find(function (m) { return m.type === options.type; }); if (!ret) throw new Error('method-type ' + options.type + ' not found');else return ret; } + /** * if no webworker support is needed, * remove idb from the list so that localstorage is been chosen */ - - if (!options.webWorkerSupport) { chooseMethods = chooseMethods.filter(function (m) { return m.type !== 'idb'; }); } - var useMethod = chooseMethods.find(function (method) { return method.canBeUsed(); }); @@ -856,13 +753,9 @@ exports.postMessage = postMessage; exports.removeMessagesById = removeMessagesById; exports.type = void 0; exports.writeMessage = writeMessage; - var _util = require("../util.js"); - var _obliviousSet = require("oblivious-set"); - var _options = require("../options.js"); - /** * this method uses indexeddb to store the messages * There is currently no observerAPI for idb @@ -871,58 +764,54 @@ var _options = require("../options.js"); * When working on this, ensure to use these performance optimizations: * @link https://rxdb.info/slow-indexeddb.html */ + var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var DB_PREFIX = 'pubkey.broadcast-channel-0-'; var OBJECT_STORE_ID = 'messages'; + /** * Use relaxed durability for faster performance on all transactions. * @link https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/ */ - var TRANSACTION_SETTINGS = { durability: 'relaxed' }; exports.TRANSACTION_SETTINGS = TRANSACTION_SETTINGS; var type = 'idb'; exports.type = type; - function getIdb() { if (typeof indexedDB !== 'undefined') return indexedDB; - if (typeof window !== 'undefined') { if (typeof window.mozIndexedDB !== 'undefined') return window.mozIndexedDB; if (typeof window.webkitIndexedDB !== 'undefined') return window.webkitIndexedDB; if (typeof window.msIndexedDB !== 'undefined') return window.msIndexedDB; } - return false; } + /** * If possible, we should explicitly commit IndexedDB transactions * for better performance. * @link https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/ */ - - function commitIndexedDBTransaction(tx) { if (tx.commit) { tx.commit(); } } - function createDatabase(channelName) { - var IndexedDB = getIdb(); // create table + var IndexedDB = getIdb(); + // create table var dbName = DB_PREFIX + channelName; + /** * All IndexedDB databases are opened without version * because it is a bit faster, especially on firefox * @link http://nparashuram.com/IndexedDB/perf/#Open%20Database%20with%20version */ - var openRequest = IndexedDB.open(dbName); - openRequest.onupgradeneeded = function (ev) { var db = ev.target.result; db.createObjectStore(OBJECT_STORE_ID, { @@ -930,24 +819,21 @@ function createDatabase(channelName) { autoIncrement: true }); }; - var dbPromise = new Promise(function (res, rej) { openRequest.onerror = function (ev) { return rej(ev); }; - openRequest.onsuccess = function () { res(openRequest.result); }; }); return dbPromise; } + /** * writes the new message to the database * so other readers can find it */ - - function writeMessage(db, readerUuid, messageJson) { var time = new Date().getTime(); var writeObject = { @@ -960,17 +846,14 @@ function writeMessage(db, readerUuid, messageJson) { tx.oncomplete = function () { return res(); }; - tx.onerror = function (ev) { return rej(ev); }; - var objectStore = tx.objectStore(OBJECT_STORE_ID); objectStore.add(writeObject); commitIndexedDBTransaction(tx); }); } - function getAllMessages(db) { var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); @@ -978,10 +861,9 @@ function getAllMessages(db) { return new Promise(function (res) { objectStore.openCursor().onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { - ret.push(cursor.value); //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); - + ret.push(cursor.value); + //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); cursor["continue"](); } else { commitIndexedDBTransaction(tx); @@ -990,31 +872,28 @@ function getAllMessages(db) { }; }); } - function getMessagesHigherThan(db, lastCursorId) { var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); var ret = []; var keyRangeValue = IDBKeyRange.bound(lastCursorId + 1, Infinity); + /** * Optimization shortcut, * if getAll() can be used, do not use a cursor. * @link https://rxdb.info/slow-indexeddb.html */ - if (objectStore.getAll) { var getAllRequest = objectStore.getAll(keyRangeValue); return new Promise(function (res, rej) { getAllRequest.onerror = function (err) { return rej(err); }; - getAllRequest.onsuccess = function (e) { res(e.target.result); }; }); } - function openCursor() { // Occasionally Safari will fail on IDBKeyRange.bound, this // catches that error, having it open the cursor to the first @@ -1026,17 +905,13 @@ function getMessagesHigherThan(db, lastCursorId) { return objectStore.openCursor(); } } - return new Promise(function (res, rej) { var openCursorRequest = openCursor(); - openCursorRequest.onerror = function (err) { return rej(err); }; - openCursorRequest.onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { if (cursor.value.id < lastCursorId + 1) { cursor["continue"](lastCursorId + 1); @@ -1051,12 +926,10 @@ function getMessagesHigherThan(db, lastCursorId) { }; }); } - function removeMessagesById(channelState, ids) { if (channelState.closed) { return Promise.resolve([]); } - var tx = channelState.db.transaction(OBJECT_STORE_ID, 'readwrite', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); return Promise.all(ids.map(function (id) { @@ -1068,7 +941,6 @@ function removeMessagesById(channelState, ids) { }); })); } - function getOldMessages(db, ttl) { var olderThen = new Date().getTime() - ttl; var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS); @@ -1077,13 +949,11 @@ function getOldMessages(db, ttl) { return new Promise(function (res) { objectStore.openCursor().onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { var msgObk = cursor.value; - if (msgObk.time < olderThen) { - ret.push(msgObk); //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); - + ret.push(msgObk); + //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); cursor["continue"](); } else { // no more old messages, @@ -1097,7 +967,6 @@ function getOldMessages(db, ttl) { }; }); } - function cleanOldMessages(channelState) { return getOldMessages(channelState.db, channelState.options.idb.ttl).then(function (tooOld) { return removeMessagesById(channelState, tooOld.map(function (msg) { @@ -1105,7 +974,6 @@ function cleanOldMessages(channelState) { })); }); } - function create(channelName, options) { options = (0, _options.fillOptionsWithDefaults)(options); return createDatabase(channelName).then(function (db) { @@ -1115,7 +983,6 @@ function create(channelName, options) { channelName: channelName, options: options, uuid: (0, _util.randomToken)(), - /** * emittedMessagesIds * contains all messages that have been emitted before @@ -1128,30 +995,27 @@ function create(channelName, options) { readQueuePromises: [], db: db }; + /** * Handle abrupt closes that do not originate from db.close(). * This could happen, for example, if the underlying storage is * removed or if the user clears the database in the browser's * history preferences. */ - db.onclose = function () { state.closed = true; if (options.idb.onclose) options.idb.onclose(); }; + /** * if service-workers are used, * we have no 'storage'-event if they post a message, * therefore we also have to set an interval */ - - _readLoop(state); - return state; }); } - function _readLoop(state) { if (state.closed) return; readNewMessages(state).then(function () { @@ -1160,25 +1024,21 @@ function _readLoop(state) { return _readLoop(state); }); } - function _filterMessage(msgObj, state) { if (msgObj.uuid === state.uuid) return false; // send by own - if (state.eMIs.has(msgObj.id)) return false; // already emitted - if (msgObj.data.time < state.messagesCallbackTime) return false; // older then onMessageCallback - return true; } + /** * reads all new messages from the database and emits them */ - - function readNewMessages(state) { // channel already closed - if (state.closed) return _util.PROMISE_RESOLVED_VOID; // if no one is listening, we do not need to scan for new messages + if (state.closed) return _util.PROMISE_RESOLVED_VOID; + // if no one is listening, we do not need to scan for new messages if (!state.messagesCallback) return _util.PROMISE_RESOLVED_VOID; return getMessagesHigherThan(state.db, state.lastCursorId).then(function (newerMessages) { var useMessages = newerMessages @@ -1186,21 +1046,18 @@ function readNewMessages(state) { * there is a bug in iOS where the msgObj can be undefined some times * so we filter them out * @link https://github.com/pubkey/broadcast-channel/issues/19 - */ - .filter(function (msgObj) { + */.filter(function (msgObj) { return !!msgObj; }).map(function (msgObj) { if (msgObj.id > state.lastCursorId) { state.lastCursorId = msgObj.id; } - return msgObj; }).filter(function (msgObj) { return _filterMessage(msgObj, state); }).sort(function (msgObjA, msgObjB) { return msgObjA.time - msgObjB.time; }); // sort by time - useMessages.forEach(function (msgObj) { if (state.messagesCallback) { state.eMIs.add(msgObj.id); @@ -1210,12 +1067,10 @@ function readNewMessages(state) { return _util.PROMISE_RESOLVED_VOID; }); } - function close(channelState) { channelState.closed = true; channelState.db.close(); } - function postMessage(channelState, messageJson) { channelState.writeBlockPromise = channelState.writeBlockPromise.then(function () { return writeMessage(channelState.db, channelState.uuid, messageJson); @@ -1227,27 +1082,21 @@ function postMessage(channelState, messageJson) { }); return channelState.writeBlockPromise; } - function onMessage(channelState, fn, time) { channelState.messagesCallbackTime = time; channelState.messagesCallback = fn; readNewMessages(channelState); } - function canBeUsed() { var idb = getIdb(); - if (!idb) { return false; } - return true; } - function averageResponseTime(options) { return options.idb.fallbackInterval * 2; } - var _default = { create: create, close: close, @@ -1278,13 +1127,9 @@ exports.postMessage = postMessage; exports.removeStorageEventListener = removeStorageEventListener; exports.storageKey = storageKey; exports.type = void 0; - var _obliviousSet = require("oblivious-set"); - var _options = require("../options.js"); - var _util = require("../util.js"); - /** * A localStorage-only method which uses localstorage and its 'storage'-event * This does not work inside of webworkers because they have no access to locastorage @@ -1292,41 +1137,38 @@ var _util = require("../util.js"); * @link https://caniuse.com/#feat=namevalue-storage * @link https://caniuse.com/#feat=indexeddb */ + var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var KEY_PREFIX = 'pubkey.broadcastChannel-'; var type = 'localstorage'; + /** * copied from crosstab * @link https://github.com/tejacques/crosstab/blob/master/src/crosstab.js#L32 */ - exports.type = type; - function getLocalStorage() { var localStorage; if (typeof window === 'undefined') return null; - try { localStorage = window.localStorage; localStorage = window['ie8-eventlistener/storage'] || window.localStorage; - } catch (e) {// New versions of Firefox throw a Security exception + } catch (e) { + // New versions of Firefox throw a Security exception // if cookies are disabled. See // https://bugzilla.mozilla.org/show_bug.cgi?id=1028153 } - return localStorage; } - function storageKey(channelName) { return KEY_PREFIX + channelName; } + /** * writes the new message to the storage * and fires the storage-event so other readers can find it */ - - function postMessage(channelState, messageJson) { return new Promise(function (res) { (0, _util.sleep)().then(function () { @@ -1339,12 +1181,12 @@ function postMessage(channelState, messageJson) { }; var value = JSON.stringify(writeObj); getLocalStorage().setItem(key, value); + /** * StorageEvent does not fire the 'storage' event * in the window that changes the state of the local storage. * So we fire it manually */ - var ev = document.createEvent('Event'); ev.initEvent('storage', true, true); ev.key = key; @@ -1354,52 +1196,42 @@ function postMessage(channelState, messageJson) { }); }); } - function addStorageEventListener(channelName, fn) { var key = storageKey(channelName); - var listener = function listener(ev) { if (ev.key === key) { fn(JSON.parse(ev.newValue)); } }; - window.addEventListener('storage', listener); return listener; } - function removeStorageEventListener(listener) { window.removeEventListener('storage', listener); } - function create(channelName, options) { options = (0, _options.fillOptionsWithDefaults)(options); - if (!canBeUsed()) { throw new Error('BroadcastChannel: localstorage cannot be used'); } - var uuid = (0, _util.randomToken)(); + /** * eMIs * contains all messages that have been emitted before * @type {ObliviousSet} */ - var eMIs = new _obliviousSet.ObliviousSet(options.localstorage.removeTimeout); var state = { channelName: channelName, uuid: uuid, eMIs: eMIs // emittedMessagesIds - }; + state.listener = addStorageEventListener(channelName, function (msgObj) { if (!state.messagesCallback) return; // no listener - if (msgObj.uuid === uuid) return; // own message - if (!msgObj.token || eMIs.has(msgObj.token)) return; // already emitted - if (msgObj.data.time && msgObj.data.time < state.messagesCallbackTime) return; // too old eMIs.add(msgObj.token); @@ -1407,20 +1239,16 @@ function create(channelName, options) { }); return state; } - function close(channelState) { removeStorageEventListener(channelState.listener); } - function onMessage(channelState, fn, time) { channelState.messagesCallbackTime = time; channelState.messagesCallback = fn; } - function canBeUsed() { var ls = getLocalStorage(); if (!ls) return false; - try { var key = '__broadcastchannel_check'; ls.setItem(key, 'works'); @@ -1431,22 +1259,17 @@ function canBeUsed() { // https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API#Private_Browsing_Incognito_modes return false; } - return true; } - function averageResponseTime() { var defaultTime = 120; var userAgent = navigator.userAgent.toLowerCase(); - if (userAgent.includes('safari') && !userAgent.includes('chrome')) { // safari is much slower so this time is higher return defaultTime * 2; } - return defaultTime; } - var _default = { create: create, close: close, @@ -1472,20 +1295,16 @@ exports.microSeconds = exports["default"] = void 0; exports.onMessage = onMessage; exports.postMessage = postMessage; exports.type = void 0; - var _util = require("../util.js"); - var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var type = 'native'; exports.type = type; - function create(channelName) { var state = { messagesCallback: null, bc: new BroadcastChannel(channelName), subFns: [] // subscriberFunctions - }; state.bc.onmessage = function (msg) { @@ -1493,15 +1312,12 @@ function create(channelName) { state.messagesCallback(msg.data); } }; - return state; } - function close(channelState) { channelState.bc.close(); channelState.subFns = []; } - function postMessage(channelState, messageJson) { try { channelState.bc.postMessage(messageJson, false); @@ -1510,31 +1326,25 @@ function postMessage(channelState, messageJson) { return Promise.reject(err); } } - function onMessage(channelState, fn) { channelState.messagesCallback = fn; } - function canBeUsed() { if (typeof window === 'undefined') { return false; } - if (typeof BroadcastChannel === 'function') { if (BroadcastChannel._pubkey) { throw new Error('BroadcastChannel: Do not overwrite window.BroadcastChannel with this module, this is not a polyfill'); } - return true; } else { return false; } } - function averageResponseTime() { return 150; } - var _default = { create: create, close: close, @@ -1560,15 +1370,12 @@ exports.microSeconds = exports["default"] = void 0; exports.onMessage = onMessage; exports.postMessage = postMessage; exports.type = void 0; - var _util = require("../util.js"); - var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var type = 'simulate'; exports.type = type; var SIMULATE_CHANNELS = new Set(); - function create(channelName) { var state = { name: channelName, @@ -1577,11 +1384,9 @@ function create(channelName) { SIMULATE_CHANNELS.add(state); return state; } - function close(channelState) { SIMULATE_CHANNELS["delete"](channelState); } - function postMessage(channelState, messageJson) { return new Promise(function (res) { return setTimeout(function () { @@ -1599,19 +1404,15 @@ function postMessage(channelState, messageJson) { }, 5); }); } - function onMessage(channelState, fn) { channelState.messagesCallback = fn; } - function canBeUsed() { return true; } - function averageResponseTime() { return 5; } - var _default = { create: create, close: close, @@ -1630,33 +1431,35 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.fillOptionsWithDefaults = fillOptionsWithDefaults; - function fillOptionsWithDefaults() { var originalOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var options = JSON.parse(JSON.stringify(originalOptions)); // main - - if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true; // indexed-db + var options = JSON.parse(JSON.stringify(originalOptions)); - if (!options.idb) options.idb = {}; // after this time the messages get deleted + // main + if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true; + // indexed-db + if (!options.idb) options.idb = {}; + // after this time the messages get deleted if (!options.idb.ttl) options.idb.ttl = 1000 * 45; - if (!options.idb.fallbackInterval) options.idb.fallbackInterval = 150; // handles abrupt db onclose events. - - if (originalOptions.idb && typeof originalOptions.idb.onclose === 'function') options.idb.onclose = originalOptions.idb.onclose; // localstorage + if (!options.idb.fallbackInterval) options.idb.fallbackInterval = 150; + // handles abrupt db onclose events. + if (originalOptions.idb && typeof originalOptions.idb.onclose === 'function') options.idb.onclose = originalOptions.idb.onclose; + // localstorage if (!options.localstorage) options.localstorage = {}; - if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60; // custom methods + if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60; - if (originalOptions.methods) options.methods = originalOptions.methods; // node + // custom methods + if (originalOptions.methods) options.methods = originalOptions.methods; + // node if (!options.node) options.node = {}; if (!options.node.ttl) options.node.ttl = 1000 * 60 * 2; // 2 minutes; - /** * On linux use 'ulimit -Hn' to get the limit of open files. * On ubuntu this was 4096 for me, so we use half of that as maxParallelWrites default. */ - if (!options.node.maxParallelWrites) options.node.maxParallelWrites = 2048; if (typeof options.node.useFastPath === 'undefined') options.node.useFastPath = true; return options; @@ -1673,7 +1476,6 @@ exports.microSeconds = microSeconds; exports.randomInt = randomInt; exports.randomToken = randomToken; exports.sleep = sleep; - /** * returns true if the given object is a promise */ @@ -1684,14 +1486,12 @@ function isPromise(obj) { return false; } } - var PROMISE_RESOLVED_FALSE = Promise.resolve(false); exports.PROMISE_RESOLVED_FALSE = PROMISE_RESOLVED_FALSE; var PROMISE_RESOLVED_TRUE = Promise.resolve(true); exports.PROMISE_RESOLVED_TRUE = PROMISE_RESOLVED_TRUE; var PROMISE_RESOLVED_VOID = Promise.resolve(); exports.PROMISE_RESOLVED_VOID = PROMISE_RESOLVED_VOID; - function sleep(time, resolveWith) { if (!time) time = 0; return new Promise(function (res) { @@ -1700,21 +1500,19 @@ function sleep(time, resolveWith) { }, time); }); } - function randomInt(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } + /** * https://stackoverflow.com/a/8084248 */ - - function randomToken() { return Math.random().toString(36).substring(2); } - var lastMs = 0; var additional = 0; + /** * returns the current time in micro-seconds, * WARNING: This is a pseudo-function @@ -1722,10 +1520,8 @@ var additional = 0; * This is enough in browsers, and this function will not be used in nodejs. * The main reason for this hack is to ensure that BroadcastChannel behaves equal to production when it is used in fast-running unit tests. */ - function microSeconds() { var ms = new Date().getTime(); - if (ms === lastMs) { additional++; return ms * 1000 + additional; @@ -1785,7 +1581,6 @@ function _interopRequireDefault(obj) { "default": obj }; } - module.exports = _interopRequireDefault, module.exports.__esModule = true, module.exports["default"] = module.exports; },{}],15:[function(require,module,exports){ function _typeof(obj) { @@ -1797,7 +1592,6 @@ function _typeof(obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, module.exports.__esModule = true, module.exports["default"] = module.exports), _typeof(obj); } - module.exports = _typeof, module.exports.__esModule = true, module.exports["default"] = module.exports; },{}],16:[function(require,module,exports){ @@ -8323,6 +8117,7 @@ var runtime = (function (exports) { var Op = Object.prototype; var hasOwn = Op.hasOwnProperty; + var defineProperty = Object.defineProperty || function (obj, key, desc) { obj[key] = desc.value; }; var undefined; // More compressible than void 0. var $Symbol = typeof Symbol === "function" ? Symbol : {}; var iteratorSymbol = $Symbol.iterator || "@@iterator"; @@ -8355,7 +8150,7 @@ var runtime = (function (exports) { // The ._invoke method unifies the implementations of the .next, // .throw, and .return methods. - generator._invoke = makeInvokeMethod(innerFn, self, context); + defineProperty(generator, "_invoke", { value: makeInvokeMethod(innerFn, self, context) }); return generator; } @@ -8416,8 +8211,12 @@ var runtime = (function (exports) { var Gp = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(IteratorPrototype); GeneratorFunction.prototype = GeneratorFunctionPrototype; - define(Gp, "constructor", GeneratorFunctionPrototype); - define(GeneratorFunctionPrototype, "constructor", GeneratorFunction); + defineProperty(Gp, "constructor", { value: GeneratorFunctionPrototype, configurable: true }); + defineProperty( + GeneratorFunctionPrototype, + "constructor", + { value: GeneratorFunction, configurable: true } + ); GeneratorFunction.displayName = define( GeneratorFunctionPrototype, toStringTagSymbol, @@ -8527,7 +8326,7 @@ var runtime = (function (exports) { // Define the unified helper method that is used to implement .next, // .throw, and .return (see defineIteratorMethods). - this._invoke = enqueue; + defineProperty(this, "_invoke", { value: enqueue }); } defineIteratorMethods(AsyncIterator.prototype); @@ -8765,7 +8564,8 @@ var runtime = (function (exports) { this.reset(true); } - exports.keys = function(object) { + exports.keys = function(val) { + var object = Object(val); var keys = []; for (var key in object) { keys.push(key); @@ -9175,45 +8975,36 @@ function getSize() { "use strict"; var _util = require("./util.js"); - /* eslint-disable */ - /** * used in docs/iframe.html */ require('@babel/polyfill'); - var msgContainer = document.getElementById('messages'); - var _require = require('../../'), - BroadcastChannel = _require.BroadcastChannel; - + BroadcastChannel = _require.BroadcastChannel; var channelName = (0, _util.getParameterByName)('channelName'); -var methodType = (0, _util.getParameterByName)('methodType'); // overwrite console.log +var methodType = (0, _util.getParameterByName)('methodType'); +// overwrite console.log var logBefore = console.log; - console.log = function (str) { logBefore('iframe: ' + str); }; - function logToDom(str) { var textnode = document.createTextNode(str); var lineBreak = document.createElement('br'); msgContainer.appendChild(textnode); msgContainer.appendChild(lineBreak); } - var channel = new BroadcastChannel(channelName, { type: methodType }); logToDom('created channel with type ' + methodType); - channel.onmessage = function (msg) { logToDom('message:'); logToDom('recieved message(' + msg.step + ') from ' + msg.from + ': '); logToDom(JSON.stringify(msg)); - if (!msg.answer) { logToDom('answer back(' + msg.step + ')'); channel.postMessage({ @@ -9230,8 +9021,8 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.getParameterByName = getParameterByName; - /* eslint no-useless-escape: "off" */ + // https://stackoverflow.com/a/901144/3443137 function getParameterByName(name, url) { if (!url) url = window.location.href; diff --git a/docs/index.js b/docs/index.js index 1d8f631e..755551db 100644 --- a/docs/index.js +++ b/docs/index.js @@ -7,13 +7,9 @@ Object.defineProperty(exports, "__esModule", { exports.OPEN_BROADCAST_CHANNELS = exports.BroadcastChannel = void 0; exports.clearNodeFolder = clearNodeFolder; exports.enforceOptions = enforceOptions; - var _util = require("./util.js"); - var _methodChooser = require("./method-chooser.js"); - var _options = require("./options.js"); - /** * Contains all open channels, * used in tests to ensure everything is closed. @@ -21,77 +17,73 @@ var _options = require("./options.js"); var OPEN_BROADCAST_CHANNELS = new Set(); exports.OPEN_BROADCAST_CHANNELS = OPEN_BROADCAST_CHANNELS; var lastId = 0; - var BroadcastChannel = function BroadcastChannel(name, options) { // identifier of the channel to debug stuff this.id = lastId++; OPEN_BROADCAST_CHANNELS.add(this); this.name = name; - if (ENFORCED_OPTIONS) { options = ENFORCED_OPTIONS; } - this.options = (0, _options.fillOptionsWithDefaults)(options); - this.method = (0, _methodChooser.chooseMethod)(this.options); // isListening + this.method = (0, _methodChooser.chooseMethod)(this.options); + // isListening this._iL = false; + /** * _onMessageListener * setting onmessage twice, * will overwrite the first listener */ - this._onML = null; + /** * _addEventListeners */ - this._addEL = { message: [], internal: [] }; + /** * Unsend message promises * where the sending is still in progress * @type {Set} */ - this._uMP = new Set(); + /** * _beforeClose * array of promises that will be awaited * before the channel is closed */ - this._befC = []; + /** * _preparePromise */ - this._prepP = null; - _prepareChannel(this); -}; // STATICS +}; + +// STATICS /** * used to identify if someone overwrites * window.BroadcastChannel with this * See methods/native.js */ - - exports.BroadcastChannel = BroadcastChannel; BroadcastChannel._pubkey = true; + /** * clears the tmp-folder if is node * @return {Promise} true if has run, false if not node */ - function clearNodeFolder(options) { options = (0, _options.fillOptionsWithDefaults)(options); var method = (0, _methodChooser.chooseMethod)(options); - if (method.type === 'node') { return method.clearNodeFolder().then(function () { return true; @@ -100,19 +92,17 @@ function clearNodeFolder(options) { return _util.PROMISE_RESOLVED_FALSE; } } + /** * if set, this method is enforced, * no mather what the options are */ - - var ENFORCED_OPTIONS; - function enforceOptions(options) { ENFORCED_OPTIONS = options; -} // PROTOTYPE - +} +// PROTOTYPE BroadcastChannel.prototype = { postMessage: function postMessage(msg) { if (this.closed) { @@ -124,87 +114,77 @@ BroadcastChannel.prototype = { */ JSON.stringify(msg)); } - return _post(this, 'message', msg); }, postInternal: function postInternal(msg) { return _post(this, 'internal', msg); }, - set onmessage(fn) { var time = this.method.microSeconds(); var listenObj = { time: time, fn: fn }; - _removeListenerObject(this, 'message', this._onML); - if (fn && typeof fn === 'function') { this._onML = listenObj; - _addListenerObject(this, 'message', listenObj); } else { this._onML = null; } }, - addEventListener: function addEventListener(type, fn) { var time = this.method.microSeconds(); var listenObj = { time: time, fn: fn }; - _addListenerObject(this, type, listenObj); }, removeEventListener: function removeEventListener(type, fn) { var obj = this._addEL[type].find(function (obj) { return obj.fn === fn; }); - _removeListenerObject(this, type, obj); }, close: function close() { var _this = this; - if (this.closed) { return; } - OPEN_BROADCAST_CHANNELS["delete"](this); this.closed = true; var awaitPrepare = this._prepP ? this._prepP : _util.PROMISE_RESOLVED_VOID; this._onML = null; this._addEL.message = []; - return awaitPrepare // wait until all current sending are processed + return awaitPrepare + // wait until all current sending are processed .then(function () { return Promise.all(Array.from(_this._uMP)); - }) // run before-close hooks + }) + // run before-close hooks .then(function () { return Promise.all(_this._befC.map(function (fn) { return fn(); })); - }) // close the channel + }) + // close the channel .then(function () { return _this.method.close(_this._state); }); }, - get type() { return this.method.type; }, - get isClosed() { return this.closed; } - }; + /** * Post a message over the channel * @returns {Promise} that resolved when the message sending is done */ - function _post(broadcastChannel, type, msg) { var time = broadcastChannel.method.microSeconds(); var msgObj = { @@ -214,25 +194,22 @@ function _post(broadcastChannel, type, msg) { }; var awaitPrepare = broadcastChannel._prepP ? broadcastChannel._prepP : _util.PROMISE_RESOLVED_VOID; return awaitPrepare.then(function () { - var sendPromise = broadcastChannel.method.postMessage(broadcastChannel._state, msgObj); // add/remove to unsend messages list + var sendPromise = broadcastChannel.method.postMessage(broadcastChannel._state, msgObj); + // add/remove to unsend messages list broadcastChannel._uMP.add(sendPromise); - sendPromise["catch"]().then(function () { return broadcastChannel._uMP["delete"](sendPromise); }); return sendPromise; }); } - function _prepareChannel(channel) { var maybePromise = channel.method.create(channel.name, channel.options); - if ((0, _util.isPromise)(maybePromise)) { channel._prepP = maybePromise; maybePromise.then(function (s) { // used in tests to simulate slow runtime - /*if (channel.options.prepareDelay) { await new Promise(res => setTimeout(res, this.options.prepareDelay)); }*/ @@ -242,30 +219,25 @@ function _prepareChannel(channel) { channel._state = maybePromise; } } - function _hasMessageListeners(channel) { if (channel._addEL.message.length > 0) return true; if (channel._addEL.internal.length > 0) return true; return false; } - function _addListenerObject(channel, type, obj) { channel._addEL[type].push(obj); - _startListening(channel); } - function _removeListenerObject(channel, type, obj) { channel._addEL[type] = channel._addEL[type].filter(function (o) { return o !== obj; }); - _stopListening(channel); } - function _startListening(channel) { if (!channel._iL && _hasMessageListeners(channel)) { // someone is listening, start subscribing + var listenerFn = function listenerFn(msgObj) { channel._addEL[msgObj.type].forEach(function (listenerObject) { /** @@ -279,15 +251,12 @@ function _startListening(channel) { */ var hundredMsInMicro = 100 * 1000; var minMessageTime = listenerObject.time - hundredMsInMicro; - if (msgObj.time >= minMessageTime) { listenerObject.fn(msgObj.data); } }); }; - var time = channel.method.microSeconds(); - if (channel._prepP) { channel._prepP.then(function () { channel._iL = true; @@ -299,7 +268,6 @@ function _startListening(channel) { } } } - function _stopListening(channel) { if (channel._iL && !_hasMessageListeners(channel)) { // noone is listening, stop subscribing @@ -312,7 +280,6 @@ function _stopListening(channel) { "use strict"; var _index = require("./index.js"); - /** * because babel can only export on default-attribute, * we use this for the non-module-build @@ -321,6 +288,7 @@ var _index = require("./index.js"); * but * var BroadcastChannel = require('broadcast-channel'); */ + module.exports = { BroadcastChannel: _index.BroadcastChannel, createLeaderElection: _index.createLeaderElection, @@ -370,9 +338,7 @@ Object.defineProperty(exports, "enforceOptions", { return _broadcastChannel.enforceOptions; } }); - var _broadcastChannel = require("./broadcast-channel.js"); - var _leaderElection = require("./leader-election.js"); },{"./broadcast-channel.js":1,"./leader-election.js":4}],4:[function(require,module,exports){ "use strict"; @@ -382,37 +348,30 @@ Object.defineProperty(exports, "__esModule", { }); exports.beLeader = beLeader; exports.createLeaderElection = createLeaderElection; - var _util = require("./util.js"); - var _unload = require("unload"); - var LeaderElection = function LeaderElection(broadcastChannel, options) { var _this = this; - this.broadcastChannel = broadcastChannel; this._options = options; this.isLeader = false; this.hasLeader = false; this.isDead = false; this.token = (0, _util.randomToken)(); + /** * Apply Queue, * used to ensure we do not run applyOnce() * in parallel. */ + this._aplQ = _util.PROMISE_RESOLVED_VOID; + // amount of unfinished applyOnce() calls + this._aplQC = 0; - this._aplQ = _util.PROMISE_RESOLVED_VOID; // amount of unfinished applyOnce() calls - - this._aplQC = 0; // things to clean up - + // things to clean up this._unl = []; // _unloads - this._lstns = []; // _listeners - this._dpL = function () {}; // onduplicate listener - - this._dpLC = false; // true when onduplicate called /** @@ -420,55 +379,47 @@ var LeaderElection = function LeaderElection(broadcastChannel, options) { * we still listen to messages to ensure the hasLeader flag * is set correctly. */ - var hasLeaderListener = function hasLeaderListener(msg) { if (msg.context === 'leader') { if (msg.action === 'death') { _this.hasLeader = false; } - if (msg.action === 'tell') { _this.hasLeader = true; } } }; - this.broadcastChannel.addEventListener('internal', hasLeaderListener); - this._lstns.push(hasLeaderListener); }; - LeaderElection.prototype = { /** * Returns true if the instance is leader, * false if not. * @async */ - applyOnce: function applyOnce( // true if the applyOnce() call came from the fallbackInterval cycle + applyOnce: function applyOnce( + // true if the applyOnce() call came from the fallbackInterval cycle isFromFallbackInterval) { var _this2 = this; - if (this.isLeader) { return (0, _util.sleep)(0, true); } - if (this.isDead) { return (0, _util.sleep)(0, false); } + /** * Already applying more then once, * -> wait for the apply queue to be finished. */ - - if (this._aplQC > 1) { return this._aplQ; } + /** * Add a new apply-run */ - - var applyRun = function applyRun() { /** * Optimization shortcuts. @@ -478,7 +429,6 @@ LeaderElection.prototype = { if (_this2.isLeader) { return _util.PROMISE_RESOLVED_TRUE; } - var stopCriteria = false; var stopCriteriaPromiseResolve; /** @@ -487,7 +437,6 @@ LeaderElection.prototype = { * have to await the responseTime when it is already clear * that the election failed. */ - var stopCriteriaPromise = new Promise(function (res) { stopCriteriaPromiseResolve = function stopCriteriaPromiseResolve() { stopCriteria = true; @@ -495,11 +444,9 @@ LeaderElection.prototype = { }; }); var recieved = []; - var handleMessage = function handleMessage(msg) { if (msg.context === 'leader' && msg.token != _this2.token) { recieved.push(msg); - if (msg.action === 'apply') { // other is applying if (msg.token > _this2.token) { @@ -510,7 +457,6 @@ LeaderElection.prototype = { stopCriteriaPromiseResolve(); } } - if (msg.action === 'tell') { // other is already leader stopCriteriaPromiseResolve(); @@ -518,8 +464,8 @@ LeaderElection.prototype = { } } }; - _this2.broadcastChannel.addEventListener('internal', handleMessage); + /** * If the applyOnce() call came from the fallbackInterval, * we can assume that the election runs in the background and @@ -530,26 +476,24 @@ LeaderElection.prototype = { * But also it takes longer which is not a problem because we anyway * run in the background. */ - - var waitForAnswerTime = isFromFallbackInterval ? _this2._options.responseTime * 4 : _this2._options.responseTime; - var applyPromise = _sendMessage(_this2, 'apply') // send out that this one is applying .then(function () { return Promise.race([(0, _util.sleep)(waitForAnswerTime), stopCriteriaPromise.then(function () { return Promise.reject(new Error()); })]); - }) // send again in case another instance was just created + }) + // send again in case another instance was just created .then(function () { return _sendMessage(_this2, 'apply'); - }) // let others time to respond + }) + // let others time to respond .then(function () { return Promise.race([(0, _util.sleep)(waitForAnswerTime), stopCriteriaPromise.then(function () { return Promise.reject(new Error()); })]); })["catch"](function () {}).then(function () { _this2.broadcastChannel.removeEventListener('internal', handleMessage); - if (!stopCriteria) { // no stop criteria -> own is leader return beLeader(_this2).then(function () { @@ -560,10 +504,8 @@ LeaderElection.prototype = { return false; } }); - return applyPromise; }; - this._aplQC = this._aplQC + 1; this._aplQ = this._aplQ.then(function () { return applyRun(); @@ -575,82 +517,68 @@ LeaderElection.prototype = { }); }, awaitLeadership: function awaitLeadership() { - if ( - /* _awaitLeadershipPromise */ + if ( /* _awaitLeadershipPromise */ !this._aLP) { this._aLP = _awaitLeadershipOnce(this); } - return this._aLP; }, - set onduplicate(fn) { this._dpL = fn; }, - die: function die() { var _this3 = this; - this._lstns.forEach(function (listener) { return _this3.broadcastChannel.removeEventListener('internal', listener); }); - this._lstns = []; - this._unl.forEach(function (uFn) { return uFn.remove(); }); - this._unl = []; - if (this.isLeader) { this.hasLeader = false; this.isLeader = false; } - this.isDead = true; return _sendMessage(this, 'death'); } }; + /** * @param leaderElector {LeaderElector} */ - function _awaitLeadershipOnce(leaderElector) { if (leaderElector.isLeader) { return _util.PROMISE_RESOLVED_VOID; } - return new Promise(function (res) { var resolved = false; - function finish() { if (resolved) { return; } - resolved = true; leaderElector.broadcastChannel.removeEventListener('internal', whenDeathListener); res(true); - } // try once now - + } + // try once now leaderElector.applyOnce().then(function () { if (leaderElector.isLeader) { finish(); } }); + /** * Try on fallbackInterval * @recursive */ - var tryOnFallBack = function tryOnFallBack() { return (0, _util.sleep)(leaderElector._options.fallbackInterval).then(function () { if (leaderElector.isDead || resolved) { return; } - if (leaderElector.isLeader) { finish(); } else { @@ -664,9 +592,9 @@ function _awaitLeadershipOnce(leaderElector) { } }); }; + tryOnFallBack(); - tryOnFallBack(); // try when other leader dies - + // try when other leader dies var whenDeathListener = function whenDeathListener(msg) { if (msg.context === 'leader' && msg.action === 'death') { leaderElector.hasLeader = false; @@ -677,17 +605,14 @@ function _awaitLeadershipOnce(leaderElector) { }); } }; - leaderElector.broadcastChannel.addEventListener('internal', whenDeathListener); - leaderElector._lstns.push(whenDeathListener); }); } + /** * sends and internal message over the broadcast-channel */ - - function _sendMessage(leaderElector, action) { var msgJson = { context: 'leader', @@ -696,21 +621,17 @@ function _sendMessage(leaderElector, action) { }; return leaderElector.broadcastChannel.postInternal(msgJson); } - function beLeader(leaderElector) { leaderElector.isLeader = true; leaderElector.hasLeader = true; var unloadFn = (0, _unload.add)(function () { return leaderElector.die(); }); - leaderElector._unl.push(unloadFn); - var isLeaderListener = function isLeaderListener(msg) { if (msg.context === 'leader' && msg.action === 'apply') { _sendMessage(leaderElector, 'tell'); } - if (msg.context === 'leader' && msg.action === 'tell' && !leaderElector._dpLC) { /** * another instance is also leader! @@ -721,49 +642,35 @@ function beLeader(leaderElector) { * @link https://github.com/pubkey/broadcast-channel/issues/385 */ leaderElector._dpLC = true; - leaderElector._dpL(); // message the lib user so the app can handle the problem - - _sendMessage(leaderElector, 'tell'); // ensure other leader also knows the problem - } }; leaderElector.broadcastChannel.addEventListener('internal', isLeaderListener); - leaderElector._lstns.push(isLeaderListener); - return _sendMessage(leaderElector, 'tell'); } - function fillOptionsWithDefaults(options, channel) { if (!options) options = {}; options = JSON.parse(JSON.stringify(options)); - if (!options.fallbackInterval) { options.fallbackInterval = 3000; } - if (!options.responseTime) { options.responseTime = channel.method.averageResponseTime(channel.options); } - return options; } - function createLeaderElection(channel, options) { if (channel._leaderElector) { throw new Error('BroadcastChannel already has a leader-elector'); } - options = fillOptionsWithDefaults(options, channel); var elector = new LeaderElection(channel, options); - channel._befC.push(function () { return elector.die(); }); - channel._leaderElector = elector; return elector; } @@ -771,59 +678,49 @@ function createLeaderElection(channel, options) { "use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); - var _typeof = require("@babel/runtime/helpers/typeof"); - Object.defineProperty(exports, "__esModule", { value: true }); exports.chooseMethod = chooseMethod; - var _native = _interopRequireDefault(require("./methods/native.js")); - var _indexedDb = _interopRequireDefault(require("./methods/indexed-db.js")); - var _localstorage = _interopRequireDefault(require("./methods/localstorage.js")); - var _simulate = _interopRequireDefault(require("./methods/simulate.js")); - - function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } - function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } - // the line below will be removed from es5/browser builds + // order is important -var METHODS = [_native["default"], // fastest +var METHODS = [_native["default"], +// fastest _indexedDb["default"], _localstorage["default"]]; - function chooseMethod(options) { - var chooseMethods = [].concat(options.methods, METHODS).filter(Boolean); // the line below will be removed from es5/browser builds + var chooseMethods = [].concat(options.methods, METHODS).filter(Boolean); + // the line below will be removed from es5/browser builds + // directly chosen if (options.type) { if (options.type === 'simulate') { // only use simulate-method if directly chosen return _simulate["default"]; } - var ret = chooseMethods.find(function (m) { return m.type === options.type; }); if (!ret) throw new Error('method-type ' + options.type + ' not found');else return ret; } + /** * if no webworker support is needed, * remove idb from the list so that localstorage is been chosen */ - - if (!options.webWorkerSupport) { chooseMethods = chooseMethods.filter(function (m) { return m.type !== 'idb'; }); } - var useMethod = chooseMethods.find(function (method) { return method.canBeUsed(); }); @@ -856,13 +753,9 @@ exports.postMessage = postMessage; exports.removeMessagesById = removeMessagesById; exports.type = void 0; exports.writeMessage = writeMessage; - var _util = require("../util.js"); - var _obliviousSet = require("oblivious-set"); - var _options = require("../options.js"); - /** * this method uses indexeddb to store the messages * There is currently no observerAPI for idb @@ -871,58 +764,54 @@ var _options = require("../options.js"); * When working on this, ensure to use these performance optimizations: * @link https://rxdb.info/slow-indexeddb.html */ + var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var DB_PREFIX = 'pubkey.broadcast-channel-0-'; var OBJECT_STORE_ID = 'messages'; + /** * Use relaxed durability for faster performance on all transactions. * @link https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/ */ - var TRANSACTION_SETTINGS = { durability: 'relaxed' }; exports.TRANSACTION_SETTINGS = TRANSACTION_SETTINGS; var type = 'idb'; exports.type = type; - function getIdb() { if (typeof indexedDB !== 'undefined') return indexedDB; - if (typeof window !== 'undefined') { if (typeof window.mozIndexedDB !== 'undefined') return window.mozIndexedDB; if (typeof window.webkitIndexedDB !== 'undefined') return window.webkitIndexedDB; if (typeof window.msIndexedDB !== 'undefined') return window.msIndexedDB; } - return false; } + /** * If possible, we should explicitly commit IndexedDB transactions * for better performance. * @link https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/ */ - - function commitIndexedDBTransaction(tx) { if (tx.commit) { tx.commit(); } } - function createDatabase(channelName) { - var IndexedDB = getIdb(); // create table + var IndexedDB = getIdb(); + // create table var dbName = DB_PREFIX + channelName; + /** * All IndexedDB databases are opened without version * because it is a bit faster, especially on firefox * @link http://nparashuram.com/IndexedDB/perf/#Open%20Database%20with%20version */ - var openRequest = IndexedDB.open(dbName); - openRequest.onupgradeneeded = function (ev) { var db = ev.target.result; db.createObjectStore(OBJECT_STORE_ID, { @@ -930,24 +819,21 @@ function createDatabase(channelName) { autoIncrement: true }); }; - var dbPromise = new Promise(function (res, rej) { openRequest.onerror = function (ev) { return rej(ev); }; - openRequest.onsuccess = function () { res(openRequest.result); }; }); return dbPromise; } + /** * writes the new message to the database * so other readers can find it */ - - function writeMessage(db, readerUuid, messageJson) { var time = new Date().getTime(); var writeObject = { @@ -960,17 +846,14 @@ function writeMessage(db, readerUuid, messageJson) { tx.oncomplete = function () { return res(); }; - tx.onerror = function (ev) { return rej(ev); }; - var objectStore = tx.objectStore(OBJECT_STORE_ID); objectStore.add(writeObject); commitIndexedDBTransaction(tx); }); } - function getAllMessages(db) { var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); @@ -978,10 +861,9 @@ function getAllMessages(db) { return new Promise(function (res) { objectStore.openCursor().onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { - ret.push(cursor.value); //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); - + ret.push(cursor.value); + //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); cursor["continue"](); } else { commitIndexedDBTransaction(tx); @@ -990,31 +872,28 @@ function getAllMessages(db) { }; }); } - function getMessagesHigherThan(db, lastCursorId) { var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); var ret = []; var keyRangeValue = IDBKeyRange.bound(lastCursorId + 1, Infinity); + /** * Optimization shortcut, * if getAll() can be used, do not use a cursor. * @link https://rxdb.info/slow-indexeddb.html */ - if (objectStore.getAll) { var getAllRequest = objectStore.getAll(keyRangeValue); return new Promise(function (res, rej) { getAllRequest.onerror = function (err) { return rej(err); }; - getAllRequest.onsuccess = function (e) { res(e.target.result); }; }); } - function openCursor() { // Occasionally Safari will fail on IDBKeyRange.bound, this // catches that error, having it open the cursor to the first @@ -1026,17 +905,13 @@ function getMessagesHigherThan(db, lastCursorId) { return objectStore.openCursor(); } } - return new Promise(function (res, rej) { var openCursorRequest = openCursor(); - openCursorRequest.onerror = function (err) { return rej(err); }; - openCursorRequest.onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { if (cursor.value.id < lastCursorId + 1) { cursor["continue"](lastCursorId + 1); @@ -1051,12 +926,10 @@ function getMessagesHigherThan(db, lastCursorId) { }; }); } - function removeMessagesById(channelState, ids) { if (channelState.closed) { return Promise.resolve([]); } - var tx = channelState.db.transaction(OBJECT_STORE_ID, 'readwrite', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); return Promise.all(ids.map(function (id) { @@ -1068,7 +941,6 @@ function removeMessagesById(channelState, ids) { }); })); } - function getOldMessages(db, ttl) { var olderThen = new Date().getTime() - ttl; var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS); @@ -1077,13 +949,11 @@ function getOldMessages(db, ttl) { return new Promise(function (res) { objectStore.openCursor().onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { var msgObk = cursor.value; - if (msgObk.time < olderThen) { - ret.push(msgObk); //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); - + ret.push(msgObk); + //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); cursor["continue"](); } else { // no more old messages, @@ -1097,7 +967,6 @@ function getOldMessages(db, ttl) { }; }); } - function cleanOldMessages(channelState) { return getOldMessages(channelState.db, channelState.options.idb.ttl).then(function (tooOld) { return removeMessagesById(channelState, tooOld.map(function (msg) { @@ -1105,7 +974,6 @@ function cleanOldMessages(channelState) { })); }); } - function create(channelName, options) { options = (0, _options.fillOptionsWithDefaults)(options); return createDatabase(channelName).then(function (db) { @@ -1115,7 +983,6 @@ function create(channelName, options) { channelName: channelName, options: options, uuid: (0, _util.randomToken)(), - /** * emittedMessagesIds * contains all messages that have been emitted before @@ -1128,30 +995,27 @@ function create(channelName, options) { readQueuePromises: [], db: db }; + /** * Handle abrupt closes that do not originate from db.close(). * This could happen, for example, if the underlying storage is * removed or if the user clears the database in the browser's * history preferences. */ - db.onclose = function () { state.closed = true; if (options.idb.onclose) options.idb.onclose(); }; + /** * if service-workers are used, * we have no 'storage'-event if they post a message, * therefore we also have to set an interval */ - - _readLoop(state); - return state; }); } - function _readLoop(state) { if (state.closed) return; readNewMessages(state).then(function () { @@ -1160,25 +1024,21 @@ function _readLoop(state) { return _readLoop(state); }); } - function _filterMessage(msgObj, state) { if (msgObj.uuid === state.uuid) return false; // send by own - if (state.eMIs.has(msgObj.id)) return false; // already emitted - if (msgObj.data.time < state.messagesCallbackTime) return false; // older then onMessageCallback - return true; } + /** * reads all new messages from the database and emits them */ - - function readNewMessages(state) { // channel already closed - if (state.closed) return _util.PROMISE_RESOLVED_VOID; // if no one is listening, we do not need to scan for new messages + if (state.closed) return _util.PROMISE_RESOLVED_VOID; + // if no one is listening, we do not need to scan for new messages if (!state.messagesCallback) return _util.PROMISE_RESOLVED_VOID; return getMessagesHigherThan(state.db, state.lastCursorId).then(function (newerMessages) { var useMessages = newerMessages @@ -1186,21 +1046,18 @@ function readNewMessages(state) { * there is a bug in iOS where the msgObj can be undefined some times * so we filter them out * @link https://github.com/pubkey/broadcast-channel/issues/19 - */ - .filter(function (msgObj) { + */.filter(function (msgObj) { return !!msgObj; }).map(function (msgObj) { if (msgObj.id > state.lastCursorId) { state.lastCursorId = msgObj.id; } - return msgObj; }).filter(function (msgObj) { return _filterMessage(msgObj, state); }).sort(function (msgObjA, msgObjB) { return msgObjA.time - msgObjB.time; }); // sort by time - useMessages.forEach(function (msgObj) { if (state.messagesCallback) { state.eMIs.add(msgObj.id); @@ -1210,12 +1067,10 @@ function readNewMessages(state) { return _util.PROMISE_RESOLVED_VOID; }); } - function close(channelState) { channelState.closed = true; channelState.db.close(); } - function postMessage(channelState, messageJson) { channelState.writeBlockPromise = channelState.writeBlockPromise.then(function () { return writeMessage(channelState.db, channelState.uuid, messageJson); @@ -1227,27 +1082,21 @@ function postMessage(channelState, messageJson) { }); return channelState.writeBlockPromise; } - function onMessage(channelState, fn, time) { channelState.messagesCallbackTime = time; channelState.messagesCallback = fn; readNewMessages(channelState); } - function canBeUsed() { var idb = getIdb(); - if (!idb) { return false; } - return true; } - function averageResponseTime(options) { return options.idb.fallbackInterval * 2; } - var _default = { create: create, close: close, @@ -1278,13 +1127,9 @@ exports.postMessage = postMessage; exports.removeStorageEventListener = removeStorageEventListener; exports.storageKey = storageKey; exports.type = void 0; - var _obliviousSet = require("oblivious-set"); - var _options = require("../options.js"); - var _util = require("../util.js"); - /** * A localStorage-only method which uses localstorage and its 'storage'-event * This does not work inside of webworkers because they have no access to locastorage @@ -1292,41 +1137,38 @@ var _util = require("../util.js"); * @link https://caniuse.com/#feat=namevalue-storage * @link https://caniuse.com/#feat=indexeddb */ + var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var KEY_PREFIX = 'pubkey.broadcastChannel-'; var type = 'localstorage'; + /** * copied from crosstab * @link https://github.com/tejacques/crosstab/blob/master/src/crosstab.js#L32 */ - exports.type = type; - function getLocalStorage() { var localStorage; if (typeof window === 'undefined') return null; - try { localStorage = window.localStorage; localStorage = window['ie8-eventlistener/storage'] || window.localStorage; - } catch (e) {// New versions of Firefox throw a Security exception + } catch (e) { + // New versions of Firefox throw a Security exception // if cookies are disabled. See // https://bugzilla.mozilla.org/show_bug.cgi?id=1028153 } - return localStorage; } - function storageKey(channelName) { return KEY_PREFIX + channelName; } + /** * writes the new message to the storage * and fires the storage-event so other readers can find it */ - - function postMessage(channelState, messageJson) { return new Promise(function (res) { (0, _util.sleep)().then(function () { @@ -1339,12 +1181,12 @@ function postMessage(channelState, messageJson) { }; var value = JSON.stringify(writeObj); getLocalStorage().setItem(key, value); + /** * StorageEvent does not fire the 'storage' event * in the window that changes the state of the local storage. * So we fire it manually */ - var ev = document.createEvent('Event'); ev.initEvent('storage', true, true); ev.key = key; @@ -1354,52 +1196,42 @@ function postMessage(channelState, messageJson) { }); }); } - function addStorageEventListener(channelName, fn) { var key = storageKey(channelName); - var listener = function listener(ev) { if (ev.key === key) { fn(JSON.parse(ev.newValue)); } }; - window.addEventListener('storage', listener); return listener; } - function removeStorageEventListener(listener) { window.removeEventListener('storage', listener); } - function create(channelName, options) { options = (0, _options.fillOptionsWithDefaults)(options); - if (!canBeUsed()) { throw new Error('BroadcastChannel: localstorage cannot be used'); } - var uuid = (0, _util.randomToken)(); + /** * eMIs * contains all messages that have been emitted before * @type {ObliviousSet} */ - var eMIs = new _obliviousSet.ObliviousSet(options.localstorage.removeTimeout); var state = { channelName: channelName, uuid: uuid, eMIs: eMIs // emittedMessagesIds - }; + state.listener = addStorageEventListener(channelName, function (msgObj) { if (!state.messagesCallback) return; // no listener - if (msgObj.uuid === uuid) return; // own message - if (!msgObj.token || eMIs.has(msgObj.token)) return; // already emitted - if (msgObj.data.time && msgObj.data.time < state.messagesCallbackTime) return; // too old eMIs.add(msgObj.token); @@ -1407,20 +1239,16 @@ function create(channelName, options) { }); return state; } - function close(channelState) { removeStorageEventListener(channelState.listener); } - function onMessage(channelState, fn, time) { channelState.messagesCallbackTime = time; channelState.messagesCallback = fn; } - function canBeUsed() { var ls = getLocalStorage(); if (!ls) return false; - try { var key = '__broadcastchannel_check'; ls.setItem(key, 'works'); @@ -1431,22 +1259,17 @@ function canBeUsed() { // https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API#Private_Browsing_Incognito_modes return false; } - return true; } - function averageResponseTime() { var defaultTime = 120; var userAgent = navigator.userAgent.toLowerCase(); - if (userAgent.includes('safari') && !userAgent.includes('chrome')) { // safari is much slower so this time is higher return defaultTime * 2; } - return defaultTime; } - var _default = { create: create, close: close, @@ -1472,20 +1295,16 @@ exports.microSeconds = exports["default"] = void 0; exports.onMessage = onMessage; exports.postMessage = postMessage; exports.type = void 0; - var _util = require("../util.js"); - var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var type = 'native'; exports.type = type; - function create(channelName) { var state = { messagesCallback: null, bc: new BroadcastChannel(channelName), subFns: [] // subscriberFunctions - }; state.bc.onmessage = function (msg) { @@ -1493,15 +1312,12 @@ function create(channelName) { state.messagesCallback(msg.data); } }; - return state; } - function close(channelState) { channelState.bc.close(); channelState.subFns = []; } - function postMessage(channelState, messageJson) { try { channelState.bc.postMessage(messageJson, false); @@ -1510,31 +1326,25 @@ function postMessage(channelState, messageJson) { return Promise.reject(err); } } - function onMessage(channelState, fn) { channelState.messagesCallback = fn; } - function canBeUsed() { if (typeof window === 'undefined') { return false; } - if (typeof BroadcastChannel === 'function') { if (BroadcastChannel._pubkey) { throw new Error('BroadcastChannel: Do not overwrite window.BroadcastChannel with this module, this is not a polyfill'); } - return true; } else { return false; } } - function averageResponseTime() { return 150; } - var _default = { create: create, close: close, @@ -1560,15 +1370,12 @@ exports.microSeconds = exports["default"] = void 0; exports.onMessage = onMessage; exports.postMessage = postMessage; exports.type = void 0; - var _util = require("../util.js"); - var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var type = 'simulate'; exports.type = type; var SIMULATE_CHANNELS = new Set(); - function create(channelName) { var state = { name: channelName, @@ -1577,11 +1384,9 @@ function create(channelName) { SIMULATE_CHANNELS.add(state); return state; } - function close(channelState) { SIMULATE_CHANNELS["delete"](channelState); } - function postMessage(channelState, messageJson) { return new Promise(function (res) { return setTimeout(function () { @@ -1599,19 +1404,15 @@ function postMessage(channelState, messageJson) { }, 5); }); } - function onMessage(channelState, fn) { channelState.messagesCallback = fn; } - function canBeUsed() { return true; } - function averageResponseTime() { return 5; } - var _default = { create: create, close: close, @@ -1630,33 +1431,35 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.fillOptionsWithDefaults = fillOptionsWithDefaults; - function fillOptionsWithDefaults() { var originalOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var options = JSON.parse(JSON.stringify(originalOptions)); // main - - if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true; // indexed-db + var options = JSON.parse(JSON.stringify(originalOptions)); - if (!options.idb) options.idb = {}; // after this time the messages get deleted + // main + if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true; + // indexed-db + if (!options.idb) options.idb = {}; + // after this time the messages get deleted if (!options.idb.ttl) options.idb.ttl = 1000 * 45; - if (!options.idb.fallbackInterval) options.idb.fallbackInterval = 150; // handles abrupt db onclose events. - - if (originalOptions.idb && typeof originalOptions.idb.onclose === 'function') options.idb.onclose = originalOptions.idb.onclose; // localstorage + if (!options.idb.fallbackInterval) options.idb.fallbackInterval = 150; + // handles abrupt db onclose events. + if (originalOptions.idb && typeof originalOptions.idb.onclose === 'function') options.idb.onclose = originalOptions.idb.onclose; + // localstorage if (!options.localstorage) options.localstorage = {}; - if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60; // custom methods + if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60; - if (originalOptions.methods) options.methods = originalOptions.methods; // node + // custom methods + if (originalOptions.methods) options.methods = originalOptions.methods; + // node if (!options.node) options.node = {}; if (!options.node.ttl) options.node.ttl = 1000 * 60 * 2; // 2 minutes; - /** * On linux use 'ulimit -Hn' to get the limit of open files. * On ubuntu this was 4096 for me, so we use half of that as maxParallelWrites default. */ - if (!options.node.maxParallelWrites) options.node.maxParallelWrites = 2048; if (typeof options.node.useFastPath === 'undefined') options.node.useFastPath = true; return options; @@ -1673,7 +1476,6 @@ exports.microSeconds = microSeconds; exports.randomInt = randomInt; exports.randomToken = randomToken; exports.sleep = sleep; - /** * returns true if the given object is a promise */ @@ -1684,14 +1486,12 @@ function isPromise(obj) { return false; } } - var PROMISE_RESOLVED_FALSE = Promise.resolve(false); exports.PROMISE_RESOLVED_FALSE = PROMISE_RESOLVED_FALSE; var PROMISE_RESOLVED_TRUE = Promise.resolve(true); exports.PROMISE_RESOLVED_TRUE = PROMISE_RESOLVED_TRUE; var PROMISE_RESOLVED_VOID = Promise.resolve(); exports.PROMISE_RESOLVED_VOID = PROMISE_RESOLVED_VOID; - function sleep(time, resolveWith) { if (!time) time = 0; return new Promise(function (res) { @@ -1700,21 +1500,19 @@ function sleep(time, resolveWith) { }, time); }); } - function randomInt(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } + /** * https://stackoverflow.com/a/8084248 */ - - function randomToken() { return Math.random().toString(36).substring(2); } - var lastMs = 0; var additional = 0; + /** * returns the current time in micro-seconds, * WARNING: This is a pseudo-function @@ -1722,10 +1520,8 @@ var additional = 0; * This is enough in browsers, and this function will not be used in nodejs. * The main reason for this hack is to ensure that BroadcastChannel behaves equal to production when it is used in fast-running unit tests. */ - function microSeconds() { var ms = new Date().getTime(); - if (ms === lastMs) { additional++; return ms * 1000 + additional; @@ -1785,7 +1581,6 @@ function _interopRequireDefault(obj) { "default": obj }; } - module.exports = _interopRequireDefault, module.exports.__esModule = true, module.exports["default"] = module.exports; },{}],15:[function(require,module,exports){ function _typeof(obj) { @@ -1797,7 +1592,6 @@ function _typeof(obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, module.exports.__esModule = true, module.exports["default"] = module.exports), _typeof(obj); } - module.exports = _typeof, module.exports.__esModule = true, module.exports["default"] = module.exports; },{}],16:[function(require,module,exports){ @@ -8323,6 +8117,7 @@ var runtime = (function (exports) { var Op = Object.prototype; var hasOwn = Op.hasOwnProperty; + var defineProperty = Object.defineProperty || function (obj, key, desc) { obj[key] = desc.value; }; var undefined; // More compressible than void 0. var $Symbol = typeof Symbol === "function" ? Symbol : {}; var iteratorSymbol = $Symbol.iterator || "@@iterator"; @@ -8355,7 +8150,7 @@ var runtime = (function (exports) { // The ._invoke method unifies the implementations of the .next, // .throw, and .return methods. - generator._invoke = makeInvokeMethod(innerFn, self, context); + defineProperty(generator, "_invoke", { value: makeInvokeMethod(innerFn, self, context) }); return generator; } @@ -8416,8 +8211,12 @@ var runtime = (function (exports) { var Gp = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(IteratorPrototype); GeneratorFunction.prototype = GeneratorFunctionPrototype; - define(Gp, "constructor", GeneratorFunctionPrototype); - define(GeneratorFunctionPrototype, "constructor", GeneratorFunction); + defineProperty(Gp, "constructor", { value: GeneratorFunctionPrototype, configurable: true }); + defineProperty( + GeneratorFunctionPrototype, + "constructor", + { value: GeneratorFunction, configurable: true } + ); GeneratorFunction.displayName = define( GeneratorFunctionPrototype, toStringTagSymbol, @@ -8527,7 +8326,7 @@ var runtime = (function (exports) { // Define the unified helper method that is used to implement .next, // .throw, and .return (see defineIteratorMethods). - this._invoke = enqueue; + defineProperty(this, "_invoke", { value: enqueue }); } defineIteratorMethods(AsyncIterator.prototype); @@ -8765,7 +8564,8 @@ var runtime = (function (exports) { this.reset(true); } - exports.keys = function(object) { + exports.keys = function(val) { + var object = Object(val); var keys = []; for (var key in object) { keys.push(key); @@ -9175,19 +8975,17 @@ function getSize() { "use strict"; /* eslint-disable */ - /** * used in docs/index.html */ require('@babel/polyfill'); - var _require = require('../../'), - BroadcastChannel = _require.BroadcastChannel, - createLeaderElection = _require.createLeaderElection; - + BroadcastChannel = _require.BroadcastChannel, + createLeaderElection = _require.createLeaderElection; var channelName = 'demo'; -var channel = new BroadcastChannel(channelName); // leader election +var channel = new BroadcastChannel(channelName); +// leader election var leaderElector = createLeaderElection(channel); leaderElector.awaitLeadership().then(function () { console.log('is leader'); @@ -9196,11 +8994,9 @@ leaderElector.awaitLeadership().then(function () { var messageInput = document.getElementById('message-input'); var submitButton = document.getElementById('submit-button'); var messagesBox = document.getElementById('messages'); - messageInput.onkeyup = function () { if (messageInput.value !== '') submitButton.disabled = false;else submitButton.disabled = true; }; - submitButton.onclick = function () { if (submitButton.disabled) return;else { console.log('postMessage ' + messageInput.value); @@ -9209,14 +9005,12 @@ submitButton.onclick = function () { messageInput.value = ''; } }; - function addTextToMessageBox(text) { var textnode = document.createTextNode(text); var lineBreak = document.createElement('br'); messagesBox.appendChild(textnode); messagesBox.appendChild(lineBreak); } - channel.onmessage = function (message) { console.dir('recieved message: ' + message); addTextToMessageBox('recieved: ' + message); diff --git a/docs/leader-iframe.js b/docs/leader-iframe.js index 71abfcbe..8a4fecfd 100644 --- a/docs/leader-iframe.js +++ b/docs/leader-iframe.js @@ -7,13 +7,9 @@ Object.defineProperty(exports, "__esModule", { exports.OPEN_BROADCAST_CHANNELS = exports.BroadcastChannel = void 0; exports.clearNodeFolder = clearNodeFolder; exports.enforceOptions = enforceOptions; - var _util = require("./util.js"); - var _methodChooser = require("./method-chooser.js"); - var _options = require("./options.js"); - /** * Contains all open channels, * used in tests to ensure everything is closed. @@ -21,77 +17,73 @@ var _options = require("./options.js"); var OPEN_BROADCAST_CHANNELS = new Set(); exports.OPEN_BROADCAST_CHANNELS = OPEN_BROADCAST_CHANNELS; var lastId = 0; - var BroadcastChannel = function BroadcastChannel(name, options) { // identifier of the channel to debug stuff this.id = lastId++; OPEN_BROADCAST_CHANNELS.add(this); this.name = name; - if (ENFORCED_OPTIONS) { options = ENFORCED_OPTIONS; } - this.options = (0, _options.fillOptionsWithDefaults)(options); - this.method = (0, _methodChooser.chooseMethod)(this.options); // isListening + this.method = (0, _methodChooser.chooseMethod)(this.options); + // isListening this._iL = false; + /** * _onMessageListener * setting onmessage twice, * will overwrite the first listener */ - this._onML = null; + /** * _addEventListeners */ - this._addEL = { message: [], internal: [] }; + /** * Unsend message promises * where the sending is still in progress * @type {Set} */ - this._uMP = new Set(); + /** * _beforeClose * array of promises that will be awaited * before the channel is closed */ - this._befC = []; + /** * _preparePromise */ - this._prepP = null; - _prepareChannel(this); -}; // STATICS +}; + +// STATICS /** * used to identify if someone overwrites * window.BroadcastChannel with this * See methods/native.js */ - - exports.BroadcastChannel = BroadcastChannel; BroadcastChannel._pubkey = true; + /** * clears the tmp-folder if is node * @return {Promise} true if has run, false if not node */ - function clearNodeFolder(options) { options = (0, _options.fillOptionsWithDefaults)(options); var method = (0, _methodChooser.chooseMethod)(options); - if (method.type === 'node') { return method.clearNodeFolder().then(function () { return true; @@ -100,19 +92,17 @@ function clearNodeFolder(options) { return _util.PROMISE_RESOLVED_FALSE; } } + /** * if set, this method is enforced, * no mather what the options are */ - - var ENFORCED_OPTIONS; - function enforceOptions(options) { ENFORCED_OPTIONS = options; -} // PROTOTYPE - +} +// PROTOTYPE BroadcastChannel.prototype = { postMessage: function postMessage(msg) { if (this.closed) { @@ -124,87 +114,77 @@ BroadcastChannel.prototype = { */ JSON.stringify(msg)); } - return _post(this, 'message', msg); }, postInternal: function postInternal(msg) { return _post(this, 'internal', msg); }, - set onmessage(fn) { var time = this.method.microSeconds(); var listenObj = { time: time, fn: fn }; - _removeListenerObject(this, 'message', this._onML); - if (fn && typeof fn === 'function') { this._onML = listenObj; - _addListenerObject(this, 'message', listenObj); } else { this._onML = null; } }, - addEventListener: function addEventListener(type, fn) { var time = this.method.microSeconds(); var listenObj = { time: time, fn: fn }; - _addListenerObject(this, type, listenObj); }, removeEventListener: function removeEventListener(type, fn) { var obj = this._addEL[type].find(function (obj) { return obj.fn === fn; }); - _removeListenerObject(this, type, obj); }, close: function close() { var _this = this; - if (this.closed) { return; } - OPEN_BROADCAST_CHANNELS["delete"](this); this.closed = true; var awaitPrepare = this._prepP ? this._prepP : _util.PROMISE_RESOLVED_VOID; this._onML = null; this._addEL.message = []; - return awaitPrepare // wait until all current sending are processed + return awaitPrepare + // wait until all current sending are processed .then(function () { return Promise.all(Array.from(_this._uMP)); - }) // run before-close hooks + }) + // run before-close hooks .then(function () { return Promise.all(_this._befC.map(function (fn) { return fn(); })); - }) // close the channel + }) + // close the channel .then(function () { return _this.method.close(_this._state); }); }, - get type() { return this.method.type; }, - get isClosed() { return this.closed; } - }; + /** * Post a message over the channel * @returns {Promise} that resolved when the message sending is done */ - function _post(broadcastChannel, type, msg) { var time = broadcastChannel.method.microSeconds(); var msgObj = { @@ -214,25 +194,22 @@ function _post(broadcastChannel, type, msg) { }; var awaitPrepare = broadcastChannel._prepP ? broadcastChannel._prepP : _util.PROMISE_RESOLVED_VOID; return awaitPrepare.then(function () { - var sendPromise = broadcastChannel.method.postMessage(broadcastChannel._state, msgObj); // add/remove to unsend messages list + var sendPromise = broadcastChannel.method.postMessage(broadcastChannel._state, msgObj); + // add/remove to unsend messages list broadcastChannel._uMP.add(sendPromise); - sendPromise["catch"]().then(function () { return broadcastChannel._uMP["delete"](sendPromise); }); return sendPromise; }); } - function _prepareChannel(channel) { var maybePromise = channel.method.create(channel.name, channel.options); - if ((0, _util.isPromise)(maybePromise)) { channel._prepP = maybePromise; maybePromise.then(function (s) { // used in tests to simulate slow runtime - /*if (channel.options.prepareDelay) { await new Promise(res => setTimeout(res, this.options.prepareDelay)); }*/ @@ -242,30 +219,25 @@ function _prepareChannel(channel) { channel._state = maybePromise; } } - function _hasMessageListeners(channel) { if (channel._addEL.message.length > 0) return true; if (channel._addEL.internal.length > 0) return true; return false; } - function _addListenerObject(channel, type, obj) { channel._addEL[type].push(obj); - _startListening(channel); } - function _removeListenerObject(channel, type, obj) { channel._addEL[type] = channel._addEL[type].filter(function (o) { return o !== obj; }); - _stopListening(channel); } - function _startListening(channel) { if (!channel._iL && _hasMessageListeners(channel)) { // someone is listening, start subscribing + var listenerFn = function listenerFn(msgObj) { channel._addEL[msgObj.type].forEach(function (listenerObject) { /** @@ -279,15 +251,12 @@ function _startListening(channel) { */ var hundredMsInMicro = 100 * 1000; var minMessageTime = listenerObject.time - hundredMsInMicro; - if (msgObj.time >= minMessageTime) { listenerObject.fn(msgObj.data); } }); }; - var time = channel.method.microSeconds(); - if (channel._prepP) { channel._prepP.then(function () { channel._iL = true; @@ -299,7 +268,6 @@ function _startListening(channel) { } } } - function _stopListening(channel) { if (channel._iL && !_hasMessageListeners(channel)) { // noone is listening, stop subscribing @@ -312,7 +280,6 @@ function _stopListening(channel) { "use strict"; var _index = require("./index.js"); - /** * because babel can only export on default-attribute, * we use this for the non-module-build @@ -321,6 +288,7 @@ var _index = require("./index.js"); * but * var BroadcastChannel = require('broadcast-channel'); */ + module.exports = { BroadcastChannel: _index.BroadcastChannel, createLeaderElection: _index.createLeaderElection, @@ -370,9 +338,7 @@ Object.defineProperty(exports, "enforceOptions", { return _broadcastChannel.enforceOptions; } }); - var _broadcastChannel = require("./broadcast-channel.js"); - var _leaderElection = require("./leader-election.js"); },{"./broadcast-channel.js":1,"./leader-election.js":4}],4:[function(require,module,exports){ "use strict"; @@ -382,37 +348,30 @@ Object.defineProperty(exports, "__esModule", { }); exports.beLeader = beLeader; exports.createLeaderElection = createLeaderElection; - var _util = require("./util.js"); - var _unload = require("unload"); - var LeaderElection = function LeaderElection(broadcastChannel, options) { var _this = this; - this.broadcastChannel = broadcastChannel; this._options = options; this.isLeader = false; this.hasLeader = false; this.isDead = false; this.token = (0, _util.randomToken)(); + /** * Apply Queue, * used to ensure we do not run applyOnce() * in parallel. */ + this._aplQ = _util.PROMISE_RESOLVED_VOID; + // amount of unfinished applyOnce() calls + this._aplQC = 0; - this._aplQ = _util.PROMISE_RESOLVED_VOID; // amount of unfinished applyOnce() calls - - this._aplQC = 0; // things to clean up - + // things to clean up this._unl = []; // _unloads - this._lstns = []; // _listeners - this._dpL = function () {}; // onduplicate listener - - this._dpLC = false; // true when onduplicate called /** @@ -420,55 +379,47 @@ var LeaderElection = function LeaderElection(broadcastChannel, options) { * we still listen to messages to ensure the hasLeader flag * is set correctly. */ - var hasLeaderListener = function hasLeaderListener(msg) { if (msg.context === 'leader') { if (msg.action === 'death') { _this.hasLeader = false; } - if (msg.action === 'tell') { _this.hasLeader = true; } } }; - this.broadcastChannel.addEventListener('internal', hasLeaderListener); - this._lstns.push(hasLeaderListener); }; - LeaderElection.prototype = { /** * Returns true if the instance is leader, * false if not. * @async */ - applyOnce: function applyOnce( // true if the applyOnce() call came from the fallbackInterval cycle + applyOnce: function applyOnce( + // true if the applyOnce() call came from the fallbackInterval cycle isFromFallbackInterval) { var _this2 = this; - if (this.isLeader) { return (0, _util.sleep)(0, true); } - if (this.isDead) { return (0, _util.sleep)(0, false); } + /** * Already applying more then once, * -> wait for the apply queue to be finished. */ - - if (this._aplQC > 1) { return this._aplQ; } + /** * Add a new apply-run */ - - var applyRun = function applyRun() { /** * Optimization shortcuts. @@ -478,7 +429,6 @@ LeaderElection.prototype = { if (_this2.isLeader) { return _util.PROMISE_RESOLVED_TRUE; } - var stopCriteria = false; var stopCriteriaPromiseResolve; /** @@ -487,7 +437,6 @@ LeaderElection.prototype = { * have to await the responseTime when it is already clear * that the election failed. */ - var stopCriteriaPromise = new Promise(function (res) { stopCriteriaPromiseResolve = function stopCriteriaPromiseResolve() { stopCriteria = true; @@ -495,11 +444,9 @@ LeaderElection.prototype = { }; }); var recieved = []; - var handleMessage = function handleMessage(msg) { if (msg.context === 'leader' && msg.token != _this2.token) { recieved.push(msg); - if (msg.action === 'apply') { // other is applying if (msg.token > _this2.token) { @@ -510,7 +457,6 @@ LeaderElection.prototype = { stopCriteriaPromiseResolve(); } } - if (msg.action === 'tell') { // other is already leader stopCriteriaPromiseResolve(); @@ -518,8 +464,8 @@ LeaderElection.prototype = { } } }; - _this2.broadcastChannel.addEventListener('internal', handleMessage); + /** * If the applyOnce() call came from the fallbackInterval, * we can assume that the election runs in the background and @@ -530,26 +476,24 @@ LeaderElection.prototype = { * But also it takes longer which is not a problem because we anyway * run in the background. */ - - var waitForAnswerTime = isFromFallbackInterval ? _this2._options.responseTime * 4 : _this2._options.responseTime; - var applyPromise = _sendMessage(_this2, 'apply') // send out that this one is applying .then(function () { return Promise.race([(0, _util.sleep)(waitForAnswerTime), stopCriteriaPromise.then(function () { return Promise.reject(new Error()); })]); - }) // send again in case another instance was just created + }) + // send again in case another instance was just created .then(function () { return _sendMessage(_this2, 'apply'); - }) // let others time to respond + }) + // let others time to respond .then(function () { return Promise.race([(0, _util.sleep)(waitForAnswerTime), stopCriteriaPromise.then(function () { return Promise.reject(new Error()); })]); })["catch"](function () {}).then(function () { _this2.broadcastChannel.removeEventListener('internal', handleMessage); - if (!stopCriteria) { // no stop criteria -> own is leader return beLeader(_this2).then(function () { @@ -560,10 +504,8 @@ LeaderElection.prototype = { return false; } }); - return applyPromise; }; - this._aplQC = this._aplQC + 1; this._aplQ = this._aplQ.then(function () { return applyRun(); @@ -575,82 +517,68 @@ LeaderElection.prototype = { }); }, awaitLeadership: function awaitLeadership() { - if ( - /* _awaitLeadershipPromise */ + if ( /* _awaitLeadershipPromise */ !this._aLP) { this._aLP = _awaitLeadershipOnce(this); } - return this._aLP; }, - set onduplicate(fn) { this._dpL = fn; }, - die: function die() { var _this3 = this; - this._lstns.forEach(function (listener) { return _this3.broadcastChannel.removeEventListener('internal', listener); }); - this._lstns = []; - this._unl.forEach(function (uFn) { return uFn.remove(); }); - this._unl = []; - if (this.isLeader) { this.hasLeader = false; this.isLeader = false; } - this.isDead = true; return _sendMessage(this, 'death'); } }; + /** * @param leaderElector {LeaderElector} */ - function _awaitLeadershipOnce(leaderElector) { if (leaderElector.isLeader) { return _util.PROMISE_RESOLVED_VOID; } - return new Promise(function (res) { var resolved = false; - function finish() { if (resolved) { return; } - resolved = true; leaderElector.broadcastChannel.removeEventListener('internal', whenDeathListener); res(true); - } // try once now - + } + // try once now leaderElector.applyOnce().then(function () { if (leaderElector.isLeader) { finish(); } }); + /** * Try on fallbackInterval * @recursive */ - var tryOnFallBack = function tryOnFallBack() { return (0, _util.sleep)(leaderElector._options.fallbackInterval).then(function () { if (leaderElector.isDead || resolved) { return; } - if (leaderElector.isLeader) { finish(); } else { @@ -664,9 +592,9 @@ function _awaitLeadershipOnce(leaderElector) { } }); }; + tryOnFallBack(); - tryOnFallBack(); // try when other leader dies - + // try when other leader dies var whenDeathListener = function whenDeathListener(msg) { if (msg.context === 'leader' && msg.action === 'death') { leaderElector.hasLeader = false; @@ -677,17 +605,14 @@ function _awaitLeadershipOnce(leaderElector) { }); } }; - leaderElector.broadcastChannel.addEventListener('internal', whenDeathListener); - leaderElector._lstns.push(whenDeathListener); }); } + /** * sends and internal message over the broadcast-channel */ - - function _sendMessage(leaderElector, action) { var msgJson = { context: 'leader', @@ -696,21 +621,17 @@ function _sendMessage(leaderElector, action) { }; return leaderElector.broadcastChannel.postInternal(msgJson); } - function beLeader(leaderElector) { leaderElector.isLeader = true; leaderElector.hasLeader = true; var unloadFn = (0, _unload.add)(function () { return leaderElector.die(); }); - leaderElector._unl.push(unloadFn); - var isLeaderListener = function isLeaderListener(msg) { if (msg.context === 'leader' && msg.action === 'apply') { _sendMessage(leaderElector, 'tell'); } - if (msg.context === 'leader' && msg.action === 'tell' && !leaderElector._dpLC) { /** * another instance is also leader! @@ -721,49 +642,35 @@ function beLeader(leaderElector) { * @link https://github.com/pubkey/broadcast-channel/issues/385 */ leaderElector._dpLC = true; - leaderElector._dpL(); // message the lib user so the app can handle the problem - - _sendMessage(leaderElector, 'tell'); // ensure other leader also knows the problem - } }; leaderElector.broadcastChannel.addEventListener('internal', isLeaderListener); - leaderElector._lstns.push(isLeaderListener); - return _sendMessage(leaderElector, 'tell'); } - function fillOptionsWithDefaults(options, channel) { if (!options) options = {}; options = JSON.parse(JSON.stringify(options)); - if (!options.fallbackInterval) { options.fallbackInterval = 3000; } - if (!options.responseTime) { options.responseTime = channel.method.averageResponseTime(channel.options); } - return options; } - function createLeaderElection(channel, options) { if (channel._leaderElector) { throw new Error('BroadcastChannel already has a leader-elector'); } - options = fillOptionsWithDefaults(options, channel); var elector = new LeaderElection(channel, options); - channel._befC.push(function () { return elector.die(); }); - channel._leaderElector = elector; return elector; } @@ -771,59 +678,49 @@ function createLeaderElection(channel, options) { "use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); - var _typeof = require("@babel/runtime/helpers/typeof"); - Object.defineProperty(exports, "__esModule", { value: true }); exports.chooseMethod = chooseMethod; - var _native = _interopRequireDefault(require("./methods/native.js")); - var _indexedDb = _interopRequireDefault(require("./methods/indexed-db.js")); - var _localstorage = _interopRequireDefault(require("./methods/localstorage.js")); - var _simulate = _interopRequireDefault(require("./methods/simulate.js")); - - function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } - function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } - // the line below will be removed from es5/browser builds + // order is important -var METHODS = [_native["default"], // fastest +var METHODS = [_native["default"], +// fastest _indexedDb["default"], _localstorage["default"]]; - function chooseMethod(options) { - var chooseMethods = [].concat(options.methods, METHODS).filter(Boolean); // the line below will be removed from es5/browser builds + var chooseMethods = [].concat(options.methods, METHODS).filter(Boolean); + // the line below will be removed from es5/browser builds + // directly chosen if (options.type) { if (options.type === 'simulate') { // only use simulate-method if directly chosen return _simulate["default"]; } - var ret = chooseMethods.find(function (m) { return m.type === options.type; }); if (!ret) throw new Error('method-type ' + options.type + ' not found');else return ret; } + /** * if no webworker support is needed, * remove idb from the list so that localstorage is been chosen */ - - if (!options.webWorkerSupport) { chooseMethods = chooseMethods.filter(function (m) { return m.type !== 'idb'; }); } - var useMethod = chooseMethods.find(function (method) { return method.canBeUsed(); }); @@ -856,13 +753,9 @@ exports.postMessage = postMessage; exports.removeMessagesById = removeMessagesById; exports.type = void 0; exports.writeMessage = writeMessage; - var _util = require("../util.js"); - var _obliviousSet = require("oblivious-set"); - var _options = require("../options.js"); - /** * this method uses indexeddb to store the messages * There is currently no observerAPI for idb @@ -871,58 +764,54 @@ var _options = require("../options.js"); * When working on this, ensure to use these performance optimizations: * @link https://rxdb.info/slow-indexeddb.html */ + var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var DB_PREFIX = 'pubkey.broadcast-channel-0-'; var OBJECT_STORE_ID = 'messages'; + /** * Use relaxed durability for faster performance on all transactions. * @link https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/ */ - var TRANSACTION_SETTINGS = { durability: 'relaxed' }; exports.TRANSACTION_SETTINGS = TRANSACTION_SETTINGS; var type = 'idb'; exports.type = type; - function getIdb() { if (typeof indexedDB !== 'undefined') return indexedDB; - if (typeof window !== 'undefined') { if (typeof window.mozIndexedDB !== 'undefined') return window.mozIndexedDB; if (typeof window.webkitIndexedDB !== 'undefined') return window.webkitIndexedDB; if (typeof window.msIndexedDB !== 'undefined') return window.msIndexedDB; } - return false; } + /** * If possible, we should explicitly commit IndexedDB transactions * for better performance. * @link https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/ */ - - function commitIndexedDBTransaction(tx) { if (tx.commit) { tx.commit(); } } - function createDatabase(channelName) { - var IndexedDB = getIdb(); // create table + var IndexedDB = getIdb(); + // create table var dbName = DB_PREFIX + channelName; + /** * All IndexedDB databases are opened without version * because it is a bit faster, especially on firefox * @link http://nparashuram.com/IndexedDB/perf/#Open%20Database%20with%20version */ - var openRequest = IndexedDB.open(dbName); - openRequest.onupgradeneeded = function (ev) { var db = ev.target.result; db.createObjectStore(OBJECT_STORE_ID, { @@ -930,24 +819,21 @@ function createDatabase(channelName) { autoIncrement: true }); }; - var dbPromise = new Promise(function (res, rej) { openRequest.onerror = function (ev) { return rej(ev); }; - openRequest.onsuccess = function () { res(openRequest.result); }; }); return dbPromise; } + /** * writes the new message to the database * so other readers can find it */ - - function writeMessage(db, readerUuid, messageJson) { var time = new Date().getTime(); var writeObject = { @@ -960,17 +846,14 @@ function writeMessage(db, readerUuid, messageJson) { tx.oncomplete = function () { return res(); }; - tx.onerror = function (ev) { return rej(ev); }; - var objectStore = tx.objectStore(OBJECT_STORE_ID); objectStore.add(writeObject); commitIndexedDBTransaction(tx); }); } - function getAllMessages(db) { var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); @@ -978,10 +861,9 @@ function getAllMessages(db) { return new Promise(function (res) { objectStore.openCursor().onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { - ret.push(cursor.value); //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); - + ret.push(cursor.value); + //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); cursor["continue"](); } else { commitIndexedDBTransaction(tx); @@ -990,31 +872,28 @@ function getAllMessages(db) { }; }); } - function getMessagesHigherThan(db, lastCursorId) { var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); var ret = []; var keyRangeValue = IDBKeyRange.bound(lastCursorId + 1, Infinity); + /** * Optimization shortcut, * if getAll() can be used, do not use a cursor. * @link https://rxdb.info/slow-indexeddb.html */ - if (objectStore.getAll) { var getAllRequest = objectStore.getAll(keyRangeValue); return new Promise(function (res, rej) { getAllRequest.onerror = function (err) { return rej(err); }; - getAllRequest.onsuccess = function (e) { res(e.target.result); }; }); } - function openCursor() { // Occasionally Safari will fail on IDBKeyRange.bound, this // catches that error, having it open the cursor to the first @@ -1026,17 +905,13 @@ function getMessagesHigherThan(db, lastCursorId) { return objectStore.openCursor(); } } - return new Promise(function (res, rej) { var openCursorRequest = openCursor(); - openCursorRequest.onerror = function (err) { return rej(err); }; - openCursorRequest.onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { if (cursor.value.id < lastCursorId + 1) { cursor["continue"](lastCursorId + 1); @@ -1051,12 +926,10 @@ function getMessagesHigherThan(db, lastCursorId) { }; }); } - function removeMessagesById(channelState, ids) { if (channelState.closed) { return Promise.resolve([]); } - var tx = channelState.db.transaction(OBJECT_STORE_ID, 'readwrite', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); return Promise.all(ids.map(function (id) { @@ -1068,7 +941,6 @@ function removeMessagesById(channelState, ids) { }); })); } - function getOldMessages(db, ttl) { var olderThen = new Date().getTime() - ttl; var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS); @@ -1077,13 +949,11 @@ function getOldMessages(db, ttl) { return new Promise(function (res) { objectStore.openCursor().onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { var msgObk = cursor.value; - if (msgObk.time < olderThen) { - ret.push(msgObk); //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); - + ret.push(msgObk); + //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); cursor["continue"](); } else { // no more old messages, @@ -1097,7 +967,6 @@ function getOldMessages(db, ttl) { }; }); } - function cleanOldMessages(channelState) { return getOldMessages(channelState.db, channelState.options.idb.ttl).then(function (tooOld) { return removeMessagesById(channelState, tooOld.map(function (msg) { @@ -1105,7 +974,6 @@ function cleanOldMessages(channelState) { })); }); } - function create(channelName, options) { options = (0, _options.fillOptionsWithDefaults)(options); return createDatabase(channelName).then(function (db) { @@ -1115,7 +983,6 @@ function create(channelName, options) { channelName: channelName, options: options, uuid: (0, _util.randomToken)(), - /** * emittedMessagesIds * contains all messages that have been emitted before @@ -1128,30 +995,27 @@ function create(channelName, options) { readQueuePromises: [], db: db }; + /** * Handle abrupt closes that do not originate from db.close(). * This could happen, for example, if the underlying storage is * removed or if the user clears the database in the browser's * history preferences. */ - db.onclose = function () { state.closed = true; if (options.idb.onclose) options.idb.onclose(); }; + /** * if service-workers are used, * we have no 'storage'-event if they post a message, * therefore we also have to set an interval */ - - _readLoop(state); - return state; }); } - function _readLoop(state) { if (state.closed) return; readNewMessages(state).then(function () { @@ -1160,25 +1024,21 @@ function _readLoop(state) { return _readLoop(state); }); } - function _filterMessage(msgObj, state) { if (msgObj.uuid === state.uuid) return false; // send by own - if (state.eMIs.has(msgObj.id)) return false; // already emitted - if (msgObj.data.time < state.messagesCallbackTime) return false; // older then onMessageCallback - return true; } + /** * reads all new messages from the database and emits them */ - - function readNewMessages(state) { // channel already closed - if (state.closed) return _util.PROMISE_RESOLVED_VOID; // if no one is listening, we do not need to scan for new messages + if (state.closed) return _util.PROMISE_RESOLVED_VOID; + // if no one is listening, we do not need to scan for new messages if (!state.messagesCallback) return _util.PROMISE_RESOLVED_VOID; return getMessagesHigherThan(state.db, state.lastCursorId).then(function (newerMessages) { var useMessages = newerMessages @@ -1186,21 +1046,18 @@ function readNewMessages(state) { * there is a bug in iOS where the msgObj can be undefined some times * so we filter them out * @link https://github.com/pubkey/broadcast-channel/issues/19 - */ - .filter(function (msgObj) { + */.filter(function (msgObj) { return !!msgObj; }).map(function (msgObj) { if (msgObj.id > state.lastCursorId) { state.lastCursorId = msgObj.id; } - return msgObj; }).filter(function (msgObj) { return _filterMessage(msgObj, state); }).sort(function (msgObjA, msgObjB) { return msgObjA.time - msgObjB.time; }); // sort by time - useMessages.forEach(function (msgObj) { if (state.messagesCallback) { state.eMIs.add(msgObj.id); @@ -1210,12 +1067,10 @@ function readNewMessages(state) { return _util.PROMISE_RESOLVED_VOID; }); } - function close(channelState) { channelState.closed = true; channelState.db.close(); } - function postMessage(channelState, messageJson) { channelState.writeBlockPromise = channelState.writeBlockPromise.then(function () { return writeMessage(channelState.db, channelState.uuid, messageJson); @@ -1227,27 +1082,21 @@ function postMessage(channelState, messageJson) { }); return channelState.writeBlockPromise; } - function onMessage(channelState, fn, time) { channelState.messagesCallbackTime = time; channelState.messagesCallback = fn; readNewMessages(channelState); } - function canBeUsed() { var idb = getIdb(); - if (!idb) { return false; } - return true; } - function averageResponseTime(options) { return options.idb.fallbackInterval * 2; } - var _default = { create: create, close: close, @@ -1278,13 +1127,9 @@ exports.postMessage = postMessage; exports.removeStorageEventListener = removeStorageEventListener; exports.storageKey = storageKey; exports.type = void 0; - var _obliviousSet = require("oblivious-set"); - var _options = require("../options.js"); - var _util = require("../util.js"); - /** * A localStorage-only method which uses localstorage and its 'storage'-event * This does not work inside of webworkers because they have no access to locastorage @@ -1292,41 +1137,38 @@ var _util = require("../util.js"); * @link https://caniuse.com/#feat=namevalue-storage * @link https://caniuse.com/#feat=indexeddb */ + var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var KEY_PREFIX = 'pubkey.broadcastChannel-'; var type = 'localstorage'; + /** * copied from crosstab * @link https://github.com/tejacques/crosstab/blob/master/src/crosstab.js#L32 */ - exports.type = type; - function getLocalStorage() { var localStorage; if (typeof window === 'undefined') return null; - try { localStorage = window.localStorage; localStorage = window['ie8-eventlistener/storage'] || window.localStorage; - } catch (e) {// New versions of Firefox throw a Security exception + } catch (e) { + // New versions of Firefox throw a Security exception // if cookies are disabled. See // https://bugzilla.mozilla.org/show_bug.cgi?id=1028153 } - return localStorage; } - function storageKey(channelName) { return KEY_PREFIX + channelName; } + /** * writes the new message to the storage * and fires the storage-event so other readers can find it */ - - function postMessage(channelState, messageJson) { return new Promise(function (res) { (0, _util.sleep)().then(function () { @@ -1339,12 +1181,12 @@ function postMessage(channelState, messageJson) { }; var value = JSON.stringify(writeObj); getLocalStorage().setItem(key, value); + /** * StorageEvent does not fire the 'storage' event * in the window that changes the state of the local storage. * So we fire it manually */ - var ev = document.createEvent('Event'); ev.initEvent('storage', true, true); ev.key = key; @@ -1354,52 +1196,42 @@ function postMessage(channelState, messageJson) { }); }); } - function addStorageEventListener(channelName, fn) { var key = storageKey(channelName); - var listener = function listener(ev) { if (ev.key === key) { fn(JSON.parse(ev.newValue)); } }; - window.addEventListener('storage', listener); return listener; } - function removeStorageEventListener(listener) { window.removeEventListener('storage', listener); } - function create(channelName, options) { options = (0, _options.fillOptionsWithDefaults)(options); - if (!canBeUsed()) { throw new Error('BroadcastChannel: localstorage cannot be used'); } - var uuid = (0, _util.randomToken)(); + /** * eMIs * contains all messages that have been emitted before * @type {ObliviousSet} */ - var eMIs = new _obliviousSet.ObliviousSet(options.localstorage.removeTimeout); var state = { channelName: channelName, uuid: uuid, eMIs: eMIs // emittedMessagesIds - }; + state.listener = addStorageEventListener(channelName, function (msgObj) { if (!state.messagesCallback) return; // no listener - if (msgObj.uuid === uuid) return; // own message - if (!msgObj.token || eMIs.has(msgObj.token)) return; // already emitted - if (msgObj.data.time && msgObj.data.time < state.messagesCallbackTime) return; // too old eMIs.add(msgObj.token); @@ -1407,20 +1239,16 @@ function create(channelName, options) { }); return state; } - function close(channelState) { removeStorageEventListener(channelState.listener); } - function onMessage(channelState, fn, time) { channelState.messagesCallbackTime = time; channelState.messagesCallback = fn; } - function canBeUsed() { var ls = getLocalStorage(); if (!ls) return false; - try { var key = '__broadcastchannel_check'; ls.setItem(key, 'works'); @@ -1431,22 +1259,17 @@ function canBeUsed() { // https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API#Private_Browsing_Incognito_modes return false; } - return true; } - function averageResponseTime() { var defaultTime = 120; var userAgent = navigator.userAgent.toLowerCase(); - if (userAgent.includes('safari') && !userAgent.includes('chrome')) { // safari is much slower so this time is higher return defaultTime * 2; } - return defaultTime; } - var _default = { create: create, close: close, @@ -1472,20 +1295,16 @@ exports.microSeconds = exports["default"] = void 0; exports.onMessage = onMessage; exports.postMessage = postMessage; exports.type = void 0; - var _util = require("../util.js"); - var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var type = 'native'; exports.type = type; - function create(channelName) { var state = { messagesCallback: null, bc: new BroadcastChannel(channelName), subFns: [] // subscriberFunctions - }; state.bc.onmessage = function (msg) { @@ -1493,15 +1312,12 @@ function create(channelName) { state.messagesCallback(msg.data); } }; - return state; } - function close(channelState) { channelState.bc.close(); channelState.subFns = []; } - function postMessage(channelState, messageJson) { try { channelState.bc.postMessage(messageJson, false); @@ -1510,31 +1326,25 @@ function postMessage(channelState, messageJson) { return Promise.reject(err); } } - function onMessage(channelState, fn) { channelState.messagesCallback = fn; } - function canBeUsed() { if (typeof window === 'undefined') { return false; } - if (typeof BroadcastChannel === 'function') { if (BroadcastChannel._pubkey) { throw new Error('BroadcastChannel: Do not overwrite window.BroadcastChannel with this module, this is not a polyfill'); } - return true; } else { return false; } } - function averageResponseTime() { return 150; } - var _default = { create: create, close: close, @@ -1560,15 +1370,12 @@ exports.microSeconds = exports["default"] = void 0; exports.onMessage = onMessage; exports.postMessage = postMessage; exports.type = void 0; - var _util = require("../util.js"); - var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var type = 'simulate'; exports.type = type; var SIMULATE_CHANNELS = new Set(); - function create(channelName) { var state = { name: channelName, @@ -1577,11 +1384,9 @@ function create(channelName) { SIMULATE_CHANNELS.add(state); return state; } - function close(channelState) { SIMULATE_CHANNELS["delete"](channelState); } - function postMessage(channelState, messageJson) { return new Promise(function (res) { return setTimeout(function () { @@ -1599,19 +1404,15 @@ function postMessage(channelState, messageJson) { }, 5); }); } - function onMessage(channelState, fn) { channelState.messagesCallback = fn; } - function canBeUsed() { return true; } - function averageResponseTime() { return 5; } - var _default = { create: create, close: close, @@ -1630,33 +1431,35 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.fillOptionsWithDefaults = fillOptionsWithDefaults; - function fillOptionsWithDefaults() { var originalOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var options = JSON.parse(JSON.stringify(originalOptions)); // main - - if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true; // indexed-db + var options = JSON.parse(JSON.stringify(originalOptions)); - if (!options.idb) options.idb = {}; // after this time the messages get deleted + // main + if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true; + // indexed-db + if (!options.idb) options.idb = {}; + // after this time the messages get deleted if (!options.idb.ttl) options.idb.ttl = 1000 * 45; - if (!options.idb.fallbackInterval) options.idb.fallbackInterval = 150; // handles abrupt db onclose events. - - if (originalOptions.idb && typeof originalOptions.idb.onclose === 'function') options.idb.onclose = originalOptions.idb.onclose; // localstorage + if (!options.idb.fallbackInterval) options.idb.fallbackInterval = 150; + // handles abrupt db onclose events. + if (originalOptions.idb && typeof originalOptions.idb.onclose === 'function') options.idb.onclose = originalOptions.idb.onclose; + // localstorage if (!options.localstorage) options.localstorage = {}; - if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60; // custom methods + if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60; - if (originalOptions.methods) options.methods = originalOptions.methods; // node + // custom methods + if (originalOptions.methods) options.methods = originalOptions.methods; + // node if (!options.node) options.node = {}; if (!options.node.ttl) options.node.ttl = 1000 * 60 * 2; // 2 minutes; - /** * On linux use 'ulimit -Hn' to get the limit of open files. * On ubuntu this was 4096 for me, so we use half of that as maxParallelWrites default. */ - if (!options.node.maxParallelWrites) options.node.maxParallelWrites = 2048; if (typeof options.node.useFastPath === 'undefined') options.node.useFastPath = true; return options; @@ -1673,7 +1476,6 @@ exports.microSeconds = microSeconds; exports.randomInt = randomInt; exports.randomToken = randomToken; exports.sleep = sleep; - /** * returns true if the given object is a promise */ @@ -1684,14 +1486,12 @@ function isPromise(obj) { return false; } } - var PROMISE_RESOLVED_FALSE = Promise.resolve(false); exports.PROMISE_RESOLVED_FALSE = PROMISE_RESOLVED_FALSE; var PROMISE_RESOLVED_TRUE = Promise.resolve(true); exports.PROMISE_RESOLVED_TRUE = PROMISE_RESOLVED_TRUE; var PROMISE_RESOLVED_VOID = Promise.resolve(); exports.PROMISE_RESOLVED_VOID = PROMISE_RESOLVED_VOID; - function sleep(time, resolveWith) { if (!time) time = 0; return new Promise(function (res) { @@ -1700,21 +1500,19 @@ function sleep(time, resolveWith) { }, time); }); } - function randomInt(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } + /** * https://stackoverflow.com/a/8084248 */ - - function randomToken() { return Math.random().toString(36).substring(2); } - var lastMs = 0; var additional = 0; + /** * returns the current time in micro-seconds, * WARNING: This is a pseudo-function @@ -1722,10 +1520,8 @@ var additional = 0; * This is enough in browsers, and this function will not be used in nodejs. * The main reason for this hack is to ensure that BroadcastChannel behaves equal to production when it is used in fast-running unit tests. */ - function microSeconds() { var ms = new Date().getTime(); - if (ms === lastMs) { additional++; return ms * 1000 + additional; @@ -1785,7 +1581,6 @@ function _interopRequireDefault(obj) { "default": obj }; } - module.exports = _interopRequireDefault, module.exports.__esModule = true, module.exports["default"] = module.exports; },{}],15:[function(require,module,exports){ function _typeof(obj) { @@ -1797,7 +1592,6 @@ function _typeof(obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, module.exports.__esModule = true, module.exports["default"] = module.exports), _typeof(obj); } - module.exports = _typeof, module.exports.__esModule = true, module.exports["default"] = module.exports; },{}],16:[function(require,module,exports){ @@ -8323,6 +8117,7 @@ var runtime = (function (exports) { var Op = Object.prototype; var hasOwn = Op.hasOwnProperty; + var defineProperty = Object.defineProperty || function (obj, key, desc) { obj[key] = desc.value; }; var undefined; // More compressible than void 0. var $Symbol = typeof Symbol === "function" ? Symbol : {}; var iteratorSymbol = $Symbol.iterator || "@@iterator"; @@ -8355,7 +8150,7 @@ var runtime = (function (exports) { // The ._invoke method unifies the implementations of the .next, // .throw, and .return methods. - generator._invoke = makeInvokeMethod(innerFn, self, context); + defineProperty(generator, "_invoke", { value: makeInvokeMethod(innerFn, self, context) }); return generator; } @@ -8416,8 +8211,12 @@ var runtime = (function (exports) { var Gp = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(IteratorPrototype); GeneratorFunction.prototype = GeneratorFunctionPrototype; - define(Gp, "constructor", GeneratorFunctionPrototype); - define(GeneratorFunctionPrototype, "constructor", GeneratorFunction); + defineProperty(Gp, "constructor", { value: GeneratorFunctionPrototype, configurable: true }); + defineProperty( + GeneratorFunctionPrototype, + "constructor", + { value: GeneratorFunction, configurable: true } + ); GeneratorFunction.displayName = define( GeneratorFunctionPrototype, toStringTagSymbol, @@ -8527,7 +8326,7 @@ var runtime = (function (exports) { // Define the unified helper method that is used to implement .next, // .throw, and .return (see defineIteratorMethods). - this._invoke = enqueue; + defineProperty(this, "_invoke", { value: enqueue }); } defineIteratorMethods(AsyncIterator.prototype); @@ -8765,7 +8564,8 @@ var runtime = (function (exports) { this.reset(true); } - exports.keys = function(object) { + exports.keys = function(val) { + var object = Object(val); var keys = []; for (var key in object) { keys.push(key); @@ -9175,30 +8975,26 @@ function getSize() { "use strict"; var _util = require("./util.js"); - /* eslint-disable */ - /** * this isframe is used to test the leader-election * in the e2e tests and the demo-page * used in docs/leader-iframe.html */ -require('@babel/polyfill'); +require('@babel/polyfill'); var _require = require('../../'), - BroadcastChannel = _require.BroadcastChannel, - createLeaderElection = _require.createLeaderElection; - + BroadcastChannel = _require.BroadcastChannel, + createLeaderElection = _require.createLeaderElection; var channelName = (0, _util.getParameterByName)('channelName'); var methodType = (0, _util.getParameterByName)('methodType'); -var boxEl = document.getElementById('box'); // overwrite console.log +var boxEl = document.getElementById('box'); +// overwrite console.log var logBefore = console.log; - console.log = function (str) { logBefore('iframe: ' + str); }; - var channel = new BroadcastChannel(channelName, { type: methodType }); @@ -9217,8 +9013,8 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.getParameterByName = getParameterByName; - /* eslint no-useless-escape: "off" */ + // https://stackoverflow.com/a/901144/3443137 function getParameterByName(name, url) { if (!url) url = window.location.href; diff --git a/docs/worker.js b/docs/worker.js index 7601c48d..e2483a4e 100644 --- a/docs/worker.js +++ b/docs/worker.js @@ -7,13 +7,9 @@ Object.defineProperty(exports, "__esModule", { exports.OPEN_BROADCAST_CHANNELS = exports.BroadcastChannel = void 0; exports.clearNodeFolder = clearNodeFolder; exports.enforceOptions = enforceOptions; - var _util = require("./util.js"); - var _methodChooser = require("./method-chooser.js"); - var _options = require("./options.js"); - /** * Contains all open channels, * used in tests to ensure everything is closed. @@ -21,77 +17,73 @@ var _options = require("./options.js"); var OPEN_BROADCAST_CHANNELS = new Set(); exports.OPEN_BROADCAST_CHANNELS = OPEN_BROADCAST_CHANNELS; var lastId = 0; - var BroadcastChannel = function BroadcastChannel(name, options) { // identifier of the channel to debug stuff this.id = lastId++; OPEN_BROADCAST_CHANNELS.add(this); this.name = name; - if (ENFORCED_OPTIONS) { options = ENFORCED_OPTIONS; } - this.options = (0, _options.fillOptionsWithDefaults)(options); - this.method = (0, _methodChooser.chooseMethod)(this.options); // isListening + this.method = (0, _methodChooser.chooseMethod)(this.options); + // isListening this._iL = false; + /** * _onMessageListener * setting onmessage twice, * will overwrite the first listener */ - this._onML = null; + /** * _addEventListeners */ - this._addEL = { message: [], internal: [] }; + /** * Unsend message promises * where the sending is still in progress * @type {Set} */ - this._uMP = new Set(); + /** * _beforeClose * array of promises that will be awaited * before the channel is closed */ - this._befC = []; + /** * _preparePromise */ - this._prepP = null; - _prepareChannel(this); -}; // STATICS +}; + +// STATICS /** * used to identify if someone overwrites * window.BroadcastChannel with this * See methods/native.js */ - - exports.BroadcastChannel = BroadcastChannel; BroadcastChannel._pubkey = true; + /** * clears the tmp-folder if is node * @return {Promise} true if has run, false if not node */ - function clearNodeFolder(options) { options = (0, _options.fillOptionsWithDefaults)(options); var method = (0, _methodChooser.chooseMethod)(options); - if (method.type === 'node') { return method.clearNodeFolder().then(function () { return true; @@ -100,19 +92,17 @@ function clearNodeFolder(options) { return _util.PROMISE_RESOLVED_FALSE; } } + /** * if set, this method is enforced, * no mather what the options are */ - - var ENFORCED_OPTIONS; - function enforceOptions(options) { ENFORCED_OPTIONS = options; -} // PROTOTYPE - +} +// PROTOTYPE BroadcastChannel.prototype = { postMessage: function postMessage(msg) { if (this.closed) { @@ -124,87 +114,77 @@ BroadcastChannel.prototype = { */ JSON.stringify(msg)); } - return _post(this, 'message', msg); }, postInternal: function postInternal(msg) { return _post(this, 'internal', msg); }, - set onmessage(fn) { var time = this.method.microSeconds(); var listenObj = { time: time, fn: fn }; - _removeListenerObject(this, 'message', this._onML); - if (fn && typeof fn === 'function') { this._onML = listenObj; - _addListenerObject(this, 'message', listenObj); } else { this._onML = null; } }, - addEventListener: function addEventListener(type, fn) { var time = this.method.microSeconds(); var listenObj = { time: time, fn: fn }; - _addListenerObject(this, type, listenObj); }, removeEventListener: function removeEventListener(type, fn) { var obj = this._addEL[type].find(function (obj) { return obj.fn === fn; }); - _removeListenerObject(this, type, obj); }, close: function close() { var _this = this; - if (this.closed) { return; } - OPEN_BROADCAST_CHANNELS["delete"](this); this.closed = true; var awaitPrepare = this._prepP ? this._prepP : _util.PROMISE_RESOLVED_VOID; this._onML = null; this._addEL.message = []; - return awaitPrepare // wait until all current sending are processed + return awaitPrepare + // wait until all current sending are processed .then(function () { return Promise.all(Array.from(_this._uMP)); - }) // run before-close hooks + }) + // run before-close hooks .then(function () { return Promise.all(_this._befC.map(function (fn) { return fn(); })); - }) // close the channel + }) + // close the channel .then(function () { return _this.method.close(_this._state); }); }, - get type() { return this.method.type; }, - get isClosed() { return this.closed; } - }; + /** * Post a message over the channel * @returns {Promise} that resolved when the message sending is done */ - function _post(broadcastChannel, type, msg) { var time = broadcastChannel.method.microSeconds(); var msgObj = { @@ -214,25 +194,22 @@ function _post(broadcastChannel, type, msg) { }; var awaitPrepare = broadcastChannel._prepP ? broadcastChannel._prepP : _util.PROMISE_RESOLVED_VOID; return awaitPrepare.then(function () { - var sendPromise = broadcastChannel.method.postMessage(broadcastChannel._state, msgObj); // add/remove to unsend messages list + var sendPromise = broadcastChannel.method.postMessage(broadcastChannel._state, msgObj); + // add/remove to unsend messages list broadcastChannel._uMP.add(sendPromise); - sendPromise["catch"]().then(function () { return broadcastChannel._uMP["delete"](sendPromise); }); return sendPromise; }); } - function _prepareChannel(channel) { var maybePromise = channel.method.create(channel.name, channel.options); - if ((0, _util.isPromise)(maybePromise)) { channel._prepP = maybePromise; maybePromise.then(function (s) { // used in tests to simulate slow runtime - /*if (channel.options.prepareDelay) { await new Promise(res => setTimeout(res, this.options.prepareDelay)); }*/ @@ -242,30 +219,25 @@ function _prepareChannel(channel) { channel._state = maybePromise; } } - function _hasMessageListeners(channel) { if (channel._addEL.message.length > 0) return true; if (channel._addEL.internal.length > 0) return true; return false; } - function _addListenerObject(channel, type, obj) { channel._addEL[type].push(obj); - _startListening(channel); } - function _removeListenerObject(channel, type, obj) { channel._addEL[type] = channel._addEL[type].filter(function (o) { return o !== obj; }); - _stopListening(channel); } - function _startListening(channel) { if (!channel._iL && _hasMessageListeners(channel)) { // someone is listening, start subscribing + var listenerFn = function listenerFn(msgObj) { channel._addEL[msgObj.type].forEach(function (listenerObject) { /** @@ -279,15 +251,12 @@ function _startListening(channel) { */ var hundredMsInMicro = 100 * 1000; var minMessageTime = listenerObject.time - hundredMsInMicro; - if (msgObj.time >= minMessageTime) { listenerObject.fn(msgObj.data); } }); }; - var time = channel.method.microSeconds(); - if (channel._prepP) { channel._prepP.then(function () { channel._iL = true; @@ -299,7 +268,6 @@ function _startListening(channel) { } } } - function _stopListening(channel) { if (channel._iL && !_hasMessageListeners(channel)) { // noone is listening, stop subscribing @@ -312,7 +280,6 @@ function _stopListening(channel) { "use strict"; var _index = require("./index.js"); - /** * because babel can only export on default-attribute, * we use this for the non-module-build @@ -321,6 +288,7 @@ var _index = require("./index.js"); * but * var BroadcastChannel = require('broadcast-channel'); */ + module.exports = { BroadcastChannel: _index.BroadcastChannel, createLeaderElection: _index.createLeaderElection, @@ -370,9 +338,7 @@ Object.defineProperty(exports, "enforceOptions", { return _broadcastChannel.enforceOptions; } }); - var _broadcastChannel = require("./broadcast-channel.js"); - var _leaderElection = require("./leader-election.js"); },{"./broadcast-channel.js":1,"./leader-election.js":4}],4:[function(require,module,exports){ "use strict"; @@ -382,37 +348,30 @@ Object.defineProperty(exports, "__esModule", { }); exports.beLeader = beLeader; exports.createLeaderElection = createLeaderElection; - var _util = require("./util.js"); - var _unload = require("unload"); - var LeaderElection = function LeaderElection(broadcastChannel, options) { var _this = this; - this.broadcastChannel = broadcastChannel; this._options = options; this.isLeader = false; this.hasLeader = false; this.isDead = false; this.token = (0, _util.randomToken)(); + /** * Apply Queue, * used to ensure we do not run applyOnce() * in parallel. */ + this._aplQ = _util.PROMISE_RESOLVED_VOID; + // amount of unfinished applyOnce() calls + this._aplQC = 0; - this._aplQ = _util.PROMISE_RESOLVED_VOID; // amount of unfinished applyOnce() calls - - this._aplQC = 0; // things to clean up - + // things to clean up this._unl = []; // _unloads - this._lstns = []; // _listeners - this._dpL = function () {}; // onduplicate listener - - this._dpLC = false; // true when onduplicate called /** @@ -420,55 +379,47 @@ var LeaderElection = function LeaderElection(broadcastChannel, options) { * we still listen to messages to ensure the hasLeader flag * is set correctly. */ - var hasLeaderListener = function hasLeaderListener(msg) { if (msg.context === 'leader') { if (msg.action === 'death') { _this.hasLeader = false; } - if (msg.action === 'tell') { _this.hasLeader = true; } } }; - this.broadcastChannel.addEventListener('internal', hasLeaderListener); - this._lstns.push(hasLeaderListener); }; - LeaderElection.prototype = { /** * Returns true if the instance is leader, * false if not. * @async */ - applyOnce: function applyOnce( // true if the applyOnce() call came from the fallbackInterval cycle + applyOnce: function applyOnce( + // true if the applyOnce() call came from the fallbackInterval cycle isFromFallbackInterval) { var _this2 = this; - if (this.isLeader) { return (0, _util.sleep)(0, true); } - if (this.isDead) { return (0, _util.sleep)(0, false); } + /** * Already applying more then once, * -> wait for the apply queue to be finished. */ - - if (this._aplQC > 1) { return this._aplQ; } + /** * Add a new apply-run */ - - var applyRun = function applyRun() { /** * Optimization shortcuts. @@ -478,7 +429,6 @@ LeaderElection.prototype = { if (_this2.isLeader) { return _util.PROMISE_RESOLVED_TRUE; } - var stopCriteria = false; var stopCriteriaPromiseResolve; /** @@ -487,7 +437,6 @@ LeaderElection.prototype = { * have to await the responseTime when it is already clear * that the election failed. */ - var stopCriteriaPromise = new Promise(function (res) { stopCriteriaPromiseResolve = function stopCriteriaPromiseResolve() { stopCriteria = true; @@ -495,11 +444,9 @@ LeaderElection.prototype = { }; }); var recieved = []; - var handleMessage = function handleMessage(msg) { if (msg.context === 'leader' && msg.token != _this2.token) { recieved.push(msg); - if (msg.action === 'apply') { // other is applying if (msg.token > _this2.token) { @@ -510,7 +457,6 @@ LeaderElection.prototype = { stopCriteriaPromiseResolve(); } } - if (msg.action === 'tell') { // other is already leader stopCriteriaPromiseResolve(); @@ -518,8 +464,8 @@ LeaderElection.prototype = { } } }; - _this2.broadcastChannel.addEventListener('internal', handleMessage); + /** * If the applyOnce() call came from the fallbackInterval, * we can assume that the election runs in the background and @@ -530,26 +476,24 @@ LeaderElection.prototype = { * But also it takes longer which is not a problem because we anyway * run in the background. */ - - var waitForAnswerTime = isFromFallbackInterval ? _this2._options.responseTime * 4 : _this2._options.responseTime; - var applyPromise = _sendMessage(_this2, 'apply') // send out that this one is applying .then(function () { return Promise.race([(0, _util.sleep)(waitForAnswerTime), stopCriteriaPromise.then(function () { return Promise.reject(new Error()); })]); - }) // send again in case another instance was just created + }) + // send again in case another instance was just created .then(function () { return _sendMessage(_this2, 'apply'); - }) // let others time to respond + }) + // let others time to respond .then(function () { return Promise.race([(0, _util.sleep)(waitForAnswerTime), stopCriteriaPromise.then(function () { return Promise.reject(new Error()); })]); })["catch"](function () {}).then(function () { _this2.broadcastChannel.removeEventListener('internal', handleMessage); - if (!stopCriteria) { // no stop criteria -> own is leader return beLeader(_this2).then(function () { @@ -560,10 +504,8 @@ LeaderElection.prototype = { return false; } }); - return applyPromise; }; - this._aplQC = this._aplQC + 1; this._aplQ = this._aplQ.then(function () { return applyRun(); @@ -575,82 +517,68 @@ LeaderElection.prototype = { }); }, awaitLeadership: function awaitLeadership() { - if ( - /* _awaitLeadershipPromise */ + if ( /* _awaitLeadershipPromise */ !this._aLP) { this._aLP = _awaitLeadershipOnce(this); } - return this._aLP; }, - set onduplicate(fn) { this._dpL = fn; }, - die: function die() { var _this3 = this; - this._lstns.forEach(function (listener) { return _this3.broadcastChannel.removeEventListener('internal', listener); }); - this._lstns = []; - this._unl.forEach(function (uFn) { return uFn.remove(); }); - this._unl = []; - if (this.isLeader) { this.hasLeader = false; this.isLeader = false; } - this.isDead = true; return _sendMessage(this, 'death'); } }; + /** * @param leaderElector {LeaderElector} */ - function _awaitLeadershipOnce(leaderElector) { if (leaderElector.isLeader) { return _util.PROMISE_RESOLVED_VOID; } - return new Promise(function (res) { var resolved = false; - function finish() { if (resolved) { return; } - resolved = true; leaderElector.broadcastChannel.removeEventListener('internal', whenDeathListener); res(true); - } // try once now - + } + // try once now leaderElector.applyOnce().then(function () { if (leaderElector.isLeader) { finish(); } }); + /** * Try on fallbackInterval * @recursive */ - var tryOnFallBack = function tryOnFallBack() { return (0, _util.sleep)(leaderElector._options.fallbackInterval).then(function () { if (leaderElector.isDead || resolved) { return; } - if (leaderElector.isLeader) { finish(); } else { @@ -664,9 +592,9 @@ function _awaitLeadershipOnce(leaderElector) { } }); }; + tryOnFallBack(); - tryOnFallBack(); // try when other leader dies - + // try when other leader dies var whenDeathListener = function whenDeathListener(msg) { if (msg.context === 'leader' && msg.action === 'death') { leaderElector.hasLeader = false; @@ -677,17 +605,14 @@ function _awaitLeadershipOnce(leaderElector) { }); } }; - leaderElector.broadcastChannel.addEventListener('internal', whenDeathListener); - leaderElector._lstns.push(whenDeathListener); }); } + /** * sends and internal message over the broadcast-channel */ - - function _sendMessage(leaderElector, action) { var msgJson = { context: 'leader', @@ -696,21 +621,17 @@ function _sendMessage(leaderElector, action) { }; return leaderElector.broadcastChannel.postInternal(msgJson); } - function beLeader(leaderElector) { leaderElector.isLeader = true; leaderElector.hasLeader = true; var unloadFn = (0, _unload.add)(function () { return leaderElector.die(); }); - leaderElector._unl.push(unloadFn); - var isLeaderListener = function isLeaderListener(msg) { if (msg.context === 'leader' && msg.action === 'apply') { _sendMessage(leaderElector, 'tell'); } - if (msg.context === 'leader' && msg.action === 'tell' && !leaderElector._dpLC) { /** * another instance is also leader! @@ -721,49 +642,35 @@ function beLeader(leaderElector) { * @link https://github.com/pubkey/broadcast-channel/issues/385 */ leaderElector._dpLC = true; - leaderElector._dpL(); // message the lib user so the app can handle the problem - - _sendMessage(leaderElector, 'tell'); // ensure other leader also knows the problem - } }; leaderElector.broadcastChannel.addEventListener('internal', isLeaderListener); - leaderElector._lstns.push(isLeaderListener); - return _sendMessage(leaderElector, 'tell'); } - function fillOptionsWithDefaults(options, channel) { if (!options) options = {}; options = JSON.parse(JSON.stringify(options)); - if (!options.fallbackInterval) { options.fallbackInterval = 3000; } - if (!options.responseTime) { options.responseTime = channel.method.averageResponseTime(channel.options); } - return options; } - function createLeaderElection(channel, options) { if (channel._leaderElector) { throw new Error('BroadcastChannel already has a leader-elector'); } - options = fillOptionsWithDefaults(options, channel); var elector = new LeaderElection(channel, options); - channel._befC.push(function () { return elector.die(); }); - channel._leaderElector = elector; return elector; } @@ -771,59 +678,49 @@ function createLeaderElection(channel, options) { "use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); - var _typeof = require("@babel/runtime/helpers/typeof"); - Object.defineProperty(exports, "__esModule", { value: true }); exports.chooseMethod = chooseMethod; - var _native = _interopRequireDefault(require("./methods/native.js")); - var _indexedDb = _interopRequireDefault(require("./methods/indexed-db.js")); - var _localstorage = _interopRequireDefault(require("./methods/localstorage.js")); - var _simulate = _interopRequireDefault(require("./methods/simulate.js")); - - function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } - function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } - // the line below will be removed from es5/browser builds + // order is important -var METHODS = [_native["default"], // fastest +var METHODS = [_native["default"], +// fastest _indexedDb["default"], _localstorage["default"]]; - function chooseMethod(options) { - var chooseMethods = [].concat(options.methods, METHODS).filter(Boolean); // the line below will be removed from es5/browser builds + var chooseMethods = [].concat(options.methods, METHODS).filter(Boolean); + // the line below will be removed from es5/browser builds + // directly chosen if (options.type) { if (options.type === 'simulate') { // only use simulate-method if directly chosen return _simulate["default"]; } - var ret = chooseMethods.find(function (m) { return m.type === options.type; }); if (!ret) throw new Error('method-type ' + options.type + ' not found');else return ret; } + /** * if no webworker support is needed, * remove idb from the list so that localstorage is been chosen */ - - if (!options.webWorkerSupport) { chooseMethods = chooseMethods.filter(function (m) { return m.type !== 'idb'; }); } - var useMethod = chooseMethods.find(function (method) { return method.canBeUsed(); }); @@ -856,13 +753,9 @@ exports.postMessage = postMessage; exports.removeMessagesById = removeMessagesById; exports.type = void 0; exports.writeMessage = writeMessage; - var _util = require("../util.js"); - var _obliviousSet = require("oblivious-set"); - var _options = require("../options.js"); - /** * this method uses indexeddb to store the messages * There is currently no observerAPI for idb @@ -871,58 +764,54 @@ var _options = require("../options.js"); * When working on this, ensure to use these performance optimizations: * @link https://rxdb.info/slow-indexeddb.html */ + var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var DB_PREFIX = 'pubkey.broadcast-channel-0-'; var OBJECT_STORE_ID = 'messages'; + /** * Use relaxed durability for faster performance on all transactions. * @link https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/ */ - var TRANSACTION_SETTINGS = { durability: 'relaxed' }; exports.TRANSACTION_SETTINGS = TRANSACTION_SETTINGS; var type = 'idb'; exports.type = type; - function getIdb() { if (typeof indexedDB !== 'undefined') return indexedDB; - if (typeof window !== 'undefined') { if (typeof window.mozIndexedDB !== 'undefined') return window.mozIndexedDB; if (typeof window.webkitIndexedDB !== 'undefined') return window.webkitIndexedDB; if (typeof window.msIndexedDB !== 'undefined') return window.msIndexedDB; } - return false; } + /** * If possible, we should explicitly commit IndexedDB transactions * for better performance. * @link https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/ */ - - function commitIndexedDBTransaction(tx) { if (tx.commit) { tx.commit(); } } - function createDatabase(channelName) { - var IndexedDB = getIdb(); // create table + var IndexedDB = getIdb(); + // create table var dbName = DB_PREFIX + channelName; + /** * All IndexedDB databases are opened without version * because it is a bit faster, especially on firefox * @link http://nparashuram.com/IndexedDB/perf/#Open%20Database%20with%20version */ - var openRequest = IndexedDB.open(dbName); - openRequest.onupgradeneeded = function (ev) { var db = ev.target.result; db.createObjectStore(OBJECT_STORE_ID, { @@ -930,24 +819,21 @@ function createDatabase(channelName) { autoIncrement: true }); }; - var dbPromise = new Promise(function (res, rej) { openRequest.onerror = function (ev) { return rej(ev); }; - openRequest.onsuccess = function () { res(openRequest.result); }; }); return dbPromise; } + /** * writes the new message to the database * so other readers can find it */ - - function writeMessage(db, readerUuid, messageJson) { var time = new Date().getTime(); var writeObject = { @@ -960,17 +846,14 @@ function writeMessage(db, readerUuid, messageJson) { tx.oncomplete = function () { return res(); }; - tx.onerror = function (ev) { return rej(ev); }; - var objectStore = tx.objectStore(OBJECT_STORE_ID); objectStore.add(writeObject); commitIndexedDBTransaction(tx); }); } - function getAllMessages(db) { var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); @@ -978,10 +861,9 @@ function getAllMessages(db) { return new Promise(function (res) { objectStore.openCursor().onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { - ret.push(cursor.value); //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); - + ret.push(cursor.value); + //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); cursor["continue"](); } else { commitIndexedDBTransaction(tx); @@ -990,31 +872,28 @@ function getAllMessages(db) { }; }); } - function getMessagesHigherThan(db, lastCursorId) { var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); var ret = []; var keyRangeValue = IDBKeyRange.bound(lastCursorId + 1, Infinity); + /** * Optimization shortcut, * if getAll() can be used, do not use a cursor. * @link https://rxdb.info/slow-indexeddb.html */ - if (objectStore.getAll) { var getAllRequest = objectStore.getAll(keyRangeValue); return new Promise(function (res, rej) { getAllRequest.onerror = function (err) { return rej(err); }; - getAllRequest.onsuccess = function (e) { res(e.target.result); }; }); } - function openCursor() { // Occasionally Safari will fail on IDBKeyRange.bound, this // catches that error, having it open the cursor to the first @@ -1026,17 +905,13 @@ function getMessagesHigherThan(db, lastCursorId) { return objectStore.openCursor(); } } - return new Promise(function (res, rej) { var openCursorRequest = openCursor(); - openCursorRequest.onerror = function (err) { return rej(err); }; - openCursorRequest.onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { if (cursor.value.id < lastCursorId + 1) { cursor["continue"](lastCursorId + 1); @@ -1051,12 +926,10 @@ function getMessagesHigherThan(db, lastCursorId) { }; }); } - function removeMessagesById(channelState, ids) { if (channelState.closed) { return Promise.resolve([]); } - var tx = channelState.db.transaction(OBJECT_STORE_ID, 'readwrite', TRANSACTION_SETTINGS); var objectStore = tx.objectStore(OBJECT_STORE_ID); return Promise.all(ids.map(function (id) { @@ -1068,7 +941,6 @@ function removeMessagesById(channelState, ids) { }); })); } - function getOldMessages(db, ttl) { var olderThen = new Date().getTime() - ttl; var tx = db.transaction(OBJECT_STORE_ID, 'readonly', TRANSACTION_SETTINGS); @@ -1077,13 +949,11 @@ function getOldMessages(db, ttl) { return new Promise(function (res) { objectStore.openCursor().onsuccess = function (ev) { var cursor = ev.target.result; - if (cursor) { var msgObk = cursor.value; - if (msgObk.time < olderThen) { - ret.push(msgObk); //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); - + ret.push(msgObk); + //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); cursor["continue"](); } else { // no more old messages, @@ -1097,7 +967,6 @@ function getOldMessages(db, ttl) { }; }); } - function cleanOldMessages(channelState) { return getOldMessages(channelState.db, channelState.options.idb.ttl).then(function (tooOld) { return removeMessagesById(channelState, tooOld.map(function (msg) { @@ -1105,7 +974,6 @@ function cleanOldMessages(channelState) { })); }); } - function create(channelName, options) { options = (0, _options.fillOptionsWithDefaults)(options); return createDatabase(channelName).then(function (db) { @@ -1115,7 +983,6 @@ function create(channelName, options) { channelName: channelName, options: options, uuid: (0, _util.randomToken)(), - /** * emittedMessagesIds * contains all messages that have been emitted before @@ -1128,30 +995,27 @@ function create(channelName, options) { readQueuePromises: [], db: db }; + /** * Handle abrupt closes that do not originate from db.close(). * This could happen, for example, if the underlying storage is * removed or if the user clears the database in the browser's * history preferences. */ - db.onclose = function () { state.closed = true; if (options.idb.onclose) options.idb.onclose(); }; + /** * if service-workers are used, * we have no 'storage'-event if they post a message, * therefore we also have to set an interval */ - - _readLoop(state); - return state; }); } - function _readLoop(state) { if (state.closed) return; readNewMessages(state).then(function () { @@ -1160,25 +1024,21 @@ function _readLoop(state) { return _readLoop(state); }); } - function _filterMessage(msgObj, state) { if (msgObj.uuid === state.uuid) return false; // send by own - if (state.eMIs.has(msgObj.id)) return false; // already emitted - if (msgObj.data.time < state.messagesCallbackTime) return false; // older then onMessageCallback - return true; } + /** * reads all new messages from the database and emits them */ - - function readNewMessages(state) { // channel already closed - if (state.closed) return _util.PROMISE_RESOLVED_VOID; // if no one is listening, we do not need to scan for new messages + if (state.closed) return _util.PROMISE_RESOLVED_VOID; + // if no one is listening, we do not need to scan for new messages if (!state.messagesCallback) return _util.PROMISE_RESOLVED_VOID; return getMessagesHigherThan(state.db, state.lastCursorId).then(function (newerMessages) { var useMessages = newerMessages @@ -1186,21 +1046,18 @@ function readNewMessages(state) { * there is a bug in iOS where the msgObj can be undefined some times * so we filter them out * @link https://github.com/pubkey/broadcast-channel/issues/19 - */ - .filter(function (msgObj) { + */.filter(function (msgObj) { return !!msgObj; }).map(function (msgObj) { if (msgObj.id > state.lastCursorId) { state.lastCursorId = msgObj.id; } - return msgObj; }).filter(function (msgObj) { return _filterMessage(msgObj, state); }).sort(function (msgObjA, msgObjB) { return msgObjA.time - msgObjB.time; }); // sort by time - useMessages.forEach(function (msgObj) { if (state.messagesCallback) { state.eMIs.add(msgObj.id); @@ -1210,12 +1067,10 @@ function readNewMessages(state) { return _util.PROMISE_RESOLVED_VOID; }); } - function close(channelState) { channelState.closed = true; channelState.db.close(); } - function postMessage(channelState, messageJson) { channelState.writeBlockPromise = channelState.writeBlockPromise.then(function () { return writeMessage(channelState.db, channelState.uuid, messageJson); @@ -1227,27 +1082,21 @@ function postMessage(channelState, messageJson) { }); return channelState.writeBlockPromise; } - function onMessage(channelState, fn, time) { channelState.messagesCallbackTime = time; channelState.messagesCallback = fn; readNewMessages(channelState); } - function canBeUsed() { var idb = getIdb(); - if (!idb) { return false; } - return true; } - function averageResponseTime(options) { return options.idb.fallbackInterval * 2; } - var _default = { create: create, close: close, @@ -1278,13 +1127,9 @@ exports.postMessage = postMessage; exports.removeStorageEventListener = removeStorageEventListener; exports.storageKey = storageKey; exports.type = void 0; - var _obliviousSet = require("oblivious-set"); - var _options = require("../options.js"); - var _util = require("../util.js"); - /** * A localStorage-only method which uses localstorage and its 'storage'-event * This does not work inside of webworkers because they have no access to locastorage @@ -1292,41 +1137,38 @@ var _util = require("../util.js"); * @link https://caniuse.com/#feat=namevalue-storage * @link https://caniuse.com/#feat=indexeddb */ + var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var KEY_PREFIX = 'pubkey.broadcastChannel-'; var type = 'localstorage'; + /** * copied from crosstab * @link https://github.com/tejacques/crosstab/blob/master/src/crosstab.js#L32 */ - exports.type = type; - function getLocalStorage() { var localStorage; if (typeof window === 'undefined') return null; - try { localStorage = window.localStorage; localStorage = window['ie8-eventlistener/storage'] || window.localStorage; - } catch (e) {// New versions of Firefox throw a Security exception + } catch (e) { + // New versions of Firefox throw a Security exception // if cookies are disabled. See // https://bugzilla.mozilla.org/show_bug.cgi?id=1028153 } - return localStorage; } - function storageKey(channelName) { return KEY_PREFIX + channelName; } + /** * writes the new message to the storage * and fires the storage-event so other readers can find it */ - - function postMessage(channelState, messageJson) { return new Promise(function (res) { (0, _util.sleep)().then(function () { @@ -1339,12 +1181,12 @@ function postMessage(channelState, messageJson) { }; var value = JSON.stringify(writeObj); getLocalStorage().setItem(key, value); + /** * StorageEvent does not fire the 'storage' event * in the window that changes the state of the local storage. * So we fire it manually */ - var ev = document.createEvent('Event'); ev.initEvent('storage', true, true); ev.key = key; @@ -1354,52 +1196,42 @@ function postMessage(channelState, messageJson) { }); }); } - function addStorageEventListener(channelName, fn) { var key = storageKey(channelName); - var listener = function listener(ev) { if (ev.key === key) { fn(JSON.parse(ev.newValue)); } }; - window.addEventListener('storage', listener); return listener; } - function removeStorageEventListener(listener) { window.removeEventListener('storage', listener); } - function create(channelName, options) { options = (0, _options.fillOptionsWithDefaults)(options); - if (!canBeUsed()) { throw new Error('BroadcastChannel: localstorage cannot be used'); } - var uuid = (0, _util.randomToken)(); + /** * eMIs * contains all messages that have been emitted before * @type {ObliviousSet} */ - var eMIs = new _obliviousSet.ObliviousSet(options.localstorage.removeTimeout); var state = { channelName: channelName, uuid: uuid, eMIs: eMIs // emittedMessagesIds - }; + state.listener = addStorageEventListener(channelName, function (msgObj) { if (!state.messagesCallback) return; // no listener - if (msgObj.uuid === uuid) return; // own message - if (!msgObj.token || eMIs.has(msgObj.token)) return; // already emitted - if (msgObj.data.time && msgObj.data.time < state.messagesCallbackTime) return; // too old eMIs.add(msgObj.token); @@ -1407,20 +1239,16 @@ function create(channelName, options) { }); return state; } - function close(channelState) { removeStorageEventListener(channelState.listener); } - function onMessage(channelState, fn, time) { channelState.messagesCallbackTime = time; channelState.messagesCallback = fn; } - function canBeUsed() { var ls = getLocalStorage(); if (!ls) return false; - try { var key = '__broadcastchannel_check'; ls.setItem(key, 'works'); @@ -1431,22 +1259,17 @@ function canBeUsed() { // https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API#Private_Browsing_Incognito_modes return false; } - return true; } - function averageResponseTime() { var defaultTime = 120; var userAgent = navigator.userAgent.toLowerCase(); - if (userAgent.includes('safari') && !userAgent.includes('chrome')) { // safari is much slower so this time is higher return defaultTime * 2; } - return defaultTime; } - var _default = { create: create, close: close, @@ -1472,20 +1295,16 @@ exports.microSeconds = exports["default"] = void 0; exports.onMessage = onMessage; exports.postMessage = postMessage; exports.type = void 0; - var _util = require("../util.js"); - var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var type = 'native'; exports.type = type; - function create(channelName) { var state = { messagesCallback: null, bc: new BroadcastChannel(channelName), subFns: [] // subscriberFunctions - }; state.bc.onmessage = function (msg) { @@ -1493,15 +1312,12 @@ function create(channelName) { state.messagesCallback(msg.data); } }; - return state; } - function close(channelState) { channelState.bc.close(); channelState.subFns = []; } - function postMessage(channelState, messageJson) { try { channelState.bc.postMessage(messageJson, false); @@ -1510,31 +1326,25 @@ function postMessage(channelState, messageJson) { return Promise.reject(err); } } - function onMessage(channelState, fn) { channelState.messagesCallback = fn; } - function canBeUsed() { if (typeof window === 'undefined') { return false; } - if (typeof BroadcastChannel === 'function') { if (BroadcastChannel._pubkey) { throw new Error('BroadcastChannel: Do not overwrite window.BroadcastChannel with this module, this is not a polyfill'); } - return true; } else { return false; } } - function averageResponseTime() { return 150; } - var _default = { create: create, close: close, @@ -1560,15 +1370,12 @@ exports.microSeconds = exports["default"] = void 0; exports.onMessage = onMessage; exports.postMessage = postMessage; exports.type = void 0; - var _util = require("../util.js"); - var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var type = 'simulate'; exports.type = type; var SIMULATE_CHANNELS = new Set(); - function create(channelName) { var state = { name: channelName, @@ -1577,11 +1384,9 @@ function create(channelName) { SIMULATE_CHANNELS.add(state); return state; } - function close(channelState) { SIMULATE_CHANNELS["delete"](channelState); } - function postMessage(channelState, messageJson) { return new Promise(function (res) { return setTimeout(function () { @@ -1599,19 +1404,15 @@ function postMessage(channelState, messageJson) { }, 5); }); } - function onMessage(channelState, fn) { channelState.messagesCallback = fn; } - function canBeUsed() { return true; } - function averageResponseTime() { return 5; } - var _default = { create: create, close: close, @@ -1630,33 +1431,35 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.fillOptionsWithDefaults = fillOptionsWithDefaults; - function fillOptionsWithDefaults() { var originalOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var options = JSON.parse(JSON.stringify(originalOptions)); // main + var options = JSON.parse(JSON.stringify(originalOptions)); - if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true; // indexed-db - - if (!options.idb) options.idb = {}; // after this time the messages get deleted + // main + if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true; + // indexed-db + if (!options.idb) options.idb = {}; + // after this time the messages get deleted if (!options.idb.ttl) options.idb.ttl = 1000 * 45; - if (!options.idb.fallbackInterval) options.idb.fallbackInterval = 150; // handles abrupt db onclose events. - - if (originalOptions.idb && typeof originalOptions.idb.onclose === 'function') options.idb.onclose = originalOptions.idb.onclose; // localstorage + if (!options.idb.fallbackInterval) options.idb.fallbackInterval = 150; + // handles abrupt db onclose events. + if (originalOptions.idb && typeof originalOptions.idb.onclose === 'function') options.idb.onclose = originalOptions.idb.onclose; + // localstorage if (!options.localstorage) options.localstorage = {}; - if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60; // custom methods + if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60; - if (originalOptions.methods) options.methods = originalOptions.methods; // node + // custom methods + if (originalOptions.methods) options.methods = originalOptions.methods; + // node if (!options.node) options.node = {}; if (!options.node.ttl) options.node.ttl = 1000 * 60 * 2; // 2 minutes; - /** * On linux use 'ulimit -Hn' to get the limit of open files. * On ubuntu this was 4096 for me, so we use half of that as maxParallelWrites default. */ - if (!options.node.maxParallelWrites) options.node.maxParallelWrites = 2048; if (typeof options.node.useFastPath === 'undefined') options.node.useFastPath = true; return options; @@ -1673,7 +1476,6 @@ exports.microSeconds = microSeconds; exports.randomInt = randomInt; exports.randomToken = randomToken; exports.sleep = sleep; - /** * returns true if the given object is a promise */ @@ -1684,14 +1486,12 @@ function isPromise(obj) { return false; } } - var PROMISE_RESOLVED_FALSE = Promise.resolve(false); exports.PROMISE_RESOLVED_FALSE = PROMISE_RESOLVED_FALSE; var PROMISE_RESOLVED_TRUE = Promise.resolve(true); exports.PROMISE_RESOLVED_TRUE = PROMISE_RESOLVED_TRUE; var PROMISE_RESOLVED_VOID = Promise.resolve(); exports.PROMISE_RESOLVED_VOID = PROMISE_RESOLVED_VOID; - function sleep(time, resolveWith) { if (!time) time = 0; return new Promise(function (res) { @@ -1700,21 +1500,19 @@ function sleep(time, resolveWith) { }, time); }); } - function randomInt(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } + /** * https://stackoverflow.com/a/8084248 */ - - function randomToken() { return Math.random().toString(36).substring(2); } - var lastMs = 0; var additional = 0; + /** * returns the current time in micro-seconds, * WARNING: This is a pseudo-function @@ -1722,10 +1520,8 @@ var additional = 0; * This is enough in browsers, and this function will not be used in nodejs. * The main reason for this hack is to ensure that BroadcastChannel behaves equal to production when it is used in fast-running unit tests. */ - function microSeconds() { var ms = new Date().getTime(); - if (ms === lastMs) { additional++; return ms * 1000 + additional; @@ -1785,7 +1581,6 @@ function _interopRequireDefault(obj) { "default": obj }; } - module.exports = _interopRequireDefault, module.exports.__esModule = true, module.exports["default"] = module.exports; },{}],15:[function(require,module,exports){ function _typeof(obj) { @@ -1797,7 +1592,6 @@ function _typeof(obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, module.exports.__esModule = true, module.exports["default"] = module.exports), _typeof(obj); } - module.exports = _typeof, module.exports.__esModule = true, module.exports["default"] = module.exports; },{}],16:[function(require,module,exports){ 'use strict'; @@ -11465,6 +11259,7 @@ var runtime = (function (exports) { var Op = Object.prototype; var hasOwn = Op.hasOwnProperty; + var defineProperty = Object.defineProperty || function (obj, key, desc) { obj[key] = desc.value; }; var undefined; // More compressible than void 0. var $Symbol = typeof Symbol === "function" ? Symbol : {}; var iteratorSymbol = $Symbol.iterator || "@@iterator"; @@ -11497,7 +11292,7 @@ var runtime = (function (exports) { // The ._invoke method unifies the implementations of the .next, // .throw, and .return methods. - generator._invoke = makeInvokeMethod(innerFn, self, context); + defineProperty(generator, "_invoke", { value: makeInvokeMethod(innerFn, self, context) }); return generator; } @@ -11558,8 +11353,12 @@ var runtime = (function (exports) { var Gp = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(IteratorPrototype); GeneratorFunction.prototype = GeneratorFunctionPrototype; - define(Gp, "constructor", GeneratorFunctionPrototype); - define(GeneratorFunctionPrototype, "constructor", GeneratorFunction); + defineProperty(Gp, "constructor", { value: GeneratorFunctionPrototype, configurable: true }); + defineProperty( + GeneratorFunctionPrototype, + "constructor", + { value: GeneratorFunction, configurable: true } + ); GeneratorFunction.displayName = define( GeneratorFunctionPrototype, toStringTagSymbol, @@ -11669,7 +11468,7 @@ var runtime = (function (exports) { // Define the unified helper method that is used to implement .next, // .throw, and .return (see defineIteratorMethods). - this._invoke = enqueue; + defineProperty(this, "_invoke", { value: enqueue }); } defineIteratorMethods(AsyncIterator.prototype); @@ -11907,7 +11706,8 @@ var runtime = (function (exports) { this.reset(true); } - exports.keys = function(object) { + exports.keys = function(val) { + var object = Object(val); var keys = []; for (var key in object) { keys.push(key); @@ -12317,49 +12117,45 @@ function getSize() { "use strict"; /* eslint-disable */ - /** * used in the test-docs as web-worker */ require('@babel/polyfill'); - var _require = require('../../'), - BroadcastChannel = _require.BroadcastChannel; - + BroadcastChannel = _require.BroadcastChannel; var _require2 = require('async-test-util'), - randomNumber = _require2.randomNumber, - randomBoolean = _require2.randomBoolean, - wait = _require2.wait; - -var resolved = Promise.resolve(); // overwrite console.log + randomNumber = _require2.randomNumber, + randomBoolean = _require2.randomBoolean, + wait = _require2.wait; +var resolved = Promise.resolve(); +// overwrite console.log try { - var logBefore = console.log; // console.log = function (str) { logBefore('worker: ' + str); } -} catch (err) {// does not work in IE11 + var logBefore = console.log; + // console.log = function (str) { logBefore('worker: ' + str); } +} catch (err) { + // does not work in IE11 } + /** * because shitware microsoft-edge sucks, the worker * when initialisation is done, * we have to set a interval here. */ - - setInterval(function () {}, 10 * 1000); var channel; self.addEventListener('message', function (e) { var data = e.data; - switch (data.cmd) { case 'start': console.log('Worker started'); console.log(JSON.stringify(data.msg)); channel = new BroadcastChannel(data.msg.channelName, { type: data.msg.methodType - }); // console.log('Worker channel-uuid: ' + channel._state.uuid); - + }); + // console.log('Worker channel-uuid: ' + channel._state.uuid); channel.onmessage = function (msg) { console.log('recieved message(' + msg.step + ') from ' + msg.from + ': ' + JSON.stringify(msg)); - if (!msg.answer) { /** * Wait a random amount of time to simulate 'normal' usage @@ -12377,21 +12173,16 @@ self.addEventListener('message', function (e) { }); } }; - self.postMessage('WORKER STARTED: '); break; - case 'stop': self.postMessage('WORKER STOPPED: ' + data.msg + '. (buttons will no longer work)'); channel.close(); self.close(); // Terminates the worker. - break; - default: self.postMessage('Unknown command: ' + data.msg); } - ; }, false); },{"../../":2,"@babel/polyfill":12,"async-test-util":19}]},{},[352]); diff --git a/package.json b/package.json index 16d25509..940e1300 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "broadcast-channel", - "version": "4.18.0", + "version": "4.18.1", "description": "A BroadcastChannel that works in New Browsers, Old Browsers, WebWorkers and NodeJs", "exports": { ".": {