Skip to content

Instantly share code, notes, and snippets.

@rabelloo
Created June 15, 2023 15:16
Show Gist options
  • Save rabelloo/51eed4457180f5bad18c7f708741da85 to your computer and use it in GitHub Desktop.
Save rabelloo/51eed4457180f5bad18c7f708741da85 to your computer and use it in GitHub Desktop.
export {};
/*
Taken from https://gist.github.com/sechel/e6aff22d9e56df02c5bd09c4afc516e6
which is the basis for https://github.com/JSmith01/broadcastchannel-polyfill
but rewritten into TypeScript and class rather than JS with .prototype extension
*/
const channels: Record<string, Set<BroadcastChannel>> = {};
class BroadcastChannel {
readonly name: string;
private id: string;
private closed: boolean;
private mc: MessageChannel;
constructor(name: string) {
this.name = String(name);
this.id = `$BroadcastChannel$${this.name}$`;
(channels[this.id] ??= new Set()).add(this);
this.closed = false;
this.mc = new MessageChannel();
this.mc.port1.start();
this.mc.port2.start();
globalThis.addEventListener('storage', (evt) => {
if (evt.storageArea !== globalThis.localStorage) return;
if (!evt.newValue) return;
if (evt.key?.substring(0, this.id.length) !== this.id) return;
this.mc.port2.postMessage(JSON.parse(evt.newValue));
});
}
// BroadcastChannel API
postMessage(message: unknown) {
if (this.closed) {
const error = new Error();
error.name = 'InvalidStateError';
throw error;
}
const value = JSON.stringify(message);
const key = `${this.id}${Date.now()}$${Math.random()}`;
// Broadcast to other contexts via storage events...
globalThis.localStorage.setItem(key, value);
setTimeout(() => globalThis.localStorage.removeItem(key));
// Broadcast to current context via ports
channels[this.id].forEach((bc) => {
if (bc === this) return;
bc.mc.port2.postMessage(JSON.parse(value));
});
}
close() {
if (this.closed) return;
this.closed = true;
this.mc.port1.close();
this.mc.port2.close();
channels[this.id].delete(this);
}
// EventTarget API
get onmessage() {
return this.mc.port1.onmessage;
}
set onmessage(value) {
this.mc.port1.onmessage = value;
}
get onmessageerror() {
return '';
}
addEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions,
) {
return this.mc.port1.addEventListener(type, listener, options);
}
removeEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | EventListenerOptions,
) {
return this.mc.port1.removeEventListener(type, listener, options);
}
dispatchEvent(event: Event) {
return this.mc.port1.dispatchEvent(event);
}
}
globalThis.BroadcastChannel = globalThis.BroadcastChannel || BroadcastChannel;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment