UNPKG

2.59 kBJavaScriptView Raw
1/* eslint-env browser */
2
3/**
4 * Helpers for cross-tab communication using broadcastchannel with LocalStorage fallback.
5 *
6 * ```js
7 * // In browser window A:
8 * broadcastchannel.subscribe('my events', data => console.log(data))
9 * broadcastchannel.publish('my events', 'Hello world!') // => A: 'Hello world!' fires synchronously in same tab
10 *
11 * // In browser window B:
12 * broadcastchannel.publish('my events', 'hello from tab B') // => A: 'hello from tab B'
13 * ```
14 *
15 * @module broadcastchannel
16 */
17
18// @todo before next major: use Uint8Array instead as buffer object
19
20import * as map from './map.js'
21import * as buffer from './buffer.js'
22import * as storage from './storage.js'
23
24/**
25 * @typedef {Object} Channel
26 * @property {Set<function(any, any):any>} Channel.subs
27 * @property {any} Channel.bc
28 */
29
30/**
31 * @type {Map<string, Channel>}
32 */
33const channels = new Map()
34
35class LocalStoragePolyfill {
36 /**
37 * @param {string} room
38 */
39 constructor (room) {
40 this.room = room
41 /**
42 * @type {null|function({data:ArrayBuffer}):void}
43 */
44 this.onmessage = null
45 storage.onChange(e => e.key === room && this.onmessage !== null && this.onmessage({ data: buffer.fromBase64(e.newValue || '') }))
46 }
47
48 /**
49 * @param {ArrayBuffer} buf
50 */
51 postMessage (buf) {
52 storage.varStorage.setItem(this.room, buffer.toBase64(buffer.createUint8ArrayFromArrayBuffer(buf)))
53 }
54}
55
56// Use BroadcastChannel or Polyfill
57const BC = typeof BroadcastChannel === 'undefined' ? LocalStoragePolyfill : BroadcastChannel
58
59/**
60 * @param {string} room
61 * @return {Channel}
62 */
63const getChannel = room =>
64 map.setIfUndefined(channels, room, () => {
65 const subs = new Set()
66 const bc = new BC(room)
67 /**
68 * @param {{data:ArrayBuffer}} e
69 */
70 bc.onmessage = e => subs.forEach(sub => sub(e.data, 'broadcastchannel'))
71 return {
72 bc, subs
73 }
74 })
75
76/**
77 * Subscribe to global `publish` events.
78 *
79 * @function
80 * @param {string} room
81 * @param {function(any, any):any} f
82 */
83export const subscribe = (room, f) => getChannel(room).subs.add(f)
84
85/**
86 * Unsubscribe from `publish` global events.
87 *
88 * @function
89 * @param {string} room
90 * @param {function(any, any):any} f
91 */
92export const unsubscribe = (room, f) => getChannel(room).subs.delete(f)
93
94/**
95 * Publish data to all subscribers (including subscribers on this tab)
96 *
97 * @function
98 * @param {string} room
99 * @param {any} data
100 * @param {any} [origin]
101 */
102export const publish = (room, data, origin = null) => {
103 const c = getChannel(room)
104 c.bc.postMessage(data)
105 c.subs.forEach(sub => sub(data, origin))
106}