(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i} */ 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 /** * 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; }); } else { return Promise.resolve(false); } } /** * if set, this method is enforced, * no mather what the options are */ var ENFORCED_OPTIONS; function enforceOptions(options) { ENFORCED_OPTIONS = options; } // PROTOTYPE BroadcastChannel.prototype = { postMessage: function postMessage(msg) { if (this.closed) { throw new Error('BroadcastChannel.postMessage(): ' + 'Cannot post message after channel has closed'); } 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; } this.closed = true; var awaitPrepare = this._prepP ? this._prepP : Promise.resolve(); this._onML = null; this._addEL.message = []; return awaitPrepare // wait until all current sending are processed .then(function () { return Promise.all(Array.from(_this._uMP)); }) // run before-close hooks .then(function () { return Promise.all(_this._befC.map(function (fn) { return fn(); })); }) // 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 = { time: time, type: type, data: msg }; var awaitPrepare = broadcastChannel._prepP ? broadcastChannel._prepP : Promise.resolve(); return awaitPrepare.then(function () { 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)); }*/ channel._state = s; }); } else { 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 (obj) { if (msgObj.time >= obj.time) { obj.fn(msgObj.data); } }); }; var time = channel.method.microSeconds(); if (channel._prepP) { channel._prepP.then(function () { channel._iL = true; channel.method.onMessage(channel._state, listenerFn, time); }); } else { channel._iL = true; channel.method.onMessage(channel._state, listenerFn, time); } } } function _stopListening(channel) { if (channel._iL && !_hasMessageListeners(channel)) { // noone is listening, stop subscribing channel._iL = false; var time = channel.method.microSeconds(); channel.method.onMessage(channel._state, null, time); } } },{"./method-chooser.js":6,"./options.js":11,"./util.js":12}],2:[function(require,module,exports){ "use strict"; var _module = require('./index.es5.js'); var BroadcastChannel = _module.BroadcastChannel; var createLeaderElection = _module.createLeaderElection; window['BroadcastChannel2'] = BroadcastChannel; window['createLeaderElection'] = createLeaderElection; },{"./index.es5.js":3}],3:[function(require,module,exports){ "use strict"; var _index = require("./index.js"); /** * because babel can only export on default-attribute, * we use this for the non-module-build * this ensures that users do not have to use * var BroadcastChannel = require('broadcast-channel').default; * but * var BroadcastChannel = require('broadcast-channel'); */ module.exports = { BroadcastChannel: _index.BroadcastChannel, createLeaderElection: _index.createLeaderElection, clearNodeFolder: _index.clearNodeFolder, enforceOptions: _index.enforceOptions, beLeader: _index.beLeader }; },{"./index.js":4}],4:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "BroadcastChannel", { enumerable: true, get: function get() { return _broadcastChannel.BroadcastChannel; } }); Object.defineProperty(exports, "clearNodeFolder", { enumerable: true, get: function get() { return _broadcastChannel.clearNodeFolder; } }); Object.defineProperty(exports, "enforceOptions", { enumerable: true, get: function get() { return _broadcastChannel.enforceOptions; } }); Object.defineProperty(exports, "createLeaderElection", { enumerable: true, get: function get() { return _leaderElection.createLeaderElection; } }); Object.defineProperty(exports, "beLeader", { enumerable: true, get: function get() { return _leaderElection.beLeader; } }); var _broadcastChannel = require("./broadcast-channel"); var _leaderElection = require("./leader-election"); },{"./broadcast-channel":1,"./leader-election":5}],5:[function(require,module,exports){ "use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.beLeader = beLeader; exports.createLeaderElection = createLeaderElection; var _util = require("./util.js"); var _unload = _interopRequireDefault(require("unload")); var LeaderElection = function LeaderElection(channel, options) { this._channel = channel; this._options = options; this.isLeader = false; this.isDead = false; this.token = (0, _util.randomToken)(); this._isApl = false; // _isApplying this._reApply = false; // things to clean up this._unl = []; // _unloads this._lstns = []; // _listeners this._invs = []; // _intervals this._dpL = function () {}; // onduplicate listener this._dpLC = false; // true when onduplicate called }; LeaderElection.prototype = { applyOnce: function applyOnce() { var _this = this; if (this.isLeader) return Promise.resolve(false); if (this.isDead) return Promise.resolve(false); // do nothing if already running if (this._isApl) { this._reApply = true; return Promise.resolve(false); } this._isApl = true; var stopCriteria = false; var recieved = []; var handleMessage = function handleMessage(msg) { if (msg.context === 'leader' && msg.token != _this.token) { recieved.push(msg); if (msg.action === 'apply') { // other is applying if (msg.token > _this.token) { // other has higher token, stop applying stopCriteria = true; } } if (msg.action === 'tell') { // other is already leader stopCriteria = true; } } }; this._channel.addEventListener('internal', handleMessage); var ret = _sendMessage(this, 'apply') // send out that this one is applying .then(function () { return (0, _util.sleep)(_this._options.responseTime); }) // let others time to respond .then(function () { if (stopCriteria) return Promise.reject(new Error());else return _sendMessage(_this, 'apply'); }).then(function () { return (0, _util.sleep)(_this._options.responseTime); }) // let others time to respond .then(function () { if (stopCriteria) return Promise.reject(new Error());else return _sendMessage(_this); }).then(function () { return beLeader(_this); }) // no one disagreed -> this one is now leader .then(function () { return true; })["catch"](function () { return false; }) // apply not successfull .then(function (success) { _this._channel.removeEventListener('internal', handleMessage); _this._isApl = false; if (!success && _this._reApply) { _this._reApply = false; return _this.applyOnce(); } else return success; }); return ret; }, awaitLeadership: function awaitLeadership() { if ( /* _awaitLeadershipPromise */ !this._aLP) { this._aLP = _awaitLeadershipOnce(this); } return this._aLP; }, set onduplicate(fn) { this._dpL = fn; }, die: function die() { var _this2 = this; if (this.isDead) return; this.isDead = true; this._lstns.forEach(function (listener) { return _this2._channel.removeEventListener('internal', listener); }); this._invs.forEach(function (interval) { return clearInterval(interval); }); this._unl.forEach(function (uFn) { uFn.remove(); }); return _sendMessage(this, 'death'); } }; function _awaitLeadershipOnce(leaderElector) { if (leaderElector.isLeader) return Promise.resolve(); return new Promise(function (res) { var resolved = false; function finish() { if (resolved) { return; } resolved = true; clearInterval(interval); leaderElector._channel.removeEventListener('internal', whenDeathListener); res(true); } // try once now leaderElector.applyOnce().then(function () { if (leaderElector.isLeader) { finish(); } }); // try on fallbackInterval var interval = setInterval(function () { leaderElector.applyOnce().then(function () { if (leaderElector.isLeader) { finish(); } }); }, leaderElector._options.fallbackInterval); leaderElector._invs.push(interval); // try when other leader dies var whenDeathListener = function whenDeathListener(msg) { if (msg.context === 'leader' && msg.action === 'death') { leaderElector.applyOnce().then(function () { if (leaderElector.isLeader) finish(); }); } }; leaderElector._channel.addEventListener('internal', whenDeathListener); leaderElector._lstns.push(whenDeathListener); }); } /** * sends and internal message over the broadcast-channel */ function _sendMessage(leaderElector, action) { var msgJson = { context: 'leader', action: action, token: leaderElector.token }; return leaderElector._channel.postInternal(msgJson); } function beLeader(leaderElector) { leaderElector.isLeader = true; var unloadFn = _unload["default"].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! * This can happen on rare events * like when the CPU is at 100% for long time * or the tabs are open very long and the browser throttles them. * @link https://github.com/pubkey/broadcast-channel/issues/414 * @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._channel.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; } },{"./util.js":12,"@babel/runtime/helpers/interopRequireDefault":13,"unload":19}],6:[function(require,module,exports){ "use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); 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 _util = require("./util"); // order is important var METHODS = [_native["default"], // fastest _indexedDb["default"], _localstorage["default"]]; /** * The NodeMethod is loaded lazy * so it will not get bundled in browser-builds */ if (_util.isNode) { /** * we use the non-transpiled code for nodejs * because it runs faster */ var NodeMethod = require('../../src/methods/' + // use this hack so that browserify and others // do not import the node-method by default // when bundling. 'node.js'); /** * this will be false for webpackbuilds * which will shim the node-method with an empty object {} */ if (typeof NodeMethod.canBeUsed === 'function') { METHODS.push(NodeMethod); } } function chooseMethod(options) { var chooseMethods = [].concat(options.methods, METHODS).filter(Boolean); // 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 && !_util.isNode) { chooseMethods = chooseMethods.filter(function (m) { return m.type !== 'idb'; }); } var useMethod = chooseMethods.find(function (method) { return method.canBeUsed(); }); if (!useMethod) throw new Error('No useable methode found:' + JSON.stringify(METHODS.map(function (m) { return m.type; })));else return useMethod; } },{"./methods/indexed-db.js":7,"./methods/localstorage.js":8,"./methods/native.js":9,"./methods/simulate.js":10,"./util":12,"@babel/runtime/helpers/interopRequireDefault":13}],7:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getIdb = getIdb; exports.createDatabase = createDatabase; exports.writeMessage = writeMessage; exports.getAllMessages = getAllMessages; exports.getMessagesHigherThan = getMessagesHigherThan; exports.removeMessageById = removeMessageById; exports.getOldMessages = getOldMessages; exports.cleanOldMessages = cleanOldMessages; exports.create = create; exports.close = close; exports.postMessage = postMessage; exports.onMessage = onMessage; exports.canBeUsed = canBeUsed; exports.averageResponseTime = averageResponseTime; exports["default"] = exports.type = exports.microSeconds = void 0; var _util = require("../util.js"); var _obliviousSet = require("oblivious-set"); var _options = require("../options"); /** * this method uses indexeddb to store the messages * There is currently no observerAPI for idb * @link https://github.com/w3c/IndexedDB/issues/51 */ var microSeconds = _util.microSeconds; exports.microSeconds = microSeconds; var DB_PREFIX = 'pubkey.broadcast-channel-0-'; var OBJECT_STORE_ID = 'messages'; 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; } function createDatabase(channelName) { var IndexedDB = getIdb(); // create table var dbName = DB_PREFIX + channelName; var openRequest = IndexedDB.open(dbName, 1); openRequest.onupgradeneeded = function (ev) { var db = ev.target.result; db.createObjectStore(OBJECT_STORE_ID, { keyPath: 'id', 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 = { uuid: readerUuid, time: time, data: messageJson }; var transaction = db.transaction([OBJECT_STORE_ID], 'readwrite'); return new Promise(function (res, rej) { transaction.oncomplete = function () { return res(); }; transaction.onerror = function (ev) { return rej(ev); }; var objectStore = transaction.objectStore(OBJECT_STORE_ID); objectStore.add(writeObject); }); } function getAllMessages(db) { var objectStore = db.transaction(OBJECT_STORE_ID).objectStore(OBJECT_STORE_ID); var ret = []; 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); cursor["continue"](); } else { res(ret); } }; }); } function getMessagesHigherThan(db, lastCursorId) { var objectStore = db.transaction(OBJECT_STORE_ID).objectStore(OBJECT_STORE_ID); var ret = []; function openCursor() { // Occasionally Safari will fail on IDBKeyRange.bound, this // catches that error, having it open the cursor to the first // item. When it gets data it will advance to the desired key. try { var keyRangeValue = IDBKeyRange.bound(lastCursorId + 1, Infinity); return objectStore.openCursor(keyRangeValue); } catch (e) { return objectStore.openCursor(); } } return new Promise(function (res) { openCursor().onsuccess = function (ev) { var cursor = ev.target.result; if (cursor) { if (cursor.value.id < lastCursorId + 1) { cursor["continue"](lastCursorId + 1); } else { ret.push(cursor.value); cursor["continue"](); } } else { res(ret); } }; }); } function removeMessageById(db, id) { var request = db.transaction([OBJECT_STORE_ID], 'readwrite').objectStore(OBJECT_STORE_ID)["delete"](id); return new Promise(function (res) { request.onsuccess = function () { return res(); }; }); } function getOldMessages(db, ttl) { var olderThen = new Date().getTime() - ttl; var objectStore = db.transaction(OBJECT_STORE_ID).objectStore(OBJECT_STORE_ID); var ret = []; 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); cursor["continue"](); } else { // no more old messages, res(ret); return; } } else { res(ret); } }; }); } function cleanOldMessages(db, ttl) { return getOldMessages(db, ttl).then(function (tooOld) { return Promise.all(tooOld.map(function (msgObj) { return removeMessageById(db, msgObj.id); })); }); } function create(channelName, options) { options = (0, _options.fillOptionsWithDefaults)(options); return createDatabase(channelName).then(function (db) { var state = { closed: false, lastCursorId: 0, channelName: channelName, options: options, uuid: (0, _util.randomToken)(), /** * emittedMessagesIds * contains all messages that have been emitted before * @type {ObliviousSet} */ eMIs: new _obliviousSet.ObliviousSet(options.idb.ttl * 2), // ensures we do not read messages in parrallel writeBlockPromise: Promise.resolve(), messagesCallback: null, 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 () { return (0, _util.sleep)(state.options.idb.fallbackInterval); }).then(function () { 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.resolve(); // if no one is listening, we do not need to scan for new messages if (!state.messagesCallback) return Promise.resolve(); return getMessagesHigherThan(state.db, state.lastCursorId).then(function (newerMessages) { var useMessages = newerMessages /** * 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) { 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); state.messagesCallback(msgObj.data); } }); return Promise.resolve(); }); } 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); }).then(function () { if ((0, _util.randomInt)(0, 10) === 0) { /* await (do not await) */ cleanOldMessages(channelState.db, channelState.options.idb.ttl); } }); return channelState.writeBlockPromise; } function onMessage(channelState, fn, time) { channelState.messagesCallbackTime = time; channelState.messagesCallback = fn; readNewMessages(channelState); } function canBeUsed() { if (_util.isNode) return false; var idb = getIdb(); if (!idb) return false; return true; } function averageResponseTime(options) { return options.idb.fallbackInterval * 2; } var _default = { create: create, close: close, onMessage: onMessage, postMessage: postMessage, canBeUsed: canBeUsed, type: type, averageResponseTime: averageResponseTime, microSeconds: microSeconds }; exports["default"] = _default; },{"../options":11,"../util.js":12,"oblivious-set":16}],8:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getLocalStorage = getLocalStorage; exports.storageKey = storageKey; exports.postMessage = postMessage; exports.addStorageEventListener = addStorageEventListener; exports.removeStorageEventListener = removeStorageEventListener; exports.create = create; exports.close = close; exports.onMessage = onMessage; exports.canBeUsed = canBeUsed; exports.averageResponseTime = averageResponseTime; exports["default"] = exports.type = exports.microSeconds = void 0; var _obliviousSet = require("oblivious-set"); var _options = require("../options"); var _util = require("../util"); /** * 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 * This is basically implemented to support IE9 or your grandmothers toaster. * @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 // 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 () { var key = storageKey(channelState.channelName); var writeObj = { token: (0, _util.randomToken)(), time: new Date().getTime(), data: messageJson, uuid: channelState.uuid }; 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; ev.newValue = value; window.dispatchEvent(ev); res(); }); }); } 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); state.messagesCallback(msgObj.data); }); return state; } function close(channelState) { removeStorageEventListener(channelState.listener); } function onMessage(channelState, fn, time) { channelState.messagesCallbackTime = time; channelState.messagesCallback = fn; } function canBeUsed() { if (_util.isNode) return false; var ls = getLocalStorage(); if (!ls) return false; try { var key = '__broadcastchannel_check'; ls.setItem(key, 'works'); ls.removeItem(key); } catch (e) { // Safari 10 in private mode will not allow write access to local // storage and fail with a QuotaExceededError. See // 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, onMessage: onMessage, postMessage: postMessage, canBeUsed: canBeUsed, type: type, averageResponseTime: averageResponseTime, microSeconds: microSeconds }; exports["default"] = _default; },{"../options":11,"../util":12,"oblivious-set":16}],9:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.create = create; exports.close = close; exports.postMessage = postMessage; exports.onMessage = onMessage; exports.canBeUsed = canBeUsed; exports.averageResponseTime = averageResponseTime; exports["default"] = exports.type = exports.microSeconds = void 0; var _util = require("../util"); 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) { if (state.messagesCallback) { state.messagesCallback(msg.data); } }; return state; } function close(channelState) { channelState.bc.close(); channelState.subFns = []; } function postMessage(channelState, messageJson) { try { channelState.bc.postMessage(messageJson, false); return Promise.resolve(); } catch (err) { return Promise.reject(err); } } function onMessage(channelState, fn) { channelState.messagesCallback = fn; } function canBeUsed() { /** * in the electron-renderer, isNode will be true even if we are in browser-context * so we also check if window is undefined */ if (_util.isNode && 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, onMessage: onMessage, postMessage: postMessage, canBeUsed: canBeUsed, type: type, averageResponseTime: averageResponseTime, microSeconds: microSeconds }; exports["default"] = _default; },{"../util":12}],10:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.create = create; exports.close = close; exports.postMessage = postMessage; exports.onMessage = onMessage; exports.canBeUsed = canBeUsed; exports.averageResponseTime = averageResponseTime; exports["default"] = exports.type = exports.microSeconds = void 0; var _util = require("../util"); 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, messagesCallback: null }; 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 () { var channelArray = Array.from(SIMULATE_CHANNELS); channelArray.filter(function (channel) { return channel.name === channelState.name; }).filter(function (channel) { return channel !== channelState; }).filter(function (channel) { return !!channel.messagesCallback; }).forEach(function (channel) { return channel.messagesCallback(messageJson); }); res(); }, 5); }); } function onMessage(channelState, fn) { channelState.messagesCallback = fn; } function canBeUsed() { return true; } function averageResponseTime() { return 5; } var _default = { create: create, close: close, onMessage: onMessage, postMessage: postMessage, canBeUsed: canBeUsed, type: type, averageResponseTime: averageResponseTime, microSeconds: microSeconds }; exports["default"] = _default; },{"../util":12}],11:[function(require,module,exports){ "use strict"; 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 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.localstorage) options.localstorage = {}; if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60; // 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; if (typeof options.node.useFastPath === 'undefined') options.node.useFastPath = true; return options; } },{}],12:[function(require,module,exports){ (function (process){(function (){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isPromise = isPromise; exports.sleep = sleep; exports.randomInt = randomInt; exports.randomToken = randomToken; exports.microSeconds = microSeconds; exports.isNode = void 0; /** * returns true if the given object is a promise */ function isPromise(obj) { if (obj && typeof obj.then === 'function') { return true; } else { return false; } } function sleep(time) { if (!time) time = 0; return new Promise(function (res) { return setTimeout(res, 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 * Performance.now is not reliable in webworkers, so we just make sure to never return the same time. * 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; } else { lastMs = ms; additional = 0; return ms * 1000; } } /** * copied from the 'detect-node' npm module * We cannot use the module directly because it causes problems with rollup * @link https://github.com/iliakan/detect-node/blob/master/index.js */ var isNode = Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]'; exports.isNode = isNode; }).call(this)}).call(this,require('_process')) },{"_process":17}],13:[function(require,module,exports){ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } module.exports = _interopRequireDefault; module.exports["default"] = module.exports, module.exports.__esModule = true; },{}],14:[function(require,module,exports){ },{}],15:[function(require,module,exports){ module.exports = false; },{}],16:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.now = exports.removeTooOldValues = exports.ObliviousSet = void 0; /** * this is a set which automatically forgets * a given entry when a new entry is set and the ttl * of the old one is over */ var ObliviousSet = /** @class */ (function () { function ObliviousSet(ttl) { this.ttl = ttl; this.set = new Set(); this.timeMap = new Map(); } ObliviousSet.prototype.has = function (value) { return this.set.has(value); }; ObliviousSet.prototype.add = function (value) { var _this = this; this.timeMap.set(value, now()); this.set.add(value); /** * When a new value is added, * start the cleanup at the next tick * to not block the cpu for more important stuff * that might happen. */ setTimeout(function () { removeTooOldValues(_this); }, 0); }; ObliviousSet.prototype.clear = function () { this.set.clear(); this.timeMap.clear(); }; return ObliviousSet; }()); exports.ObliviousSet = ObliviousSet; /** * Removes all entries from the set * where the TTL has expired */ function removeTooOldValues(obliviousSet) { var olderThen = now() - obliviousSet.ttl; var iterator = obliviousSet.set[Symbol.iterator](); /** * Because we can assume the new values are added at the bottom, * we start from the top and stop as soon as we reach a non-too-old value. */ while (true) { var value = iterator.next().value; if (!value) { return; // no more elements } var time = obliviousSet.timeMap.get(value); if (time < olderThen) { obliviousSet.timeMap.delete(value); obliviousSet.set.delete(value); } else { // We reached a value that is not old enough return; } } } exports.removeTooOldValues = removeTooOldValues; function now() { return new Date().getTime(); } exports.now = now; },{}],17:[function(require,module,exports){ // shim for using process in browser var process = module.exports = {}; // cached from whatever global is present so that test runners that stub it // don't break things. But we need to wrap it in a try catch in case it is // wrapped in strict mode code which doesn't define any globals. It's inside a // function because try/catches deoptimize in certain engines. var cachedSetTimeout; var cachedClearTimeout; function defaultSetTimout() { throw new Error('setTimeout has not been defined'); } function defaultClearTimeout () { throw new Error('clearTimeout has not been defined'); } (function () { try { if (typeof setTimeout === 'function') { cachedSetTimeout = setTimeout; } else { cachedSetTimeout = defaultSetTimout; } } catch (e) { cachedSetTimeout = defaultSetTimout; } try { if (typeof clearTimeout === 'function') { cachedClearTimeout = clearTimeout; } else { cachedClearTimeout = defaultClearTimeout; } } catch (e) { cachedClearTimeout = defaultClearTimeout; } } ()) function runTimeout(fun) { if (cachedSetTimeout === setTimeout) { //normal enviroments in sane situations return setTimeout(fun, 0); } // if setTimeout wasn't available but was latter defined if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { cachedSetTimeout = setTimeout; return setTimeout(fun, 0); } try { // when when somebody has screwed with setTimeout but no I.E. maddness return cachedSetTimeout(fun, 0); } catch(e){ try { // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally return cachedSetTimeout.call(null, fun, 0); } catch(e){ // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error return cachedSetTimeout.call(this, fun, 0); } } } function runClearTimeout(marker) { if (cachedClearTimeout === clearTimeout) { //normal enviroments in sane situations return clearTimeout(marker); } // if clearTimeout wasn't available but was latter defined if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { cachedClearTimeout = clearTimeout; return clearTimeout(marker); } try { // when when somebody has screwed with setTimeout but no I.E. maddness return cachedClearTimeout(marker); } catch (e){ try { // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally return cachedClearTimeout.call(null, marker); } catch (e){ // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. // Some versions of I.E. have different rules for clearTimeout vs setTimeout return cachedClearTimeout.call(this, marker); } } } var queue = []; var draining = false; var currentQueue; var queueIndex = -1; function cleanUpNextTick() { if (!draining || !currentQueue) { return; } draining = false; if (currentQueue.length) { queue = currentQueue.concat(queue); } else { queueIndex = -1; } if (queue.length) { drainQueue(); } } function drainQueue() { if (draining) { return; } var timeout = runTimeout(cleanUpNextTick); draining = true; var len = queue.length; while(len) { currentQueue = queue; queue = []; while (++queueIndex < len) { if (currentQueue) { currentQueue[queueIndex].run(); } } queueIndex = -1; len = queue.length; } currentQueue = null; draining = false; runClearTimeout(timeout); } process.nextTick = function (fun) { var args = new Array(arguments.length - 1); if (arguments.length > 1) { for (var i = 1; i < arguments.length; i++) { args[i - 1] = arguments[i]; } } queue.push(new Item(fun, args)); if (queue.length === 1 && !draining) { runTimeout(drainQueue); } }; // v8 likes predictible objects function Item(fun, array) { this.fun = fun; this.array = array; } Item.prototype.run = function () { this.fun.apply(null, this.array); }; process.title = 'browser'; process.browser = true; process.env = {}; process.argv = []; process.version = ''; // empty string to avoid regexp issues process.versions = {}; function noop() {} process.on = noop; process.addListener = noop; process.once = noop; process.off = noop; process.removeListener = noop; process.removeAllListeners = noop; process.emit = noop; process.prependListener = noop; process.prependOnceListener = noop; process.listeners = function (name) { return [] } process.binding = function (name) { throw new Error('process.binding is not supported'); }; process.cwd = function () { return '/' }; process.chdir = function (dir) { throw new Error('process.chdir is not supported'); }; process.umask = function() { return 0; }; },{}],18:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; /* global WorkerGlobalScope */ function add(fn) { if (typeof WorkerGlobalScope === 'function' && self instanceof WorkerGlobalScope) {// this is run inside of a webworker } else { /** * if we are on react-native, there is no window.addEventListener * @link https://github.com/pubkey/unload/issues/6 */ if (typeof window.addEventListener !== 'function') return; /** * for normal browser-windows, we use the beforeunload-event */ window.addEventListener('beforeunload', function () { fn(); }, true); /** * for iframes, we have to use the unload-event * @link https://stackoverflow.com/q/47533670/3443137 */ window.addEventListener('unload', function () { fn(); }, true); } /** * TODO add fallback for safari-mobile * @link https://stackoverflow.com/a/26193516/3443137 */ } var _default = { add: add }; exports["default"] = _default; },{}],19:[function(require,module,exports){ "use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.add = add; exports.runAll = runAll; exports.removeAll = removeAll; exports.getSize = getSize; exports["default"] = void 0; var _detectNode = _interopRequireDefault(require("detect-node")); var _browser = _interopRequireDefault(require("./browser.js")); var _node = _interopRequireDefault(require("./node.js")); var USE_METHOD = _detectNode["default"] ? _node["default"] : _browser["default"]; var LISTENERS = new Set(); var startedListening = false; function startListening() { if (startedListening) return; startedListening = true; USE_METHOD.add(runAll); } function add(fn) { startListening(); if (typeof fn !== 'function') throw new Error('Listener is no function'); LISTENERS.add(fn); var addReturn = { remove: function remove() { return LISTENERS["delete"](fn); }, run: function run() { LISTENERS["delete"](fn); return fn(); } }; return addReturn; } function runAll() { var promises = []; LISTENERS.forEach(function (fn) { promises.push(fn()); LISTENERS["delete"](fn); }); return Promise.all(promises); } function removeAll() { LISTENERS.clear(); } function getSize() { return LISTENERS.size; } var _default = { add: add, runAll: runAll, removeAll: removeAll, getSize: getSize }; exports["default"] = _default; },{"./browser.js":18,"./node.js":14,"@babel/runtime/helpers/interopRequireDefault":13,"detect-node":15}]},{},[2]);