broadcast-channel
Advanced tools
Comparing version 2.1.0 to 2.1.1
var BroadcastChannel = require('./index.es5.js'); | ||
var LeaderElection = require('./leader-election/index.es5.js'); | ||
@@ -3,0 +4,0 @@ |
@@ -9,5 +9,3 @@ /** | ||
*/ | ||
import BroadcastChannel from './index.js'; | ||
module.exports = BroadcastChannel; |
import { isPromise } from './util.js'; | ||
import { chooseMethod } from './method-chooser.js'; | ||
import { fillOptionsWithDefaults } from './options.js'; | ||
var BroadcastChannel = function BroadcastChannel(name, options) { | ||
this.name = name; | ||
this.options = fillOptionsWithDefaults(options); | ||
this.method = chooseMethod(this.options); | ||
this.name = name; | ||
this.options = fillOptionsWithDefaults(options); | ||
this.method = chooseMethod(this.options); // isListening | ||
this._isListening = false; | ||
this._iL = false; | ||
/** | ||
* _onMessageListener | ||
* setting onmessage twice, | ||
* will overwrite the first listener | ||
*/ | ||
/** | ||
* setting onmessage twice, | ||
* will overwrite the first listener | ||
*/ | ||
this._onMessageListener = null; | ||
this._onML = null; | ||
/** | ||
* _addEventListeners | ||
*/ | ||
this._addEventListeners = { | ||
message: [], | ||
internal: [] | ||
}; | ||
this._addEL = { | ||
message: [], | ||
internal: [] | ||
}; | ||
/** | ||
* _beforeClose | ||
* array of promises that will be awaited | ||
* before the channel is closed | ||
*/ | ||
/** | ||
* array of promises that will be awaited | ||
* before the channel is closed | ||
*/ | ||
this._beforeClose = []; | ||
this._befC = []; | ||
/** | ||
* _preparePromise | ||
*/ | ||
this._preparePromise = null; | ||
_prepareChannel(this); | ||
}; | ||
this._prepP = null; | ||
// STATICS | ||
_prepareChannel(this); | ||
}; // STATICS | ||
@@ -42,4 +47,5 @@ /** | ||
*/ | ||
BroadcastChannel._pubkey = true; | ||
/** | ||
@@ -49,162 +55,171 @@ * clears the tmp-folder if is node | ||
*/ | ||
BroadcastChannel.clearNodeFolder = function (options) { | ||
options = fillOptionsWithDefaults(options); | ||
var method = chooseMethod(options); | ||
if (method.type === 'node') { | ||
return method.clearNodeFolder().then(function () { | ||
return true; | ||
}); | ||
} else { | ||
return Promise.resolve(false); | ||
} | ||
}; | ||
options = fillOptionsWithDefaults(options); | ||
var method = chooseMethod(options); | ||
// PROTOTYPE | ||
if (method.type === 'node') { | ||
return method.clearNodeFolder().then(function () { | ||
return true; | ||
}); | ||
} else { | ||
return Promise.resolve(false); | ||
} | ||
}; // PROTOTYPE | ||
BroadcastChannel.prototype = { | ||
_post: function _post(type, msg) { | ||
var _this = this; | ||
postMessage: function postMessage(msg) { | ||
if (this.closed) { | ||
throw new Error('BroadcastChannel.postMessage(): ' + 'Cannot post message after channel has closed'); | ||
} | ||
var time = this.method.microSeconds(); | ||
var msgObj = { | ||
time: time, | ||
type: type, | ||
data: msg | ||
}; | ||
return _post(this, 'message', msg); | ||
}, | ||
postInternal: function postInternal(msg) { | ||
return _post(this, 'internal', msg); | ||
}, | ||
var awaitPrepare = this._preparePromise ? this._preparePromise : Promise.resolve(); | ||
return awaitPrepare.then(function () { | ||
return _this.method.postMessage(_this._state, msgObj); | ||
}); | ||
}, | ||
postMessage: function postMessage(msg) { | ||
if (this.closed) { | ||
throw new Error('BroadcastChannel.postMessage(): ' + 'Cannot post message after channel has closed'); | ||
} | ||
return this._post('message', msg); | ||
}, | ||
postInternal: function postInternal(msg) { | ||
return this._post('internal', msg); | ||
}, | ||
set onmessage(fn) { | ||
var time = this.method.microSeconds(); | ||
var listenObj = { | ||
time: time, | ||
fn: fn | ||
}; | ||
set onmessage(fn) { | ||
var time = this.method.microSeconds(); | ||
var listenObj = { | ||
time: time, | ||
fn: fn | ||
}; | ||
_removeListenerObject(this, 'message', this._onMessageListener); | ||
if (fn && typeof fn === 'function') { | ||
this._onMessageListener = listenObj; | ||
_addListenerObject(this, 'message', listenObj); | ||
} else { | ||
this._onMessageListener = null; | ||
} | ||
}, | ||
_removeListenerObject(this, 'message', this._onML); | ||
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._addEventListeners[type].find(function (obj) { | ||
return obj.fn === fn; | ||
}); | ||
_removeListenerObject(this, type, obj); | ||
}, | ||
close: function close() { | ||
var _this2 = this; | ||
if (fn && typeof fn === 'function') { | ||
this._onML = listenObj; | ||
if (this.closed) return; | ||
this.closed = true; | ||
var awaitPrepare = this._preparePromise ? this._preparePromise : Promise.resolve(); | ||
_addListenerObject(this, 'message', listenObj); | ||
} else { | ||
this._onML = null; | ||
} | ||
}, | ||
this._onMessageListener = null; | ||
this._addEventListeners.message = []; | ||
addEventListener: function addEventListener(type, fn) { | ||
var time = this.method.microSeconds(); | ||
var listenObj = { | ||
time: time, | ||
fn: fn | ||
}; | ||
return awaitPrepare.then(function () { | ||
return Promise.all(_this2._beforeClose.map(function (fn) { | ||
return fn(); | ||
})); | ||
}).then(function () { | ||
return _this2.method.close(_this2._state); | ||
}); | ||
}, | ||
_addListenerObject(this, type, listenObj); | ||
}, | ||
removeEventListener: function removeEventListener(type, fn) { | ||
var obj = this._addEL[type].find(function (obj) { | ||
return obj.fn === fn; | ||
}); | ||
get type() { | ||
return this.method.type; | ||
} | ||
_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.then(function () { | ||
return Promise.all(_this._befC.map(function (fn) { | ||
return fn(); | ||
})); | ||
}).then(function () { | ||
return _this.method.close(_this._state); | ||
}); | ||
}, | ||
get type() { | ||
return this.method.type; | ||
} | ||
}; | ||
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 () { | ||
return broadcastChannel.method.postMessage(broadcastChannel._state, msgObj); | ||
}); | ||
} | ||
function _prepareChannel(channel) { | ||
var maybePromise = channel.method.create(channel.name, channel.options); | ||
if (isPromise(maybePromise)) { | ||
channel._preparePromise = 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; | ||
} | ||
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)); | ||
}*/ | ||
channel._state = s; | ||
}); | ||
} else { | ||
channel._state = maybePromise; | ||
} | ||
} | ||
function _hasMessageListeners(channel) { | ||
if (channel._addEventListeners.message.length > 0) return true; | ||
if (channel._addEventListeners.internal.length > 0) return true; | ||
return false; | ||
if (channel._addEL.message.length > 0) return true; | ||
if (channel._addEL.internal.length > 0) return true; | ||
return false; | ||
} | ||
function _addListenerObject(channel, type, obj) { | ||
channel._addEventListeners[type].push(obj); | ||
_startListening(channel); | ||
channel._addEL[type].push(obj); | ||
_startListening(channel); | ||
} | ||
function _removeListenerObject(channel, type, obj) { | ||
channel._addEventListeners[type] = channel._addEventListeners[type].filter(function (o) { | ||
return o !== obj; | ||
}); | ||
_stopListening(channel); | ||
channel._addEL[type] = channel._addEL[type].filter(function (o) { | ||
return o !== obj; | ||
}); | ||
_stopListening(channel); | ||
} | ||
function _startListening(channel) { | ||
if (!channel._isListening && _hasMessageListeners(channel)) { | ||
// someone is listening, start subscribing | ||
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 listenerFn = function listenerFn(msgObj) { | ||
channel._addEventListeners[msgObj.type].forEach(function (obj) { | ||
if (msgObj.time >= obj.time) { | ||
obj.fn(msgObj.data); | ||
} | ||
}); | ||
}; | ||
var time = channel.method.microSeconds(); | ||
var time = channel.method.microSeconds(); | ||
if (channel._preparePromise) { | ||
channel._preparePromise.then(function () { | ||
channel._isListening = true; | ||
channel.method.onMessage(channel._state, listenerFn, time); | ||
}); | ||
} else { | ||
channel._isListening = true; | ||
channel.method.onMessage(channel._state, listenerFn, time); | ||
} | ||
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._isListening && !_hasMessageListeners(channel)) { | ||
// noone is listening, stop subscribing | ||
channel._isListening = false; | ||
var time = channel.method.microSeconds(); | ||
channel.method.onMessage(channel._state, null, time); | ||
} | ||
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); | ||
} | ||
} | ||
export default BroadcastChannel; |
@@ -5,5 +5,3 @@ /** | ||
*/ | ||
import LeaderElection from './index.js'; | ||
module.exports = LeaderElection; |
import { sleep, randomToken } from '../util.js'; | ||
import unload from 'unload'; | ||
var LeaderElection = function LeaderElection(channel, options) { | ||
this._channel = channel; | ||
this._options = options; | ||
this._channel = channel; | ||
this._options = options; | ||
this.isLeader = false; | ||
this.isDead = false; | ||
this.token = randomToken(10); | ||
this._isApl = false; // _isApplying | ||
this.isLeader = false; | ||
this.isDead = false; | ||
this.token = randomToken(10); | ||
this._reApply = false; // things to clean up | ||
this._isApplying = false; | ||
this._reApply = false; | ||
this._unl = []; // _unloads | ||
// things to clean up | ||
this._unloads = []; | ||
this._listeners = []; | ||
this._intervals = []; | ||
this._lstns = []; // _listeners | ||
this._invs = []; // _intervals | ||
}; | ||
LeaderElection.prototype = { | ||
applyOnce: function applyOnce() { | ||
var _this = this; | ||
applyOnce: function applyOnce() { | ||
var _this = this; | ||
if (this.isLeader) return Promise.resolve(false); | ||
if (this.isDead) return Promise.resolve(false); | ||
if (this.isLeader) return Promise.resolve(false); | ||
if (this.isDead) return Promise.resolve(false); // do nothing if already running | ||
// do nothing if already running | ||
if (this._isApplying) { | ||
this._reApply = true; | ||
return Promise.resolve(false); | ||
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; | ||
} | ||
} | ||
this._isApplying = true; | ||
var stopCriteria = false; | ||
var recieved = []; | ||
if (msg.action === 'tell') { | ||
// other is already leader | ||
stopCriteria = true; | ||
} | ||
} | ||
}; | ||
var handleMessage = function handleMessage(msg) { | ||
if (msg.context === 'leader' && msg.token != _this.token) { | ||
recieved.push(msg); | ||
this._channel.addEventListener('internal', handleMessage); | ||
if (msg.action === 'apply') { | ||
// other is applying | ||
if (msg.token > _this.token) { | ||
// other has higher token, stop applying | ||
stopCriteria = true; | ||
} | ||
} | ||
var ret = _sendMessage(this, 'apply') // send out that this one is applying | ||
.then(function () { | ||
return 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 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); | ||
if (msg.action === 'tell') { | ||
// other is already leader | ||
stopCriteria = true; | ||
} | ||
} | ||
}; | ||
this._channel.addEventListener('internal', handleMessage); | ||
_this._isApl = false; | ||
var ret = this._sendMessage('apply') // send out that this one is applying | ||
.then(function () { | ||
return sleep(_this._options.responseTime); | ||
}) // let others time to respond | ||
.then(function () { | ||
if (stopCriteria) return Promise.reject(new Error());else return _this._sendMessage('apply'); | ||
}).then(function () { | ||
return sleep(_this._options.responseTime); | ||
}) // let others time to respond | ||
.then(function () { | ||
if (stopCriteria) return Promise.reject(new Error());else return _this._sendMessage(); | ||
}).then(function () { | ||
return _this._beLeader(); | ||
}) // 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._isApplying = false; | ||
if (!success && _this._reApply) { | ||
_this._reApply = false; | ||
return _this.applyOnce(); | ||
} else return success; | ||
}); | ||
return ret; | ||
}, | ||
_awaitLeadershipOnce: function _awaitLeadershipOnce() { | ||
var _this2 = this; | ||
if (!success && _this._reApply) { | ||
_this._reApply = false; | ||
return _this.applyOnce(); | ||
} else return success; | ||
}); | ||
if (this.isLeader) return Promise.resolve(); | ||
return ret; | ||
}, | ||
awaitLeadership: function awaitLeadership() { | ||
if (!this._awaitLeadershipPromise) { | ||
this._awaitLeadershipPromise = _awaitLeadershipOnce(this); | ||
} | ||
return new Promise(function (res) { | ||
var resolved = false; | ||
return this._awaitLeadershipPromise; | ||
}, | ||
die: function die() { | ||
var _this2 = this; | ||
var finish = function finish() { | ||
if (resolved) return; | ||
resolved = true; | ||
clearInterval(interval); | ||
_this2._channel.removeEventListener('internal', whenDeathListener); | ||
res(true); | ||
}; | ||
if (this.isDead) return; | ||
this.isDead = true; | ||
// try once now | ||
_this2.applyOnce().then(function () { | ||
if (_this2.isLeader) finish(); | ||
}); | ||
this._lstns.forEach(function (listener) { | ||
return _this2._channel.removeEventListener('internal', listener); | ||
}); | ||
// try on fallbackInterval | ||
var interval = setInterval(function () { | ||
_this2.applyOnce().then(function () { | ||
if (_this2.isLeader) finish(); | ||
}); | ||
}, _this2._options.fallbackInterval); | ||
_this2._intervals.push(interval); | ||
this._invs.forEach(function (interval) { | ||
return clearInterval(interval); | ||
}); | ||
// try when other leader dies | ||
var whenDeathListener = function whenDeathListener(msg) { | ||
if (msg.context === 'leader' && msg.action === 'death') { | ||
_this2.applyOnce().then(function () { | ||
if (_this2.isLeader) finish(); | ||
}); | ||
} | ||
}; | ||
_this2._channel.addEventListener('internal', whenDeathListener); | ||
_this2._listeners.push(whenDeathListener); | ||
}); | ||
}, | ||
awaitLeadership: function awaitLeadership() { | ||
if (!this._awaitLeadershipPromise) { | ||
this._awaitLeadershipPromise = this._awaitLeadershipOnce(); | ||
} | ||
return this._awaitLeadershipPromise; | ||
}, | ||
die: function die() { | ||
var _this3 = this; | ||
this._unl.forEach(function (uFn) { | ||
uFn.remove(); | ||
}); | ||
if (this.isDead) return; | ||
this.isDead = true; | ||
return _sendMessage(this, 'death'); | ||
} | ||
}; | ||
this._listeners.forEach(function (listener) { | ||
return _this3._channel.removeEventListener('internal', listener); | ||
}); | ||
this._intervals.forEach(function (interval) { | ||
return clearInterval(interval); | ||
}); | ||
this._unloads.forEach(function (uFn) { | ||
uFn.remove(); | ||
}); | ||
return this._sendMessage('death'); | ||
}, | ||
function _awaitLeadershipOnce(leaderElector) { | ||
if (leaderElector.isLeader) return Promise.resolve(); | ||
return new Promise(function (res) { | ||
var resolved = false; | ||
var finish = function finish() { | ||
if (resolved) return; | ||
resolved = true; | ||
clearInterval(interval); | ||
/** | ||
* sends and internal message over the broadcast-channel | ||
*/ | ||
_sendMessage: function _sendMessage(action) { | ||
var msgJson = { | ||
context: 'leader', | ||
action: action, | ||
token: this.token | ||
}; | ||
return this._channel.postInternal(msgJson); | ||
}, | ||
_beLeader: function _beLeader() { | ||
var _this4 = this; | ||
leaderElector._channel.removeEventListener('internal', whenDeathListener); | ||
this.isLeader = true; | ||
var unloadFn = unload.add(function () { | ||
return _this4.die(); | ||
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(); | ||
}); | ||
this._unloads.push(unloadFn); | ||
} | ||
}; | ||
var isLeaderListener = function isLeaderListener(msg) { | ||
if (msg.context === 'leader' && msg.action === 'apply') { | ||
_this4._sendMessage('tell'); | ||
} | ||
}; | ||
this._channel.addEventListener('internal', isLeaderListener); | ||
this._listeners.push(isLeaderListener); | ||
return this._sendMessage('tell'); | ||
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.add(function () { | ||
return leaderElector.die(); | ||
}); | ||
leaderElector._unl.push(unloadFn); | ||
var isLeaderListener = function isLeaderListener(msg) { | ||
if (msg.context === 'leader' && msg.action === 'apply') { | ||
_sendMessage(leaderElector, 'tell'); | ||
} | ||
}; | ||
}; | ||
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) options = {}; | ||
options = JSON.parse(JSON.stringify(options)); | ||
if (!options.fallbackInterval) { | ||
options.fallbackInterval = 3000; | ||
} | ||
if (!options.fallbackInterval) { | ||
options.fallbackInterval = 3000; | ||
} | ||
if (!options.responseTime) { | ||
options.responseTime = channel.method.averageResponseTime(channel.options); | ||
} | ||
if (!options.responseTime) { | ||
options.responseTime = channel.method.averageResponseTime(channel.options); | ||
} | ||
return options; | ||
return options; | ||
} | ||
export function create(channel, options) { | ||
if (channel._leaderElector) { | ||
throw new Error('BroadcastChannel already has a leader-elector'); | ||
} | ||
if (channel._leaderElector) { | ||
throw new Error('BroadcastChannel already has a leader-elector'); | ||
} | ||
options = fillOptionsWithDefaults(options, channel); | ||
var elector = new LeaderElection(channel, options); | ||
channel._beforeClose.push(function () { | ||
return elector.die(); | ||
}); | ||
options = fillOptionsWithDefaults(options, channel); | ||
var elector = new LeaderElection(channel, options); | ||
channel._leaderElector = elector; | ||
return elector; | ||
channel._befC.push(function () { | ||
return elector.die(); | ||
}); | ||
channel._leaderElector = elector; | ||
return elector; | ||
} | ||
export default { | ||
create: create | ||
create: create | ||
}; |
import isNode from 'detect-node'; | ||
import NativeMethod from './methods/native.js'; | ||
import IndexeDbMethod from './methods/indexed-db.js'; | ||
import LocalstorageMethod from './methods/localstorage.js'; | ||
import LocalstorageMethod from './methods/localstorage.js'; // order is important | ||
// order is important | ||
var METHODS = [NativeMethod, // fastest | ||
IndexeDbMethod, LocalstorageMethod]; | ||
var REQUIRE_FUN = require; | ||
/** | ||
@@ -17,42 +13,43 @@ * The NodeMethod is loaded lazy | ||
*/ | ||
if (isNode) { | ||
/** | ||
* we use the non-transpiled code for nodejs | ||
* because it runs faster | ||
*/ | ||
var NodeMethod = REQUIRE_FUN('../../src/methods/node.js'); | ||
/** | ||
* this will be false for webpackbuilds | ||
* which will shim the node-method with an empty object {} | ||
*/ | ||
/** | ||
* we use the non-transpiled code for nodejs | ||
* because it runs faster | ||
*/ | ||
var NodeMethod = REQUIRE_FUN('../../src/methods/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); | ||
} | ||
if (typeof NodeMethod.canBeUsed === 'function') { | ||
METHODS.push(NodeMethod); | ||
} | ||
} | ||
export function chooseMethod(options) { | ||
// directly chosen | ||
if (options.type) { | ||
var ret = METHODS.find(function (m) { | ||
return m.type === options.type; | ||
}); | ||
if (!ret) throw new Error('method-type ' + options.type + ' not found');else return ret; | ||
} | ||
// directly chosen | ||
if (options.type) { | ||
var ret = METHODS.find(function (m) { | ||
return m.type === options.type; | ||
}); | ||
if (!ret) throw new Error('method-type ' + options.type + ' not found');else return ret; | ||
} | ||
var chooseMethods = METHODS; | ||
if (!options.webWorkerSupport && !isNode) { | ||
// prefer localstorage over idb when no webworker-support needed | ||
chooseMethods = METHODS.filter(function (m) { | ||
return m.type !== 'idb'; | ||
}); | ||
} | ||
var chooseMethods = METHODS; | ||
var useMethod = chooseMethods.find(function (method) { | ||
return method.canBeUsed(); | ||
if (!options.webWorkerSupport && !isNode) { | ||
// prefer localstorage over idb when no webworker-support needed | ||
chooseMethods = METHODS.filter(function (m) { | ||
return m.type !== 'idb'; | ||
}); | ||
if (!useMethod) throw new Error('No useable methode found:' + JSON.stringify(METHODS.map(function (m) { | ||
return m.type; | ||
})));else return useMethod; | ||
} | ||
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; | ||
} |
@@ -6,52 +6,42 @@ /** | ||
*/ | ||
import isNode from 'detect-node'; | ||
import { sleep, randomInt, randomToken, microSeconds as micro } from '../util.js'; | ||
export var microSeconds = micro; | ||
import ObliviousSet from '../oblivious-set'; | ||
import { fillOptionsWithDefaults } from '../options'; | ||
var DB_PREFIX = 'pubkey.broadcast-channel-0-'; | ||
var OBJECT_STORE_ID = 'messages'; | ||
export var type = 'idb'; | ||
export function getIdb() { | ||
if (typeof indexedDB !== 'undefined') return indexedDB; | ||
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 (typeof indexedDB !== 'undefined') return indexedDB; | ||
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; | ||
} | ||
export function createDatabase(channelName) { | ||
var IndexedDB = getIdb(); | ||
var IndexedDB = getIdb(); // create table | ||
// create table | ||
var dbName = DB_PREFIX + channelName; | ||
var openRequest = IndexedDB.open(dbName, 1); | ||
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); | ||
}; | ||
openRequest.onupgradeneeded = function (ev) { | ||
var db = ev.target.result; | ||
db.createObjectStore(OBJECT_STORE_ID, { | ||
keyPath: 'id', | ||
autoIncrement: true | ||
}); | ||
}; | ||
return dbPromise; | ||
var dbPromise = new Promise(function (res, rej) { | ||
openRequest.onerror = function (ev) { | ||
return rej(ev); | ||
}; | ||
openRequest.onsuccess = function () { | ||
res(openRequest.result); | ||
}; | ||
}); | ||
return dbPromise; | ||
} | ||
/** | ||
@@ -61,229 +51,219 @@ * writes the new message to the database | ||
*/ | ||
export function writeMessage(db, readerUuid, messageJson) { | ||
var time = new Date().getTime(); | ||
var writeObject = { | ||
uuid: readerUuid, | ||
time: time, | ||
data: 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(); | ||
}; | ||
var transaction = db.transaction([OBJECT_STORE_ID], 'readwrite'); | ||
transaction.onerror = function (ev) { | ||
return rej(ev); | ||
}; | ||
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); | ||
}); | ||
var objectStore = transaction.objectStore(OBJECT_STORE_ID); | ||
objectStore.add(writeObject); | ||
}); | ||
} | ||
export 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; | ||
export 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); | ||
} | ||
}; | ||
}); | ||
if (cursor) { | ||
ret.push(cursor.value); //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); | ||
cursor["continue"](); | ||
} else { | ||
res(ret); | ||
} | ||
}; | ||
}); | ||
} | ||
export function getMessagesHigherThen(db, lastCursorId) { | ||
var objectStore = db.transaction(OBJECT_STORE_ID).objectStore(OBJECT_STORE_ID); | ||
var ret = []; | ||
var keyRangeValue = IDBKeyRange.bound(lastCursorId + 1, Infinity); | ||
return new Promise(function (res) { | ||
objectStore.openCursor(keyRangeValue).onsuccess = function (ev) { | ||
var cursor = ev.target.result; | ||
export function getMessagesHigherThen(db, lastCursorId) { | ||
var objectStore = db.transaction(OBJECT_STORE_ID).objectStore(OBJECT_STORE_ID); | ||
var ret = []; | ||
var keyRangeValue = IDBKeyRange.bound(lastCursorId + 1, Infinity); | ||
return new Promise(function (res) { | ||
objectStore.openCursor(keyRangeValue).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); | ||
} | ||
}; | ||
}); | ||
if (cursor) { | ||
ret.push(cursor.value); //alert("Name for SSN " + cursor.key + " is " + cursor.value.name); | ||
cursor["continue"](); | ||
} else { | ||
res(ret); | ||
} | ||
}; | ||
}); | ||
} | ||
export 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(); | ||
}; | ||
}); | ||
var request = db.transaction([OBJECT_STORE_ID], 'readwrite').objectStore(OBJECT_STORE_ID)["delete"](id); | ||
return new Promise(function (res) { | ||
request.onsuccess = function () { | ||
return res(); | ||
}; | ||
}); | ||
} | ||
export 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; | ||
export 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); | ||
} | ||
}; | ||
}); | ||
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); | ||
} | ||
}; | ||
}); | ||
} | ||
export function cleanOldMessages(db, ttl) { | ||
return getOldMessages(db, ttl).then(function (tooOld) { | ||
return Promise.all(tooOld.map(function (msgObj) { | ||
return removeMessageById(db, msgObj.id); | ||
})); | ||
}); | ||
return getOldMessages(db, ttl).then(function (tooOld) { | ||
return Promise.all(tooOld.map(function (msgObj) { | ||
return removeMessageById(db, msgObj.id); | ||
})); | ||
}); | ||
} | ||
export function create(channelName, options) { | ||
options = fillOptionsWithDefaults(options); | ||
options = fillOptionsWithDefaults(options); | ||
var uuid = randomToken(10); | ||
return createDatabase(channelName).then(function (db) { | ||
var state = { | ||
closed: false, | ||
lastCursorId: 0, | ||
channelName: channelName, | ||
options: options, | ||
uuid: uuid, | ||
// contains all messages that have been emitted before | ||
emittedMessagesIds: new ObliviousSet(options.idb.ttl * 2), | ||
// ensures we do not read messages in parrallel | ||
writeBlockPromise: Promise.resolve(), | ||
messagesCallback: null, | ||
readQueuePromises: [], | ||
db: db | ||
}; | ||
/** | ||
* if service-workers are used, | ||
* we have no 'storage'-event if they post a message, | ||
* therefore we also have to set an interval | ||
*/ | ||
var uuid = randomToken(10); | ||
_readLoop(state); | ||
return createDatabase(channelName).then(function (db) { | ||
var state = { | ||
closed: false, | ||
lastCursorId: 0, | ||
channelName: channelName, | ||
options: options, | ||
uuid: uuid, | ||
// contains all messages that have been emitted before | ||
emittedMessagesIds: new ObliviousSet(options.idb.ttl * 2), | ||
// ensures we do not read messages in parrallel | ||
writeBlockPromise: Promise.resolve(), | ||
messagesCallback: null, | ||
readQueuePromises: [], | ||
db: db | ||
}; | ||
/** | ||
* 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; | ||
}); | ||
return state; | ||
}); | ||
} | ||
function _readLoop(state) { | ||
if (state.closed) return; | ||
return readNewMessages(state).then(function () { | ||
return sleep(state.options.idb.fallbackInterval); | ||
}).then(function () { | ||
return _readLoop(state); | ||
}); | ||
if (state.closed) return; | ||
return readNewMessages(state).then(function () { | ||
return 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.emittedMessagesIds.has(msgObj.id)) return false; // already emitted | ||
if (msgObj.data.time < state.messagesCallbackTime) return false; // older then onMessageCallback | ||
return true; | ||
if (msgObj.uuid === state.uuid) return false; // send by own | ||
if (state.emittedMessagesIds.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 | ||
// channel already closed | ||
if (state.closed) return Promise.resolve(); | ||
if (!state.messagesCallback) return Promise.resolve(); | ||
return getMessagesHigherThen(state.db, state.lastCursorId).then(function (newerMessages) { | ||
var useMessages = newerMessages.map(function (msgObj) { | ||
if (msgObj.id > state.lastCursorId) { | ||
state.lastCursorId = msgObj.id; | ||
} | ||
// if no one is listening, we do not need to scan for new messages | ||
if (!state.messagesCallback) return Promise.resolve(); | ||
return msgObj; | ||
}).filter(function (msgObj) { | ||
return _filterMessage(msgObj, state); | ||
}).sort(function (msgObjA, msgObjB) { | ||
return msgObjA.time - msgObjB.time; | ||
}); // sort by time | ||
return getMessagesHigherThen(state.db, state.lastCursorId).then(function (newerMessages) { | ||
var useMessages = newerMessages.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.emittedMessagesIds.add(msgObj.id); | ||
state.messagesCallback(msgObj.data); | ||
} | ||
}); | ||
return Promise.resolve(); | ||
useMessages.forEach(function (msgObj) { | ||
if (state.messagesCallback) { | ||
state.emittedMessagesIds.add(msgObj.id); | ||
state.messagesCallback(msgObj.data); | ||
} | ||
}); | ||
return Promise.resolve(); | ||
}); | ||
} | ||
export function close(channelState) { | ||
channelState.closed = true; | ||
channelState.db.close(); | ||
channelState.closed = true; | ||
channelState.db.close(); | ||
} | ||
export function postMessage(channelState, messageJson) { | ||
channelState.writeBlockPromise = channelState.writeBlockPromise.then(function () { | ||
return writeMessage(channelState.db, channelState.uuid, messageJson); | ||
}).then(function () { | ||
if (randomInt(0, 10) === 0) { | ||
/* await (do not await) */cleanOldMessages(channelState.db, channelState.options.idb.ttl); | ||
} | ||
}); | ||
return channelState.writeBlockPromise; | ||
channelState.writeBlockPromise = channelState.writeBlockPromise.then(function () { | ||
return writeMessage(channelState.db, channelState.uuid, messageJson); | ||
}).then(function () { | ||
if (randomInt(0, 10) === 0) { | ||
/* await (do not await) */ | ||
cleanOldMessages(channelState.db, channelState.options.idb.ttl); | ||
} | ||
}); | ||
return channelState.writeBlockPromise; | ||
} | ||
export function onMessage(channelState, fn, time) { | ||
channelState.messagesCallbackTime = time; | ||
channelState.messagesCallback = fn; | ||
readNewMessages(channelState); | ||
channelState.messagesCallbackTime = time; | ||
channelState.messagesCallback = fn; | ||
readNewMessages(channelState); | ||
} | ||
export function canBeUsed() { | ||
if (isNode) return false; | ||
var idb = getIdb(); | ||
if (!idb) return false; | ||
return true; | ||
if (isNode) return false; | ||
var idb = getIdb(); | ||
if (!idb) return false; | ||
return true; | ||
} | ||
export function averageResponseTime(options) { | ||
return options.idb.fallbackInterval * 2; | ||
return options.idb.fallbackInterval * 2; | ||
} | ||
export default { | ||
create: create, | ||
close: close, | ||
onMessage: onMessage, | ||
postMessage: postMessage, | ||
canBeUsed: canBeUsed, | ||
type: type, | ||
averageResponseTime: averageResponseTime, | ||
microSeconds: microSeconds | ||
create: create, | ||
close: close, | ||
onMessage: onMessage, | ||
postMessage: postMessage, | ||
canBeUsed: canBeUsed, | ||
type: type, | ||
averageResponseTime: averageResponseTime, | ||
microSeconds: microSeconds | ||
}; |
@@ -8,15 +8,9 @@ /** | ||
*/ | ||
import isNode from 'detect-node'; | ||
import ObliviousSet from '../oblivious-set'; | ||
import { fillOptionsWithDefaults } from '../options'; | ||
import { sleep, randomToken, microSeconds as micro } from '../util'; | ||
export var microSeconds = micro; | ||
var KEY_PREFIX = 'pubkey.broadcastChannel-'; | ||
export var type = 'localstorage'; | ||
/** | ||
@@ -26,20 +20,20 @@ * copied from crosstab | ||
*/ | ||
export function getLocalStorage() { | ||
var localStorage = void 0; | ||
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; | ||
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; | ||
} | ||
export function storageKey(channelName) { | ||
return KEY_PREFIX + channelName; | ||
return KEY_PREFIX + channelName; | ||
} | ||
/** | ||
@@ -49,108 +43,102 @@ * writes the new message to the storage | ||
*/ | ||
export function postMessage(channelState, messageJson) { | ||
return new Promise(function (res) { | ||
sleep().then(function () { | ||
var key = storageKey(channelState.channelName); | ||
var writeObj = { | ||
token: randomToken(10), | ||
time: new Date().getTime(), | ||
data: messageJson, | ||
uuid: channelState.uuid | ||
}; | ||
var value = JSON.stringify(writeObj); | ||
localStorage.setItem(key, value); | ||
return new Promise(function (res) { | ||
sleep().then(function () { | ||
var key = storageKey(channelState.channelName); | ||
var writeObj = { | ||
token: randomToken(10), | ||
time: new Date().getTime(), | ||
data: messageJson, | ||
uuid: channelState.uuid | ||
}; | ||
var value = JSON.stringify(writeObj); | ||
localStorage.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 | ||
*/ | ||
/** | ||
* 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(); | ||
}); | ||
var ev = document.createEvent('Event'); | ||
ev.initEvent('storage', true, true); | ||
ev.key = key; | ||
ev.newValue = value; | ||
window.dispatchEvent(ev); | ||
res(); | ||
}); | ||
}); | ||
} | ||
export function addStorageEventListener(channelName, fn) { | ||
var key = storageKey(channelName); | ||
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; | ||
var listener = function listener(ev) { | ||
if (ev.key === key) { | ||
fn(JSON.parse(ev.newValue)); | ||
} | ||
}; | ||
window.addEventListener('storage', listener); | ||
return listener; | ||
} | ||
export function removeStorageEventListener(listener) { | ||
window.removeEventListener('storage', listener); | ||
window.removeEventListener('storage', listener); | ||
} | ||
export function create(channelName, options) { | ||
options = fillOptionsWithDefaults(options); | ||
if (!canBeUsed()) { | ||
throw new Error('BroadcastChannel: localstorage cannot be used'); | ||
} | ||
options = fillOptionsWithDefaults(options); | ||
var startTime = new Date().getTime(); | ||
var uuid = randomToken(10); | ||
if (!canBeUsed()) { | ||
throw new Error('BroadcastChannel: localstorage cannot be used'); | ||
} | ||
// contains all messages that have been emitted before | ||
var emittedMessagesIds = new ObliviousSet(options.localstorage.removeTimeout); | ||
var startTime = new Date().getTime(); | ||
var uuid = randomToken(10); // contains all messages that have been emitted before | ||
var state = { | ||
startTime: startTime, | ||
channelName: channelName, | ||
options: options, | ||
uuid: uuid, | ||
emittedMessagesIds: emittedMessagesIds | ||
}; | ||
var emittedMessagesIds = new ObliviousSet(options.localstorage.removeTimeout); | ||
var state = { | ||
startTime: startTime, | ||
channelName: channelName, | ||
options: options, | ||
uuid: uuid, | ||
emittedMessagesIds: emittedMessagesIds | ||
}; | ||
state.listener = addStorageEventListener(channelName, function (msgObj) { | ||
if (!state.messagesCallback) return; // no listener | ||
state.listener = addStorageEventListener(channelName, function (msgObj) { | ||
if (!state.messagesCallback) return; // no listener | ||
if (msgObj.uuid === uuid) return; // own message | ||
if (!msgObj.token || emittedMessagesIds.has(msgObj.token)) return; // already emitted | ||
if (msgObj.data.time && msgObj.data.time < state.messagesCallbackTime) return; // too old | ||
if (msgObj.uuid === uuid) return; // own message | ||
emittedMessagesIds.add(msgObj.token); | ||
state.messagesCallback(msgObj.data); | ||
}); | ||
if (!msgObj.token || emittedMessagesIds.has(msgObj.token)) return; // already emitted | ||
return state; | ||
if (msgObj.data.time && msgObj.data.time < state.messagesCallbackTime) return; // too old | ||
emittedMessagesIds.add(msgObj.token); | ||
state.messagesCallback(msgObj.data); | ||
}); | ||
return state; | ||
} | ||
export function close(channelState) { | ||
removeStorageEventListener(channelState.listener); | ||
removeStorageEventListener(channelState.listener); | ||
} | ||
export function onMessage(channelState, fn, time) { | ||
channelState.messagesCallbackTime = time; | ||
channelState.messagesCallback = fn; | ||
channelState.messagesCallbackTime = time; | ||
channelState.messagesCallback = fn; | ||
} | ||
export function canBeUsed() { | ||
if (isNode) return false; | ||
var ls = getLocalStorage(); | ||
if (!ls) return false; | ||
return true; | ||
if (isNode) return false; | ||
var ls = getLocalStorage(); | ||
if (!ls) return false; | ||
return true; | ||
} | ||
export function averageResponseTime() { | ||
return 120; | ||
return 120; | ||
} | ||
export default { | ||
create: create, | ||
close: close, | ||
onMessage: onMessage, | ||
postMessage: postMessage, | ||
canBeUsed: canBeUsed, | ||
type: type, | ||
averageResponseTime: averageResponseTime, | ||
microSeconds: microSeconds | ||
create: create, | ||
close: close, | ||
onMessage: onMessage, | ||
postMessage: postMessage, | ||
canBeUsed: canBeUsed, | ||
type: type, | ||
averageResponseTime: averageResponseTime, | ||
microSeconds: microSeconds | ||
}; |
import isNode from 'detect-node'; | ||
import { randomToken, microSeconds as micro } from '../util'; | ||
export var microSeconds = micro; | ||
export var type = 'native'; | ||
export function create(channelName, options) { | ||
if (!options) options = {}; | ||
var state = { | ||
uuid: randomToken(10), | ||
channelName: channelName, | ||
options: options, | ||
messagesCallback: null, | ||
bc: new BroadcastChannel(channelName), | ||
subscriberFunctions: [] | ||
}; | ||
if (!options) options = {}; | ||
var state = { | ||
uuid: randomToken(10), | ||
channelName: channelName, | ||
options: options, | ||
messagesCallback: null, | ||
bc: new BroadcastChannel(channelName), | ||
subscriberFunctions: [] | ||
}; | ||
state.bc.onmessage = function (msg) { | ||
if (state.messagesCallback) { | ||
state.messagesCallback(msg.data); | ||
} | ||
}; | ||
state.bc.onmessage = function (msg) { | ||
if (state.messagesCallback) { | ||
state.messagesCallback(msg.data); | ||
} | ||
}; | ||
return state; | ||
return state; | ||
} | ||
export function close(channelState) { | ||
channelState.bc.close(); | ||
channelState.subscriberFunctions = []; | ||
channelState.bc.close(); | ||
channelState.subscriberFunctions = []; | ||
} | ||
export function postMessage(channelState, messageJson) { | ||
channelState.bc.postMessage(messageJson, false); | ||
channelState.bc.postMessage(messageJson, false); | ||
} | ||
export function onMessage(channelState, fn, time) { | ||
channelState.messagesCallbackTime = time; | ||
channelState.messagesCallback = fn; | ||
channelState.messagesCallbackTime = time; | ||
channelState.messagesCallback = fn; | ||
} | ||
export 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 (isNode && typeof window === 'undefined') return false; | ||
/** | ||
* in the electron-renderer, isNode will be true even if we are in browser-context | ||
* so we also check if window is undefined | ||
*/ | ||
if (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'); | ||
} | ||
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; | ||
return true; | ||
} else return false; | ||
} | ||
export function averageResponseTime() { | ||
return 100; | ||
return 100; | ||
} | ||
export default { | ||
create: create, | ||
close: close, | ||
onMessage: onMessage, | ||
postMessage: postMessage, | ||
canBeUsed: canBeUsed, | ||
type: type, | ||
averageResponseTime: averageResponseTime, | ||
microSeconds: microSeconds | ||
create: create, | ||
close: close, | ||
onMessage: onMessage, | ||
postMessage: postMessage, | ||
canBeUsed: canBeUsed, | ||
type: type, | ||
averageResponseTime: averageResponseTime, | ||
microSeconds: microSeconds | ||
}; |
@@ -1,743 +0,786 @@ | ||
import _regeneratorRuntime from 'babel-runtime/regenerator'; | ||
import _asyncToGenerator from 'babel-runtime/helpers/asyncToGenerator'; | ||
import _regeneratorRuntime from "@babel/runtime/regenerator"; | ||
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator"; | ||
var ensureFoldersExist = function () { | ||
var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(channelName, paths) { | ||
return _regeneratorRuntime.wrap(function _callee$(_context) { | ||
while (1) { | ||
switch (_context.prev = _context.next) { | ||
case 0: | ||
paths = paths || getPaths(channelName); | ||
/** | ||
* this method is used in nodejs-environments. | ||
* The ipc is handled via sockets and file-writes to the tmp-folder | ||
*/ | ||
var util = require('util'); | ||
if (!ENSURE_BASE_FOLDER_EXISTS_PROMISE) { | ||
ENSURE_BASE_FOLDER_EXISTS_PROMISE = mkdir(paths.base)['catch'](function () { | ||
return null; | ||
}); | ||
} | ||
_context.next = 4; | ||
return ENSURE_BASE_FOLDER_EXISTS_PROMISE; | ||
var fs = require('fs'); | ||
case 4: | ||
_context.next = 6; | ||
return mkdir(paths.channelBase)['catch'](function () { | ||
return null; | ||
}); | ||
var os = require('os'); | ||
case 6: | ||
_context.next = 8; | ||
return Promise.all([mkdir(paths.readers)['catch'](function () { | ||
return null; | ||
}), mkdir(paths.messages)['catch'](function () { | ||
return null; | ||
})]); | ||
var events = require('events'); | ||
case 8: | ||
case 'end': | ||
return _context.stop(); | ||
} | ||
} | ||
}, _callee, this); | ||
})); | ||
var net = require('net'); | ||
return function ensureFoldersExist(_x, _x2) { | ||
return _ref.apply(this, arguments); | ||
}; | ||
}(); | ||
var path = require('path'); | ||
/** | ||
* removes the tmp-folder | ||
* @return {Promise<true>} | ||
*/ | ||
var micro = require('nano-time'); | ||
var rimraf = require('rimraf'); | ||
var clearNodeFolder = function () { | ||
var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() { | ||
var paths, removePath; | ||
return _regeneratorRuntime.wrap(function _callee2$(_context2) { | ||
while (1) { | ||
switch (_context2.prev = _context2.next) { | ||
case 0: | ||
paths = getPaths('foobar'); | ||
removePath = paths.base; | ||
var sha3_224 = require('js-sha3').sha3_224; | ||
if (!(!removePath || removePath === '' || removePath === '/')) { | ||
_context2.next = 4; | ||
break; | ||
} | ||
var isNode = require('detect-node'); | ||
throw new Error('BroadcastChannel.clearNodeFolder(): path is wrong'); | ||
var unload = require('unload'); | ||
case 4: | ||
ENSURE_BASE_FOLDER_EXISTS_PROMISE = null; | ||
_context2.next = 7; | ||
return removeDir(paths.base); | ||
var fillOptionsWithDefaults = require('../../dist/lib/options.js').fillOptionsWithDefaults; | ||
case 7: | ||
ENSURE_BASE_FOLDER_EXISTS_PROMISE = null; | ||
return _context2.abrupt('return', true); | ||
var ownUtil = require('../../dist/lib/util.js'); | ||
case 9: | ||
case 'end': | ||
return _context2.stop(); | ||
} | ||
} | ||
}, _callee2, this); | ||
})); | ||
var randomInt = ownUtil.randomInt; | ||
var randomToken = ownUtil.randomToken; | ||
return function clearNodeFolder() { | ||
return _ref2.apply(this, arguments); | ||
}; | ||
}(); | ||
var ObliviousSet = require('../../dist/lib/oblivious-set')["default"]; | ||
/** | ||
* creates the socket-file and subscribes to it | ||
* @return {{emitter: EventEmitter, server: any}} | ||
* windows sucks, so we have handle windows-type of socket-paths | ||
* @link https://gist.github.com/domenic/2790533#gistcomment-331356 | ||
*/ | ||
var createSocketEventEmitter = function () { | ||
var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3(channelName, readerUuid, paths) { | ||
var pathToSocket, emitter, server; | ||
return _regeneratorRuntime.wrap(function _callee3$(_context3) { | ||
while (1) { | ||
switch (_context3.prev = _context3.next) { | ||
case 0: | ||
pathToSocket = socketPath(channelName, readerUuid, paths); | ||
emitter = new events.EventEmitter(); | ||
server = net.createServer(function (stream) { | ||
stream.on('end', function () {}); | ||
stream.on('data', function (msg) { | ||
emitter.emit('data', msg.toString()); | ||
}); | ||
}); | ||
_context3.next = 5; | ||
return new Promise(function (resolve, reject) { | ||
server.listen(pathToSocket, function (err, res) { | ||
if (err) reject(err);else resolve(res); | ||
}); | ||
}); | ||
case 5: | ||
return _context3.abrupt('return', { | ||
path: pathToSocket, | ||
emitter: emitter, | ||
server: server | ||
}); | ||
case 6: | ||
case 'end': | ||
return _context3.stop(); | ||
} | ||
} | ||
}, _callee3, this); | ||
})); | ||
function cleanPipeName(str) { | ||
if (process.platform === 'win32' && !str.startsWith('\\\\.\\pipe\\')) { | ||
str = str.replace(/^\//, ''); | ||
str = str.replace(/\//g, '-'); | ||
return '\\\\.\\pipe\\' + str; | ||
} else { | ||
return str; | ||
} | ||
} | ||
return function createSocketEventEmitter(_x3, _x4, _x5) { | ||
return _ref3.apply(this, arguments); | ||
}; | ||
}(); | ||
var mkdir = util.promisify(fs.mkdir); | ||
var writeFile = util.promisify(fs.writeFile); | ||
var readFile = util.promisify(fs.readFile); | ||
var unlink = util.promisify(fs.unlink); | ||
var readdir = util.promisify(fs.readdir); | ||
var removeDir = util.promisify(rimraf); | ||
var OTHER_INSTANCES = {}; | ||
var TMP_FOLDER_NAME = 'pubkey.bc'; | ||
var TMP_FOLDER_BASE = path.join(os.tmpdir(), TMP_FOLDER_NAME); | ||
var getPathsCache = new Map(); | ||
var openClientConnection = function () { | ||
var _ref4 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4(channelName, readerUuid) { | ||
var pathToSocket, client; | ||
return _regeneratorRuntime.wrap(function _callee4$(_context4) { | ||
while (1) { | ||
switch (_context4.prev = _context4.next) { | ||
case 0: | ||
pathToSocket = socketPath(channelName, readerUuid); | ||
client = new net.Socket(); | ||
_context4.next = 4; | ||
return new Promise(function (res) { | ||
client.connect(pathToSocket, res); | ||
}); | ||
function getPaths(channelName) { | ||
if (!getPathsCache.has(channelName)) { | ||
var channelHash = sha3_224(channelName); // use hash incase of strange characters | ||
case 4: | ||
return _context4.abrupt('return', client); | ||
/** | ||
* because the lenght 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 | ||
*/ | ||
case 5: | ||
case 'end': | ||
return _context4.stop(); | ||
} | ||
} | ||
}, _callee4, this); | ||
})); | ||
return function openClientConnection(_x6, _x7) { | ||
return _ref4.apply(this, arguments); | ||
var channelFolder = 'A' + channelHash.substring(0, 20); | ||
var channelPathBase = path.join(TMP_FOLDER_BASE, channelFolder); | ||
var folderPathReaders = path.join(channelPathBase, 'rdrs'); | ||
var folderPathMessages = path.join(channelPathBase, 'messages'); | ||
var ret = { | ||
base: TMP_FOLDER_BASE, | ||
channelBase: channelPathBase, | ||
readers: folderPathReaders, | ||
messages: folderPathMessages | ||
}; | ||
}(); | ||
getPathsCache.set(channelName, ret); | ||
return ret; | ||
} | ||
/** | ||
* writes the new message to the file-system | ||
* so other readers can find it | ||
* @return {Promise} | ||
*/ | ||
return getPathsCache.get(channelName); | ||
} | ||
var ENSURE_BASE_FOLDER_EXISTS_PROMISE = null; | ||
function ensureFoldersExist(_x, _x2) { | ||
return _ensureFoldersExist.apply(this, arguments); | ||
} | ||
/** | ||
* returns the uuids of all readers | ||
* @return {string[]} | ||
* removes the tmp-folder | ||
* @return {Promise<true>} | ||
*/ | ||
var getReadersUuids = function () { | ||
var _ref5 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5(channelName, paths) { | ||
var readersPath, files; | ||
return _regeneratorRuntime.wrap(function _callee5$(_context5) { | ||
while (1) { | ||
switch (_context5.prev = _context5.next) { | ||
case 0: | ||
paths = paths || getPaths(channelName); | ||
readersPath = paths.readers; | ||
_context5.next = 4; | ||
return readdir(readersPath); | ||
case 4: | ||
files = _context5.sent; | ||
return _context5.abrupt('return', files.map(function (file) { | ||
return file.split('.'); | ||
}).filter(function (split) { | ||
return split[1] === 'json'; | ||
}) // do not scan .socket-files | ||
.map(function (split) { | ||
return split[0]; | ||
})); | ||
case 6: | ||
case 'end': | ||
return _context5.stop(); | ||
} | ||
function _ensureFoldersExist() { | ||
_ensureFoldersExist = _asyncToGenerator( | ||
/*#__PURE__*/ | ||
_regeneratorRuntime.mark(function _callee4(channelName, paths) { | ||
return _regeneratorRuntime.wrap(function _callee4$(_context4) { | ||
while (1) { | ||
switch (_context4.prev = _context4.next) { | ||
case 0: | ||
paths = paths || getPaths(channelName); | ||
if (!ENSURE_BASE_FOLDER_EXISTS_PROMISE) { | ||
ENSURE_BASE_FOLDER_EXISTS_PROMISE = mkdir(paths.base)["catch"](function () { | ||
return null; | ||
}); | ||
} | ||
}, _callee5, this); | ||
})); | ||
return function getReadersUuids(_x8, _x9) { | ||
return _ref5.apply(this, arguments); | ||
}; | ||
}(); | ||
_context4.next = 4; | ||
return ENSURE_BASE_FOLDER_EXISTS_PROMISE; | ||
var messagePath = function () { | ||
var _ref6 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee6(channelName, time, token, writerUuid) { | ||
var fileName, msgPath; | ||
return _regeneratorRuntime.wrap(function _callee6$(_context6) { | ||
while (1) { | ||
switch (_context6.prev = _context6.next) { | ||
case 0: | ||
fileName = time + '_' + writerUuid + '_' + token + '.json'; | ||
msgPath = path.join(getPaths(channelName).messages, fileName); | ||
return _context6.abrupt('return', msgPath); | ||
case 4: | ||
_context4.next = 6; | ||
return mkdir(paths.channelBase)["catch"](function () { | ||
return null; | ||
}); | ||
case 3: | ||
case 'end': | ||
return _context6.stop(); | ||
} | ||
} | ||
}, _callee6, this); | ||
})); | ||
case 6: | ||
_context4.next = 8; | ||
return Promise.all([mkdir(paths.readers)["catch"](function () { | ||
return null; | ||
}), mkdir(paths.messages)["catch"](function () { | ||
return null; | ||
})]); | ||
return function messagePath(_x10, _x11, _x12, _x13) { | ||
return _ref6.apply(this, arguments); | ||
}; | ||
}(); | ||
case 8: | ||
case "end": | ||
return _context4.stop(); | ||
} | ||
} | ||
}, _callee4, this); | ||
})); | ||
return _ensureFoldersExist.apply(this, arguments); | ||
} | ||
var getAllMessages = function () { | ||
var _ref7 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee7(channelName, paths) { | ||
var messagesPath, files; | ||
return _regeneratorRuntime.wrap(function _callee7$(_context7) { | ||
while (1) { | ||
switch (_context7.prev = _context7.next) { | ||
case 0: | ||
paths = paths || getPaths(channelName); | ||
messagesPath = paths.messages; | ||
_context7.next = 4; | ||
return readdir(messagesPath); | ||
function clearNodeFolder() { | ||
return _clearNodeFolder.apply(this, arguments); | ||
} | ||
case 4: | ||
files = _context7.sent; | ||
return _context7.abrupt('return', files.map(function (file) { | ||
var fileName = file.split('.')[0]; | ||
var split = fileName.split('_'); | ||
function _clearNodeFolder() { | ||
_clearNodeFolder = _asyncToGenerator( | ||
/*#__PURE__*/ | ||
_regeneratorRuntime.mark(function _callee5() { | ||
var paths, removePath; | ||
return _regeneratorRuntime.wrap(function _callee5$(_context5) { | ||
while (1) { | ||
switch (_context5.prev = _context5.next) { | ||
case 0: | ||
paths = getPaths('foobar'); | ||
removePath = paths.base; | ||
return { | ||
path: path.join(messagesPath, file), | ||
time: parseInt(split[0]), | ||
senderUuid: split[1], | ||
token: split[2] | ||
}; | ||
})); | ||
case 6: | ||
case 'end': | ||
return _context7.stop(); | ||
} | ||
if (!(!removePath || removePath === '' || removePath === '/')) { | ||
_context5.next = 4; | ||
break; | ||
} | ||
}, _callee7, this); | ||
})); | ||
return function getAllMessages(_x14, _x15) { | ||
return _ref7.apply(this, arguments); | ||
}; | ||
}(); | ||
throw new Error('BroadcastChannel.clearNodeFolder(): path is wrong'); | ||
var cleanOldMessages = function () { | ||
var _ref8 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee8(messageObjects, ttl) { | ||
var olderThen; | ||
return _regeneratorRuntime.wrap(function _callee8$(_context8) { | ||
while (1) { | ||
switch (_context8.prev = _context8.next) { | ||
case 0: | ||
olderThen = Date.now() - ttl; | ||
_context8.next = 3; | ||
return Promise.all(messageObjects.filter(function (obj) { | ||
return obj.time / 1000 < olderThen; | ||
}).map(function (obj) { | ||
return unlink(obj.path)['catch'](function () { | ||
return null; | ||
}); | ||
})); | ||
case 4: | ||
ENSURE_BASE_FOLDER_EXISTS_PROMISE = null; | ||
_context5.next = 7; | ||
return removeDir(paths.base); | ||
case 3: | ||
case 'end': | ||
return _context8.stop(); | ||
} | ||
} | ||
}, _callee8, this); | ||
})); | ||
case 7: | ||
ENSURE_BASE_FOLDER_EXISTS_PROMISE = null; | ||
return _context5.abrupt("return", true); | ||
return function cleanOldMessages(_x16, _x17) { | ||
return _ref8.apply(this, arguments); | ||
}; | ||
}(); | ||
case 9: | ||
case "end": | ||
return _context5.stop(); | ||
} | ||
} | ||
}, _callee5, this); | ||
})); | ||
return _clearNodeFolder.apply(this, arguments); | ||
} | ||
function socketPath(channelName, readerUuid, paths) { | ||
paths = paths || getPaths(channelName); | ||
var socketPath = path.join(paths.readers, readerUuid + '.s'); | ||
return cleanPipeName(socketPath); | ||
} | ||
function socketInfoPath(channelName, readerUuid, paths) { | ||
paths = paths || getPaths(channelName); | ||
var socketPath = path.join(paths.readers, readerUuid + '.json'); | ||
return socketPath; | ||
} | ||
/** | ||
* creates a new channelState | ||
* @return {Promise<any>} | ||
* 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 | ||
*/ | ||
var create = function () { | ||
var _ref9 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee9(channelName) { | ||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
var time, paths, ensureFolderExistsPromise, uuid, state, _ref10, socketEE, infoFilePath; | ||
return _regeneratorRuntime.wrap(function _callee9$(_context9) { | ||
while (1) { | ||
switch (_context9.prev = _context9.next) { | ||
case 0: | ||
options = fillOptionsWithDefaults(options); | ||
time = microSeconds(); | ||
paths = getPaths(channelName); | ||
ensureFolderExistsPromise = ensureFoldersExist(channelName, paths); | ||
uuid = randomToken(10); | ||
state = { | ||
time: time, | ||
channelName: channelName, | ||
options: options, | ||
uuid: uuid, | ||
paths: paths, | ||
// contains all messages that have been emitted before | ||
emittedMessagesIds: new ObliviousSet(options.node.ttl * 2), | ||
messagesCallbackTime: null, | ||
messagesCallback: null, | ||
// ensures we do not read messages in parrallel | ||
writeBlockPromise: Promise.resolve(), | ||
otherReaderClients: {}, | ||
// ensure if process crashes, everything is cleaned up | ||
removeUnload: unload.add(function () { | ||
return close(state); | ||
}), | ||
closed: false | ||
}; | ||
function createSocketInfoFile(channelName, readerUuid, paths) { | ||
var pathToFile = socketInfoPath(channelName, readerUuid, paths); | ||
return writeFile(pathToFile, JSON.stringify({ | ||
time: microSeconds() | ||
})).then(function () { | ||
return pathToFile; | ||
}); | ||
} | ||
/** | ||
* creates the socket-file and subscribes to it | ||
* @return {{emitter: EventEmitter, server: any}} | ||
*/ | ||
if (!OTHER_INSTANCES[channelName]) OTHER_INSTANCES[channelName] = []; | ||
OTHER_INSTANCES[channelName].push(state); | ||
function createSocketEventEmitter(_x3, _x4, _x5) { | ||
return _createSocketEventEmitter.apply(this, arguments); | ||
} | ||
_context9.next = 10; | ||
return ensureFolderExistsPromise; | ||
function _createSocketEventEmitter() { | ||
_createSocketEventEmitter = _asyncToGenerator( | ||
/*#__PURE__*/ | ||
_regeneratorRuntime.mark(function _callee6(channelName, readerUuid, paths) { | ||
var pathToSocket, emitter, server; | ||
return _regeneratorRuntime.wrap(function _callee6$(_context6) { | ||
while (1) { | ||
switch (_context6.prev = _context6.next) { | ||
case 0: | ||
pathToSocket = socketPath(channelName, readerUuid, paths); | ||
emitter = new events.EventEmitter(); | ||
server = net.createServer(function (stream) { | ||
stream.on('end', function () {}); | ||
stream.on('data', function (msg) { | ||
emitter.emit('data', msg.toString()); | ||
}); | ||
}); | ||
_context6.next = 5; | ||
return new Promise(function (resolve, reject) { | ||
server.listen(pathToSocket, function (err, res) { | ||
if (err) reject(err);else resolve(res); | ||
}); | ||
}); | ||
case 10: | ||
_context9.next = 12; | ||
return Promise.all([createSocketEventEmitter(channelName, uuid, paths), createSocketInfoFile(channelName, uuid, paths), refreshReaderClients(state)]); | ||
case 5: | ||
return _context6.abrupt("return", { | ||
path: pathToSocket, | ||
emitter: emitter, | ||
server: server | ||
}); | ||
case 12: | ||
_ref10 = _context9.sent; | ||
socketEE = _ref10[0]; | ||
infoFilePath = _ref10[1]; | ||
case 6: | ||
case "end": | ||
return _context6.stop(); | ||
} | ||
} | ||
}, _callee6, this); | ||
})); | ||
return _createSocketEventEmitter.apply(this, arguments); | ||
} | ||
state.socketEE = socketEE; | ||
state.infoFilePath = infoFilePath; | ||
function openClientConnection(_x6, _x7) { | ||
return _openClientConnection.apply(this, arguments); | ||
} | ||
/** | ||
* writes the new message to the file-system | ||
* so other readers can find it | ||
* @return {Promise} | ||
*/ | ||
// 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 | ||
var singleOnes = data.split('|'); | ||
singleOnes.filter(function (single) { | ||
return single !== ''; | ||
}).forEach(function (single) { | ||
try { | ||
var obj = JSON.parse(single); | ||
handleMessagePing(state, obj); | ||
} catch (err) { | ||
throw new Error('could not parse data: ' + single); | ||
} | ||
}); | ||
}); | ||
function _openClientConnection() { | ||
_openClientConnection = _asyncToGenerator( | ||
/*#__PURE__*/ | ||
_regeneratorRuntime.mark(function _callee7(channelName, readerUuid) { | ||
var pathToSocket, client; | ||
return _regeneratorRuntime.wrap(function _callee7$(_context7) { | ||
while (1) { | ||
switch (_context7.prev = _context7.next) { | ||
case 0: | ||
pathToSocket = socketPath(channelName, readerUuid); | ||
client = new net.Socket(); | ||
_context7.next = 4; | ||
return new Promise(function (res) { | ||
client.connect(pathToSocket, res); | ||
}); | ||
return _context9.abrupt('return', state); | ||
case 4: | ||
return _context7.abrupt("return", client); | ||
case 19: | ||
case 'end': | ||
return _context9.stop(); | ||
} | ||
} | ||
}, _callee9, this); | ||
})); | ||
case 5: | ||
case "end": | ||
return _context7.stop(); | ||
} | ||
} | ||
}, _callee7, this); | ||
})); | ||
return _openClientConnection.apply(this, arguments); | ||
} | ||
return function create(_x18) { | ||
return _ref9.apply(this, arguments); | ||
function writeMessage(channelName, readerUuid, messageJson, paths) { | ||
paths = paths || getPaths(channelName); | ||
var time = microSeconds(); | ||
var writeObject = { | ||
uuid: readerUuid, | ||
time: time, | ||
data: messageJson | ||
}; | ||
var token = randomToken(12); | ||
var fileName = time + '_' + readerUuid + '_' + token + '.json'; | ||
var msgPath = path.join(paths.messages, fileName); | ||
return writeFile(msgPath, JSON.stringify(writeObject)).then(function () { | ||
return { | ||
time: time, | ||
uuid: readerUuid, | ||
token: token, | ||
path: msgPath | ||
}; | ||
}(); | ||
}); | ||
} | ||
/** | ||
* when the socket pings, so that we now new messages came, | ||
* run this | ||
* returns the uuids of all readers | ||
* @return {string[]} | ||
*/ | ||
var handleMessagePing = function () { | ||
var _ref11 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee10(state, msgObj) { | ||
var messages, useMessages; | ||
return _regeneratorRuntime.wrap(function _callee10$(_context10) { | ||
while (1) { | ||
switch (_context10.prev = _context10.next) { | ||
case 0: | ||
if (state.messagesCallback) { | ||
_context10.next = 2; | ||
break; | ||
} | ||
return _context10.abrupt('return'); | ||
case 2: | ||
messages = void 0; | ||
function getReadersUuids(_x8, _x9) { | ||
return _getReadersUuids.apply(this, arguments); | ||
} | ||
if (msgObj) { | ||
_context10.next = 9; | ||
break; | ||
} | ||
function _getReadersUuids() { | ||
_getReadersUuids = _asyncToGenerator( | ||
/*#__PURE__*/ | ||
_regeneratorRuntime.mark(function _callee8(channelName, paths) { | ||
var readersPath, files; | ||
return _regeneratorRuntime.wrap(function _callee8$(_context8) { | ||
while (1) { | ||
switch (_context8.prev = _context8.next) { | ||
case 0: | ||
paths = paths || getPaths(channelName); | ||
readersPath = paths.readers; | ||
_context8.next = 4; | ||
return readdir(readersPath); | ||
_context10.next = 6; | ||
return getAllMessages(state.channelName, state.paths); | ||
case 4: | ||
files = _context8.sent; | ||
return _context8.abrupt("return", files.map(function (file) { | ||
return file.split('.'); | ||
}).filter(function (split) { | ||
return split[1] === 'json'; | ||
}) // do not scan .socket-files | ||
.map(function (split) { | ||
return split[0]; | ||
})); | ||
case 6: | ||
messages = _context10.sent; | ||
_context10.next = 10; | ||
break; | ||
case 6: | ||
case "end": | ||
return _context8.stop(); | ||
} | ||
} | ||
}, _callee8, this); | ||
})); | ||
return _getReadersUuids.apply(this, arguments); | ||
} | ||
case 9: | ||
// get single message | ||
messages = [getSingleMessage(state.channelName, msgObj, state.paths)]; | ||
function messagePath(_x10, _x11, _x12, _x13) { | ||
return _messagePath.apply(this, arguments); | ||
} | ||
case 10: | ||
useMessages = messages.filter(function (msgObj) { | ||
return _filterMessage(msgObj, state); | ||
}).sort(function (msgObjA, msgObjB) { | ||
return msgObjA.time - msgObjB.time; | ||
}); // sort by time | ||
function _messagePath() { | ||
_messagePath = _asyncToGenerator( | ||
/*#__PURE__*/ | ||
_regeneratorRuntime.mark(function _callee9(channelName, time, token, writerUuid) { | ||
var fileName, msgPath; | ||
return _regeneratorRuntime.wrap(function _callee9$(_context9) { | ||
while (1) { | ||
switch (_context9.prev = _context9.next) { | ||
case 0: | ||
fileName = time + '_' + writerUuid + '_' + token + '.json'; | ||
msgPath = path.join(getPaths(channelName).messages, fileName); | ||
return _context9.abrupt("return", msgPath); | ||
case 3: | ||
case "end": | ||
return _context9.stop(); | ||
} | ||
} | ||
}, _callee9, this); | ||
})); | ||
return _messagePath.apply(this, arguments); | ||
} | ||
// if no listener or message, so not do anything | ||
function getAllMessages(_x14, _x15) { | ||
return _getAllMessages.apply(this, arguments); | ||
} | ||
if (!(!useMessages.length || !state.messagesCallback)) { | ||
_context10.next = 13; | ||
break; | ||
} | ||
function _getAllMessages() { | ||
_getAllMessages = _asyncToGenerator( | ||
/*#__PURE__*/ | ||
_regeneratorRuntime.mark(function _callee10(channelName, paths) { | ||
var messagesPath, files; | ||
return _regeneratorRuntime.wrap(function _callee10$(_context10) { | ||
while (1) { | ||
switch (_context10.prev = _context10.next) { | ||
case 0: | ||
paths = paths || getPaths(channelName); | ||
messagesPath = paths.messages; | ||
_context10.next = 4; | ||
return readdir(messagesPath); | ||
return _context10.abrupt('return'); | ||
case 4: | ||
files = _context10.sent; | ||
return _context10.abrupt("return", files.map(function (file) { | ||
var fileName = file.split('.')[0]; | ||
var split = fileName.split('_'); | ||
return { | ||
path: path.join(messagesPath, file), | ||
time: parseInt(split[0]), | ||
senderUuid: split[1], | ||
token: split[2] | ||
}; | ||
})); | ||
case 13: | ||
_context10.next = 15; | ||
return Promise.all(useMessages.map(function (msgObj) { | ||
return readMessage(msgObj).then(function (content) { | ||
return msgObj.content = content; | ||
}); | ||
})); | ||
case 6: | ||
case "end": | ||
return _context10.stop(); | ||
} | ||
} | ||
}, _callee10, this); | ||
})); | ||
return _getAllMessages.apply(this, arguments); | ||
} | ||
case 15: | ||
function getSingleMessage(channelName, msgObj, paths) { | ||
paths = paths || getPaths(channelName); | ||
return { | ||
path: path.join(paths.messages, msgObj.t + '_' + msgObj.u + '_' + msgObj.to + '.json'), | ||
time: msgObj.t, | ||
senderUuid: msgObj.u, | ||
token: msgObj.to | ||
}; | ||
} | ||
useMessages.forEach(function (msgObj) { | ||
state.emittedMessagesIds.add(msgObj.token); | ||
function readMessage(messageObj) { | ||
return readFile(messageObj.path, 'utf8').then(function (content) { | ||
return JSON.parse(content); | ||
}); | ||
} | ||
if (state.messagesCallback) { | ||
// emit to subscribers | ||
state.messagesCallback(msgObj.content.data); | ||
} | ||
}); | ||
function cleanOldMessages(_x16, _x17) { | ||
return _cleanOldMessages.apply(this, arguments); | ||
} | ||
case 16: | ||
case 'end': | ||
return _context10.stop(); | ||
} | ||
} | ||
}, _callee10, this); | ||
})); | ||
function _cleanOldMessages() { | ||
_cleanOldMessages = _asyncToGenerator( | ||
/*#__PURE__*/ | ||
_regeneratorRuntime.mark(function _callee11(messageObjects, ttl) { | ||
var olderThen; | ||
return _regeneratorRuntime.wrap(function _callee11$(_context11) { | ||
while (1) { | ||
switch (_context11.prev = _context11.next) { | ||
case 0: | ||
olderThen = Date.now() - ttl; | ||
_context11.next = 3; | ||
return Promise.all(messageObjects.filter(function (obj) { | ||
return obj.time / 1000 < olderThen; | ||
}).map(function (obj) { | ||
return unlink(obj.path)["catch"](function () { | ||
return null; | ||
}); | ||
})); | ||
return function handleMessagePing(_x20, _x21) { | ||
return _ref11.apply(this, arguments); | ||
}; | ||
}(); | ||
case 3: | ||
case "end": | ||
return _context11.stop(); | ||
} | ||
} | ||
}, _callee11, this); | ||
})); | ||
return _cleanOldMessages.apply(this, arguments); | ||
} | ||
var type = 'node'; | ||
/** | ||
* ensures that the channelState is connected with all other readers | ||
* @return {Promise<void>} | ||
* creates a new channelState | ||
* @return {Promise<any>} | ||
*/ | ||
function create(_x18) { | ||
return _create.apply(this, arguments); | ||
} | ||
/** | ||
* this method is used in nodejs-environments. | ||
* The ipc is handled via sockets and file-writes to the tmp-folder | ||
*/ | ||
function _create() { | ||
_create = _asyncToGenerator( | ||
/*#__PURE__*/ | ||
_regeneratorRuntime.mark(function _callee12(channelName) { | ||
var options, | ||
time, | ||
paths, | ||
ensureFolderExistsPromise, | ||
uuid, | ||
state, | ||
_ref5, | ||
socketEE, | ||
infoFilePath, | ||
_args12 = arguments; | ||
var util = require('util'); | ||
var fs = require('fs'); | ||
var os = require('os'); | ||
var events = require('events'); | ||
var net = require('net'); | ||
var path = require('path'); | ||
var micro = require('nano-time'); | ||
var rimraf = require('rimraf'); | ||
var sha3_224 = require('js-sha3').sha3_224; | ||
var isNode = require('detect-node'); | ||
var unload = require('unload'); | ||
return _regeneratorRuntime.wrap(function _callee12$(_context12) { | ||
while (1) { | ||
switch (_context12.prev = _context12.next) { | ||
case 0: | ||
options = _args12.length > 1 && _args12[1] !== undefined ? _args12[1] : {}; | ||
options = fillOptionsWithDefaults(options); | ||
time = microSeconds(); | ||
paths = getPaths(channelName); | ||
ensureFolderExistsPromise = ensureFoldersExist(channelName, paths); | ||
uuid = randomToken(10); | ||
state = { | ||
time: time, | ||
channelName: channelName, | ||
options: options, | ||
uuid: uuid, | ||
paths: paths, | ||
// contains all messages that have been emitted before | ||
emittedMessagesIds: new ObliviousSet(options.node.ttl * 2), | ||
messagesCallbackTime: null, | ||
messagesCallback: null, | ||
// ensures we do not read messages in parrallel | ||
writeBlockPromise: Promise.resolve(), | ||
otherReaderClients: {}, | ||
// ensure if process crashes, everything is cleaned up | ||
removeUnload: unload.add(function () { | ||
return close(state); | ||
}), | ||
closed: false | ||
}; | ||
if (!OTHER_INSTANCES[channelName]) OTHER_INSTANCES[channelName] = []; | ||
OTHER_INSTANCES[channelName].push(state); | ||
_context12.next = 11; | ||
return ensureFolderExistsPromise; | ||
var fillOptionsWithDefaults = require('../../dist/lib/options.js').fillOptionsWithDefaults; | ||
var ownUtil = require('../../dist/lib/util.js'); | ||
var randomInt = ownUtil.randomInt; | ||
var randomToken = ownUtil.randomToken; | ||
var ObliviousSet = require('../../dist/lib/oblivious-set')['default']; | ||
case 11: | ||
_context12.next = 13; | ||
return Promise.all([createSocketEventEmitter(channelName, uuid, paths), createSocketInfoFile(channelName, uuid, paths), refreshReaderClients(state)]); | ||
/** | ||
* windows sucks, so we have handle windows-type of socket-paths | ||
* @link https://gist.github.com/domenic/2790533#gistcomment-331356 | ||
*/ | ||
function cleanPipeName(str) { | ||
if (process.platform === 'win32' && !str.startsWith('\\\\.\\pipe\\')) { | ||
str = str.replace(/^\//, ''); | ||
str = str.replace(/\//g, '-'); | ||
return '\\\\.\\pipe\\' + str; | ||
} else { | ||
return str; | ||
} | ||
} | ||
case 13: | ||
_ref5 = _context12.sent; | ||
socketEE = _ref5[0]; | ||
infoFilePath = _ref5[1]; | ||
state.socketEE = socketEE; | ||
state.infoFilePath = infoFilePath; // when new message comes in, we read it and emit it | ||
var mkdir = util.promisify(fs.mkdir); | ||
var writeFile = util.promisify(fs.writeFile); | ||
var readFile = util.promisify(fs.readFile); | ||
var unlink = util.promisify(fs.unlink); | ||
var readdir = util.promisify(fs.readdir); | ||
var removeDir = util.promisify(rimraf); | ||
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 | ||
var singleOnes = data.split('|'); | ||
singleOnes.filter(function (single) { | ||
return single !== ''; | ||
}).forEach(function (single) { | ||
try { | ||
var obj = JSON.parse(single); | ||
handleMessagePing(state, obj); | ||
} catch (err) { | ||
throw new Error('could not parse data: ' + single); | ||
} | ||
}); | ||
}); | ||
return _context12.abrupt("return", state); | ||
var OTHER_INSTANCES = {}; | ||
var TMP_FOLDER_NAME = 'pubkey.bc'; | ||
var TMP_FOLDER_BASE = path.join(os.tmpdir(), TMP_FOLDER_NAME); | ||
var getPathsCache = new Map(); | ||
case 20: | ||
case "end": | ||
return _context12.stop(); | ||
} | ||
} | ||
}, _callee12, this); | ||
})); | ||
return _create.apply(this, arguments); | ||
} | ||
function getPaths(channelName) { | ||
if (!getPathsCache.has(channelName)) { | ||
var channelHash = sha3_224(channelName); // use hash incase of strange characters | ||
/** | ||
* because the lenght 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); | ||
function _filterMessage(msgObj, state) { | ||
if (msgObj.senderUuid === state.uuid) return false; // not send by own | ||
var channelPathBase = path.join(TMP_FOLDER_BASE, channelFolder); | ||
var folderPathReaders = path.join(channelPathBase, 'rdrs'); | ||
var folderPathMessages = path.join(channelPathBase, 'messages'); | ||
if (state.emittedMessagesIds.has(msgObj.token)) return false; // not already emitted | ||
var ret = { | ||
base: TMP_FOLDER_BASE, | ||
channelBase: channelPathBase, | ||
readers: folderPathReaders, | ||
messages: folderPathMessages | ||
}; | ||
getPathsCache.set(channelName, ret); | ||
return ret; | ||
} | ||
return getPathsCache.get(channelName); | ||
} | ||
if (!state.messagesCallback) return false; // no listener | ||
var ENSURE_BASE_FOLDER_EXISTS_PROMISE = null; | ||
if (msgObj.time < state.messagesCallbackTime) return false; // not older then onMessageCallback | ||
if (msgObj.time < state.time) return false; // msgObj is older then channel | ||
function socketPath(channelName, readerUuid, paths) { | ||
paths = paths || getPaths(channelName); | ||
var socketPath = path.join(paths.readers, readerUuid + '.s'); | ||
return cleanPipeName(socketPath); | ||
state.emittedMessagesIds.add(msgObj.token); | ||
return true; | ||
} | ||
/** | ||
* when the socket pings, so that we now new messages came, | ||
* run this | ||
*/ | ||
function socketInfoPath(channelName, readerUuid, paths) { | ||
paths = paths || getPaths(channelName); | ||
var socketPath = path.join(paths.readers, readerUuid + '.json'); | ||
return socketPath; | ||
function handleMessagePing(_x19, _x20) { | ||
return _handleMessagePing.apply(this, arguments); | ||
} | ||
/** | ||
* 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 | ||
* ensures that the channelState is connected with all other readers | ||
* @return {Promise<void>} | ||
*/ | ||
function createSocketInfoFile(channelName, readerUuid, paths) { | ||
var pathToFile = socketInfoPath(channelName, readerUuid, paths); | ||
return writeFile(pathToFile, JSON.stringify({ | ||
time: microSeconds() | ||
})).then(function () { | ||
return pathToFile; | ||
}); | ||
}function writeMessage(channelName, readerUuid, messageJson, paths) { | ||
paths = paths || getPaths(channelName); | ||
var time = microSeconds(); | ||
var writeObject = { | ||
uuid: readerUuid, | ||
time: time, | ||
data: messageJson | ||
}; | ||
var token = randomToken(12); | ||
var fileName = time + '_' + readerUuid + '_' + token + '.json'; | ||
var msgPath = path.join(paths.messages, fileName); | ||
function _handleMessagePing() { | ||
_handleMessagePing = _asyncToGenerator( | ||
/*#__PURE__*/ | ||
_regeneratorRuntime.mark(function _callee13(state, msgObj) { | ||
var messages, useMessages; | ||
return _regeneratorRuntime.wrap(function _callee13$(_context13) { | ||
while (1) { | ||
switch (_context13.prev = _context13.next) { | ||
case 0: | ||
if (state.messagesCallback) { | ||
_context13.next = 2; | ||
break; | ||
} | ||
return writeFile(msgPath, JSON.stringify(writeObject)).then(function () { | ||
return { | ||
time: time, | ||
uuid: readerUuid, | ||
token: token, | ||
path: msgPath | ||
}; | ||
}); | ||
} | ||
return _context13.abrupt("return"); | ||
function getSingleMessage(channelName, msgObj, paths) { | ||
paths = paths || getPaths(channelName); | ||
case 2: | ||
if (msgObj) { | ||
_context13.next = 8; | ||
break; | ||
} | ||
return { | ||
path: path.join(paths.messages, msgObj.t + '_' + msgObj.u + '_' + msgObj.to + '.json'), | ||
time: msgObj.t, | ||
senderUuid: msgObj.u, | ||
token: msgObj.to | ||
}; | ||
} | ||
_context13.next = 5; | ||
return getAllMessages(state.channelName, state.paths); | ||
function readMessage(messageObj) { | ||
return readFile(messageObj.path, 'utf8').then(function (content) { | ||
return JSON.parse(content); | ||
}); | ||
} | ||
case 5: | ||
messages = _context13.sent; | ||
_context13.next = 9; | ||
break; | ||
var type = 'node'; | ||
case 8: | ||
// get single message | ||
messages = [getSingleMessage(state.channelName, msgObj, state.paths)]; | ||
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 | ||
case 9: | ||
useMessages = messages.filter(function (msgObj) { | ||
return _filterMessage(msgObj, state); | ||
}).sort(function (msgObjA, msgObjB) { | ||
return msgObjA.time - msgObjB.time; | ||
}); // sort by time | ||
// if no listener or message, so not do anything | ||
state.emittedMessagesIds.add(msgObj.token); | ||
return true; | ||
}function refreshReaderClients(channelState) { | ||
var _this = this; | ||
if (!(!useMessages.length || !state.messagesCallback)) { | ||
_context13.next = 12; | ||
break; | ||
} | ||
return getReadersUuids(channelState.channelName, channelState.paths).then(function (otherReaders) { | ||
// remove subscriptions to closed readers | ||
Object.keys(channelState.otherReaderClients).filter(function (readerUuid) { | ||
return !otherReaders.includes(readerUuid); | ||
}).forEach(function () { | ||
var _ref12 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee11(readerUuid) { | ||
return _regeneratorRuntime.wrap(function _callee11$(_context11) { | ||
while (1) { | ||
switch (_context11.prev = _context11.next) { | ||
case 0: | ||
_context11.prev = 0; | ||
_context11.next = 3; | ||
return channelState.otherReaderClients[readerUuid].destroy(); | ||
return _context13.abrupt("return"); | ||
case 3: | ||
_context11.next = 7; | ||
break; | ||
case 12: | ||
_context13.next = 14; | ||
return Promise.all(useMessages.map(function (msgObj) { | ||
return readMessage(msgObj).then(function (content) { | ||
return msgObj.content = content; | ||
}); | ||
})); | ||
case 5: | ||
_context11.prev = 5; | ||
_context11.t0 = _context11['catch'](0); | ||
case 14: | ||
useMessages.forEach(function (msgObj) { | ||
state.emittedMessagesIds.add(msgObj.token); | ||
case 7: | ||
delete channelState.otherReaderClients[readerUuid]; | ||
if (state.messagesCallback) { | ||
// emit to subscribers | ||
state.messagesCallback(msgObj.content.data); | ||
} | ||
}); | ||
case 8: | ||
case 'end': | ||
return _context11.stop(); | ||
} | ||
} | ||
}, _callee11, _this, [[0, 5]]); | ||
})); | ||
case 15: | ||
case "end": | ||
return _context13.stop(); | ||
} | ||
} | ||
}, _callee13, this); | ||
})); | ||
return _handleMessagePing.apply(this, arguments); | ||
} | ||
return function (_x22) { | ||
return _ref12.apply(this, arguments); | ||
}; | ||
}()); | ||
function refreshReaderClients(channelState) { | ||
return getReadersUuids(channelState.channelName, channelState.paths).then(function (otherReaders) { | ||
// remove subscriptions to closed readers | ||
Object.keys(channelState.otherReaderClients).filter(function (readerUuid) { | ||
return !otherReaders.includes(readerUuid); | ||
}).forEach( | ||
/*#__PURE__*/ | ||
function () { | ||
var _ref = _asyncToGenerator( | ||
/*#__PURE__*/ | ||
_regeneratorRuntime.mark(function _callee(readerUuid) { | ||
return _regeneratorRuntime.wrap(function _callee$(_context) { | ||
while (1) { | ||
switch (_context.prev = _context.next) { | ||
case 0: | ||
_context.prev = 0; | ||
_context.next = 3; | ||
return channelState.otherReaderClients[readerUuid].destroy(); | ||
// add new readers | ||
return Promise.all(otherReaders.filter(function (readerUuid) { | ||
return readerUuid !== channelState.uuid; | ||
}) // not own | ||
.filter(function (readerUuid) { | ||
return !channelState.otherReaderClients[readerUuid]; | ||
}) // not already has client | ||
.map(function () { | ||
var _ref13 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee12(readerUuid) { | ||
var client; | ||
return _regeneratorRuntime.wrap(function _callee12$(_context12) { | ||
while (1) { | ||
switch (_context12.prev = _context12.next) { | ||
case 0: | ||
_context12.prev = 0; | ||
case 3: | ||
_context.next = 7; | ||
break; | ||
if (!channelState.closed) { | ||
_context12.next = 3; | ||
break; | ||
} | ||
case 5: | ||
_context.prev = 5; | ||
_context.t0 = _context["catch"](0); | ||
return _context12.abrupt('return'); | ||
case 7: | ||
delete channelState.otherReaderClients[readerUuid]; | ||
case 3: | ||
_context12.next = 5; | ||
return openClientConnection(channelState.channelName, readerUuid); | ||
case 8: | ||
case "end": | ||
return _context.stop(); | ||
} | ||
} | ||
}, _callee, this, [[0, 5]]); | ||
})); | ||
case 5: | ||
client = _context12.sent; | ||
return function (_x21) { | ||
return _ref.apply(this, arguments); | ||
}; | ||
}()); // add new readers | ||
channelState.otherReaderClients[readerUuid] = client; | ||
_context12.next = 11; | ||
break; | ||
return Promise.all(otherReaders.filter(function (readerUuid) { | ||
return readerUuid !== channelState.uuid; | ||
}) // not own | ||
.filter(function (readerUuid) { | ||
return !channelState.otherReaderClients[readerUuid]; | ||
}) // not already has client | ||
.map( | ||
/*#__PURE__*/ | ||
function () { | ||
var _ref2 = _asyncToGenerator( | ||
/*#__PURE__*/ | ||
_regeneratorRuntime.mark(function _callee2(readerUuid) { | ||
var client; | ||
return _regeneratorRuntime.wrap(function _callee2$(_context2) { | ||
while (1) { | ||
switch (_context2.prev = _context2.next) { | ||
case 0: | ||
_context2.prev = 0; | ||
case 9: | ||
_context12.prev = 9; | ||
_context12.t0 = _context12['catch'](0); | ||
if (!channelState.closed) { | ||
_context2.next = 3; | ||
break; | ||
} | ||
case 11: | ||
case 'end': | ||
return _context12.stop(); | ||
} | ||
} | ||
}, _callee12, _this, [[0, 9]]); | ||
})); | ||
return _context2.abrupt("return"); | ||
return function (_x23) { | ||
return _ref13.apply(this, arguments); | ||
}; | ||
}() | ||
// this might throw if the other channel is closed at the same time when this one is running refresh | ||
// so we do not throw an error | ||
)); | ||
}); | ||
case 3: | ||
_context2.next = 5; | ||
return openClientConnection(channelState.channelName, readerUuid); | ||
case 5: | ||
client = _context2.sent; | ||
channelState.otherReaderClients[readerUuid] = client; | ||
_context2.next = 11; | ||
break; | ||
case 9: | ||
_context2.prev = 9; | ||
_context2.t0 = _context2["catch"](0); | ||
case 11: | ||
case "end": | ||
return _context2.stop(); | ||
} | ||
} | ||
}, _callee2, this, [[0, 9]]); | ||
})); | ||
return function (_x22) { | ||
return _ref2.apply(this, arguments); | ||
}; | ||
}())); | ||
}); | ||
} | ||
/** | ||
@@ -747,63 +790,63 @@ * post a message to the other readers | ||
*/ | ||
function postMessage(channelState, messageJson) { | ||
var _this2 = this; | ||
var writePromise = writeMessage(channelState.channelName, channelState.uuid, messageJson, channelState.paths); | ||
channelState.writeBlockPromise = channelState.writeBlockPromise.then(_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee13() { | ||
var _ref15, msgObj, pingStr, writeToReadersPromise; | ||
return _regeneratorRuntime.wrap(function _callee13$(_context13) { | ||
while (1) { | ||
switch (_context13.prev = _context13.next) { | ||
case 0: | ||
_context13.next = 2; | ||
return new Promise(function (res) { | ||
return setTimeout(res, 0); | ||
}); | ||
function postMessage(channelState, messageJson) { | ||
var writePromise = writeMessage(channelState.channelName, channelState.uuid, messageJson, channelState.paths); | ||
channelState.writeBlockPromise = channelState.writeBlockPromise.then( | ||
/*#__PURE__*/ | ||
_asyncToGenerator( | ||
/*#__PURE__*/ | ||
_regeneratorRuntime.mark(function _callee3() { | ||
var _ref4, msgObj, pingStr, writeToReadersPromise; | ||
case 2: | ||
_context13.next = 4; | ||
return Promise.all([writePromise, refreshReaderClients(channelState)]); | ||
return _regeneratorRuntime.wrap(function _callee3$(_context3) { | ||
while (1) { | ||
switch (_context3.prev = _context3.next) { | ||
case 0: | ||
_context3.next = 2; | ||
return new Promise(function (res) { | ||
return setTimeout(res, 0); | ||
}); | ||
case 4: | ||
_ref15 = _context13.sent; | ||
msgObj = _ref15[0]; | ||
case 2: | ||
_context3.next = 4; | ||
return Promise.all([writePromise, refreshReaderClients(channelState)]); | ||
emitOverFastPath(channelState, msgObj, messageJson); | ||
pingStr = '{"t":' + msgObj.time + ',"u":"' + msgObj.uuid + '","to":"' + msgObj.token + '"}|'; | ||
writeToReadersPromise = Promise.all(Object.values(channelState.otherReaderClients).filter(function (client) { | ||
return client.writable; | ||
}) // client might have closed in between | ||
.map(function (client) { | ||
return new Promise(function (res) { | ||
client.write(pingStr, res); | ||
}); | ||
})); | ||
case 4: | ||
_ref4 = _context3.sent; | ||
msgObj = _ref4[0]; | ||
emitOverFastPath(channelState, msgObj, messageJson); | ||
pingStr = '{"t":' + msgObj.time + ',"u":"' + msgObj.uuid + '","to":"' + msgObj.token + '"}|'; | ||
writeToReadersPromise = Promise.all(Object.values(channelState.otherReaderClients).filter(function (client) { | ||
return client.writable; | ||
}) // client might have closed in between | ||
.map(function (client) { | ||
return new Promise(function (res) { | ||
client.write(pingStr, res); | ||
}); | ||
})); | ||
/** | ||
* clean up old messages | ||
* to not waste resources on cleaning up, | ||
* only if random-int matches, we clean up old messages | ||
*/ | ||
/** | ||
* clean up old messages | ||
* 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); | ||
}); | ||
} | ||
if (randomInt(0, 20) === 0) { | ||
/* await */ | ||
getAllMessages(channelState.channelName, channelState.paths).then(function (allMessages) { | ||
return cleanOldMessages(allMessages, channelState.options.node.ttl); | ||
}); | ||
} | ||
return _context3.abrupt("return", writeToReadersPromise); | ||
return _context13.abrupt('return', writeToReadersPromise); | ||
case 11: | ||
case 'end': | ||
return _context13.stop(); | ||
} | ||
} | ||
}, _callee13, _this2); | ||
}))); | ||
return channelState.writeBlockPromise; | ||
case 11: | ||
case "end": | ||
return _context3.stop(); | ||
} | ||
} | ||
}, _callee3, this); | ||
}))); | ||
return channelState.writeBlockPromise; | ||
} | ||
/** | ||
@@ -815,101 +858,99 @@ * When multiple BroadcastChannels with the same name | ||
*/ | ||
function emitOverFastPath(state, msgObj, messageJson) { | ||
if (!state.options.node.useFastPath) return; // disabled | ||
var others = OTHER_INSTANCES[state.channelName].filter(function (s) { | ||
return s !== state; | ||
}); | ||
if (!state.options.node.useFastPath) return; // disabled | ||
var checkObj = { | ||
time: msgObj.time, | ||
senderUuid: msgObj.uuid, | ||
token: msgObj.token | ||
}; | ||
others.filter(function (otherState) { | ||
return _filterMessage(checkObj, otherState); | ||
}).forEach(function (otherState) { | ||
otherState.messagesCallback(messageJson); | ||
}); | ||
var others = OTHER_INSTANCES[state.channelName].filter(function (s) { | ||
return s !== state; | ||
}); | ||
var checkObj = { | ||
time: msgObj.time, | ||
senderUuid: msgObj.uuid, | ||
token: msgObj.token | ||
}; | ||
others.filter(function (otherState) { | ||
return _filterMessage(checkObj, otherState); | ||
}).forEach(function (otherState) { | ||
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); | ||
var time = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : microSeconds(); | ||
channelState.messagesCallbackTime = time; | ||
channelState.messagesCallback = fn; | ||
handleMessagePing(channelState); | ||
} | ||
function close(channelState) { | ||
if (channelState.closed) return; | ||
channelState.closed = true; | ||
channelState.emittedMessagesIds.clear(); | ||
OTHER_INSTANCES[channelState.channelName] = OTHER_INSTANCES[channelState.channelName].filter(function (o) { | ||
return o !== channelState; | ||
}); | ||
if (channelState.closed) return; | ||
channelState.closed = true; | ||
channelState.emittedMessagesIds.clear(); | ||
OTHER_INSTANCES[channelState.channelName] = OTHER_INSTANCES[channelState.channelName].filter(function (o) { | ||
return o !== channelState; | ||
}); | ||
if (channelState.removeUnload) { | ||
channelState.removeUnload.remove(); | ||
} | ||
if (channelState.removeUnload) { | ||
channelState.removeUnload.remove(); | ||
} | ||
/** | ||
* the server get closed lazy because others might still write on it | ||
* and have not found out that the infoFile was deleted | ||
*/ | ||
/** | ||
* the server get closed lazy because others might still write on it | ||
* and have not found out that the infoFile was deleted | ||
*/ | ||
setTimeout(function () { | ||
return channelState.socketEE.server.close(); | ||
}, 200); | ||
channelState.socketEE.emitter.removeAllListeners(); | ||
Object.values(channelState.otherReaderClients).forEach(function (client) { | ||
return client.destroy(); | ||
}); | ||
unlink(channelState.infoFilePath)['catch'](function () { | ||
return null; | ||
}); | ||
setTimeout(function () { | ||
return channelState.socketEE.server.close(); | ||
}, 200); | ||
channelState.socketEE.emitter.removeAllListeners(); | ||
Object.values(channelState.otherReaderClients).forEach(function (client) { | ||
return client.destroy(); | ||
}); | ||
unlink(channelState.infoFilePath)["catch"](function () { | ||
return null; | ||
}); | ||
} | ||
function canBeUsed() { | ||
return isNode; | ||
return isNode; | ||
} | ||
function averageResponseTime() { | ||
return 50; | ||
return 50; | ||
} | ||
function microSeconds() { | ||
return parseInt(micro.microseconds()); | ||
return parseInt(micro.microseconds()); | ||
} | ||
module.exports = { | ||
cleanPipeName: cleanPipeName, | ||
getPaths: getPaths, | ||
ensureFoldersExist: ensureFoldersExist, | ||
clearNodeFolder: clearNodeFolder, | ||
socketPath: socketPath, | ||
socketInfoPath: socketInfoPath, | ||
createSocketInfoFile: createSocketInfoFile, | ||
createSocketEventEmitter: createSocketEventEmitter, | ||
openClientConnection: openClientConnection, | ||
writeMessage: writeMessage, | ||
getReadersUuids: getReadersUuids, | ||
messagePath: messagePath, | ||
getAllMessages: getAllMessages, | ||
getSingleMessage: getSingleMessage, | ||
readMessage: readMessage, | ||
cleanOldMessages: cleanOldMessages, | ||
type: type, | ||
create: create, | ||
_filterMessage: _filterMessage, | ||
handleMessagePing: handleMessagePing, | ||
refreshReaderClients: refreshReaderClients, | ||
postMessage: postMessage, | ||
emitOverFastPath: emitOverFastPath, | ||
onMessage: onMessage, | ||
close: close, | ||
canBeUsed: canBeUsed, | ||
averageResponseTime: averageResponseTime, | ||
microSeconds: microSeconds | ||
cleanPipeName: cleanPipeName, | ||
getPaths: getPaths, | ||
ensureFoldersExist: ensureFoldersExist, | ||
clearNodeFolder: clearNodeFolder, | ||
socketPath: socketPath, | ||
socketInfoPath: socketInfoPath, | ||
createSocketInfoFile: createSocketInfoFile, | ||
createSocketEventEmitter: createSocketEventEmitter, | ||
openClientConnection: openClientConnection, | ||
writeMessage: writeMessage, | ||
getReadersUuids: getReadersUuids, | ||
messagePath: messagePath, | ||
getAllMessages: getAllMessages, | ||
getSingleMessage: getSingleMessage, | ||
readMessage: readMessage, | ||
cleanOldMessages: cleanOldMessages, | ||
type: type, | ||
create: create, | ||
_filterMessage: _filterMessage, | ||
handleMessagePing: handleMessagePing, | ||
refreshReaderClients: refreshReaderClients, | ||
postMessage: postMessage, | ||
emitOverFastPath: emitOverFastPath, | ||
onMessage: onMessage, | ||
close: close, | ||
canBeUsed: canBeUsed, | ||
averageResponseTime: averageResponseTime, | ||
microSeconds: microSeconds | ||
}; |
@@ -5,44 +5,45 @@ /** | ||
*/ | ||
var ObliviousSet = function ObliviousSet(ttl) { | ||
this.ttl = ttl; | ||
this.set = new Set(); | ||
this.timeMap = new Map(); | ||
this.has = this.set.has.bind(this.set); | ||
this.ttl = ttl; | ||
this.set = new Set(); | ||
this.timeMap = new Map(); | ||
this.has = this.set.has.bind(this.set); | ||
}; | ||
ObliviousSet.prototype = { | ||
_removeTooOldValues: function _removeTooOldValues() { | ||
var olderThen = now() - this.ttl; | ||
var iterator = this.set[Symbol.iterator](); | ||
_removeTooOldValues: function _removeTooOldValues() { | ||
var olderThen = now() - this.ttl; | ||
var iterator = this.set[Symbol.iterator](); | ||
while (true) { | ||
var value = iterator.next().value; | ||
if (!value) return; // no more elements | ||
var time = this.timeMap.get(value); | ||
if (time < olderThen) { | ||
this.timeMap["delete"](value); | ||
this.set["delete"](value); | ||
} else { | ||
// we reached a value that is not old enough | ||
return; | ||
} | ||
} | ||
}, | ||
add: function add(value) { | ||
this.timeMap.set(value, now()); | ||
this.set.add(value); | ||
this._removeTooOldValues(); | ||
}, | ||
clear: function clear() { | ||
this.set.clear(); | ||
this.timeMap.clear(); | ||
while (true) { | ||
var value = iterator.next().value; | ||
if (!value) return; // no more elements | ||
var time = this.timeMap.get(value); | ||
if (time < olderThen) { | ||
this.timeMap["delete"](value); | ||
this.set["delete"](value); | ||
} else { | ||
// we reached a value that is not old enough | ||
return; | ||
} | ||
} | ||
}, | ||
add: function add(value) { | ||
this.timeMap.set(value, now()); | ||
this.set.add(value); | ||
this._removeTooOldValues(); | ||
}, | ||
clear: function clear() { | ||
this.set.clear(); | ||
this.timeMap.clear(); | ||
} | ||
}; | ||
function now() { | ||
return new Date().getTime(); | ||
return new Date().getTime(); | ||
} | ||
export default ObliviousSet; |
export function fillOptionsWithDefaults(options) { | ||
if (!options) options = {}; | ||
options = JSON.parse(JSON.stringify(options)); | ||
if (!options) options = {}; | ||
options = JSON.parse(JSON.stringify(options)); // main | ||
// main | ||
if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true; | ||
if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true; // indexed-db | ||
// 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; | ||
if (!options.idb) options.idb = {}; // after this time the messages get deleted | ||
// localstorage | ||
if (!options.localstorage) options.localstorage = {}; | ||
if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60; | ||
if (!options.idb.ttl) options.idb.ttl = 1000 * 45; | ||
if (!options.idb.fallbackInterval) options.idb.fallbackInterval = 150; // localstorage | ||
// 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; | ||
if (!options.localstorage) options.localstorage = {}; | ||
if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60; // node | ||
return options; | ||
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; | ||
} |
@@ -5,36 +5,34 @@ /** | ||
export function isPromise(obj) { | ||
if (obj && typeof obj.then === 'function') { | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
if (obj && typeof obj.then === 'function') { | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
} | ||
export function sleep(time) { | ||
if (!time) time = 0; | ||
return new Promise(function (res) { | ||
return setTimeout(res, time); | ||
}); | ||
if (!time) time = 0; | ||
return new Promise(function (res) { | ||
return setTimeout(res, time); | ||
}); | ||
} | ||
export function randomInt(min, max) { | ||
return Math.floor(Math.random() * (max - min + 1) + min); | ||
return Math.floor(Math.random() * (max - min + 1) + min); | ||
} | ||
/** | ||
* https://stackoverflow.com/a/1349426/3443137 | ||
*/ | ||
export function randomToken(length) { | ||
if (!length) length = 5; | ||
var text = ''; | ||
var possible = 'abcdefghijklmnopqrstuvwxzy0123456789'; | ||
if (!length) length = 5; | ||
var text = ''; | ||
var possible = 'abcdefghijklmnopqrstuvwxzy0123456789'; | ||
for (var i = 0; i < length; i++) { | ||
text += possible.charAt(Math.floor(Math.random() * possible.length)); | ||
}return text; | ||
for (var i = 0; i < length; i++) { | ||
text += possible.charAt(Math.floor(Math.random() * possible.length)); | ||
} | ||
return text; | ||
} | ||
var lastMs = 0; | ||
var additional = 0; | ||
/** | ||
@@ -47,12 +45,14 @@ * returns the current time in micro-seconds, | ||
*/ | ||
export function microSeconds() { | ||
var ms = new Date().getTime(); | ||
if (ms === lastMs) { | ||
additional++; | ||
return ms * 1000 + additional; | ||
} else { | ||
lastMs = ms; | ||
additional = 0; | ||
return ms * 1000; | ||
} | ||
var ms = new Date().getTime(); | ||
if (ms === lastMs) { | ||
additional++; | ||
return ms * 1000 + additional; | ||
} else { | ||
lastMs = ms; | ||
additional = 0; | ||
return ms * 1000; | ||
} | ||
} |
@@ -1,4 +0,5 @@ | ||
'use strict'; | ||
"use strict"; | ||
var BroadcastChannel = require('./index.es5.js'); | ||
var LeaderElection = require('./leader-election/index.es5.js'); | ||
@@ -5,0 +6,0 @@ |
@@ -1,16 +0,15 @@ | ||
'use strict'; | ||
"use strict"; | ||
var _index = require('./index.js'); | ||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); | ||
var _index2 = _interopRequireDefault(_index); | ||
var _index = _interopRequireDefault(require("./index.js")); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } | ||
module.exports = _index2['default']; /** | ||
* 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'); | ||
*/ | ||
/** | ||
* 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 = _index["default"]; |
@@ -1,42 +0,50 @@ | ||
'use strict'; | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
value: true | ||
}); | ||
exports["default"] = void 0; | ||
var _util = require('./util.js'); | ||
var _util = require("./util.js"); | ||
var _methodChooser = require('./method-chooser.js'); | ||
var _methodChooser = require("./method-chooser.js"); | ||
var _options = require('./options.js'); | ||
var _options = require("./options.js"); | ||
var BroadcastChannel = function BroadcastChannel(name, options) { | ||
this.name = name; | ||
this.options = (0, _options.fillOptionsWithDefaults)(options); | ||
this.method = (0, _methodChooser.chooseMethod)(this.options); | ||
this.name = name; | ||
this.options = (0, _options.fillOptionsWithDefaults)(options); | ||
this.method = (0, _methodChooser.chooseMethod)(this.options); // isListening | ||
this._isListening = false; | ||
this._iL = false; | ||
/** | ||
* _onMessageListener | ||
* setting onmessage twice, | ||
* will overwrite the first listener | ||
*/ | ||
/** | ||
* setting onmessage twice, | ||
* will overwrite the first listener | ||
*/ | ||
this._onMessageListener = null; | ||
this._onML = null; | ||
/** | ||
* _addEventListeners | ||
*/ | ||
this._addEventListeners = { | ||
message: [], | ||
internal: [] | ||
}; | ||
this._addEL = { | ||
message: [], | ||
internal: [] | ||
}; | ||
/** | ||
* _beforeClose | ||
* array of promises that will be awaited | ||
* before the channel is closed | ||
*/ | ||
/** | ||
* array of promises that will be awaited | ||
* before the channel is closed | ||
*/ | ||
this._beforeClose = []; | ||
this._befC = []; | ||
/** | ||
* _preparePromise | ||
*/ | ||
this._preparePromise = null; | ||
_prepareChannel(this); | ||
}; | ||
this._prepP = null; | ||
// STATICS | ||
_prepareChannel(this); | ||
}; // STATICS | ||
@@ -48,4 +56,5 @@ /** | ||
*/ | ||
BroadcastChannel._pubkey = true; | ||
/** | ||
@@ -55,162 +64,172 @@ * clears the tmp-folder if is node | ||
*/ | ||
BroadcastChannel.clearNodeFolder = function (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); | ||
} | ||
}; | ||
options = (0, _options.fillOptionsWithDefaults)(options); | ||
var method = (0, _methodChooser.chooseMethod)(options); | ||
// PROTOTYPE | ||
if (method.type === 'node') { | ||
return method.clearNodeFolder().then(function () { | ||
return true; | ||
}); | ||
} else { | ||
return Promise.resolve(false); | ||
} | ||
}; // PROTOTYPE | ||
BroadcastChannel.prototype = { | ||
_post: function _post(type, msg) { | ||
var _this = this; | ||
postMessage: function postMessage(msg) { | ||
if (this.closed) { | ||
throw new Error('BroadcastChannel.postMessage(): ' + 'Cannot post message after channel has closed'); | ||
} | ||
var time = this.method.microSeconds(); | ||
var msgObj = { | ||
time: time, | ||
type: type, | ||
data: msg | ||
}; | ||
return _post(this, 'message', msg); | ||
}, | ||
postInternal: function postInternal(msg) { | ||
return _post(this, 'internal', msg); | ||
}, | ||
var awaitPrepare = this._preparePromise ? this._preparePromise : Promise.resolve(); | ||
return awaitPrepare.then(function () { | ||
return _this.method.postMessage(_this._state, msgObj); | ||
}); | ||
}, | ||
postMessage: function postMessage(msg) { | ||
if (this.closed) { | ||
throw new Error('BroadcastChannel.postMessage(): ' + 'Cannot post message after channel has closed'); | ||
} | ||
return this._post('message', msg); | ||
}, | ||
postInternal: function postInternal(msg) { | ||
return this._post('internal', msg); | ||
}, | ||
set onmessage(fn) { | ||
var time = this.method.microSeconds(); | ||
var listenObj = { | ||
time: time, | ||
fn: fn | ||
}; | ||
set onmessage(fn) { | ||
var time = this.method.microSeconds(); | ||
var listenObj = { | ||
time: time, | ||
fn: fn | ||
}; | ||
_removeListenerObject(this, 'message', this._onMessageListener); | ||
if (fn && typeof fn === 'function') { | ||
this._onMessageListener = listenObj; | ||
_addListenerObject(this, 'message', listenObj); | ||
} else { | ||
this._onMessageListener = null; | ||
} | ||
}, | ||
_removeListenerObject(this, 'message', this._onML); | ||
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._addEventListeners[type].find(function (obj) { | ||
return obj.fn === fn; | ||
}); | ||
_removeListenerObject(this, type, obj); | ||
}, | ||
close: function close() { | ||
var _this2 = this; | ||
if (fn && typeof fn === 'function') { | ||
this._onML = listenObj; | ||
if (this.closed) return; | ||
this.closed = true; | ||
var awaitPrepare = this._preparePromise ? this._preparePromise : Promise.resolve(); | ||
_addListenerObject(this, 'message', listenObj); | ||
} else { | ||
this._onML = null; | ||
} | ||
}, | ||
this._onMessageListener = null; | ||
this._addEventListeners.message = []; | ||
addEventListener: function addEventListener(type, fn) { | ||
var time = this.method.microSeconds(); | ||
var listenObj = { | ||
time: time, | ||
fn: fn | ||
}; | ||
return awaitPrepare.then(function () { | ||
return Promise.all(_this2._beforeClose.map(function (fn) { | ||
return fn(); | ||
})); | ||
}).then(function () { | ||
return _this2.method.close(_this2._state); | ||
}); | ||
}, | ||
_addListenerObject(this, type, listenObj); | ||
}, | ||
removeEventListener: function removeEventListener(type, fn) { | ||
var obj = this._addEL[type].find(function (obj) { | ||
return obj.fn === fn; | ||
}); | ||
get type() { | ||
return this.method.type; | ||
} | ||
_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.then(function () { | ||
return Promise.all(_this._befC.map(function (fn) { | ||
return fn(); | ||
})); | ||
}).then(function () { | ||
return _this.method.close(_this._state); | ||
}); | ||
}, | ||
get type() { | ||
return this.method.type; | ||
} | ||
}; | ||
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 () { | ||
return broadcastChannel.method.postMessage(broadcastChannel._state, msgObj); | ||
}); | ||
} | ||
function _prepareChannel(channel) { | ||
var maybePromise = channel.method.create(channel.name, channel.options); | ||
if ((0, _util.isPromise)(maybePromise)) { | ||
channel._preparePromise = 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; | ||
} | ||
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._addEventListeners.message.length > 0) return true; | ||
if (channel._addEventListeners.internal.length > 0) return true; | ||
return false; | ||
if (channel._addEL.message.length > 0) return true; | ||
if (channel._addEL.internal.length > 0) return true; | ||
return false; | ||
} | ||
function _addListenerObject(channel, type, obj) { | ||
channel._addEventListeners[type].push(obj); | ||
_startListening(channel); | ||
channel._addEL[type].push(obj); | ||
_startListening(channel); | ||
} | ||
function _removeListenerObject(channel, type, obj) { | ||
channel._addEventListeners[type] = channel._addEventListeners[type].filter(function (o) { | ||
return o !== obj; | ||
}); | ||
_stopListening(channel); | ||
channel._addEL[type] = channel._addEL[type].filter(function (o) { | ||
return o !== obj; | ||
}); | ||
_stopListening(channel); | ||
} | ||
function _startListening(channel) { | ||
if (!channel._isListening && _hasMessageListeners(channel)) { | ||
// someone is listening, start subscribing | ||
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 listenerFn = function listenerFn(msgObj) { | ||
channel._addEventListeners[msgObj.type].forEach(function (obj) { | ||
if (msgObj.time >= obj.time) { | ||
obj.fn(msgObj.data); | ||
} | ||
}); | ||
}; | ||
var time = channel.method.microSeconds(); | ||
var time = channel.method.microSeconds(); | ||
if (channel._preparePromise) { | ||
channel._preparePromise.then(function () { | ||
channel._isListening = true; | ||
channel.method.onMessage(channel._state, listenerFn, time); | ||
}); | ||
} else { | ||
channel._isListening = true; | ||
channel.method.onMessage(channel._state, listenerFn, time); | ||
} | ||
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._isListening && !_hasMessageListeners(channel)) { | ||
// noone is listening, stop subscribing | ||
channel._isListening = false; | ||
var time = channel.method.microSeconds(); | ||
channel.method.onMessage(channel._state, null, time); | ||
} | ||
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); | ||
} | ||
} | ||
exports['default'] = BroadcastChannel; | ||
var _default = BroadcastChannel; | ||
exports["default"] = _default; |
@@ -1,12 +0,11 @@ | ||
'use strict'; | ||
"use strict"; | ||
var _index = require('./index.js'); | ||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); | ||
var _index2 = _interopRequireDefault(_index); | ||
var _index = _interopRequireDefault(require("./index.js")); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } | ||
module.exports = _index2['default']; /** | ||
* because babel can only export on default-attribute, | ||
* we use this for the non-module-build | ||
*/ | ||
/** | ||
* because babel can only export on default-attribute, | ||
* we use this for the non-module-build | ||
*/ | ||
module.exports = _index["default"]; |
@@ -1,228 +0,242 @@ | ||
'use strict'; | ||
"use strict"; | ||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
value: true | ||
}); | ||
exports.create = create; | ||
exports["default"] = void 0; | ||
var _util = require('../util.js'); | ||
var _util = require("../util.js"); | ||
var _unload = require('unload'); | ||
var _unload = _interopRequireDefault(require("unload")); | ||
var _unload2 = _interopRequireDefault(_unload); | ||
var LeaderElection = function LeaderElection(channel, options) { | ||
this._channel = channel; | ||
this._options = options; | ||
this.isLeader = false; | ||
this.isDead = false; | ||
this.token = (0, _util.randomToken)(10); | ||
this._isApl = false; // _isApplying | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } | ||
this._reApply = false; // things to clean up | ||
var LeaderElection = function LeaderElection(channel, options) { | ||
this._channel = channel; | ||
this._options = options; | ||
this._unl = []; // _unloads | ||
this.isLeader = false; | ||
this.isDead = false; | ||
this.token = (0, _util.randomToken)(10); | ||
this._lstns = []; // _listeners | ||
this._isApplying = false; | ||
this._reApply = false; | ||
// things to clean up | ||
this._unloads = []; | ||
this._listeners = []; | ||
this._intervals = []; | ||
this._invs = []; // _intervals | ||
}; | ||
LeaderElection.prototype = { | ||
applyOnce: function applyOnce() { | ||
var _this = this; | ||
applyOnce: function applyOnce() { | ||
var _this = this; | ||
if (this.isLeader) return Promise.resolve(false); | ||
if (this.isDead) return Promise.resolve(false); | ||
if (this.isLeader) return Promise.resolve(false); | ||
if (this.isDead) return Promise.resolve(false); // do nothing if already running | ||
// do nothing if already running | ||
if (this._isApplying) { | ||
this._reApply = true; | ||
return Promise.resolve(false); | ||
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; | ||
} | ||
} | ||
this._isApplying = true; | ||
var stopCriteria = false; | ||
var recieved = []; | ||
if (msg.action === 'tell') { | ||
// other is already leader | ||
stopCriteria = true; | ||
} | ||
} | ||
}; | ||
var handleMessage = function handleMessage(msg) { | ||
if (msg.context === 'leader' && msg.token != _this.token) { | ||
recieved.push(msg); | ||
this._channel.addEventListener('internal', handleMessage); | ||
if (msg.action === 'apply') { | ||
// other is applying | ||
if (msg.token > _this.token) { | ||
// other has higher token, stop applying | ||
stopCriteria = true; | ||
} | ||
} | ||
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); | ||
if (msg.action === 'tell') { | ||
// other is already leader | ||
stopCriteria = true; | ||
} | ||
} | ||
}; | ||
this._channel.addEventListener('internal', handleMessage); | ||
_this._isApl = false; | ||
var ret = this._sendMessage('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 _this._sendMessage('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 _this._sendMessage(); | ||
}).then(function () { | ||
return _this._beLeader(); | ||
}) // 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._isApplying = false; | ||
if (!success && _this._reApply) { | ||
_this._reApply = false; | ||
return _this.applyOnce(); | ||
} else return success; | ||
}); | ||
return ret; | ||
}, | ||
_awaitLeadershipOnce: function _awaitLeadershipOnce() { | ||
var _this2 = this; | ||
if (!success && _this._reApply) { | ||
_this._reApply = false; | ||
return _this.applyOnce(); | ||
} else return success; | ||
}); | ||
if (this.isLeader) return Promise.resolve(); | ||
return ret; | ||
}, | ||
awaitLeadership: function awaitLeadership() { | ||
if (!this._awaitLeadershipPromise) { | ||
this._awaitLeadershipPromise = _awaitLeadershipOnce(this); | ||
} | ||
return new Promise(function (res) { | ||
var resolved = false; | ||
return this._awaitLeadershipPromise; | ||
}, | ||
die: function die() { | ||
var _this2 = this; | ||
var finish = function finish() { | ||
if (resolved) return; | ||
resolved = true; | ||
clearInterval(interval); | ||
_this2._channel.removeEventListener('internal', whenDeathListener); | ||
res(true); | ||
}; | ||
if (this.isDead) return; | ||
this.isDead = true; | ||
// try once now | ||
_this2.applyOnce().then(function () { | ||
if (_this2.isLeader) finish(); | ||
}); | ||
this._lstns.forEach(function (listener) { | ||
return _this2._channel.removeEventListener('internal', listener); | ||
}); | ||
// try on fallbackInterval | ||
var interval = setInterval(function () { | ||
_this2.applyOnce().then(function () { | ||
if (_this2.isLeader) finish(); | ||
}); | ||
}, _this2._options.fallbackInterval); | ||
_this2._intervals.push(interval); | ||
this._invs.forEach(function (interval) { | ||
return clearInterval(interval); | ||
}); | ||
// try when other leader dies | ||
var whenDeathListener = function whenDeathListener(msg) { | ||
if (msg.context === 'leader' && msg.action === 'death') { | ||
_this2.applyOnce().then(function () { | ||
if (_this2.isLeader) finish(); | ||
}); | ||
} | ||
}; | ||
_this2._channel.addEventListener('internal', whenDeathListener); | ||
_this2._listeners.push(whenDeathListener); | ||
}); | ||
}, | ||
awaitLeadership: function awaitLeadership() { | ||
if (!this._awaitLeadershipPromise) { | ||
this._awaitLeadershipPromise = this._awaitLeadershipOnce(); | ||
} | ||
return this._awaitLeadershipPromise; | ||
}, | ||
die: function die() { | ||
var _this3 = this; | ||
this._unl.forEach(function (uFn) { | ||
uFn.remove(); | ||
}); | ||
if (this.isDead) return; | ||
this.isDead = true; | ||
return _sendMessage(this, 'death'); | ||
} | ||
}; | ||
this._listeners.forEach(function (listener) { | ||
return _this3._channel.removeEventListener('internal', listener); | ||
}); | ||
this._intervals.forEach(function (interval) { | ||
return clearInterval(interval); | ||
}); | ||
this._unloads.forEach(function (uFn) { | ||
uFn.remove(); | ||
}); | ||
return this._sendMessage('death'); | ||
}, | ||
function _awaitLeadershipOnce(leaderElector) { | ||
if (leaderElector.isLeader) return Promise.resolve(); | ||
return new Promise(function (res) { | ||
var resolved = false; | ||
var finish = function finish() { | ||
if (resolved) return; | ||
resolved = true; | ||
clearInterval(interval); | ||
/** | ||
* sends and internal message over the broadcast-channel | ||
*/ | ||
_sendMessage: function _sendMessage(action) { | ||
var msgJson = { | ||
context: 'leader', | ||
action: action, | ||
token: this.token | ||
}; | ||
return this._channel.postInternal(msgJson); | ||
}, | ||
_beLeader: function _beLeader() { | ||
var _this4 = this; | ||
leaderElector._channel.removeEventListener('internal', whenDeathListener); | ||
this.isLeader = true; | ||
var unloadFn = _unload2['default'].add(function () { | ||
return _this4.die(); | ||
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(); | ||
}); | ||
this._unloads.push(unloadFn); | ||
} | ||
}; | ||
var isLeaderListener = function isLeaderListener(msg) { | ||
if (msg.context === 'leader' && msg.action === 'apply') { | ||
_this4._sendMessage('tell'); | ||
} | ||
}; | ||
this._channel.addEventListener('internal', isLeaderListener); | ||
this._listeners.push(isLeaderListener); | ||
return this._sendMessage('tell'); | ||
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'); | ||
} | ||
}; | ||
}; | ||
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) options = {}; | ||
options = JSON.parse(JSON.stringify(options)); | ||
if (!options.fallbackInterval) { | ||
options.fallbackInterval = 3000; | ||
} | ||
if (!options.fallbackInterval) { | ||
options.fallbackInterval = 3000; | ||
} | ||
if (!options.responseTime) { | ||
options.responseTime = channel.method.averageResponseTime(channel.options); | ||
} | ||
if (!options.responseTime) { | ||
options.responseTime = channel.method.averageResponseTime(channel.options); | ||
} | ||
return options; | ||
return options; | ||
} | ||
function create(channel, options) { | ||
if (channel._leaderElector) { | ||
throw new Error('BroadcastChannel already has a leader-elector'); | ||
} | ||
if (channel._leaderElector) { | ||
throw new Error('BroadcastChannel already has a leader-elector'); | ||
} | ||
options = fillOptionsWithDefaults(options, channel); | ||
var elector = new LeaderElection(channel, options); | ||
channel._beforeClose.push(function () { | ||
return elector.die(); | ||
}); | ||
options = fillOptionsWithDefaults(options, channel); | ||
var elector = new LeaderElection(channel, options); | ||
channel._leaderElector = elector; | ||
return elector; | ||
channel._befC.push(function () { | ||
return elector.die(); | ||
}); | ||
channel._leaderElector = elector; | ||
return elector; | ||
} | ||
exports['default'] = { | ||
create: create | ||
}; | ||
var _default = { | ||
create: create | ||
}; | ||
exports["default"] = _default; |
@@ -1,32 +0,22 @@ | ||
'use strict'; | ||
"use strict"; | ||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
value: true | ||
}); | ||
exports.chooseMethod = chooseMethod; | ||
var _detectNode = require('detect-node'); | ||
var _detectNode = _interopRequireDefault(require("detect-node")); | ||
var _detectNode2 = _interopRequireDefault(_detectNode); | ||
var _native = _interopRequireDefault(require("./methods/native.js")); | ||
var _native = require('./methods/native.js'); | ||
var _indexedDb = _interopRequireDefault(require("./methods/indexed-db.js")); | ||
var _native2 = _interopRequireDefault(_native); | ||
var _localstorage = _interopRequireDefault(require("./methods/localstorage.js")); | ||
var _indexedDb = require('./methods/indexed-db.js'); | ||
var _indexedDb2 = _interopRequireDefault(_indexedDb); | ||
var _localstorage = require('./methods/localstorage.js'); | ||
var _localstorage2 = _interopRequireDefault(_localstorage); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } | ||
// order is important | ||
var METHODS = [_native2['default'], // fastest | ||
_indexedDb2['default'], _localstorage2['default']]; | ||
var METHODS = [_native["default"], // fastest | ||
_indexedDb["default"], _localstorage["default"]]; | ||
var REQUIRE_FUN = require; | ||
/** | ||
@@ -36,42 +26,43 @@ * The NodeMethod is loaded lazy | ||
*/ | ||
if (_detectNode2['default']) { | ||
/** | ||
* we use the non-transpiled code for nodejs | ||
* because it runs faster | ||
*/ | ||
var NodeMethod = REQUIRE_FUN('../../src/methods/node.js'); | ||
if (_detectNode["default"]) { | ||
/** | ||
* we use the non-transpiled code for nodejs | ||
* because it runs faster | ||
*/ | ||
var NodeMethod = REQUIRE_FUN('../../src/methods/node.js'); | ||
/** | ||
* this will be false for webpackbuilds | ||
* which will shim the node-method with an empty object {} | ||
*/ | ||
/** | ||
* this will be false for webpackbuilds | ||
* which will shim the node-method with an empty object {} | ||
*/ | ||
if (typeof NodeMethod.canBeUsed === 'function') { | ||
METHODS.push(NodeMethod); | ||
} | ||
if (typeof NodeMethod.canBeUsed === 'function') { | ||
METHODS.push(NodeMethod); | ||
} | ||
} | ||
function chooseMethod(options) { | ||
// directly chosen | ||
if (options.type) { | ||
var ret = METHODS.find(function (m) { | ||
return m.type === options.type; | ||
}); | ||
if (!ret) throw new Error('method-type ' + options.type + ' not found');else return ret; | ||
} | ||
// directly chosen | ||
if (options.type) { | ||
var ret = METHODS.find(function (m) { | ||
return m.type === options.type; | ||
}); | ||
if (!ret) throw new Error('method-type ' + options.type + ' not found');else return ret; | ||
} | ||
var chooseMethods = METHODS; | ||
if (!options.webWorkerSupport && !_detectNode2['default']) { | ||
// prefer localstorage over idb when no webworker-support needed | ||
chooseMethods = METHODS.filter(function (m) { | ||
return m.type !== 'idb'; | ||
}); | ||
} | ||
var chooseMethods = METHODS; | ||
var useMethod = chooseMethods.find(function (method) { | ||
return method.canBeUsed(); | ||
if (!options.webWorkerSupport && !_detectNode["default"]) { | ||
// prefer localstorage over idb when no webworker-support needed | ||
chooseMethods = METHODS.filter(function (m) { | ||
return m.type !== 'idb'; | ||
}); | ||
if (!useMethod) throw new Error('No useable methode found:' + JSON.stringify(METHODS.map(function (m) { | ||
return m.type; | ||
})));else return useMethod; | ||
} | ||
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; | ||
} |
@@ -1,7 +0,8 @@ | ||
'use strict'; | ||
"use strict"; | ||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
value: true | ||
}); | ||
exports.type = exports.microSeconds = undefined; | ||
exports.getIdb = getIdb; | ||
@@ -21,17 +22,12 @@ exports.createDatabase = createDatabase; | ||
exports.averageResponseTime = averageResponseTime; | ||
exports["default"] = exports.type = exports.microSeconds = void 0; | ||
var _detectNode = require('detect-node'); | ||
var _detectNode = _interopRequireDefault(require("detect-node")); | ||
var _detectNode2 = _interopRequireDefault(_detectNode); | ||
var _util = require("../util.js"); | ||
var _util = require('../util.js'); | ||
var _obliviousSet = _interopRequireDefault(require("../oblivious-set")); | ||
var _obliviousSet = require('../oblivious-set'); | ||
var _options = require("../options"); | ||
var _obliviousSet2 = _interopRequireDefault(_obliviousSet); | ||
var _options = require('../options'); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } | ||
/** | ||
@@ -42,46 +38,42 @@ * this method uses indexeddb to store the messages | ||
*/ | ||
var microSeconds = exports.microSeconds = _util.microSeconds; | ||
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; | ||
var type = exports.type = 'idb'; | ||
function getIdb() { | ||
if (typeof indexedDB !== 'undefined') return indexedDB; | ||
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 (typeof indexedDB !== 'undefined') return indexedDB; | ||
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(); | ||
var IndexedDB = getIdb(); // create table | ||
// create table | ||
var dbName = DB_PREFIX + channelName; | ||
var openRequest = IndexedDB.open(dbName, 1); | ||
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); | ||
}; | ||
openRequest.onupgradeneeded = function (ev) { | ||
var db = ev.target.result; | ||
db.createObjectStore(OBJECT_STORE_ID, { | ||
keyPath: 'id', | ||
autoIncrement: true | ||
}); | ||
}; | ||
return dbPromise; | ||
var dbPromise = new Promise(function (res, rej) { | ||
openRequest.onerror = function (ev) { | ||
return rej(ev); | ||
}; | ||
openRequest.onsuccess = function () { | ||
res(openRequest.result); | ||
}; | ||
}); | ||
return dbPromise; | ||
} | ||
/** | ||
@@ -91,229 +83,232 @@ * writes the new message to the database | ||
*/ | ||
function writeMessage(db, readerUuid, messageJson) { | ||
var time = new Date().getTime(); | ||
var writeObject = { | ||
uuid: readerUuid, | ||
time: time, | ||
data: 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(); | ||
}; | ||
var transaction = db.transaction([OBJECT_STORE_ID], 'readwrite'); | ||
transaction.onerror = function (ev) { | ||
return rej(ev); | ||
}; | ||
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); | ||
}); | ||
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); | ||
} | ||
}; | ||
}); | ||
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 getMessagesHigherThen(db, lastCursorId) { | ||
var objectStore = db.transaction(OBJECT_STORE_ID).objectStore(OBJECT_STORE_ID); | ||
var ret = []; | ||
var keyRangeValue = IDBKeyRange.bound(lastCursorId + 1, Infinity); | ||
return new Promise(function (res) { | ||
objectStore.openCursor(keyRangeValue).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); | ||
} | ||
}; | ||
}); | ||
var objectStore = db.transaction(OBJECT_STORE_ID).objectStore(OBJECT_STORE_ID); | ||
var ret = []; | ||
var keyRangeValue = IDBKeyRange.bound(lastCursorId + 1, Infinity); | ||
return new Promise(function (res) { | ||
objectStore.openCursor(keyRangeValue).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 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(); | ||
}; | ||
}); | ||
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); | ||
} | ||
}; | ||
}); | ||
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); | ||
})); | ||
}); | ||
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); | ||
options = (0, _options.fillOptionsWithDefaults)(options); | ||
var uuid = (0, _util.randomToken)(10); | ||
return createDatabase(channelName).then(function (db) { | ||
var state = { | ||
closed: false, | ||
lastCursorId: 0, | ||
channelName: channelName, | ||
options: options, | ||
uuid: uuid, | ||
// contains all messages that have been emitted before | ||
emittedMessagesIds: new _obliviousSet["default"](options.idb.ttl * 2), | ||
// ensures we do not read messages in parrallel | ||
writeBlockPromise: Promise.resolve(), | ||
messagesCallback: null, | ||
readQueuePromises: [], | ||
db: db | ||
}; | ||
/** | ||
* if service-workers are used, | ||
* we have no 'storage'-event if they post a message, | ||
* therefore we also have to set an interval | ||
*/ | ||
var uuid = (0, _util.randomToken)(10); | ||
_readLoop(state); | ||
return createDatabase(channelName).then(function (db) { | ||
var state = { | ||
closed: false, | ||
lastCursorId: 0, | ||
channelName: channelName, | ||
options: options, | ||
uuid: uuid, | ||
// contains all messages that have been emitted before | ||
emittedMessagesIds: new _obliviousSet2['default'](options.idb.ttl * 2), | ||
// ensures we do not read messages in parrallel | ||
writeBlockPromise: Promise.resolve(), | ||
messagesCallback: null, | ||
readQueuePromises: [], | ||
db: db | ||
}; | ||
/** | ||
* 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; | ||
}); | ||
return state; | ||
}); | ||
} | ||
function _readLoop(state) { | ||
if (state.closed) return; | ||
return readNewMessages(state).then(function () { | ||
return (0, _util.sleep)(state.options.idb.fallbackInterval); | ||
}).then(function () { | ||
return _readLoop(state); | ||
}); | ||
if (state.closed) return; | ||
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.emittedMessagesIds.has(msgObj.id)) return false; // already emitted | ||
if (msgObj.data.time < state.messagesCallbackTime) return false; // older then onMessageCallback | ||
return true; | ||
if (msgObj.uuid === state.uuid) return false; // send by own | ||
if (state.emittedMessagesIds.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 | ||
// channel already closed | ||
if (state.closed) return Promise.resolve(); | ||
if (!state.messagesCallback) return Promise.resolve(); | ||
return getMessagesHigherThen(state.db, state.lastCursorId).then(function (newerMessages) { | ||
var useMessages = newerMessages.map(function (msgObj) { | ||
if (msgObj.id > state.lastCursorId) { | ||
state.lastCursorId = msgObj.id; | ||
} | ||
// if no one is listening, we do not need to scan for new messages | ||
if (!state.messagesCallback) return Promise.resolve(); | ||
return msgObj; | ||
}).filter(function (msgObj) { | ||
return _filterMessage(msgObj, state); | ||
}).sort(function (msgObjA, msgObjB) { | ||
return msgObjA.time - msgObjB.time; | ||
}); // sort by time | ||
return getMessagesHigherThen(state.db, state.lastCursorId).then(function (newerMessages) { | ||
var useMessages = newerMessages.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.emittedMessagesIds.add(msgObj.id); | ||
state.messagesCallback(msgObj.data); | ||
} | ||
}); | ||
return Promise.resolve(); | ||
useMessages.forEach(function (msgObj) { | ||
if (state.messagesCallback) { | ||
state.emittedMessagesIds.add(msgObj.id); | ||
state.messagesCallback(msgObj.data); | ||
} | ||
}); | ||
return Promise.resolve(); | ||
}); | ||
} | ||
function close(channelState) { | ||
channelState.closed = true; | ||
channelState.db.close(); | ||
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; | ||
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); | ||
channelState.messagesCallbackTime = time; | ||
channelState.messagesCallback = fn; | ||
readNewMessages(channelState); | ||
} | ||
function canBeUsed() { | ||
if (_detectNode2['default']) return false; | ||
var idb = getIdb(); | ||
if (!idb) return false; | ||
return true; | ||
if (_detectNode["default"]) return false; | ||
var idb = getIdb(); | ||
if (!idb) return false; | ||
return true; | ||
} | ||
function averageResponseTime(options) { | ||
return options.idb.fallbackInterval * 2; | ||
return options.idb.fallbackInterval * 2; | ||
} | ||
exports['default'] = { | ||
create: create, | ||
close: close, | ||
onMessage: onMessage, | ||
postMessage: postMessage, | ||
canBeUsed: canBeUsed, | ||
type: type, | ||
averageResponseTime: averageResponseTime, | ||
microSeconds: microSeconds | ||
}; | ||
var _default = { | ||
create: create, | ||
close: close, | ||
onMessage: onMessage, | ||
postMessage: postMessage, | ||
canBeUsed: canBeUsed, | ||
type: type, | ||
averageResponseTime: averageResponseTime, | ||
microSeconds: microSeconds | ||
}; | ||
exports["default"] = _default; |
@@ -1,7 +0,8 @@ | ||
'use strict'; | ||
"use strict"; | ||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
value: true | ||
}); | ||
exports.type = exports.microSeconds = undefined; | ||
exports.getLocalStorage = getLocalStorage; | ||
@@ -17,17 +18,12 @@ exports.storageKey = storageKey; | ||
exports.averageResponseTime = averageResponseTime; | ||
exports["default"] = exports.type = exports.microSeconds = void 0; | ||
var _detectNode = require('detect-node'); | ||
var _detectNode = _interopRequireDefault(require("detect-node")); | ||
var _detectNode2 = _interopRequireDefault(_detectNode); | ||
var _obliviousSet = _interopRequireDefault(require("../oblivious-set")); | ||
var _obliviousSet = require('../oblivious-set'); | ||
var _options = require("../options"); | ||
var _obliviousSet2 = _interopRequireDefault(_obliviousSet); | ||
var _util = require("../util"); | ||
var _options = require('../options'); | ||
var _util = require('../util'); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } | ||
/** | ||
@@ -40,8 +36,6 @@ * A localStorage-only method which uses localstorage and its 'storage'-event | ||
*/ | ||
var microSeconds = exports.microSeconds = _util.microSeconds; | ||
var microSeconds = _util.microSeconds; | ||
exports.microSeconds = microSeconds; | ||
var KEY_PREFIX = 'pubkey.broadcastChannel-'; | ||
var type = exports.type = 'localstorage'; | ||
var type = 'localstorage'; | ||
/** | ||
@@ -51,20 +45,23 @@ * copied from crosstab | ||
*/ | ||
exports.type = type; | ||
function getLocalStorage() { | ||
var localStorage = void 0; | ||
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; | ||
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; | ||
return KEY_PREFIX + channelName; | ||
} | ||
/** | ||
@@ -74,108 +71,112 @@ * writes the new message to the storage | ||
*/ | ||
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)(10), | ||
time: new Date().getTime(), | ||
data: messageJson, | ||
uuid: channelState.uuid | ||
}; | ||
var value = JSON.stringify(writeObj); | ||
localStorage.setItem(key, value); | ||
return new Promise(function (res) { | ||
(0, _util.sleep)().then(function () { | ||
var key = storageKey(channelState.channelName); | ||
var writeObj = { | ||
token: (0, _util.randomToken)(10), | ||
time: new Date().getTime(), | ||
data: messageJson, | ||
uuid: channelState.uuid | ||
}; | ||
var value = JSON.stringify(writeObj); | ||
localStorage.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 | ||
*/ | ||
/** | ||
* 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(); | ||
}); | ||
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; | ||
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); | ||
window.removeEventListener('storage', listener); | ||
} | ||
function create(channelName, options) { | ||
options = (0, _options.fillOptionsWithDefaults)(options); | ||
if (!canBeUsed()) { | ||
throw new Error('BroadcastChannel: localstorage cannot be used'); | ||
} | ||
options = (0, _options.fillOptionsWithDefaults)(options); | ||
var startTime = new Date().getTime(); | ||
var uuid = (0, _util.randomToken)(10); | ||
if (!canBeUsed()) { | ||
throw new Error('BroadcastChannel: localstorage cannot be used'); | ||
} | ||
// contains all messages that have been emitted before | ||
var emittedMessagesIds = new _obliviousSet2['default'](options.localstorage.removeTimeout); | ||
var startTime = new Date().getTime(); | ||
var uuid = (0, _util.randomToken)(10); // contains all messages that have been emitted before | ||
var state = { | ||
startTime: startTime, | ||
channelName: channelName, | ||
options: options, | ||
uuid: uuid, | ||
emittedMessagesIds: emittedMessagesIds | ||
}; | ||
var emittedMessagesIds = new _obliviousSet["default"](options.localstorage.removeTimeout); | ||
var state = { | ||
startTime: startTime, | ||
channelName: channelName, | ||
options: options, | ||
uuid: uuid, | ||
emittedMessagesIds: emittedMessagesIds | ||
}; | ||
state.listener = addStorageEventListener(channelName, function (msgObj) { | ||
if (!state.messagesCallback) return; // no listener | ||
state.listener = addStorageEventListener(channelName, function (msgObj) { | ||
if (!state.messagesCallback) return; // no listener | ||
if (msgObj.uuid === uuid) return; // own message | ||
if (!msgObj.token || emittedMessagesIds.has(msgObj.token)) return; // already emitted | ||
if (msgObj.data.time && msgObj.data.time < state.messagesCallbackTime) return; // too old | ||
if (msgObj.uuid === uuid) return; // own message | ||
emittedMessagesIds.add(msgObj.token); | ||
state.messagesCallback(msgObj.data); | ||
}); | ||
if (!msgObj.token || emittedMessagesIds.has(msgObj.token)) return; // already emitted | ||
return state; | ||
if (msgObj.data.time && msgObj.data.time < state.messagesCallbackTime) return; // too old | ||
emittedMessagesIds.add(msgObj.token); | ||
state.messagesCallback(msgObj.data); | ||
}); | ||
return state; | ||
} | ||
function close(channelState) { | ||
removeStorageEventListener(channelState.listener); | ||
removeStorageEventListener(channelState.listener); | ||
} | ||
function onMessage(channelState, fn, time) { | ||
channelState.messagesCallbackTime = time; | ||
channelState.messagesCallback = fn; | ||
channelState.messagesCallbackTime = time; | ||
channelState.messagesCallback = fn; | ||
} | ||
function canBeUsed() { | ||
if (_detectNode2['default']) return false; | ||
var ls = getLocalStorage(); | ||
if (!ls) return false; | ||
return true; | ||
if (_detectNode["default"]) return false; | ||
var ls = getLocalStorage(); | ||
if (!ls) return false; | ||
return true; | ||
} | ||
function averageResponseTime() { | ||
return 120; | ||
return 120; | ||
} | ||
exports['default'] = { | ||
create: create, | ||
close: close, | ||
onMessage: onMessage, | ||
postMessage: postMessage, | ||
canBeUsed: canBeUsed, | ||
type: type, | ||
averageResponseTime: averageResponseTime, | ||
microSeconds: microSeconds | ||
}; | ||
var _default = { | ||
create: create, | ||
close: close, | ||
onMessage: onMessage, | ||
postMessage: postMessage, | ||
canBeUsed: canBeUsed, | ||
type: type, | ||
averageResponseTime: averageResponseTime, | ||
microSeconds: microSeconds | ||
}; | ||
exports["default"] = _default; |
@@ -1,7 +0,8 @@ | ||
'use strict'; | ||
"use strict"; | ||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
value: true | ||
}); | ||
exports.type = exports.microSeconds = undefined; | ||
exports.create = create; | ||
@@ -13,78 +14,77 @@ exports.close = close; | ||
exports.averageResponseTime = averageResponseTime; | ||
exports["default"] = exports.type = exports.microSeconds = void 0; | ||
var _detectNode = require('detect-node'); | ||
var _detectNode = _interopRequireDefault(require("detect-node")); | ||
var _detectNode2 = _interopRequireDefault(_detectNode); | ||
var _util = require("../util"); | ||
var _util = require('../util'); | ||
var microSeconds = _util.microSeconds; | ||
exports.microSeconds = microSeconds; | ||
var type = 'native'; | ||
exports.type = type; | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } | ||
var microSeconds = exports.microSeconds = _util.microSeconds; | ||
var type = exports.type = 'native'; | ||
function create(channelName, options) { | ||
if (!options) options = {}; | ||
var state = { | ||
uuid: (0, _util.randomToken)(10), | ||
channelName: channelName, | ||
options: options, | ||
messagesCallback: null, | ||
bc: new BroadcastChannel(channelName), | ||
subscriberFunctions: [] | ||
}; | ||
if (!options) options = {}; | ||
var state = { | ||
uuid: (0, _util.randomToken)(10), | ||
channelName: channelName, | ||
options: options, | ||
messagesCallback: null, | ||
bc: new BroadcastChannel(channelName), | ||
subscriberFunctions: [] | ||
}; | ||
state.bc.onmessage = function (msg) { | ||
if (state.messagesCallback) { | ||
state.messagesCallback(msg.data); | ||
} | ||
}; | ||
state.bc.onmessage = function (msg) { | ||
if (state.messagesCallback) { | ||
state.messagesCallback(msg.data); | ||
} | ||
}; | ||
return state; | ||
return state; | ||
} | ||
function close(channelState) { | ||
channelState.bc.close(); | ||
channelState.subscriberFunctions = []; | ||
channelState.bc.close(); | ||
channelState.subscriberFunctions = []; | ||
} | ||
function postMessage(channelState, messageJson) { | ||
channelState.bc.postMessage(messageJson, false); | ||
channelState.bc.postMessage(messageJson, false); | ||
} | ||
function onMessage(channelState, fn, time) { | ||
channelState.messagesCallbackTime = time; | ||
channelState.messagesCallback = fn; | ||
channelState.messagesCallbackTime = time; | ||
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 (_detectNode["default"] && typeof window === 'undefined') return false; | ||
/** | ||
* in the electron-renderer, isNode will be true even if we are in browser-context | ||
* so we also check if window is undefined | ||
*/ | ||
if (_detectNode2['default'] && 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'); | ||
} | ||
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; | ||
return true; | ||
} else return false; | ||
} | ||
function averageResponseTime() { | ||
return 100; | ||
return 100; | ||
} | ||
exports['default'] = { | ||
create: create, | ||
close: close, | ||
onMessage: onMessage, | ||
postMessage: postMessage, | ||
canBeUsed: canBeUsed, | ||
type: type, | ||
averageResponseTime: averageResponseTime, | ||
microSeconds: microSeconds | ||
}; | ||
var _default = { | ||
create: create, | ||
close: close, | ||
onMessage: onMessage, | ||
postMessage: postMessage, | ||
canBeUsed: canBeUsed, | ||
type: type, | ||
averageResponseTime: averageResponseTime, | ||
microSeconds: microSeconds | ||
}; | ||
exports["default"] = _default; |
@@ -1,757 +0,791 @@ | ||
'use strict'; | ||
"use strict"; | ||
var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); | ||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); | ||
var _slicedToArray3 = _interopRequireDefault(_slicedToArray2); | ||
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); | ||
var _regenerator = require('babel-runtime/regenerator'); | ||
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); | ||
var _regenerator2 = _interopRequireDefault(_regenerator); | ||
/** | ||
* this method is used in nodejs-environments. | ||
* The ipc is handled via sockets and file-writes to the tmp-folder | ||
*/ | ||
var util = require('util'); | ||
var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); | ||
var fs = require('fs'); | ||
var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); | ||
var os = require('os'); | ||
var ensureFoldersExist = function () { | ||
var _ref = (0, _asyncToGenerator3['default'])( /*#__PURE__*/_regenerator2['default'].mark(function _callee(channelName, paths) { | ||
return _regenerator2['default'].wrap(function _callee$(_context) { | ||
while (1) { | ||
switch (_context.prev = _context.next) { | ||
case 0: | ||
paths = paths || getPaths(channelName); | ||
var events = require('events'); | ||
if (!ENSURE_BASE_FOLDER_EXISTS_PROMISE) { | ||
ENSURE_BASE_FOLDER_EXISTS_PROMISE = mkdir(paths.base)['catch'](function () { | ||
return null; | ||
}); | ||
} | ||
_context.next = 4; | ||
return ENSURE_BASE_FOLDER_EXISTS_PROMISE; | ||
var net = require('net'); | ||
case 4: | ||
_context.next = 6; | ||
return mkdir(paths.channelBase)['catch'](function () { | ||
return null; | ||
}); | ||
var path = require('path'); | ||
case 6: | ||
_context.next = 8; | ||
return Promise.all([mkdir(paths.readers)['catch'](function () { | ||
return null; | ||
}), mkdir(paths.messages)['catch'](function () { | ||
return null; | ||
})]); | ||
var micro = require('nano-time'); | ||
case 8: | ||
case 'end': | ||
return _context.stop(); | ||
} | ||
} | ||
}, _callee, this); | ||
})); | ||
var rimraf = require('rimraf'); | ||
return function ensureFoldersExist(_x, _x2) { | ||
return _ref.apply(this, arguments); | ||
}; | ||
}(); | ||
var sha3_224 = require('js-sha3').sha3_224; | ||
/** | ||
* removes the tmp-folder | ||
* @return {Promise<true>} | ||
*/ | ||
var isNode = require('detect-node'); | ||
var unload = require('unload'); | ||
var clearNodeFolder = function () { | ||
var _ref2 = (0, _asyncToGenerator3['default'])( /*#__PURE__*/_regenerator2['default'].mark(function _callee2() { | ||
var paths, removePath; | ||
return _regenerator2['default'].wrap(function _callee2$(_context2) { | ||
while (1) { | ||
switch (_context2.prev = _context2.next) { | ||
case 0: | ||
paths = getPaths('foobar'); | ||
removePath = paths.base; | ||
var fillOptionsWithDefaults = require('../../dist/lib/options.js').fillOptionsWithDefaults; | ||
if (!(!removePath || removePath === '' || removePath === '/')) { | ||
_context2.next = 4; | ||
break; | ||
} | ||
var ownUtil = require('../../dist/lib/util.js'); | ||
throw new Error('BroadcastChannel.clearNodeFolder(): path is wrong'); | ||
var randomInt = ownUtil.randomInt; | ||
var randomToken = ownUtil.randomToken; | ||
case 4: | ||
ENSURE_BASE_FOLDER_EXISTS_PROMISE = null; | ||
_context2.next = 7; | ||
return removeDir(paths.base); | ||
case 7: | ||
ENSURE_BASE_FOLDER_EXISTS_PROMISE = null; | ||
return _context2.abrupt('return', true); | ||
case 9: | ||
case 'end': | ||
return _context2.stop(); | ||
} | ||
} | ||
}, _callee2, this); | ||
})); | ||
return function clearNodeFolder() { | ||
return _ref2.apply(this, arguments); | ||
}; | ||
}(); | ||
var ObliviousSet = require('../../dist/lib/oblivious-set')["default"]; | ||
/** | ||
* creates the socket-file and subscribes to it | ||
* @return {{emitter: EventEmitter, server: any}} | ||
* windows sucks, so we have handle windows-type of socket-paths | ||
* @link https://gist.github.com/domenic/2790533#gistcomment-331356 | ||
*/ | ||
var createSocketEventEmitter = function () { | ||
var _ref3 = (0, _asyncToGenerator3['default'])( /*#__PURE__*/_regenerator2['default'].mark(function _callee3(channelName, readerUuid, paths) { | ||
var pathToSocket, emitter, server; | ||
return _regenerator2['default'].wrap(function _callee3$(_context3) { | ||
while (1) { | ||
switch (_context3.prev = _context3.next) { | ||
case 0: | ||
pathToSocket = socketPath(channelName, readerUuid, paths); | ||
emitter = new events.EventEmitter(); | ||
server = net.createServer(function (stream) { | ||
stream.on('end', function () {}); | ||
stream.on('data', function (msg) { | ||
emitter.emit('data', msg.toString()); | ||
}); | ||
}); | ||
_context3.next = 5; | ||
return new Promise(function (resolve, reject) { | ||
server.listen(pathToSocket, function (err, res) { | ||
if (err) reject(err);else resolve(res); | ||
}); | ||
}); | ||
case 5: | ||
return _context3.abrupt('return', { | ||
path: pathToSocket, | ||
emitter: emitter, | ||
server: server | ||
}); | ||
case 6: | ||
case 'end': | ||
return _context3.stop(); | ||
} | ||
} | ||
}, _callee3, this); | ||
})); | ||
function cleanPipeName(str) { | ||
if (process.platform === 'win32' && !str.startsWith('\\\\.\\pipe\\')) { | ||
str = str.replace(/^\//, ''); | ||
str = str.replace(/\//g, '-'); | ||
return '\\\\.\\pipe\\' + str; | ||
} else { | ||
return str; | ||
} | ||
} | ||
return function createSocketEventEmitter(_x3, _x4, _x5) { | ||
return _ref3.apply(this, arguments); | ||
}; | ||
}(); | ||
var mkdir = util.promisify(fs.mkdir); | ||
var writeFile = util.promisify(fs.writeFile); | ||
var readFile = util.promisify(fs.readFile); | ||
var unlink = util.promisify(fs.unlink); | ||
var readdir = util.promisify(fs.readdir); | ||
var removeDir = util.promisify(rimraf); | ||
var OTHER_INSTANCES = {}; | ||
var TMP_FOLDER_NAME = 'pubkey.bc'; | ||
var TMP_FOLDER_BASE = path.join(os.tmpdir(), TMP_FOLDER_NAME); | ||
var getPathsCache = new Map(); | ||
var openClientConnection = function () { | ||
var _ref4 = (0, _asyncToGenerator3['default'])( /*#__PURE__*/_regenerator2['default'].mark(function _callee4(channelName, readerUuid) { | ||
var pathToSocket, client; | ||
return _regenerator2['default'].wrap(function _callee4$(_context4) { | ||
while (1) { | ||
switch (_context4.prev = _context4.next) { | ||
case 0: | ||
pathToSocket = socketPath(channelName, readerUuid); | ||
client = new net.Socket(); | ||
_context4.next = 4; | ||
return new Promise(function (res) { | ||
client.connect(pathToSocket, res); | ||
}); | ||
function getPaths(channelName) { | ||
if (!getPathsCache.has(channelName)) { | ||
var channelHash = sha3_224(channelName); // use hash incase of strange characters | ||
case 4: | ||
return _context4.abrupt('return', client); | ||
/** | ||
* because the lenght 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 | ||
*/ | ||
case 5: | ||
case 'end': | ||
return _context4.stop(); | ||
} | ||
} | ||
}, _callee4, this); | ||
})); | ||
return function openClientConnection(_x6, _x7) { | ||
return _ref4.apply(this, arguments); | ||
var channelFolder = 'A' + channelHash.substring(0, 20); | ||
var channelPathBase = path.join(TMP_FOLDER_BASE, channelFolder); | ||
var folderPathReaders = path.join(channelPathBase, 'rdrs'); | ||
var folderPathMessages = path.join(channelPathBase, 'messages'); | ||
var ret = { | ||
base: TMP_FOLDER_BASE, | ||
channelBase: channelPathBase, | ||
readers: folderPathReaders, | ||
messages: folderPathMessages | ||
}; | ||
}(); | ||
getPathsCache.set(channelName, ret); | ||
return ret; | ||
} | ||
/** | ||
* writes the new message to the file-system | ||
* so other readers can find it | ||
* @return {Promise} | ||
*/ | ||
return getPathsCache.get(channelName); | ||
} | ||
var ENSURE_BASE_FOLDER_EXISTS_PROMISE = null; | ||
function ensureFoldersExist(_x, _x2) { | ||
return _ensureFoldersExist.apply(this, arguments); | ||
} | ||
/** | ||
* returns the uuids of all readers | ||
* @return {string[]} | ||
* removes the tmp-folder | ||
* @return {Promise<true>} | ||
*/ | ||
var getReadersUuids = function () { | ||
var _ref5 = (0, _asyncToGenerator3['default'])( /*#__PURE__*/_regenerator2['default'].mark(function _callee5(channelName, paths) { | ||
var readersPath, files; | ||
return _regenerator2['default'].wrap(function _callee5$(_context5) { | ||
while (1) { | ||
switch (_context5.prev = _context5.next) { | ||
case 0: | ||
paths = paths || getPaths(channelName); | ||
readersPath = paths.readers; | ||
_context5.next = 4; | ||
return readdir(readersPath); | ||
case 4: | ||
files = _context5.sent; | ||
return _context5.abrupt('return', files.map(function (file) { | ||
return file.split('.'); | ||
}).filter(function (split) { | ||
return split[1] === 'json'; | ||
}) // do not scan .socket-files | ||
.map(function (split) { | ||
return split[0]; | ||
})); | ||
case 6: | ||
case 'end': | ||
return _context5.stop(); | ||
} | ||
function _ensureFoldersExist() { | ||
_ensureFoldersExist = (0, _asyncToGenerator2["default"])( | ||
/*#__PURE__*/ | ||
_regenerator["default"].mark(function _callee4(channelName, paths) { | ||
return _regenerator["default"].wrap(function _callee4$(_context4) { | ||
while (1) { | ||
switch (_context4.prev = _context4.next) { | ||
case 0: | ||
paths = paths || getPaths(channelName); | ||
if (!ENSURE_BASE_FOLDER_EXISTS_PROMISE) { | ||
ENSURE_BASE_FOLDER_EXISTS_PROMISE = mkdir(paths.base)["catch"](function () { | ||
return null; | ||
}); | ||
} | ||
}, _callee5, this); | ||
})); | ||
return function getReadersUuids(_x8, _x9) { | ||
return _ref5.apply(this, arguments); | ||
}; | ||
}(); | ||
_context4.next = 4; | ||
return ENSURE_BASE_FOLDER_EXISTS_PROMISE; | ||
var messagePath = function () { | ||
var _ref6 = (0, _asyncToGenerator3['default'])( /*#__PURE__*/_regenerator2['default'].mark(function _callee6(channelName, time, token, writerUuid) { | ||
var fileName, msgPath; | ||
return _regenerator2['default'].wrap(function _callee6$(_context6) { | ||
while (1) { | ||
switch (_context6.prev = _context6.next) { | ||
case 0: | ||
fileName = time + '_' + writerUuid + '_' + token + '.json'; | ||
msgPath = path.join(getPaths(channelName).messages, fileName); | ||
return _context6.abrupt('return', msgPath); | ||
case 4: | ||
_context4.next = 6; | ||
return mkdir(paths.channelBase)["catch"](function () { | ||
return null; | ||
}); | ||
case 3: | ||
case 'end': | ||
return _context6.stop(); | ||
} | ||
} | ||
}, _callee6, this); | ||
})); | ||
case 6: | ||
_context4.next = 8; | ||
return Promise.all([mkdir(paths.readers)["catch"](function () { | ||
return null; | ||
}), mkdir(paths.messages)["catch"](function () { | ||
return null; | ||
})]); | ||
return function messagePath(_x10, _x11, _x12, _x13) { | ||
return _ref6.apply(this, arguments); | ||
}; | ||
}(); | ||
case 8: | ||
case "end": | ||
return _context4.stop(); | ||
} | ||
} | ||
}, _callee4, this); | ||
})); | ||
return _ensureFoldersExist.apply(this, arguments); | ||
} | ||
var getAllMessages = function () { | ||
var _ref7 = (0, _asyncToGenerator3['default'])( /*#__PURE__*/_regenerator2['default'].mark(function _callee7(channelName, paths) { | ||
var messagesPath, files; | ||
return _regenerator2['default'].wrap(function _callee7$(_context7) { | ||
while (1) { | ||
switch (_context7.prev = _context7.next) { | ||
case 0: | ||
paths = paths || getPaths(channelName); | ||
messagesPath = paths.messages; | ||
_context7.next = 4; | ||
return readdir(messagesPath); | ||
function clearNodeFolder() { | ||
return _clearNodeFolder.apply(this, arguments); | ||
} | ||
case 4: | ||
files = _context7.sent; | ||
return _context7.abrupt('return', files.map(function (file) { | ||
var fileName = file.split('.')[0]; | ||
var split = fileName.split('_'); | ||
function _clearNodeFolder() { | ||
_clearNodeFolder = (0, _asyncToGenerator2["default"])( | ||
/*#__PURE__*/ | ||
_regenerator["default"].mark(function _callee5() { | ||
var paths, removePath; | ||
return _regenerator["default"].wrap(function _callee5$(_context5) { | ||
while (1) { | ||
switch (_context5.prev = _context5.next) { | ||
case 0: | ||
paths = getPaths('foobar'); | ||
removePath = paths.base; | ||
return { | ||
path: path.join(messagesPath, file), | ||
time: parseInt(split[0]), | ||
senderUuid: split[1], | ||
token: split[2] | ||
}; | ||
})); | ||
case 6: | ||
case 'end': | ||
return _context7.stop(); | ||
} | ||
if (!(!removePath || removePath === '' || removePath === '/')) { | ||
_context5.next = 4; | ||
break; | ||
} | ||
}, _callee7, this); | ||
})); | ||
return function getAllMessages(_x14, _x15) { | ||
return _ref7.apply(this, arguments); | ||
}; | ||
}(); | ||
throw new Error('BroadcastChannel.clearNodeFolder(): path is wrong'); | ||
var cleanOldMessages = function () { | ||
var _ref8 = (0, _asyncToGenerator3['default'])( /*#__PURE__*/_regenerator2['default'].mark(function _callee8(messageObjects, ttl) { | ||
var olderThen; | ||
return _regenerator2['default'].wrap(function _callee8$(_context8) { | ||
while (1) { | ||
switch (_context8.prev = _context8.next) { | ||
case 0: | ||
olderThen = Date.now() - ttl; | ||
_context8.next = 3; | ||
return Promise.all(messageObjects.filter(function (obj) { | ||
return obj.time / 1000 < olderThen; | ||
}).map(function (obj) { | ||
return unlink(obj.path)['catch'](function () { | ||
return null; | ||
}); | ||
})); | ||
case 4: | ||
ENSURE_BASE_FOLDER_EXISTS_PROMISE = null; | ||
_context5.next = 7; | ||
return removeDir(paths.base); | ||
case 3: | ||
case 'end': | ||
return _context8.stop(); | ||
} | ||
} | ||
}, _callee8, this); | ||
})); | ||
case 7: | ||
ENSURE_BASE_FOLDER_EXISTS_PROMISE = null; | ||
return _context5.abrupt("return", true); | ||
return function cleanOldMessages(_x16, _x17) { | ||
return _ref8.apply(this, arguments); | ||
}; | ||
}(); | ||
case 9: | ||
case "end": | ||
return _context5.stop(); | ||
} | ||
} | ||
}, _callee5, this); | ||
})); | ||
return _clearNodeFolder.apply(this, arguments); | ||
} | ||
function socketPath(channelName, readerUuid, paths) { | ||
paths = paths || getPaths(channelName); | ||
var socketPath = path.join(paths.readers, readerUuid + '.s'); | ||
return cleanPipeName(socketPath); | ||
} | ||
function socketInfoPath(channelName, readerUuid, paths) { | ||
paths = paths || getPaths(channelName); | ||
var socketPath = path.join(paths.readers, readerUuid + '.json'); | ||
return socketPath; | ||
} | ||
/** | ||
* creates a new channelState | ||
* @return {Promise<any>} | ||
* 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 | ||
*/ | ||
var create = function () { | ||
var _ref9 = (0, _asyncToGenerator3['default'])( /*#__PURE__*/_regenerator2['default'].mark(function _callee9(channelName) { | ||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
var time, paths, ensureFolderExistsPromise, uuid, state, _ref10, _ref11, socketEE, infoFilePath; | ||
return _regenerator2['default'].wrap(function _callee9$(_context9) { | ||
while (1) { | ||
switch (_context9.prev = _context9.next) { | ||
case 0: | ||
options = fillOptionsWithDefaults(options); | ||
time = microSeconds(); | ||
paths = getPaths(channelName); | ||
ensureFolderExistsPromise = ensureFoldersExist(channelName, paths); | ||
uuid = randomToken(10); | ||
state = { | ||
time: time, | ||
channelName: channelName, | ||
options: options, | ||
uuid: uuid, | ||
paths: paths, | ||
// contains all messages that have been emitted before | ||
emittedMessagesIds: new ObliviousSet(options.node.ttl * 2), | ||
messagesCallbackTime: null, | ||
messagesCallback: null, | ||
// ensures we do not read messages in parrallel | ||
writeBlockPromise: Promise.resolve(), | ||
otherReaderClients: {}, | ||
// ensure if process crashes, everything is cleaned up | ||
removeUnload: unload.add(function () { | ||
return close(state); | ||
}), | ||
closed: false | ||
}; | ||
function createSocketInfoFile(channelName, readerUuid, paths) { | ||
var pathToFile = socketInfoPath(channelName, readerUuid, paths); | ||
return writeFile(pathToFile, JSON.stringify({ | ||
time: microSeconds() | ||
})).then(function () { | ||
return pathToFile; | ||
}); | ||
} | ||
/** | ||
* creates the socket-file and subscribes to it | ||
* @return {{emitter: EventEmitter, server: any}} | ||
*/ | ||
if (!OTHER_INSTANCES[channelName]) OTHER_INSTANCES[channelName] = []; | ||
OTHER_INSTANCES[channelName].push(state); | ||
function createSocketEventEmitter(_x3, _x4, _x5) { | ||
return _createSocketEventEmitter.apply(this, arguments); | ||
} | ||
_context9.next = 10; | ||
return ensureFolderExistsPromise; | ||
function _createSocketEventEmitter() { | ||
_createSocketEventEmitter = (0, _asyncToGenerator2["default"])( | ||
/*#__PURE__*/ | ||
_regenerator["default"].mark(function _callee6(channelName, readerUuid, paths) { | ||
var pathToSocket, emitter, server; | ||
return _regenerator["default"].wrap(function _callee6$(_context6) { | ||
while (1) { | ||
switch (_context6.prev = _context6.next) { | ||
case 0: | ||
pathToSocket = socketPath(channelName, readerUuid, paths); | ||
emitter = new events.EventEmitter(); | ||
server = net.createServer(function (stream) { | ||
stream.on('end', function () {}); | ||
stream.on('data', function (msg) { | ||
emitter.emit('data', msg.toString()); | ||
}); | ||
}); | ||
_context6.next = 5; | ||
return new Promise(function (resolve, reject) { | ||
server.listen(pathToSocket, function (err, res) { | ||
if (err) reject(err);else resolve(res); | ||
}); | ||
}); | ||
case 10: | ||
_context9.next = 12; | ||
return Promise.all([createSocketEventEmitter(channelName, uuid, paths), createSocketInfoFile(channelName, uuid, paths), refreshReaderClients(state)]); | ||
case 5: | ||
return _context6.abrupt("return", { | ||
path: pathToSocket, | ||
emitter: emitter, | ||
server: server | ||
}); | ||
case 12: | ||
_ref10 = _context9.sent; | ||
_ref11 = (0, _slicedToArray3['default'])(_ref10, 2); | ||
socketEE = _ref11[0]; | ||
infoFilePath = _ref11[1]; | ||
case 6: | ||
case "end": | ||
return _context6.stop(); | ||
} | ||
} | ||
}, _callee6, this); | ||
})); | ||
return _createSocketEventEmitter.apply(this, arguments); | ||
} | ||
state.socketEE = socketEE; | ||
state.infoFilePath = infoFilePath; | ||
function openClientConnection(_x6, _x7) { | ||
return _openClientConnection.apply(this, arguments); | ||
} | ||
/** | ||
* writes the new message to the file-system | ||
* so other readers can find it | ||
* @return {Promise} | ||
*/ | ||
// 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 | ||
var singleOnes = data.split('|'); | ||
singleOnes.filter(function (single) { | ||
return single !== ''; | ||
}).forEach(function (single) { | ||
try { | ||
var obj = JSON.parse(single); | ||
handleMessagePing(state, obj); | ||
} catch (err) { | ||
throw new Error('could not parse data: ' + single); | ||
} | ||
}); | ||
}); | ||
function _openClientConnection() { | ||
_openClientConnection = (0, _asyncToGenerator2["default"])( | ||
/*#__PURE__*/ | ||
_regenerator["default"].mark(function _callee7(channelName, readerUuid) { | ||
var pathToSocket, client; | ||
return _regenerator["default"].wrap(function _callee7$(_context7) { | ||
while (1) { | ||
switch (_context7.prev = _context7.next) { | ||
case 0: | ||
pathToSocket = socketPath(channelName, readerUuid); | ||
client = new net.Socket(); | ||
_context7.next = 4; | ||
return new Promise(function (res) { | ||
client.connect(pathToSocket, res); | ||
}); | ||
return _context9.abrupt('return', state); | ||
case 4: | ||
return _context7.abrupt("return", client); | ||
case 20: | ||
case 'end': | ||
return _context9.stop(); | ||
} | ||
} | ||
}, _callee9, this); | ||
})); | ||
case 5: | ||
case "end": | ||
return _context7.stop(); | ||
} | ||
} | ||
}, _callee7, this); | ||
})); | ||
return _openClientConnection.apply(this, arguments); | ||
} | ||
return function create(_x19) { | ||
return _ref9.apply(this, arguments); | ||
function writeMessage(channelName, readerUuid, messageJson, paths) { | ||
paths = paths || getPaths(channelName); | ||
var time = microSeconds(); | ||
var writeObject = { | ||
uuid: readerUuid, | ||
time: time, | ||
data: messageJson | ||
}; | ||
var token = randomToken(12); | ||
var fileName = time + '_' + readerUuid + '_' + token + '.json'; | ||
var msgPath = path.join(paths.messages, fileName); | ||
return writeFile(msgPath, JSON.stringify(writeObject)).then(function () { | ||
return { | ||
time: time, | ||
uuid: readerUuid, | ||
token: token, | ||
path: msgPath | ||
}; | ||
}(); | ||
}); | ||
} | ||
/** | ||
* when the socket pings, so that we now new messages came, | ||
* run this | ||
* returns the uuids of all readers | ||
* @return {string[]} | ||
*/ | ||
var handleMessagePing = function () { | ||
var _ref12 = (0, _asyncToGenerator3['default'])( /*#__PURE__*/_regenerator2['default'].mark(function _callee10(state, msgObj) { | ||
var messages, useMessages; | ||
return _regenerator2['default'].wrap(function _callee10$(_context10) { | ||
while (1) { | ||
switch (_context10.prev = _context10.next) { | ||
case 0: | ||
if (state.messagesCallback) { | ||
_context10.next = 2; | ||
break; | ||
} | ||
return _context10.abrupt('return'); | ||
case 2: | ||
messages = void 0; | ||
function getReadersUuids(_x8, _x9) { | ||
return _getReadersUuids.apply(this, arguments); | ||
} | ||
if (msgObj) { | ||
_context10.next = 9; | ||
break; | ||
} | ||
function _getReadersUuids() { | ||
_getReadersUuids = (0, _asyncToGenerator2["default"])( | ||
/*#__PURE__*/ | ||
_regenerator["default"].mark(function _callee8(channelName, paths) { | ||
var readersPath, files; | ||
return _regenerator["default"].wrap(function _callee8$(_context8) { | ||
while (1) { | ||
switch (_context8.prev = _context8.next) { | ||
case 0: | ||
paths = paths || getPaths(channelName); | ||
readersPath = paths.readers; | ||
_context8.next = 4; | ||
return readdir(readersPath); | ||
_context10.next = 6; | ||
return getAllMessages(state.channelName, state.paths); | ||
case 4: | ||
files = _context8.sent; | ||
return _context8.abrupt("return", files.map(function (file) { | ||
return file.split('.'); | ||
}).filter(function (split) { | ||
return split[1] === 'json'; | ||
}) // do not scan .socket-files | ||
.map(function (split) { | ||
return split[0]; | ||
})); | ||
case 6: | ||
messages = _context10.sent; | ||
_context10.next = 10; | ||
break; | ||
case 6: | ||
case "end": | ||
return _context8.stop(); | ||
} | ||
} | ||
}, _callee8, this); | ||
})); | ||
return _getReadersUuids.apply(this, arguments); | ||
} | ||
case 9: | ||
// get single message | ||
messages = [getSingleMessage(state.channelName, msgObj, state.paths)]; | ||
function messagePath(_x10, _x11, _x12, _x13) { | ||
return _messagePath.apply(this, arguments); | ||
} | ||
case 10: | ||
useMessages = messages.filter(function (msgObj) { | ||
return _filterMessage(msgObj, state); | ||
}).sort(function (msgObjA, msgObjB) { | ||
return msgObjA.time - msgObjB.time; | ||
}); // sort by time | ||
function _messagePath() { | ||
_messagePath = (0, _asyncToGenerator2["default"])( | ||
/*#__PURE__*/ | ||
_regenerator["default"].mark(function _callee9(channelName, time, token, writerUuid) { | ||
var fileName, msgPath; | ||
return _regenerator["default"].wrap(function _callee9$(_context9) { | ||
while (1) { | ||
switch (_context9.prev = _context9.next) { | ||
case 0: | ||
fileName = time + '_' + writerUuid + '_' + token + '.json'; | ||
msgPath = path.join(getPaths(channelName).messages, fileName); | ||
return _context9.abrupt("return", msgPath); | ||
case 3: | ||
case "end": | ||
return _context9.stop(); | ||
} | ||
} | ||
}, _callee9, this); | ||
})); | ||
return _messagePath.apply(this, arguments); | ||
} | ||
// if no listener or message, so not do anything | ||
function getAllMessages(_x14, _x15) { | ||
return _getAllMessages.apply(this, arguments); | ||
} | ||
if (!(!useMessages.length || !state.messagesCallback)) { | ||
_context10.next = 13; | ||
break; | ||
} | ||
function _getAllMessages() { | ||
_getAllMessages = (0, _asyncToGenerator2["default"])( | ||
/*#__PURE__*/ | ||
_regenerator["default"].mark(function _callee10(channelName, paths) { | ||
var messagesPath, files; | ||
return _regenerator["default"].wrap(function _callee10$(_context10) { | ||
while (1) { | ||
switch (_context10.prev = _context10.next) { | ||
case 0: | ||
paths = paths || getPaths(channelName); | ||
messagesPath = paths.messages; | ||
_context10.next = 4; | ||
return readdir(messagesPath); | ||
return _context10.abrupt('return'); | ||
case 4: | ||
files = _context10.sent; | ||
return _context10.abrupt("return", files.map(function (file) { | ||
var fileName = file.split('.')[0]; | ||
var split = fileName.split('_'); | ||
return { | ||
path: path.join(messagesPath, file), | ||
time: parseInt(split[0]), | ||
senderUuid: split[1], | ||
token: split[2] | ||
}; | ||
})); | ||
case 13: | ||
_context10.next = 15; | ||
return Promise.all(useMessages.map(function (msgObj) { | ||
return readMessage(msgObj).then(function (content) { | ||
return msgObj.content = content; | ||
}); | ||
})); | ||
case 6: | ||
case "end": | ||
return _context10.stop(); | ||
} | ||
} | ||
}, _callee10, this); | ||
})); | ||
return _getAllMessages.apply(this, arguments); | ||
} | ||
case 15: | ||
function getSingleMessage(channelName, msgObj, paths) { | ||
paths = paths || getPaths(channelName); | ||
return { | ||
path: path.join(paths.messages, msgObj.t + '_' + msgObj.u + '_' + msgObj.to + '.json'), | ||
time: msgObj.t, | ||
senderUuid: msgObj.u, | ||
token: msgObj.to | ||
}; | ||
} | ||
useMessages.forEach(function (msgObj) { | ||
state.emittedMessagesIds.add(msgObj.token); | ||
function readMessage(messageObj) { | ||
return readFile(messageObj.path, 'utf8').then(function (content) { | ||
return JSON.parse(content); | ||
}); | ||
} | ||
if (state.messagesCallback) { | ||
// emit to subscribers | ||
state.messagesCallback(msgObj.content.data); | ||
} | ||
}); | ||
function cleanOldMessages(_x16, _x17) { | ||
return _cleanOldMessages.apply(this, arguments); | ||
} | ||
case 16: | ||
case 'end': | ||
return _context10.stop(); | ||
} | ||
} | ||
}, _callee10, this); | ||
})); | ||
function _cleanOldMessages() { | ||
_cleanOldMessages = (0, _asyncToGenerator2["default"])( | ||
/*#__PURE__*/ | ||
_regenerator["default"].mark(function _callee11(messageObjects, ttl) { | ||
var olderThen; | ||
return _regenerator["default"].wrap(function _callee11$(_context11) { | ||
while (1) { | ||
switch (_context11.prev = _context11.next) { | ||
case 0: | ||
olderThen = Date.now() - ttl; | ||
_context11.next = 3; | ||
return Promise.all(messageObjects.filter(function (obj) { | ||
return obj.time / 1000 < olderThen; | ||
}).map(function (obj) { | ||
return unlink(obj.path)["catch"](function () { | ||
return null; | ||
}); | ||
})); | ||
return function handleMessagePing(_x20, _x21) { | ||
return _ref12.apply(this, arguments); | ||
}; | ||
}(); | ||
case 3: | ||
case "end": | ||
return _context11.stop(); | ||
} | ||
} | ||
}, _callee11, this); | ||
})); | ||
return _cleanOldMessages.apply(this, arguments); | ||
} | ||
var type = 'node'; | ||
/** | ||
* ensures that the channelState is connected with all other readers | ||
* @return {Promise<void>} | ||
* creates a new channelState | ||
* @return {Promise<any>} | ||
*/ | ||
function create(_x18) { | ||
return _create.apply(this, arguments); | ||
} | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } | ||
function _create() { | ||
_create = (0, _asyncToGenerator2["default"])( | ||
/*#__PURE__*/ | ||
_regenerator["default"].mark(function _callee12(channelName) { | ||
var options, | ||
time, | ||
paths, | ||
ensureFolderExistsPromise, | ||
uuid, | ||
state, | ||
_ref5, | ||
socketEE, | ||
infoFilePath, | ||
_args12 = arguments; | ||
/** | ||
* this method is used in nodejs-environments. | ||
* The ipc is handled via sockets and file-writes to the tmp-folder | ||
*/ | ||
return _regenerator["default"].wrap(function _callee12$(_context12) { | ||
while (1) { | ||
switch (_context12.prev = _context12.next) { | ||
case 0: | ||
options = _args12.length > 1 && _args12[1] !== undefined ? _args12[1] : {}; | ||
options = fillOptionsWithDefaults(options); | ||
time = microSeconds(); | ||
paths = getPaths(channelName); | ||
ensureFolderExistsPromise = ensureFoldersExist(channelName, paths); | ||
uuid = randomToken(10); | ||
state = { | ||
time: time, | ||
channelName: channelName, | ||
options: options, | ||
uuid: uuid, | ||
paths: paths, | ||
// contains all messages that have been emitted before | ||
emittedMessagesIds: new ObliviousSet(options.node.ttl * 2), | ||
messagesCallbackTime: null, | ||
messagesCallback: null, | ||
// ensures we do not read messages in parrallel | ||
writeBlockPromise: Promise.resolve(), | ||
otherReaderClients: {}, | ||
// ensure if process crashes, everything is cleaned up | ||
removeUnload: unload.add(function () { | ||
return close(state); | ||
}), | ||
closed: false | ||
}; | ||
if (!OTHER_INSTANCES[channelName]) OTHER_INSTANCES[channelName] = []; | ||
OTHER_INSTANCES[channelName].push(state); | ||
_context12.next = 11; | ||
return ensureFolderExistsPromise; | ||
var util = require('util'); | ||
var fs = require('fs'); | ||
var os = require('os'); | ||
var events = require('events'); | ||
var net = require('net'); | ||
var path = require('path'); | ||
var micro = require('nano-time'); | ||
var rimraf = require('rimraf'); | ||
var sha3_224 = require('js-sha3').sha3_224; | ||
var isNode = require('detect-node'); | ||
var unload = require('unload'); | ||
case 11: | ||
_context12.next = 13; | ||
return Promise.all([createSocketEventEmitter(channelName, uuid, paths), createSocketInfoFile(channelName, uuid, paths), refreshReaderClients(state)]); | ||
var fillOptionsWithDefaults = require('../../dist/lib/options.js').fillOptionsWithDefaults; | ||
var ownUtil = require('../../dist/lib/util.js'); | ||
var randomInt = ownUtil.randomInt; | ||
var randomToken = ownUtil.randomToken; | ||
var ObliviousSet = require('../../dist/lib/oblivious-set')['default']; | ||
case 13: | ||
_ref5 = _context12.sent; | ||
socketEE = _ref5[0]; | ||
infoFilePath = _ref5[1]; | ||
state.socketEE = socketEE; | ||
state.infoFilePath = infoFilePath; // when new message comes in, we read it and emit it | ||
/** | ||
* windows sucks, so we have handle windows-type of socket-paths | ||
* @link https://gist.github.com/domenic/2790533#gistcomment-331356 | ||
*/ | ||
function cleanPipeName(str) { | ||
if (process.platform === 'win32' && !str.startsWith('\\\\.\\pipe\\')) { | ||
str = str.replace(/^\//, ''); | ||
str = str.replace(/\//g, '-'); | ||
return '\\\\.\\pipe\\' + str; | ||
} else { | ||
return str; | ||
} | ||
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 | ||
var singleOnes = data.split('|'); | ||
singleOnes.filter(function (single) { | ||
return single !== ''; | ||
}).forEach(function (single) { | ||
try { | ||
var obj = JSON.parse(single); | ||
handleMessagePing(state, obj); | ||
} catch (err) { | ||
throw new Error('could not parse data: ' + single); | ||
} | ||
}); | ||
}); | ||
return _context12.abrupt("return", state); | ||
case 20: | ||
case "end": | ||
return _context12.stop(); | ||
} | ||
} | ||
}, _callee12, this); | ||
})); | ||
return _create.apply(this, arguments); | ||
} | ||
var mkdir = util.promisify(fs.mkdir); | ||
var writeFile = util.promisify(fs.writeFile); | ||
var readFile = util.promisify(fs.readFile); | ||
var unlink = util.promisify(fs.unlink); | ||
var readdir = util.promisify(fs.readdir); | ||
var removeDir = util.promisify(rimraf); | ||
function _filterMessage(msgObj, state) { | ||
if (msgObj.senderUuid === state.uuid) return false; // not send by own | ||
var OTHER_INSTANCES = {}; | ||
var TMP_FOLDER_NAME = 'pubkey.bc'; | ||
var TMP_FOLDER_BASE = path.join(os.tmpdir(), TMP_FOLDER_NAME); | ||
var getPathsCache = new Map(); | ||
if (state.emittedMessagesIds.has(msgObj.token)) return false; // not already emitted | ||
function getPaths(channelName) { | ||
if (!getPathsCache.has(channelName)) { | ||
var channelHash = sha3_224(channelName); // use hash incase of strange characters | ||
/** | ||
* because the lenght 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); | ||
if (!state.messagesCallback) return false; // no listener | ||
var channelPathBase = path.join(TMP_FOLDER_BASE, channelFolder); | ||
var folderPathReaders = path.join(channelPathBase, 'rdrs'); | ||
var folderPathMessages = path.join(channelPathBase, 'messages'); | ||
if (msgObj.time < state.messagesCallbackTime) return false; // not older then onMessageCallback | ||
var ret = { | ||
base: TMP_FOLDER_BASE, | ||
channelBase: channelPathBase, | ||
readers: folderPathReaders, | ||
messages: folderPathMessages | ||
}; | ||
getPathsCache.set(channelName, ret); | ||
return ret; | ||
} | ||
return getPathsCache.get(channelName); | ||
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 | ||
*/ | ||
var ENSURE_BASE_FOLDER_EXISTS_PROMISE = null; | ||
function socketPath(channelName, readerUuid, paths) { | ||
paths = paths || getPaths(channelName); | ||
var socketPath = path.join(paths.readers, readerUuid + '.s'); | ||
return cleanPipeName(socketPath); | ||
function handleMessagePing(_x19, _x20) { | ||
return _handleMessagePing.apply(this, arguments); | ||
} | ||
function socketInfoPath(channelName, readerUuid, paths) { | ||
paths = paths || getPaths(channelName); | ||
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 | ||
* ensures that the channelState is connected with all other readers | ||
* @return {Promise<void>} | ||
*/ | ||
function createSocketInfoFile(channelName, readerUuid, paths) { | ||
var pathToFile = socketInfoPath(channelName, readerUuid, paths); | ||
return writeFile(pathToFile, JSON.stringify({ | ||
time: microSeconds() | ||
})).then(function () { | ||
return pathToFile; | ||
}); | ||
}function writeMessage(channelName, readerUuid, messageJson, paths) { | ||
paths = paths || getPaths(channelName); | ||
var time = microSeconds(); | ||
var writeObject = { | ||
uuid: readerUuid, | ||
time: time, | ||
data: messageJson | ||
}; | ||
var token = randomToken(12); | ||
var fileName = time + '_' + readerUuid + '_' + token + '.json'; | ||
var msgPath = path.join(paths.messages, fileName); | ||
function _handleMessagePing() { | ||
_handleMessagePing = (0, _asyncToGenerator2["default"])( | ||
/*#__PURE__*/ | ||
_regenerator["default"].mark(function _callee13(state, msgObj) { | ||
var messages, useMessages; | ||
return _regenerator["default"].wrap(function _callee13$(_context13) { | ||
while (1) { | ||
switch (_context13.prev = _context13.next) { | ||
case 0: | ||
if (state.messagesCallback) { | ||
_context13.next = 2; | ||
break; | ||
} | ||
return writeFile(msgPath, JSON.stringify(writeObject)).then(function () { | ||
return { | ||
time: time, | ||
uuid: readerUuid, | ||
token: token, | ||
path: msgPath | ||
}; | ||
}); | ||
} | ||
return _context13.abrupt("return"); | ||
function getSingleMessage(channelName, msgObj, paths) { | ||
paths = paths || getPaths(channelName); | ||
case 2: | ||
if (msgObj) { | ||
_context13.next = 8; | ||
break; | ||
} | ||
return { | ||
path: path.join(paths.messages, msgObj.t + '_' + msgObj.u + '_' + msgObj.to + '.json'), | ||
time: msgObj.t, | ||
senderUuid: msgObj.u, | ||
token: msgObj.to | ||
}; | ||
} | ||
_context13.next = 5; | ||
return getAllMessages(state.channelName, state.paths); | ||
function readMessage(messageObj) { | ||
return readFile(messageObj.path, 'utf8').then(function (content) { | ||
return JSON.parse(content); | ||
}); | ||
} | ||
case 5: | ||
messages = _context13.sent; | ||
_context13.next = 9; | ||
break; | ||
var type = 'node'; | ||
case 8: | ||
// get single message | ||
messages = [getSingleMessage(state.channelName, msgObj, state.paths)]; | ||
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 | ||
case 9: | ||
useMessages = messages.filter(function (msgObj) { | ||
return _filterMessage(msgObj, state); | ||
}).sort(function (msgObjA, msgObjB) { | ||
return msgObjA.time - msgObjB.time; | ||
}); // sort by time | ||
// if no listener or message, so not do anything | ||
state.emittedMessagesIds.add(msgObj.token); | ||
return true; | ||
}function refreshReaderClients(channelState) { | ||
var _this = this; | ||
if (!(!useMessages.length || !state.messagesCallback)) { | ||
_context13.next = 12; | ||
break; | ||
} | ||
return getReadersUuids(channelState.channelName, channelState.paths).then(function (otherReaders) { | ||
// remove subscriptions to closed readers | ||
Object.keys(channelState.otherReaderClients).filter(function (readerUuid) { | ||
return !otherReaders.includes(readerUuid); | ||
}).forEach(function () { | ||
var _ref13 = (0, _asyncToGenerator3['default'])( /*#__PURE__*/_regenerator2['default'].mark(function _callee11(readerUuid) { | ||
return _regenerator2['default'].wrap(function _callee11$(_context11) { | ||
while (1) { | ||
switch (_context11.prev = _context11.next) { | ||
case 0: | ||
_context11.prev = 0; | ||
_context11.next = 3; | ||
return channelState.otherReaderClients[readerUuid].destroy(); | ||
return _context13.abrupt("return"); | ||
case 3: | ||
_context11.next = 7; | ||
break; | ||
case 12: | ||
_context13.next = 14; | ||
return Promise.all(useMessages.map(function (msgObj) { | ||
return readMessage(msgObj).then(function (content) { | ||
return msgObj.content = content; | ||
}); | ||
})); | ||
case 5: | ||
_context11.prev = 5; | ||
_context11.t0 = _context11['catch'](0); | ||
case 14: | ||
useMessages.forEach(function (msgObj) { | ||
state.emittedMessagesIds.add(msgObj.token); | ||
case 7: | ||
delete channelState.otherReaderClients[readerUuid]; | ||
if (state.messagesCallback) { | ||
// emit to subscribers | ||
state.messagesCallback(msgObj.content.data); | ||
} | ||
}); | ||
case 8: | ||
case 'end': | ||
return _context11.stop(); | ||
} | ||
} | ||
}, _callee11, _this, [[0, 5]]); | ||
})); | ||
case 15: | ||
case "end": | ||
return _context13.stop(); | ||
} | ||
} | ||
}, _callee13, this); | ||
})); | ||
return _handleMessagePing.apply(this, arguments); | ||
} | ||
return function (_x22) { | ||
return _ref13.apply(this, arguments); | ||
}; | ||
}()); | ||
function refreshReaderClients(channelState) { | ||
return getReadersUuids(channelState.channelName, channelState.paths).then(function (otherReaders) { | ||
// remove subscriptions to closed readers | ||
Object.keys(channelState.otherReaderClients).filter(function (readerUuid) { | ||
return !otherReaders.includes(readerUuid); | ||
}).forEach( | ||
/*#__PURE__*/ | ||
function () { | ||
var _ref = (0, _asyncToGenerator2["default"])( | ||
/*#__PURE__*/ | ||
_regenerator["default"].mark(function _callee(readerUuid) { | ||
return _regenerator["default"].wrap(function _callee$(_context) { | ||
while (1) { | ||
switch (_context.prev = _context.next) { | ||
case 0: | ||
_context.prev = 0; | ||
_context.next = 3; | ||
return channelState.otherReaderClients[readerUuid].destroy(); | ||
// add new readers | ||
return Promise.all(otherReaders.filter(function (readerUuid) { | ||
return readerUuid !== channelState.uuid; | ||
}) // not own | ||
.filter(function (readerUuid) { | ||
return !channelState.otherReaderClients[readerUuid]; | ||
}) // not already has client | ||
.map(function () { | ||
var _ref14 = (0, _asyncToGenerator3['default'])( /*#__PURE__*/_regenerator2['default'].mark(function _callee12(readerUuid) { | ||
var client; | ||
return _regenerator2['default'].wrap(function _callee12$(_context12) { | ||
while (1) { | ||
switch (_context12.prev = _context12.next) { | ||
case 0: | ||
_context12.prev = 0; | ||
case 3: | ||
_context.next = 7; | ||
break; | ||
if (!channelState.closed) { | ||
_context12.next = 3; | ||
break; | ||
} | ||
case 5: | ||
_context.prev = 5; | ||
_context.t0 = _context["catch"](0); | ||
return _context12.abrupt('return'); | ||
case 7: | ||
delete channelState.otherReaderClients[readerUuid]; | ||
case 3: | ||
_context12.next = 5; | ||
return openClientConnection(channelState.channelName, readerUuid); | ||
case 8: | ||
case "end": | ||
return _context.stop(); | ||
} | ||
} | ||
}, _callee, this, [[0, 5]]); | ||
})); | ||
case 5: | ||
client = _context12.sent; | ||
return function (_x21) { | ||
return _ref.apply(this, arguments); | ||
}; | ||
}()); // add new readers | ||
channelState.otherReaderClients[readerUuid] = client; | ||
_context12.next = 11; | ||
break; | ||
return Promise.all(otherReaders.filter(function (readerUuid) { | ||
return readerUuid !== channelState.uuid; | ||
}) // not own | ||
.filter(function (readerUuid) { | ||
return !channelState.otherReaderClients[readerUuid]; | ||
}) // not already has client | ||
.map( | ||
/*#__PURE__*/ | ||
function () { | ||
var _ref2 = (0, _asyncToGenerator2["default"])( | ||
/*#__PURE__*/ | ||
_regenerator["default"].mark(function _callee2(readerUuid) { | ||
var client; | ||
return _regenerator["default"].wrap(function _callee2$(_context2) { | ||
while (1) { | ||
switch (_context2.prev = _context2.next) { | ||
case 0: | ||
_context2.prev = 0; | ||
case 9: | ||
_context12.prev = 9; | ||
_context12.t0 = _context12['catch'](0); | ||
if (!channelState.closed) { | ||
_context2.next = 3; | ||
break; | ||
} | ||
case 11: | ||
case 'end': | ||
return _context12.stop(); | ||
} | ||
} | ||
}, _callee12, _this, [[0, 9]]); | ||
})); | ||
return _context2.abrupt("return"); | ||
return function (_x23) { | ||
return _ref14.apply(this, arguments); | ||
}; | ||
}() | ||
// this might throw if the other channel is closed at the same time when this one is running refresh | ||
// so we do not throw an error | ||
)); | ||
}); | ||
case 3: | ||
_context2.next = 5; | ||
return openClientConnection(channelState.channelName, readerUuid); | ||
case 5: | ||
client = _context2.sent; | ||
channelState.otherReaderClients[readerUuid] = client; | ||
_context2.next = 11; | ||
break; | ||
case 9: | ||
_context2.prev = 9; | ||
_context2.t0 = _context2["catch"](0); | ||
case 11: | ||
case "end": | ||
return _context2.stop(); | ||
} | ||
} | ||
}, _callee2, this, [[0, 9]]); | ||
})); | ||
return function (_x22) { | ||
return _ref2.apply(this, arguments); | ||
}; | ||
}())); | ||
}); | ||
} | ||
/** | ||
@@ -761,64 +795,63 @@ * post a message to the other readers | ||
*/ | ||
function postMessage(channelState, messageJson) { | ||
var _this2 = this; | ||
var writePromise = writeMessage(channelState.channelName, channelState.uuid, messageJson, channelState.paths); | ||
channelState.writeBlockPromise = channelState.writeBlockPromise.then((0, _asyncToGenerator3['default'])( /*#__PURE__*/_regenerator2['default'].mark(function _callee13() { | ||
var _ref16, _ref17, msgObj, pingStr, writeToReadersPromise; | ||
return _regenerator2['default'].wrap(function _callee13$(_context13) { | ||
while (1) { | ||
switch (_context13.prev = _context13.next) { | ||
case 0: | ||
_context13.next = 2; | ||
return new Promise(function (res) { | ||
return setTimeout(res, 0); | ||
}); | ||
function postMessage(channelState, messageJson) { | ||
var writePromise = writeMessage(channelState.channelName, channelState.uuid, messageJson, channelState.paths); | ||
channelState.writeBlockPromise = channelState.writeBlockPromise.then( | ||
/*#__PURE__*/ | ||
(0, _asyncToGenerator2["default"])( | ||
/*#__PURE__*/ | ||
_regenerator["default"].mark(function _callee3() { | ||
var _ref4, msgObj, pingStr, writeToReadersPromise; | ||
case 2: | ||
_context13.next = 4; | ||
return Promise.all([writePromise, refreshReaderClients(channelState)]); | ||
return _regenerator["default"].wrap(function _callee3$(_context3) { | ||
while (1) { | ||
switch (_context3.prev = _context3.next) { | ||
case 0: | ||
_context3.next = 2; | ||
return new Promise(function (res) { | ||
return setTimeout(res, 0); | ||
}); | ||
case 4: | ||
_ref16 = _context13.sent; | ||
_ref17 = (0, _slicedToArray3['default'])(_ref16, 1); | ||
msgObj = _ref17[0]; | ||
case 2: | ||
_context3.next = 4; | ||
return Promise.all([writePromise, refreshReaderClients(channelState)]); | ||
emitOverFastPath(channelState, msgObj, messageJson); | ||
pingStr = '{"t":' + msgObj.time + ',"u":"' + msgObj.uuid + '","to":"' + msgObj.token + '"}|'; | ||
writeToReadersPromise = Promise.all(Object.values(channelState.otherReaderClients).filter(function (client) { | ||
return client.writable; | ||
}) // client might have closed in between | ||
.map(function (client) { | ||
return new Promise(function (res) { | ||
client.write(pingStr, res); | ||
}); | ||
})); | ||
case 4: | ||
_ref4 = _context3.sent; | ||
msgObj = _ref4[0]; | ||
emitOverFastPath(channelState, msgObj, messageJson); | ||
pingStr = '{"t":' + msgObj.time + ',"u":"' + msgObj.uuid + '","to":"' + msgObj.token + '"}|'; | ||
writeToReadersPromise = Promise.all(Object.values(channelState.otherReaderClients).filter(function (client) { | ||
return client.writable; | ||
}) // client might have closed in between | ||
.map(function (client) { | ||
return new Promise(function (res) { | ||
client.write(pingStr, res); | ||
}); | ||
})); | ||
/** | ||
* clean up old messages | ||
* to not waste resources on cleaning up, | ||
* only if random-int matches, we clean up old messages | ||
*/ | ||
/** | ||
* clean up old messages | ||
* 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); | ||
}); | ||
} | ||
if (randomInt(0, 20) === 0) { | ||
/* await */ | ||
getAllMessages(channelState.channelName, channelState.paths).then(function (allMessages) { | ||
return cleanOldMessages(allMessages, channelState.options.node.ttl); | ||
}); | ||
} | ||
return _context3.abrupt("return", writeToReadersPromise); | ||
return _context13.abrupt('return', writeToReadersPromise); | ||
case 12: | ||
case 'end': | ||
return _context13.stop(); | ||
} | ||
} | ||
}, _callee13, _this2); | ||
}))); | ||
return channelState.writeBlockPromise; | ||
case 11: | ||
case "end": | ||
return _context3.stop(); | ||
} | ||
} | ||
}, _callee3, this); | ||
}))); | ||
return channelState.writeBlockPromise; | ||
} | ||
/** | ||
@@ -830,101 +863,99 @@ * When multiple BroadcastChannels with the same name | ||
*/ | ||
function emitOverFastPath(state, msgObj, messageJson) { | ||
if (!state.options.node.useFastPath) return; // disabled | ||
var others = OTHER_INSTANCES[state.channelName].filter(function (s) { | ||
return s !== state; | ||
}); | ||
if (!state.options.node.useFastPath) return; // disabled | ||
var checkObj = { | ||
time: msgObj.time, | ||
senderUuid: msgObj.uuid, | ||
token: msgObj.token | ||
}; | ||
others.filter(function (otherState) { | ||
return _filterMessage(checkObj, otherState); | ||
}).forEach(function (otherState) { | ||
otherState.messagesCallback(messageJson); | ||
}); | ||
var others = OTHER_INSTANCES[state.channelName].filter(function (s) { | ||
return s !== state; | ||
}); | ||
var checkObj = { | ||
time: msgObj.time, | ||
senderUuid: msgObj.uuid, | ||
token: msgObj.token | ||
}; | ||
others.filter(function (otherState) { | ||
return _filterMessage(checkObj, otherState); | ||
}).forEach(function (otherState) { | ||
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); | ||
var time = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : microSeconds(); | ||
channelState.messagesCallbackTime = time; | ||
channelState.messagesCallback = fn; | ||
handleMessagePing(channelState); | ||
} | ||
function close(channelState) { | ||
if (channelState.closed) return; | ||
channelState.closed = true; | ||
channelState.emittedMessagesIds.clear(); | ||
OTHER_INSTANCES[channelState.channelName] = OTHER_INSTANCES[channelState.channelName].filter(function (o) { | ||
return o !== channelState; | ||
}); | ||
if (channelState.closed) return; | ||
channelState.closed = true; | ||
channelState.emittedMessagesIds.clear(); | ||
OTHER_INSTANCES[channelState.channelName] = OTHER_INSTANCES[channelState.channelName].filter(function (o) { | ||
return o !== channelState; | ||
}); | ||
if (channelState.removeUnload) { | ||
channelState.removeUnload.remove(); | ||
} | ||
if (channelState.removeUnload) { | ||
channelState.removeUnload.remove(); | ||
} | ||
/** | ||
* the server get closed lazy because others might still write on it | ||
* and have not found out that the infoFile was deleted | ||
*/ | ||
/** | ||
* the server get closed lazy because others might still write on it | ||
* and have not found out that the infoFile was deleted | ||
*/ | ||
setTimeout(function () { | ||
return channelState.socketEE.server.close(); | ||
}, 200); | ||
channelState.socketEE.emitter.removeAllListeners(); | ||
Object.values(channelState.otherReaderClients).forEach(function (client) { | ||
return client.destroy(); | ||
}); | ||
unlink(channelState.infoFilePath)['catch'](function () { | ||
return null; | ||
}); | ||
setTimeout(function () { | ||
return channelState.socketEE.server.close(); | ||
}, 200); | ||
channelState.socketEE.emitter.removeAllListeners(); | ||
Object.values(channelState.otherReaderClients).forEach(function (client) { | ||
return client.destroy(); | ||
}); | ||
unlink(channelState.infoFilePath)["catch"](function () { | ||
return null; | ||
}); | ||
} | ||
function canBeUsed() { | ||
return isNode; | ||
return isNode; | ||
} | ||
function averageResponseTime() { | ||
return 50; | ||
return 50; | ||
} | ||
function microSeconds() { | ||
return parseInt(micro.microseconds()); | ||
return parseInt(micro.microseconds()); | ||
} | ||
module.exports = { | ||
cleanPipeName: cleanPipeName, | ||
getPaths: getPaths, | ||
ensureFoldersExist: ensureFoldersExist, | ||
clearNodeFolder: clearNodeFolder, | ||
socketPath: socketPath, | ||
socketInfoPath: socketInfoPath, | ||
createSocketInfoFile: createSocketInfoFile, | ||
createSocketEventEmitter: createSocketEventEmitter, | ||
openClientConnection: openClientConnection, | ||
writeMessage: writeMessage, | ||
getReadersUuids: getReadersUuids, | ||
messagePath: messagePath, | ||
getAllMessages: getAllMessages, | ||
getSingleMessage: getSingleMessage, | ||
readMessage: readMessage, | ||
cleanOldMessages: cleanOldMessages, | ||
type: type, | ||
create: create, | ||
_filterMessage: _filterMessage, | ||
handleMessagePing: handleMessagePing, | ||
refreshReaderClients: refreshReaderClients, | ||
postMessage: postMessage, | ||
emitOverFastPath: emitOverFastPath, | ||
onMessage: onMessage, | ||
close: close, | ||
canBeUsed: canBeUsed, | ||
averageResponseTime: averageResponseTime, | ||
microSeconds: microSeconds | ||
cleanPipeName: cleanPipeName, | ||
getPaths: getPaths, | ||
ensureFoldersExist: ensureFoldersExist, | ||
clearNodeFolder: clearNodeFolder, | ||
socketPath: socketPath, | ||
socketInfoPath: socketInfoPath, | ||
createSocketInfoFile: createSocketInfoFile, | ||
createSocketEventEmitter: createSocketEventEmitter, | ||
openClientConnection: openClientConnection, | ||
writeMessage: writeMessage, | ||
getReadersUuids: getReadersUuids, | ||
messagePath: messagePath, | ||
getAllMessages: getAllMessages, | ||
getSingleMessage: getSingleMessage, | ||
readMessage: readMessage, | ||
cleanOldMessages: cleanOldMessages, | ||
type: type, | ||
create: create, | ||
_filterMessage: _filterMessage, | ||
handleMessagePing: handleMessagePing, | ||
refreshReaderClients: refreshReaderClients, | ||
postMessage: postMessage, | ||
emitOverFastPath: emitOverFastPath, | ||
onMessage: onMessage, | ||
close: close, | ||
canBeUsed: canBeUsed, | ||
averageResponseTime: averageResponseTime, | ||
microSeconds: microSeconds | ||
}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
value: true | ||
}); | ||
exports["default"] = void 0; | ||
/** | ||
@@ -10,44 +12,46 @@ * | ||
*/ | ||
var ObliviousSet = function ObliviousSet(ttl) { | ||
this.ttl = ttl; | ||
this.set = new Set(); | ||
this.timeMap = new Map(); | ||
this.has = this.set.has.bind(this.set); | ||
this.ttl = ttl; | ||
this.set = new Set(); | ||
this.timeMap = new Map(); | ||
this.has = this.set.has.bind(this.set); | ||
}; | ||
ObliviousSet.prototype = { | ||
_removeTooOldValues: function _removeTooOldValues() { | ||
var olderThen = now() - this.ttl; | ||
var iterator = this.set[Symbol.iterator](); | ||
_removeTooOldValues: function _removeTooOldValues() { | ||
var olderThen = now() - this.ttl; | ||
var iterator = this.set[Symbol.iterator](); | ||
while (true) { | ||
var value = iterator.next().value; | ||
if (!value) return; // no more elements | ||
var time = this.timeMap.get(value); | ||
if (time < olderThen) { | ||
this.timeMap["delete"](value); | ||
this.set["delete"](value); | ||
} else { | ||
// we reached a value that is not old enough | ||
return; | ||
} | ||
} | ||
}, | ||
add: function add(value) { | ||
this.timeMap.set(value, now()); | ||
this.set.add(value); | ||
this._removeTooOldValues(); | ||
}, | ||
clear: function clear() { | ||
this.set.clear(); | ||
this.timeMap.clear(); | ||
while (true) { | ||
var value = iterator.next().value; | ||
if (!value) return; // no more elements | ||
var time = this.timeMap.get(value); | ||
if (time < olderThen) { | ||
this.timeMap["delete"](value); | ||
this.set["delete"](value); | ||
} else { | ||
// we reached a value that is not old enough | ||
return; | ||
} | ||
} | ||
}, | ||
add: function add(value) { | ||
this.timeMap.set(value, now()); | ||
this.set.add(value); | ||
this._removeTooOldValues(); | ||
}, | ||
clear: function clear() { | ||
this.set.clear(); | ||
this.timeMap.clear(); | ||
} | ||
}; | ||
function now() { | ||
return new Date().getTime(); | ||
return new Date().getTime(); | ||
} | ||
exports["default"] = ObliviousSet; | ||
var _default = ObliviousSet; | ||
exports["default"] = _default; |
@@ -1,30 +0,27 @@ | ||
'use strict'; | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
value: true | ||
}); | ||
exports.fillOptionsWithDefaults = fillOptionsWithDefaults; | ||
function fillOptionsWithDefaults(options) { | ||
if (!options) options = {}; | ||
options = JSON.parse(JSON.stringify(options)); | ||
if (!options) options = {}; | ||
options = JSON.parse(JSON.stringify(options)); // main | ||
// main | ||
if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true; | ||
if (typeof options.webWorkerSupport === 'undefined') options.webWorkerSupport = true; // indexed-db | ||
// 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; | ||
if (!options.idb) options.idb = {}; // after this time the messages get deleted | ||
// localstorage | ||
if (!options.localstorage) options.localstorage = {}; | ||
if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60; | ||
if (!options.idb.ttl) options.idb.ttl = 1000 * 45; | ||
if (!options.idb.fallbackInterval) options.idb.fallbackInterval = 150; // localstorage | ||
// 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; | ||
if (!options.localstorage) options.localstorage = {}; | ||
if (!options.localstorage.removeTimeout) options.localstorage.removeTimeout = 1000 * 60; // node | ||
return options; | ||
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; | ||
} |
@@ -1,5 +0,5 @@ | ||
'use strict'; | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
value: true | ||
}); | ||
@@ -11,2 +11,3 @@ exports.isPromise = isPromise; | ||
exports.microSeconds = microSeconds; | ||
/** | ||
@@ -16,31 +17,34 @@ * returns true if the given object is a promise | ||
function isPromise(obj) { | ||
if (obj && typeof obj.then === 'function') { | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
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); | ||
}); | ||
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); | ||
return Math.floor(Math.random() * (max - min + 1) + min); | ||
} | ||
/** | ||
* https://stackoverflow.com/a/1349426/3443137 | ||
*/ | ||
function randomToken(length) { | ||
if (!length) length = 5; | ||
var text = ''; | ||
var possible = 'abcdefghijklmnopqrstuvwxzy0123456789'; | ||
if (!length) length = 5; | ||
var text = ''; | ||
var possible = 'abcdefghijklmnopqrstuvwxzy0123456789'; | ||
for (var i = 0; i < length; i++) { | ||
text += possible.charAt(Math.floor(Math.random() * possible.length)); | ||
}return text; | ||
for (var i = 0; i < length; i++) { | ||
text += possible.charAt(Math.floor(Math.random() * possible.length)); | ||
} | ||
return text; | ||
} | ||
@@ -50,3 +54,2 @@ | ||
var additional = 0; | ||
/** | ||
@@ -59,12 +62,14 @@ * returns the current time in micro-seconds, | ||
*/ | ||
function microSeconds() { | ||
var ms = new Date().getTime(); | ||
if (ms === lastMs) { | ||
additional++; | ||
return ms * 1000 + additional; | ||
} else { | ||
lastMs = ms; | ||
additional = 0; | ||
return ms * 1000; | ||
} | ||
var ms = new Date().getTime(); | ||
if (ms === lastMs) { | ||
additional++; | ||
return ms * 1000 + additional; | ||
} else { | ||
lastMs = ms; | ||
additional = 0; | ||
return ms * 1000; | ||
} | ||
} |
{ | ||
"name": "broadcast-channel", | ||
"version": "2.1.0", | ||
"version": "2.1.1", | ||
"description": "A BroadcastChannel implementation that works with new browsers, older browsers and Node.js", | ||
@@ -46,4 +46,4 @@ "homepage": "https://github.com/pubkey/broadcast-channel#readme", | ||
"build:es6": "rimraf -rf dist/es && cross-env NODE_ENV=es6 babel src --out-dir dist/es", | ||
"build:es5": "cross-env NODE_ENV=es5 node node_modules/babel-cli/bin/babel.js src --out-dir dist/lib", | ||
"build:test": "cross-env NODE_ENV=es5 node node_modules/babel-cli/bin/babel.js test --out-dir test_tmp", | ||
"build:es5": "cross-env NODE_ENV=es5 babel src --out-dir dist/lib", | ||
"build:test": "cross-env NODE_ENV=es5 babel test --out-dir test_tmp", | ||
"build:browser": "browserify test_tmp/scripts/index.js > docs/index.js", | ||
@@ -62,3 +62,3 @@ "build:worker": "browserify test_tmp/scripts/worker.js > docs/worker.js", | ||
"dependencies": { | ||
"babel-runtime": "6.26.0", | ||
"@babel/runtime": "7.0.0", | ||
"custom-idle-queue": "2.0.1", | ||
@@ -73,26 +73,15 @@ "detect-node": "2.0.4", | ||
"devDependencies": { | ||
"@babel/cli": "7.0.0", | ||
"@babel/core": "7.0.0", | ||
"@babel/types": "7.0.0-beta.51", | ||
"@babel/plugin-check-constants": "7.0.0-beta.38", | ||
"@babel/plugin-proposal-object-rest-spread": "7.0.0", | ||
"@babel/plugin-transform-member-expression-literals": "7.0.0", | ||
"@babel/plugin-transform-property-literals": "7.0.0", | ||
"@babel/plugin-transform-runtime": "7.0.0", | ||
"@babel/polyfill": "7.0.0", | ||
"@babel/preset-env": "7.0.0", | ||
"@babel/types": "7.0.0", | ||
"@types/core-js": "2.5.0", | ||
"assert": "1.4.1", | ||
"async-test-util": "1.6.1", | ||
"babel-cli": "6.26.0", | ||
"babel-core": "6.26.3", | ||
"babel-loader": "8.0.2", | ||
"babel-plugin-transform-async-to-generator": "6.24.1", | ||
"babel-plugin-transform-class-properties": "6.24.1", | ||
"babel-plugin-transform-es2015-block-scoping": "6.26.0", | ||
"babel-plugin-transform-es2015-constants": "6.1.4", | ||
"babel-plugin-transform-es3-member-expression-literals": "6.22.0", | ||
"babel-plugin-transform-es3-property-literals": "6.22.0", | ||
"babel-plugin-transform-object-rest-spread": "6.26.0", | ||
"babel-plugin-transform-regenerator": "6.26.0", | ||
"babel-plugin-transform-runtime": "6.23.0", | ||
"babel-polyfill": "6.26.0", | ||
"babel-preset-es2015": "6.24.1", | ||
"babel-preset-es2015-native-modules": "6.9.4", | ||
"babel-preset-es2015-rollup": "3.0.0", | ||
"babel-preset-es2016": "6.24.1", | ||
"babel-preset-es2017": "6.24.1", | ||
"babel-preset-latest": "6.24.1", | ||
"browserify": "16.2.2", | ||
@@ -103,3 +92,3 @@ "child-process-promise": "2.2.1", | ||
"convert-hrtime": "2.0.0", | ||
"copyfiles": "2.0.0", | ||
"copyfiles": "2.1.0", | ||
"cross-env": "5.2.0", | ||
@@ -126,3 +115,3 @@ "eslint": "5.5.0", | ||
"random-token": "0.0.8", | ||
"testcafe": "0.21.1", | ||
"testcafe": "0.22.0", | ||
"testcafe-hammerhead": "14.2.6", | ||
@@ -129,0 +118,0 @@ "ts-node": "7.0.1", |
@@ -20,11 +20,16 @@ import { | ||
this._isListening = false; | ||
// isListening | ||
this._iL = false; | ||
/** | ||
* _onMessageListener | ||
* setting onmessage twice, | ||
* will overwrite the first listener | ||
*/ | ||
this._onMessageListener = null; | ||
this._onML = null; | ||
this._addEventListeners = { | ||
/** | ||
* _addEventListeners | ||
*/ | ||
this._addEL = { | ||
message: [], | ||
@@ -35,8 +40,12 @@ internal: [] | ||
/** | ||
* _beforeClose | ||
* array of promises that will be awaited | ||
* before the channel is closed | ||
*/ | ||
this._beforeClose = []; | ||
this._befC = []; | ||
this._preparePromise = null; | ||
/** | ||
* _preparePromise | ||
*/ | ||
this._prepP = null; | ||
_prepareChannel(this); | ||
@@ -70,18 +79,2 @@ }; | ||
BroadcastChannel.prototype = { | ||
_post(type, msg) { | ||
const time = this.method.microSeconds(); | ||
const msgObj = { | ||
time, | ||
type, | ||
data: msg | ||
}; | ||
const awaitPrepare = this._preparePromise ? this._preparePromise : Promise.resolve(); | ||
return awaitPrepare.then(() => { | ||
return this.method.postMessage( | ||
this._state, | ||
msgObj | ||
); | ||
}); | ||
}, | ||
postMessage(msg) { | ||
@@ -94,6 +87,6 @@ if (this.closed) { | ||
} | ||
return this._post('message', msg); | ||
return _post(this, 'message', msg); | ||
}, | ||
postInternal(msg) { | ||
return this._post('internal', msg); | ||
return _post(this, 'internal', msg); | ||
}, | ||
@@ -106,8 +99,8 @@ set onmessage(fn) { | ||
}; | ||
_removeListenerObject(this, 'message', this._onMessageListener); | ||
_removeListenerObject(this, 'message', this._onML); | ||
if (fn && typeof fn === 'function') { | ||
this._onMessageListener = listenObj; | ||
this._onML = listenObj; | ||
_addListenerObject(this, 'message', listenObj); | ||
} else { | ||
this._onMessageListener = null; | ||
this._onML = null; | ||
} | ||
@@ -125,3 +118,3 @@ }, | ||
removeEventListener(type, fn) { | ||
const obj = this._addEventListeners[type].find(obj => obj.fn === fn); | ||
const obj = this._addEL[type].find(obj => obj.fn === fn); | ||
_removeListenerObject(this, type, obj); | ||
@@ -133,9 +126,9 @@ }, | ||
this.closed = true; | ||
const awaitPrepare = this._preparePromise ? this._preparePromise : Promise.resolve(); | ||
const awaitPrepare = this._prepP ? this._prepP : Promise.resolve(); | ||
this._onMessageListener = null; | ||
this._addEventListeners.message = []; | ||
this._onML = null; | ||
this._addEL.message = []; | ||
return awaitPrepare | ||
.then(() => Promise.all(this._beforeClose.map(fn => fn()))) | ||
.then(() => Promise.all(this._befC.map(fn => fn()))) | ||
.then(() => { | ||
@@ -152,6 +145,24 @@ return this.method.close( | ||
function _post(broadcastChannel, type, msg) { | ||
const time = broadcastChannel.method.microSeconds(); | ||
const msgObj = { | ||
time, | ||
type, | ||
data: msg | ||
}; | ||
const awaitPrepare = broadcastChannel._prepP ? broadcastChannel._prepP : Promise.resolve(); | ||
return awaitPrepare.then(() => { | ||
return broadcastChannel.method.postMessage( | ||
broadcastChannel._state, | ||
msgObj | ||
); | ||
}); | ||
} | ||
function _prepareChannel(channel) { | ||
const maybePromise = channel.method.create(channel.name, channel.options); | ||
if (isPromise(maybePromise)) { | ||
channel._preparePromise = maybePromise; | ||
channel._prepP = maybePromise; | ||
maybePromise.then(s => { | ||
@@ -171,4 +182,4 @@ // used in tests to simulate slow runtime | ||
function _hasMessageListeners(channel) { | ||
if (channel._addEventListeners.message.length > 0) return true; | ||
if (channel._addEventListeners.internal.length > 0) return true; | ||
if (channel._addEL.message.length > 0) return true; | ||
if (channel._addEL.internal.length > 0) return true; | ||
return false; | ||
@@ -178,3 +189,3 @@ } | ||
function _addListenerObject(channel, type, obj) { | ||
channel._addEventListeners[type].push(obj); | ||
channel._addEL[type].push(obj); | ||
_startListening(channel); | ||
@@ -184,3 +195,3 @@ } | ||
function _removeListenerObject(channel, type, obj) { | ||
channel._addEventListeners[type] = channel._addEventListeners[type].filter(o => o !== obj); | ||
channel._addEL[type] = channel._addEL[type].filter(o => o !== obj); | ||
_stopListening(channel); | ||
@@ -190,7 +201,7 @@ } | ||
function _startListening(channel) { | ||
if (!channel._isListening && _hasMessageListeners(channel)) { | ||
if (!channel._iL && _hasMessageListeners(channel)) { | ||
// someone is listening, start subscribing | ||
const listenerFn = msgObj => { | ||
channel._addEventListeners[msgObj.type].forEach(obj => { | ||
channel._addEL[msgObj.type].forEach(obj => { | ||
if (msgObj.time >= obj.time) { | ||
@@ -203,5 +214,5 @@ obj.fn(msgObj.data); | ||
const time = channel.method.microSeconds(); | ||
if (channel._preparePromise) { | ||
channel._preparePromise.then(() => { | ||
channel._isListening = true; | ||
if (channel._prepP) { | ||
channel._prepP.then(() => { | ||
channel._iL = true; | ||
channel.method.onMessage( | ||
@@ -214,3 +225,3 @@ channel._state, | ||
} else { | ||
channel._isListening = true; | ||
channel._iL = true; | ||
channel.method.onMessage( | ||
@@ -226,5 +237,5 @@ channel._state, | ||
function _stopListening(channel) { | ||
if (channel._isListening && !_hasMessageListeners(channel)) { | ||
if (channel._iL && !_hasMessageListeners(channel)) { | ||
// noone is listening, stop subscribing | ||
channel._isListening = false; | ||
channel._iL = false; | ||
const time = channel.method.microSeconds(); | ||
@@ -231,0 +242,0 @@ channel.method.onMessage( |
@@ -8,3 +8,3 @@ import { | ||
const LeaderElection = function (channel, options) { | ||
const LeaderElection = function(channel, options) { | ||
this._channel = channel; | ||
@@ -17,13 +17,12 @@ this._options = options; | ||
this._isApplying = false; | ||
this._isApl = false; // _isApplying | ||
this._reApply = false; | ||
// things to clean up | ||
this._unloads = []; | ||
this._listeners = []; | ||
this._intervals = []; | ||
this._unl = []; // _unloads | ||
this._lstns = []; // _listeners | ||
this._invs = []; // _intervals | ||
}; | ||
LeaderElection.prototype = { | ||
applyOnce() { | ||
@@ -34,7 +33,7 @@ if (this.isLeader) return Promise.resolve(false); | ||
// do nothing if already running | ||
if (this._isApplying) { | ||
if (this._isApl) { | ||
this._reApply = true; | ||
return Promise.resolve(false); | ||
} | ||
this._isApplying = true; | ||
this._isApl = true; | ||
@@ -66,7 +65,7 @@ let stopCriteria = false; | ||
const ret = this._sendMessage('apply') // send out that this one is applying | ||
const ret = _sendMessage(this, 'apply') // send out that this one is applying | ||
.then(() => sleep(this._options.responseTime)) // let others time to respond | ||
.then(() => { | ||
if (stopCriteria) return Promise.reject(new Error()); | ||
else return this._sendMessage('apply'); | ||
else return _sendMessage(this, 'apply'); | ||
}) | ||
@@ -76,5 +75,5 @@ .then(() => sleep(this._options.responseTime)) // let others time to respond | ||
if (stopCriteria) return Promise.reject(new Error()); | ||
else return this._sendMessage(); | ||
else return _sendMessage(this); | ||
}) | ||
.then(() => this._beLeader()) // no one disagreed -> this one is now leader | ||
.then(() => _beLeader(this)) // no one disagreed -> this one is now leader | ||
.then(() => true) | ||
@@ -84,3 +83,3 @@ .catch(() => false) // apply not successfull | ||
this._channel.removeEventListener('internal', handleMessage); | ||
this._isApplying = false; | ||
this._isApl = false; | ||
if (!success && this._reApply) { | ||
@@ -94,45 +93,5 @@ this._reApply = false; | ||
_awaitLeadershipOnce() { | ||
if (this.isLeader) return Promise.resolve(); | ||
return new Promise((res) => { | ||
let resolved = false; | ||
const finish = () => { | ||
if (resolved) return; | ||
resolved = true; | ||
clearInterval(interval); | ||
this._channel.removeEventListener('internal', whenDeathListener); | ||
res(true); | ||
}; | ||
// try once now | ||
this.applyOnce().then(() => { | ||
if (this.isLeader) finish(); | ||
}); | ||
// try on fallbackInterval | ||
const interval = setInterval(() => { | ||
this.applyOnce().then(() => { | ||
if (this.isLeader) finish(); | ||
}); | ||
}, this._options.fallbackInterval); | ||
this._intervals.push(interval); | ||
// try when other leader dies | ||
const whenDeathListener = msg => { | ||
if (msg.context === 'leader' && msg.action === 'death') { | ||
this.applyOnce().then(() => { | ||
if (this.isLeader) finish(); | ||
}); | ||
} | ||
}; | ||
this._channel.addEventListener('internal', whenDeathListener); | ||
this._listeners.push(whenDeathListener); | ||
}); | ||
}, | ||
awaitLeadership() { | ||
if (!this._awaitLeadershipPromise) { | ||
this._awaitLeadershipPromise = this._awaitLeadershipOnce(); | ||
this._awaitLeadershipPromise = _awaitLeadershipOnce(this); | ||
} | ||
@@ -146,38 +105,79 @@ return this._awaitLeadershipPromise; | ||
this._listeners.forEach(listener => this._channel.removeEventListener('internal', listener)); | ||
this._intervals.forEach(interval => clearInterval(interval)); | ||
this._unloads.forEach(uFn => { | ||
this._lstns.forEach(listener => this._channel.removeEventListener('internal', listener)); | ||
this._invs.forEach(interval => clearInterval(interval)); | ||
this._unl.forEach(uFn => { | ||
uFn.remove(); | ||
}); | ||
return this._sendMessage('death'); | ||
}, | ||
return _sendMessage(this, 'death'); | ||
} | ||
}; | ||
/** | ||
* sends and internal message over the broadcast-channel | ||
*/ | ||
_sendMessage(action) { | ||
const msgJson = { | ||
context: 'leader', | ||
action, | ||
token: this.token | ||
function _awaitLeadershipOnce(leaderElector) { | ||
if (leaderElector.isLeader) return Promise.resolve(); | ||
return new Promise((res) => { | ||
let resolved = false; | ||
const finish = () => { | ||
if (resolved) return; | ||
resolved = true; | ||
clearInterval(interval); | ||
leaderElector._channel.removeEventListener('internal', whenDeathListener); | ||
res(true); | ||
}; | ||
return this._channel.postInternal(msgJson); | ||
}, | ||
_beLeader() { | ||
this.isLeader = true; | ||
const unloadFn = unload.add(() => this.die()); | ||
this._unloads.push(unloadFn); | ||
// try once now | ||
leaderElector.applyOnce().then(() => { | ||
if (leaderElector.isLeader) finish(); | ||
}); | ||
const isLeaderListener = msg => { | ||
if (msg.context === 'leader' && msg.action === 'apply') { | ||
this._sendMessage('tell'); | ||
// try on fallbackInterval | ||
const interval = setInterval(() => { | ||
leaderElector.applyOnce().then(() => { | ||
if (leaderElector.isLeader) finish(); | ||
}); | ||
}, leaderElector._options.fallbackInterval); | ||
leaderElector._invs.push(interval); | ||
// try when other leader dies | ||
const whenDeathListener = msg => { | ||
if (msg.context === 'leader' && msg.action === 'death') { | ||
leaderElector.applyOnce().then(() => { | ||
if (leaderElector.isLeader) finish(); | ||
}); | ||
} | ||
}; | ||
this._channel.addEventListener('internal', isLeaderListener); | ||
this._listeners.push(isLeaderListener); | ||
return this._sendMessage('tell'); | ||
} | ||
}; | ||
leaderElector._channel.addEventListener('internal', whenDeathListener); | ||
leaderElector._lstns.push(whenDeathListener); | ||
}); | ||
} | ||
/** | ||
* sends and internal message over the broadcast-channel | ||
*/ | ||
function _sendMessage(leaderElector, action) { | ||
const msgJson = { | ||
context: 'leader', | ||
action, | ||
token: leaderElector.token | ||
}; | ||
return leaderElector._channel.postInternal(msgJson); | ||
} | ||
function _beLeader(leaderElector) { | ||
leaderElector.isLeader = true; | ||
const unloadFn = unload.add(() => leaderElector.die()); | ||
leaderElector._unl.push(unloadFn); | ||
const isLeaderListener = msg => { | ||
if (msg.context === 'leader' && msg.action === 'apply') { | ||
_sendMessage(leaderElector, 'tell'); | ||
} | ||
}; | ||
leaderElector._channel.addEventListener('internal', isLeaderListener); | ||
leaderElector._lstns.push(isLeaderListener); | ||
return _sendMessage(leaderElector, 'tell'); | ||
} | ||
function fillOptionsWithDefaults(options, channel) { | ||
@@ -205,3 +205,3 @@ if (!options) options = {}; | ||
const elector = new LeaderElection(channel, options); | ||
channel._beforeClose.push(() => elector.die()); | ||
channel._befC.push(() => elector.die()); | ||
@@ -215,2 +215,2 @@ channel._leaderElector = elector; | ||
create | ||
}; | ||
}; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
47
49
5298
191024
+ Added@babel/runtime@7.0.0
+ Added@babel/runtime@7.0.0(transitive)
+ Addedregenerator-runtime@0.12.1(transitive)
- Removedbabel-runtime@6.26.0
- Removedbabel-runtime@6.26.0(transitive)
- Removedcore-js@2.6.12(transitive)
- Removedregenerator-runtime@0.11.1(transitive)