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