UNPKG

@toruslabs/broadcast-channel

Version:

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

146 lines (137 loc) 4.86 kB
import { ObliviousSet } from 'oblivious-set'; import { fillOptionsWithDefaults } from '../options.js'; import { microSeconds as microSeconds$1, sleep, generateRandomId } from '../util.js'; /** * A localStorage-only method which uses localstorage and its 'storage'-event * This does not work inside of webworkers because they have no access to locastorage * This is basically implemented to support IE9 or your grandmothers toaster. * @link https://caniuse.com/#feat=namevalue-storage * @link https://caniuse.com/#feat=indexeddb */ const microSeconds = microSeconds$1; const KEY_PREFIX = "pubkey.broadcastChannel-"; const type = "localstorage"; /** * copied from crosstab * @link https://github.com/tejacques/crosstab/blob/master/src/crosstab.js#L32 */ function getLocalStorage() { let localStorage = null; if (typeof window === "undefined") return null; try { localStorage = window.localStorage; localStorage = window["ie8-eventlistener/storage"] || window.localStorage; } catch { // 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; } /** * writes the new message to the storage * and fires the storage-event so other readers can find it */ function postMessage(channelState, messageJson) { return new Promise((resolve, reject) => { sleep().then(() => { var _getLocalStorage; const key = storageKey(channelState.channelName); const writeObj = { token: generateRandomId(), time: Date.now(), data: messageJson, uuid: channelState.uuid }; const value = JSON.stringify(writeObj); // eslint-disable-next-line promise/always-return (_getLocalStorage = getLocalStorage()) === null || _getLocalStorage === void 0 || _getLocalStorage.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 */ const ev = document.createEvent("StorageEvent"); ev.initStorageEvent("storage", true, true, key, null, value, "", null); window.dispatchEvent(ev); resolve(); }).catch(reject); }); } function addStorageEventListener(channelName, fn) { const key = storageKey(channelName); const listener = ev => { if (ev.key === key && ev.newValue) { fn(JSON.parse(ev.newValue)); } }; window.addEventListener("storage", listener); return listener; } function removeStorageEventListener(listener) { window.removeEventListener("storage", listener); } function canBeUsed() { const ls = getLocalStorage(); if (!ls) return false; try { const key = "__broadcastchannel_check"; ls.setItem(key, "works"); ls.removeItem(key); } catch { // Safari 10 in private mode will not allow write access to local // storage and fail with a QuotaExceededError. See // https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API#Private_Browsing_Incognito_modes return false; } return true; } function create(channelName, options) { const filledOptions = fillOptionsWithDefaults(options); if (!canBeUsed()) { throw new Error("BroadcastChannel: localstorage cannot be used"); } const uuid = generateRandomId(); /** * eMIs * contains all messages that have been emitted before */ const eMIs = new ObliviousSet(filledOptions.localstorage.removeTimeout); const state = { channelName, uuid, time: microSeconds$1(), eMIs // emittedMessagesIds }; state.listener = addStorageEventListener(channelName, msgObj => { if (!state.messagesCallback) return; // no listener if (msgObj.uuid === uuid) return; // own message if (!msgObj.token || eMIs.has(msgObj.token)) return; // already emitted if (msgObj.data.time && msgObj.data.time < (state.messagesCallbackTime || 0)) return; // too old eMIs.add(msgObj.token); state.messagesCallback(msgObj.data); }); return state; } function close(channelState) { if (channelState.listener) { removeStorageEventListener(channelState.listener); } } function onMessage(channelState, fn, time) { channelState.messagesCallbackTime = time; channelState.messagesCallback = fn; } function averageResponseTime() { const defaultTime = 120; const userAgent = navigator.userAgent.toLowerCase(); if (userAgent.includes("safari") && !userAgent.includes("chrome")) { // safari is much slower so this time is higher return defaultTime * 2; } return defaultTime; } export { addStorageEventListener, averageResponseTime, canBeUsed, close, create, getLocalStorage, microSeconds, onMessage, postMessage, removeStorageEventListener, storageKey, type };