Skip to content

Commit 69c76a4

Browse files
committed
5.0.0
1 parent 063a2cb commit 69c76a4

33 files changed

+3138
-2391
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
## X.X.X (comming soon)
55

6+
## 5.0.0 (23 March 2023)
7+
68
- Use [Web Locks API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API) for leader election if possible.
79
- `LeaderElector.hasLeader` is now a function that returns a `Promise<boolean>`.
810

dist/es5node/index.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Object.defineProperty(exports, "OPEN_BROADCAST_CHANNELS", {
1818
Object.defineProperty(exports, "beLeader", {
1919
enumerable: true,
2020
get: function get() {
21-
return _leaderElection.beLeader;
21+
return _leaderElectionUtil.beLeader;
2222
}
2323
});
2424
Object.defineProperty(exports, "clearNodeFolder", {
@@ -40,4 +40,5 @@ Object.defineProperty(exports, "enforceOptions", {
4040
}
4141
});
4242
var _broadcastChannel = require("./broadcast-channel.js");
43-
var _leaderElection = require("./leader-election.js");
43+
var _leaderElection = require("./leader-election.js");
44+
var _leaderElectionUtil = require("./leader-election-util.js");
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"use strict";
2+
3+
Object.defineProperty(exports, "__esModule", {
4+
value: true
5+
});
6+
exports.beLeader = beLeader;
7+
exports.sendLeaderMessage = sendLeaderMessage;
8+
var _unload = require("unload");
9+
/**
10+
* sends and internal message over the broadcast-channel
11+
*/
12+
function sendLeaderMessage(leaderElector, action) {
13+
var msgJson = {
14+
context: 'leader',
15+
action: action,
16+
token: leaderElector.token
17+
};
18+
return leaderElector.broadcastChannel.postInternal(msgJson);
19+
}
20+
function beLeader(leaderElector) {
21+
leaderElector.isLeader = true;
22+
leaderElector._hasLeader = true;
23+
var unloadFn = (0, _unload.add)(function () {
24+
return leaderElector.die();
25+
});
26+
leaderElector._unl.push(unloadFn);
27+
var isLeaderListener = function isLeaderListener(msg) {
28+
if (msg.context === 'leader' && msg.action === 'apply') {
29+
sendLeaderMessage(leaderElector, 'tell');
30+
}
31+
if (msg.context === 'leader' && msg.action === 'tell' && !leaderElector._dpLC) {
32+
/**
33+
* another instance is also leader!
34+
* This can happen on rare events
35+
* like when the CPU is at 100% for long time
36+
* or the tabs are open very long and the browser throttles them.
37+
* @link https://github.com/pubkey/broadcast-channel/issues/414
38+
* @link https://github.com/pubkey/broadcast-channel/issues/385
39+
*/
40+
leaderElector._dpLC = true;
41+
leaderElector._dpL(); // message the lib user so the app can handle the problem
42+
sendLeaderMessage(leaderElector, 'tell'); // ensure other leader also knows the problem
43+
}
44+
};
45+
46+
leaderElector.broadcastChannel.addEventListener('internal', isLeaderListener);
47+
leaderElector._lstns.push(isLeaderListener);
48+
return sendLeaderMessage(leaderElector, 'tell');
49+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"use strict";
2+
3+
Object.defineProperty(exports, "__esModule", {
4+
value: true
5+
});
6+
exports.LeaderElectionWebLock = void 0;
7+
var _util = require("./util.js");
8+
var _leaderElectionUtil = require("./leader-election-util.js");
9+
/**
10+
* A faster version of the leader elector that uses the WebLock API
11+
* @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API
12+
*/
13+
var LeaderElectionWebLock = function LeaderElectionWebLock(broadcastChannel, options) {
14+
var _this = this;
15+
this.broadcastChannel = broadcastChannel;
16+
broadcastChannel._befC.push(function () {
17+
return _this.die();
18+
});
19+
this._options = options;
20+
this.isLeader = false;
21+
this.isDead = false;
22+
this.token = (0, _util.randomToken)();
23+
this._lstns = [];
24+
this._unl = [];
25+
this._dpL = function () {}; // onduplicate listener
26+
this._dpLC = false; // true when onduplicate called
27+
28+
this._wKMC = {}; // stuff for cleanup
29+
};
30+
exports.LeaderElectionWebLock = LeaderElectionWebLock;
31+
LeaderElectionWebLock.prototype = {
32+
hasLeader: function hasLeader() {
33+
return navigator.locks.query().then(function (locks) {
34+
if (locks.held && locks.held.length > 0) {
35+
return true;
36+
} else {
37+
return false;
38+
}
39+
});
40+
},
41+
awaitLeadership: function awaitLeadership() {
42+
var _this2 = this;
43+
if (!this._wLMP) {
44+
this._wKMC.c = new AbortController();
45+
var returnPromise = new Promise(function (res, rej) {
46+
_this2._wKMC.res = res;
47+
_this2._wKMC.rej = rej;
48+
});
49+
this._wLMP = new Promise(function (res) {
50+
var lockId = 'pubkey-bc||' + _this2.broadcastChannel.method.type + '||' + _this2.broadcastChannel.name;
51+
navigator.locks.request(lockId, {
52+
signal: _this2._wKMC.c.signal
53+
}, function () {
54+
(0, _leaderElectionUtil.beLeader)(_this2);
55+
res();
56+
return returnPromise;
57+
});
58+
});
59+
}
60+
return this._wLMP;
61+
},
62+
set onduplicate(_fn) {
63+
// Do nothing because there are no duplicates in the WebLock version
64+
},
65+
die: function die() {
66+
var _this3 = this;
67+
var ret = (0, _leaderElectionUtil.sendLeaderMessage)(this, 'death');
68+
this._lstns.forEach(function (listener) {
69+
return _this3.broadcastChannel.removeEventListener('internal', listener);
70+
});
71+
this._lstns = [];
72+
this._unl.forEach(function (uFn) {
73+
return uFn.remove();
74+
});
75+
this._unl = [];
76+
if (this.isLeader) {
77+
this.isLeader = false;
78+
}
79+
this.isDead = true;
80+
if (this._wKMC.res) {
81+
this._wKMC.res();
82+
}
83+
if (this._wKMC.c) {
84+
this._wKMC.c.abort();
85+
}
86+
return ret;
87+
}
88+
};

dist/es5node/leader-election.js

Lines changed: 16 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@
33
Object.defineProperty(exports, "__esModule", {
44
value: true
55
});
6-
exports.beLeader = beLeader;
76
exports.createLeaderElection = createLeaderElection;
87
var _util = require("./util.js");
9-
var _unload = require("unload");
8+
var _leaderElectionUtil = require("./leader-election-util.js");
9+
var _leaderElectionWebLock = require("./leader-election-web-lock.js");
1010
var LeaderElection = function LeaderElection(broadcastChannel, options) {
1111
var _this = this;
1212
this.broadcastChannel = broadcastChannel;
1313
this._options = options;
1414
this.isLeader = false;
15-
this.hasLeader = false;
15+
this._hasLeader = false;
1616
this.isDead = false;
1717
this.token = (0, _util.randomToken)();
1818

@@ -39,17 +39,20 @@ var LeaderElection = function LeaderElection(broadcastChannel, options) {
3939
var hasLeaderListener = function hasLeaderListener(msg) {
4040
if (msg.context === 'leader') {
4141
if (msg.action === 'death') {
42-
_this.hasLeader = false;
42+
_this._hasLeader = false;
4343
}
4444
if (msg.action === 'tell') {
45-
_this.hasLeader = true;
45+
_this._hasLeader = true;
4646
}
4747
}
4848
};
4949
this.broadcastChannel.addEventListener('internal', hasLeaderListener);
5050
this._lstns.push(hasLeaderListener);
5151
};
5252
LeaderElection.prototype = {
53+
hasLeader: function hasLeader() {
54+
return Promise.resolve(this._hasLeader);
55+
},
5356
/**
5457
* Returns true if the instance is leader,
5558
* false if not.
@@ -115,7 +118,7 @@ LeaderElection.prototype = {
115118
if (msg.action === 'tell') {
116119
// other is already leader
117120
stopCriteriaPromiseResolve();
118-
_this2.hasLeader = true;
121+
_this2._hasLeader = true;
119122
}
120123
}
121124
};
@@ -132,15 +135,15 @@ LeaderElection.prototype = {
132135
* run in the background.
133136
*/
134137
var waitForAnswerTime = isFromFallbackInterval ? _this2._options.responseTime * 4 : _this2._options.responseTime;
135-
return _sendMessage(_this2, 'apply') // send out that this one is applying
138+
return (0, _leaderElectionUtil.sendLeaderMessage)(_this2, 'apply') // send out that this one is applying
136139
.then(function () {
137140
return Promise.race([(0, _util.sleep)(waitForAnswerTime), stopCriteriaPromise.then(function () {
138141
return Promise.reject(new Error());
139142
})]);
140143
})
141144
// send again in case another instance was just created
142145
.then(function () {
143-
return _sendMessage(_this2, 'apply');
146+
return (0, _leaderElectionUtil.sendLeaderMessage)(_this2, 'apply');
144147
})
145148
// let others time to respond
146149
.then(function () {
@@ -151,7 +154,7 @@ LeaderElection.prototype = {
151154
_this2.broadcastChannel.removeEventListener('internal', handleMessage);
152155
if (!stopCriteria) {
153156
// no stop criteria -> own is leader
154-
return beLeader(_this2).then(function () {
157+
return (0, _leaderElectionUtil.beLeader)(_this2).then(function () {
155158
return true;
156159
});
157160
} else {
@@ -191,11 +194,11 @@ LeaderElection.prototype = {
191194
});
192195
this._unl = [];
193196
if (this.isLeader) {
194-
this.hasLeader = false;
197+
this._hasLeader = false;
195198
this.isLeader = false;
196199
}
197200
this.isDead = true;
198-
return _sendMessage(this, 'death');
201+
return (0, _leaderElectionUtil.sendLeaderMessage)(this, 'death');
199202
}
200203
};
201204

@@ -251,7 +254,7 @@ function _awaitLeadershipOnce(leaderElector) {
251254
// try when other leader dies
252255
var whenDeathListener = function whenDeathListener(msg) {
253256
if (msg.context === 'leader' && msg.action === 'death') {
254-
leaderElector.hasLeader = false;
257+
leaderElector._hasLeader = false;
255258
leaderElector.applyOnce().then(function () {
256259
if (leaderElector.isLeader) {
257260
finish();
@@ -263,48 +266,6 @@ function _awaitLeadershipOnce(leaderElector) {
263266
leaderElector._lstns.push(whenDeathListener);
264267
});
265268
}
266-
267-
/**
268-
* sends and internal message over the broadcast-channel
269-
*/
270-
function _sendMessage(leaderElector, action) {
271-
var msgJson = {
272-
context: 'leader',
273-
action: action,
274-
token: leaderElector.token
275-
};
276-
return leaderElector.broadcastChannel.postInternal(msgJson);
277-
}
278-
function beLeader(leaderElector) {
279-
leaderElector.isLeader = true;
280-
leaderElector.hasLeader = true;
281-
var unloadFn = (0, _unload.add)(function () {
282-
return leaderElector.die();
283-
});
284-
leaderElector._unl.push(unloadFn);
285-
var isLeaderListener = function isLeaderListener(msg) {
286-
if (msg.context === 'leader' && msg.action === 'apply') {
287-
_sendMessage(leaderElector, 'tell');
288-
}
289-
if (msg.context === 'leader' && msg.action === 'tell' && !leaderElector._dpLC) {
290-
/**
291-
* another instance is also leader!
292-
* This can happen on rare events
293-
* like when the CPU is at 100% for long time
294-
* or the tabs are open very long and the browser throttles them.
295-
* @link https://github.com/pubkey/broadcast-channel/issues/414
296-
* @link https://github.com/pubkey/broadcast-channel/issues/385
297-
*/
298-
leaderElector._dpLC = true;
299-
leaderElector._dpL(); // message the lib user so the app can handle the problem
300-
_sendMessage(leaderElector, 'tell'); // ensure other leader also knows the problem
301-
}
302-
};
303-
304-
leaderElector.broadcastChannel.addEventListener('internal', isLeaderListener);
305-
leaderElector._lstns.push(isLeaderListener);
306-
return _sendMessage(leaderElector, 'tell');
307-
}
308269
function fillOptionsWithDefaults(options, channel) {
309270
if (!options) options = {};
310271
options = JSON.parse(JSON.stringify(options));
@@ -321,7 +282,7 @@ function createLeaderElection(channel, options) {
321282
throw new Error('BroadcastChannel already has a leader-elector');
322283
}
323284
options = fillOptionsWithDefaults(options, channel);
324-
var elector = new LeaderElection(channel, options);
285+
var elector = (0, _util.supportsWebLockAPI)() ? new _leaderElectionWebLock.LeaderElectionWebLock(channel, options) : new LeaderElection(channel, options);
325286
channel._befC.push(function () {
326287
return elector.die();
327288
});

dist/es5node/method-chooser.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,11 @@ function chooseMethod(options) {
4848
var useMethod = chooseMethods.find(function (method) {
4949
return method.canBeUsed();
5050
});
51-
if (!useMethod) throw new Error("No usable method found in " + JSON.stringify(METHODS.map(function (m) {
52-
return m.type;
53-
})));else return useMethod;
51+
if (!useMethod) {
52+
throw new Error("No usable method found in " + JSON.stringify(METHODS.map(function (m) {
53+
return m.type;
54+
})));
55+
} else {
56+
return useMethod;
57+
}
5458
}

dist/es5node/util.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ exports.microSeconds = microSeconds;
99
exports.randomInt = randomInt;
1010
exports.randomToken = randomToken;
1111
exports.sleep = sleep;
12+
exports.supportsWebLockAPI = supportsWebLockAPI;
1213
/**
1314
* returns true if the given object is a promise
1415
*/
@@ -59,4 +60,16 @@ function microSeconds() {
5960
additional = 0;
6061
return ms * 1000;
6162
}
63+
}
64+
65+
/**
66+
* Check if WebLock API is supported.
67+
* @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API
68+
*/
69+
function supportsWebLockAPI() {
70+
if (typeof navigator !== 'undefined' && typeof navigator.locks !== 'undefined' && typeof navigator.locks.request === 'function') {
71+
return true;
72+
} else {
73+
return false;
74+
}
6275
}

dist/esbrowser/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { BroadcastChannel, clearNodeFolder, enforceOptions, OPEN_BROADCAST_CHANNELS } from './broadcast-channel.js';
2-
export { createLeaderElection, beLeader } from './leader-election.js';
2+
export { createLeaderElection } from './leader-election.js';
3+
export { beLeader } from './leader-election-util.js';

0 commit comments

Comments
 (0)