UNPKG

@toruslabs/broadcast-channel

Version:

A BroadcastChannel that works in New Browsers, Old Browsers, WebWorkers

195 lines (191 loc) 5.96 kB
'use strict'; var _defineProperty = require('@babel/runtime/helpers/defineProperty'); var methodChooser = require('./method-chooser.js'); var options = require('./options.js'); var util = require('./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$1) { _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$1 = ENFORCED_OPTIONS; } this.options = options.fillOptionsWithDefaults(options$1 || {}); this.method = methodChooser.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 : util.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) : util.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 : util.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 (util.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); } } exports.BroadcastChannel = BroadcastChannel; exports.OPEN_BROADCAST_CHANNELS = OPEN_BROADCAST_CHANNELS; exports.enforceOptions = enforceOptions;