"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; }