@r_wohl/web-channel-message
Version:
A light weight type-safe library for communicating via the Channel Message Web API
1 lines • 12 kB
Source Map (JSON)
{"version":3,"sources":["../src/simple-subject.ts","../src/shared-web-channel.ts","../src/channel-observer.ts"],"sourcesContent":["import { ChannelObserver } from \"./channel-observer\";\r\nimport { ObserverMessage } from \"./types\";\r\n\r\nclass SimpleSubject {\r\n\tprivate observers: Map<string, ChannelObserver[]> = new Map();\r\n\r\n\tsubscribe(observer: ChannelObserver, key: string = \"default\") {\r\n\t\tif (this.observers.has(key)) {\r\n\t\t\tthis.observers.get(key)?.push(observer);\r\n\t\t} else {\r\n\t\t\tthis.observers.set(key, [observer]);\r\n\t\t}\r\n\t}\r\n\r\n\tunsubscribe(observer: ChannelObserver, key: string = \"default\") {\r\n\t\tconst observersForKey = this.observers.get(key);\r\n\r\n\t\tif (!observersForKey) return;\r\n\r\n\t\tconst observerIndex = observersForKey.indexOf(observer);\r\n\r\n\t\tif (observerIndex === -1) return;\r\n\r\n\t\tobserversForKey.splice(observerIndex, 1);\r\n\t}\r\n\r\n\tupdate(data: ObserverMessage) {\r\n\t\tfor (const [key, observerGroup] of this.observers.entries()) {\r\n\t\t\tif (\r\n\t\t\t\tdata.key === \"all\" ||\r\n\t\t\t\tdata.key === key ||\r\n\t\t\t\t(data.key === undefined && key === \"default\")\r\n\t\t\t) {\r\n\t\t\t\tfor (const observer of observerGroup) {\r\n\t\t\t\t\tobserver.update(data);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\nexport default SimpleSubject;\r\n","import SimpleSubject from \"./simple-subject\";\r\nimport {\r\n\tObserverMessage,\r\n\tConnectionUpdate,\r\n\tMessage,\r\n\tUserMessage,\r\n} from \"./types\";\r\n\r\nconst callbacks: Map<string, (...args: any[]) => any> = new Map();\r\n\r\nlet isSupported: boolean = true;\r\n\r\nexport class SharedWebChannel {\r\n\tpublic worker: SharedWorker | undefined;\r\n\tpublic subject: SimpleSubject;\r\n\tpublic connections: number = 1;\r\n\tprivate connectionsUpdateCallback: ((...args: any[]) => any) | undefined;\r\n\r\n\t/**\r\n\t * Constructs a new SharedWebChannel instance. If you'll need more then one\r\n\t * instance throughout your application, it is recommended to provide a name.\r\n\t *\r\n\t * When omitted, the name will default to \"default-shared-worker\".\r\n\t *\r\n\t */\r\n\tconstructor(name: string = \"default-shared-worker\") {\r\n\t\tthis.subject = new SimpleSubject();\r\n\r\n\t\tif (typeof window == \"undefined\") {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\ttry {\r\n\t\t\tthis.worker = new SharedWorker(\r\n\t\t\t\tnew URL(\"./worker.js\", import.meta.url),\r\n\t\t\t\t{\r\n\t\t\t\t\ttype: \"module\",\r\n\t\t\t\t\tname,\r\n\t\t\t\t}\r\n\t\t\t);\r\n\t\t} catch (e) {\r\n\t\t\tisSupported = false;\r\n\r\n\t\t\tconsole.warn(\r\n\t\t\t\t\"The shared worker module feature doesn't appear to be supported in this environment\"\r\n\t\t\t);\r\n\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tconst forwardUpdate = (data: ObserverMessage) => {\r\n\t\t\tthis.updateObservers(data);\r\n\t\t};\r\n\r\n\t\tconst forwardConnectionUpdate = (data: ConnectionUpdate) => {\r\n\t\t\tthis.connections = data.channelData.connections;\r\n\t\t\tif (this.connectionsUpdateCallback) {\r\n\t\t\t\tthis.connectionsUpdateCallback(data.channelData.connections);\r\n\t\t\t}\r\n\t\t};\r\n\r\n\t\tthis.worker.port.onmessage = function (event: MessageEvent) {\r\n\t\t\tconst receivedMessage = event.data as Message;\r\n\t\t\tconsole.debug(\r\n\t\t\t\t\"message received from shared worker: \",\r\n\t\t\t\treceivedMessage\r\n\t\t\t);\r\n\r\n\t\t\tif (receivedMessage.type === \"callback\") {\r\n\t\t\t\tconst callback = callbacks.get(receivedMessage.callbackKey);\r\n\t\t\t\tif (callback) {\r\n\t\t\t\t\tcallback(receivedMessage.payload);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (receivedMessage.type === \"observer\") {\r\n\t\t\t\tforwardUpdate(receivedMessage);\r\n\t\t\t}\r\n\r\n\t\t\tif (receivedMessage.type === \"internal\") {\r\n\t\t\t\tforwardConnectionUpdate(receivedMessage);\r\n\t\t\t}\r\n\t\t};\r\n\r\n\t\t// for desktop browers\r\n\t\twindow.addEventListener(\"beforeunload\", () => {\r\n\t\t\tthis.terminate();\r\n\t\t});\r\n\r\n\t\t// for iOS Safari\r\n\t\twindow.addEventListener(\"unload\", () => {\r\n\t\t\tthis.terminate();\r\n\t\t});\r\n\r\n\t\t// for iOS/Android in general\r\n\t\twindow.addEventListener(\"pagehide\", () => {\r\n\t\t\tthis.terminate();\r\n\t\t});\r\n\r\n\t\tthis.worker.addEventListener(\"close\", () => {\r\n\t\t\tthis.terminate();\r\n\t\t});\r\n\t}\r\n\r\n\tprivate updateObservers(data: ObserverMessage) {\r\n\t\tthis.subject.update(data);\r\n\t}\r\n\r\n\t/**\r\n\t * Sends a `UserMessage` object to the SharedWorker\r\n\t * so it can be forwarded to other active channels.\r\n\t *\r\n\t * @example\r\n\t *\r\n\t * channel.sendMessage({\r\n\t * //type: \"callback\" to trigger a callback function with corresponding callbackKey or \"observer\" to update one or more ChannelObservers.\r\n\t * type: \"callback\",\r\n\t * // action: \"broadcast\" to send to all OTHER app instances or \"all\" to send to all.\r\n\t * action: \"broadcast\",\r\n\t * // payload: optional; in \"callback\" mode this will be your callback's input\r\n\t * payload: \"bg-red-500\",\r\n\t * callbackKey: \"set-bg-color\",\r\n\t *});\r\n\t *\r\n\t */\r\n\tsendMessage(message: UserMessage) {\r\n\t\t// if sharedworkers are not supported, but the application instance from\r\n\t\t// which the message is sent is supposed to execute a callback or update an observer,\r\n\t\t// then execute the corresponding callback/update the corresponding observers\r\n\t\tif (!isSupported) {\r\n\t\t\tconsole.warn(\r\n\t\t\t\t\"The shared worker module feature doesn't appear to be supported in this environment\"\r\n\t\t\t);\r\n\r\n\t\t\tif (message.type === \"callback\" && message.action === \"all\") {\r\n\t\t\t\tconst callback = callbacks.get(message.callbackKey);\r\n\t\t\t\tif (callback) {\r\n\t\t\t\t\tcallback(message.payload);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (message.type === \"observer\" && message.action === \"all\") {\r\n\t\t\t\tthis.updateObservers(message);\r\n\t\t\t}\r\n\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tthis.worker?.port.postMessage(message);\r\n\t}\r\n\r\n\t/**\r\n\t * Registers a callback with a key in a Map object. When a message sent with type `callback`\r\n\t * is received the `SharedWebChannel` will look for a callback with the corresponding\r\n\t * `callbackKey`, and -if found- execute it with the value in `payload` as input.\r\n\t *\r\n\t * @example\r\n\t *\r\n\t * channel.registerCallback(\"set-bg-color\", setBgColor);\r\n\t *\r\n\t */\r\n\tregisterCallback(key: string, callback: (...args: any[]) => any) {\r\n\t\tcallbacks.set(key, callback);\r\n\t}\r\n\r\n\t/**\r\n\t * Registers a callback to be executed when the number of open connections\r\n\t * (the number of open browser session in different tabs/windows) changes.\r\n\t *\r\n\t * @example\r\n\t *\r\n\t * channel.onConnectionsUpdate(setInstances);\r\n\t *\r\n\t */\r\n\tonConnectionsUpdate(callback: (...args: any[]) => any) {\r\n\t\tthis.connectionsUpdateCallback = callback;\r\n\t}\r\n\r\n\tprivate terminate() {\r\n\t\tconsole.debug(\"terminating port connection\");\r\n\t\tthis.worker?.port.postMessage({\r\n\t\t\ttype: \"close\",\r\n\t\t});\r\n\t\tthis.worker?.port.close();\r\n\t\tthis.worker = undefined;\r\n\r\n\t\twindow.removeEventListener(\"beforeunload\", () => {\r\n\t\t\tthis.terminate();\r\n\t\t});\r\n\r\n\t\twindow.removeEventListener(\"unload\", () => {\r\n\t\t\tthis.terminate();\r\n\t\t});\r\n\r\n\t\twindow.removeEventListener(\"pagehide\", () => {\r\n\t\t\tthis.terminate();\r\n\t\t});\r\n\t}\r\n}\r\n","import { SharedWebChannel } from \"./shared-web-channel\";\r\nimport { ObserverMessage } from \"./types\";\r\n\r\nexport class ChannelObserver {\r\n\tprivate onUpdate: (...args: any[]) => any;\r\n\r\n\t/**\r\n\t * Constructs a new `ChannelObserver` instance, that immediately subscribes to the provided channel's subject.\r\n\t * When the `SharedWebChannel` receives a message with action set to `observer`, it will update it's subject's subscribed observers.\r\n\t * If a `key` is provided then only messages with that key or `key: \"all\"` will trigger it's `onUpdate` function.\r\n\t *\r\n\t * @example\r\n\t *\r\n\t * const observer = new ChannelObserver(channel, (message) => {\r\n\t * const payload = message.payload as MyCustomType;\r\n\t *\r\n\t * if (payload) {\r\n\t * handlePayload(payload);\r\n\t * }\r\n\t * });\r\n\t *\r\n\t * //and to cleanup the observer:\r\n\t *\r\n\t * channel.subject.unsubscribe(observer);\r\n\t */\r\n\tconstructor(\r\n\t\tchannel: SharedWebChannel,\r\n\t\tonUpdate: (data: ObserverMessage) => any,\r\n\t\tkey?: string\r\n\t) {\r\n\t\tthis.onUpdate = onUpdate;\r\n\r\n\t\tchannel.subject.subscribe(this, key);\r\n\t}\r\n\r\n\tpublic update: <R>(data: ObserverMessage) => R | void = (data) => {\r\n\t\tthis.onUpdate(data);\r\n\t};\r\n}\r\n"],"mappings":"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","names":[]}