@toruslabs/broadcast-channel
Version:
A BroadcastChannel that works in New Browsers, Old Browsers, WebWorkers
195 lines (188 loc) • 5.86 kB
JavaScript
import _defineProperty from '@babel/runtime/helpers/defineProperty';
import { chooseMethod } from './method-chooser.js';
import { fillOptionsWithDefaults } from './options.js';
import { PROMISE_RESOLVED_VOID, isPromise } from './util.js';
let ENFORCED_OPTIONS;
function enforceOptions(options) {
ENFORCED_OPTIONS = options;
}
/**
* Contains all open channels,
* used in tests to ensure everything is closed.
*/
const OPEN_BROADCAST_CHANNELS = new Set();
let lastId = 0;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
class BroadcastChannel {
// beforeClose
constructor(name, options) {
_defineProperty(this, "id", void 0);
_defineProperty(this, "name", void 0);
_defineProperty(this, "options", void 0);
_defineProperty(this, "method", void 0);
_defineProperty(this, "closed", void 0);
_defineProperty(this, "_addEL", void 0);
_defineProperty(this, "_prepP", void 0);
// preparePromise
_defineProperty(this, "_state", void 0);
_defineProperty(this, "_uMP", void 0);
// unsent message promises
_defineProperty(this, "_iL", void 0);
// isListening
_defineProperty(this, "_onML", void 0);
// onMessageListener
_defineProperty(this, "_befC", void 0);
this.id = lastId++;
OPEN_BROADCAST_CHANNELS.add(this);
this.name = name;
if (ENFORCED_OPTIONS) {
options = ENFORCED_OPTIONS;
}
this.options = fillOptionsWithDefaults(options || {});
this.method = chooseMethod(this.options);
this.closed = false;
this._iL = false;
this._onML = null;
this._addEL = {
message: [],
internal: []
};
this._uMP = new Set();
this._befC = [];
this._prepP = null;
_prepareChannel(this);
}
get type() {
return this.method.type;
}
get isClosed() {
return this.closed;
}
set onmessage(fn) {
const time = this.method.microSeconds();
const listenObj = {
time,
fn: fn
};
_removeListenerObject(this, "message", this._onML);
if (fn && typeof fn === "function") {
this._onML = listenObj;
_addListenerObject(this, "message", listenObj);
} else {
this._onML = null;
}
}
postMessage(msg) {
if (this.closed) {
throw new Error(`BroadcastChannel.postMessage(): Cannot post message after channel has closed ${JSON.stringify(msg)}`);
}
return _post(this, "message", msg);
}
postInternal(msg) {
return _post(this, "internal", msg);
}
addEventListener(type, fn) {
const time = this.method.microSeconds();
const listenObj = {
time,
fn: fn
};
_addListenerObject(this, type, listenObj);
}
removeEventListener(type, fn) {
const obj = this._addEL[type].find(o => o.fn === fn);
_removeListenerObject(this, type, obj);
}
close() {
if (this.closed) {
return Promise.resolve();
}
OPEN_BROADCAST_CHANNELS.delete(this);
this.closed = true;
const awaitPrepare = this._prepP ? this._prepP : PROMISE_RESOLVED_VOID;
this._onML = null;
this._addEL.message = [];
return awaitPrepare.then(() => Promise.all(Array.from(this._uMP))).then(() => Promise.all(this._befC.map(fn => fn()))).then(() => this.method.close ? this.method.close(this._state) : PROMISE_RESOLVED_VOID);
}
}
_defineProperty(BroadcastChannel, "_pubkey", true);
function _post(broadcastChannel, type, msg) {
const time = broadcastChannel.method.microSeconds();
const msgObj = {
time,
type,
data: msg
};
const awaitPrepare = broadcastChannel._prepP ? broadcastChannel._prepP : PROMISE_RESOLVED_VOID;
return awaitPrepare.then(() => {
const sendPromise = broadcastChannel.method.postMessage(broadcastChannel._state, msgObj);
broadcastChannel._uMP.add(sendPromise);
// eslint-disable-next-line promise/catch-or-return
sendPromise.catch(() => {}).then(() => broadcastChannel._uMP.delete(sendPromise));
return sendPromise;
});
}
function _prepareChannel(channel) {
const maybePromise = channel.method.create(channel.name, channel.options);
if (isPromise(maybePromise)) {
const promise = maybePromise;
channel._prepP = promise;
promise.then(s => {
channel._state = s;
return s;
}).catch(err => {
throw err;
});
} else {
channel._state = maybePromise;
}
}
function _hasMessageListeners(channel) {
if (channel._addEL.message.length > 0) return true;
if (channel._addEL.internal.length > 0) return true;
return false;
}
function _startListening(channel) {
if (!channel._iL && _hasMessageListeners(channel)) {
const listenerFn = msgObj => {
channel._addEL[msgObj.type].forEach(listenerObject => {
if (msgObj.time >= listenerObject.time) {
listenerObject.fn(msgObj.data);
} else if (channel.method.type === "server") {
listenerObject.fn(msgObj.data);
}
});
};
const time = channel.method.microSeconds();
if (channel._prepP) {
channel._prepP.then(() => {
channel._iL = true;
channel.method.onMessage(channel._state, listenerFn, time);
return true;
}).catch(err => {
throw err;
});
} else {
channel._iL = true;
channel.method.onMessage(channel._state, listenerFn, time);
}
}
}
function _stopListening(channel) {
if (channel._iL && !_hasMessageListeners(channel)) {
channel._iL = false;
const time = channel.method.microSeconds();
channel.method.onMessage(channel._state, null, time);
}
}
function _addListenerObject(channel, type, obj) {
channel._addEL[type].push(obj);
_startListening(channel);
}
function _removeListenerObject(channel, type, obj) {
if (obj) {
channel._addEL[type] = channel._addEL[type].filter(o => o !== obj);
_stopListening(channel);
}
}
export { BroadcastChannel, OPEN_BROADCAST_CHANNELS, enforceOptions };