broadcast-channel
Version:
A BroadcastChannel that works in New Browsers, Old Browsers, WebWorkers, NodeJs, Deno and iframes
114 lines (102 loc) • 3.62 kB
JavaScript
import {
randomToken
} from './util.js';
import {
sendLeaderMessage,
beLeader
} from './leader-election-util.js';
/**
* A faster version of the leader elector that uses the WebLock API
* @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API
*/
export const LeaderElectionWebLock = function (broadcastChannel, options) {
this.broadcastChannel = broadcastChannel;
broadcastChannel._befC.push(() => this.die());
this._options = options;
this.isLeader = false;
this.isDead = false;
this.token = randomToken();
this._lstns = [];
this._unl = [];
this._dpL = () => { }; // onduplicate listener
this._dpLC = false; // true when onduplicate called
this._wKMC = {}; // stuff for cleanup
// lock name
this.lN = 'pubkey-bc||' + broadcastChannel.method.type + '||' + broadcastChannel.name;
};
const LEADER_DIE_ABORT_SIGNAL_MESSAGE = 'LeaderElectionWebLock.die() called';
LeaderElectionWebLock.prototype = {
hasLeader() {
return navigator.locks.query().then(locks => {
const relevantLocks = locks.held ? locks.held.filter(lock => lock.name === this.lN) : [];
if (relevantLocks && relevantLocks.length > 0) {
return true;
} else {
return false;
}
});
},
awaitLeadership() {
if (!this._wLMP) {
this._wKMC.c = new AbortController();
const returnPromise = new Promise((res, rej) => {
this._wKMC.res = res;
this._wKMC.rej = rej;
});
this._wLMP = new Promise((res, reject) => {
navigator.locks.request(
this.lN,
{
signal: this._wKMC.c.signal
},
() => {
// if the lock resolved, we can drop the abort controller
this._wKMC.c = undefined;
beLeader(this);
res();
return returnPromise;
}
).catch((err) => {
if (err.message && err.message === LEADER_DIE_ABORT_SIGNAL_MESSAGE) {
/**
* In this case we do nothing!
* The leader died and awaitLeadership()
* will never resolve. Also since this is not an error,
* it will not throw.
*/
} else {
if (this._wKMC.rej) {
this._wKMC.rej(err);
}
reject(err);
}
});
});
}
return this._wLMP;
},
set onduplicate(_fn) {
// Do nothing because there are no duplicates in the WebLock version
},
die() {
this._lstns.forEach(listener => this.broadcastChannel.removeEventListener('internal', listener));
this._lstns = [];
this._unl.forEach(uFn => uFn.remove());
this._unl = [];
if (this.isLeader) {
this.isLeader = false;
}
this.isDead = true;
if (this._wKMC.res) {
this._wKMC.res();
}
/**
* We have to fire an abort signal
* so that the navigator.locks.request stops.
*/
if (this._wKMC.c) {
this._wKMC.c.abort(new Error(LEADER_DIE_ABORT_SIGNAL_MESSAGE));
}
return sendLeaderMessage(this, 'death');
}
};