summaryrefslogtreecommitdiff
path: root/background
diff options
context:
space:
mode:
authorWojtek Kosior <koszko@koszko.org>2021-12-14 21:40:23 +0100
committerWojtek Kosior <koszko@koszko.org>2021-12-14 22:06:58 +0100
commit58fe4c7d806359bed299f74ba56902ab396a6ed1 (patch)
tree38cc0620fe36c5bd7ef7df7dd9f24d332a51fdee /background
parent79446ca52cea0864ebe2540ba774cc386ee2f8bc (diff)
downloadbrowser-extension-58fe4c7d806359bed299f74ba56902ab396a6ed1.tar.gz
browser-extension-58fe4c7d806359bed299f74ba56902ab396a6ed1.zip
facilitate broadcasting messages to different execution contexts within the webextension
Diffstat (limited to 'background')
-rw-r--r--background/broadcast_broker.js184
1 files changed, 184 insertions, 0 deletions
diff --git a/background/broadcast_broker.js b/background/broadcast_broker.js
new file mode 100644
index 0000000..7af8769
--- /dev/null
+++ b/background/broadcast_broker.js
@@ -0,0 +1,184 @@
+/**
+ * This file is part of Haketilo.
+ *
+ * Function: Facilitate broadcasting messages between different execution
+ * contexts of the extension
+ *
+ * Copyright (C) 2021 Wojtek Kosior
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * As additional permission under GNU GPL version 3 section 7, you
+ * may distribute forms of that code without the copy of the GNU
+ * GPL normally required by section 4, provided you include this
+ * license notice and, in case of non-source distribution, a URL
+ * through which recipients can access the Corresponding Source.
+ * If you modify file(s) with this exception, you may extend this
+ * exception to your version of the file(s), but you are not
+ * obligated to do so. If you do not wish to do so, delete this
+ * exception statement from your version.
+ *
+ * As a special exception to the GPL, any HTML file which merely
+ * makes function calls to this code, and for that purpose
+ * includes it by reference shall be deemed a separate work for
+ * copyright law purposes. If you modify this code, you may extend
+ * this exception to your version of the code, but you are not
+ * obligated to do so. If you do not wish to do so, delete this
+ * exception statement from your version.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * I, Wojtek Kosior, thereby promise not to sue for violation of this file's
+ * license. Although I request that you do not make use this code in a
+ * proprietary program, I am not going to enforce this in court.
+ */
+
+/*
+ * IMPORTS_START
+ * IMPORT listen_for_connection
+ * IMPORT CONNECTION_TYPE
+ * IMPORTS_END
+ */
+
+let next_id = 1;
+
+const listeners_by_channel = new Map();
+
+function new_broadcast_listener(port)
+{
+ listener_ctx = {port, id: ++next_id, channels: new Set()};
+ port.onMessage.addListener(msg => listener_command(msg, listener_ctx));
+ port.onDisconnect.addListener(msg => listener_remove(msg, listener_ctx));
+}
+
+function listener_command(msg, listener_ctx)
+{
+ const [disposition, name] = msg;
+
+ if (disposition === "subscribe")
+ subscribe_channel(name, listener_ctx);
+ else if (disposition === "unsubscribe")
+ unsubscribe_channel(name, listener_ctx);
+ else
+ throw `bad broadcast listener disposition '${disposition}'`;
+}
+
+function subscribe_channel(channel_name, listener_ctx)
+{
+ if (!listeners_by_channel.has(channel_name))
+ listeners_by_channel.set(channel_name, new Map());
+
+ listeners_by_channel.get(channel_name).set(listener_ctx.id, listener_ctx);
+
+ listener_ctx.channels.add(channel_name);
+}
+
+function unsubscribe_channel(channel_name, listener_ctx)
+{
+ const channel_listeners =
+ listeners_by_channel.get(channel_name) || new Map();
+ channel_listeners.delete(listener_ctx.id);
+
+ if (channel_listeners.size == 0)
+ listeners_by_channel.delete(channel_name);
+
+ listener_ctx.channels.delete(channel_name);
+}
+
+function remove_broadcast_listener(listener_ctx)
+{
+ for (const channel_name of [...listener_ctx.channels.keys()])
+ unsubscribe_channel(channel_name, listener_ctx);
+}
+
+function new_broadcast_sender(port)
+{
+ sender_ctx = {prepared_broadcasts: new Set()};
+ port.onMessage.addListener(msg => sender_command(msg, sender_ctx));
+ port.onDisconnect.addListener(msg => flush(sender_ctx));
+}
+
+function sender_command(msg, sender_ctx)
+{
+ const [disposition, name, value, timeout] = msg;
+
+ if (disposition === "prepare")
+ prepare(sender_ctx, name, value, timeout)
+ else if (disposition === "discard")
+ sender_ctx.prepared_broadcasts = new Set();
+ else if (disposition === "flush")
+ flush(sender_ctx);
+ else if (disposition === "broadcast")
+ broadcast(name, value);
+ else
+ throw `bad broadcast sender disposition '${disposition}'`;
+}
+
+function prepare(sender_ctx, channel_name, value, timeout)
+{
+ broadcast_data = [channel_name, value];
+ sender_ctx.prepared_broadcasts.add(broadcast_data);
+
+ if (timeout === 0)
+ return;
+
+ setTimeout(() => prepare_timeout_cb(sender_ctx, broadcast_data), timeout);
+}
+
+function prepare_timeout_cb(sender_ctx, broadcast_data)
+{
+ if (sender_ctx.prepared_broadcasts.has(broadcast_data)) {
+ sender_ctx.prepared_broadcasts.delete(broadcast_data);
+ broadcast(...broadcast_data);
+ }
+}
+
+function flush(sender_ctx)
+{
+ console.log('flushing', sender_ctx.prepared_broadcasts);
+ sender_ctx.prepared_broadcasts.forEach(nv => broadcast(...nv));
+ sender_ctx.prepared_broadcasts = new Set();
+}
+
+function broadcast(channel_name, value)
+{
+ const listeners = listeners_by_channel.get(channel_name);
+ if (listeners == undefined)
+ return;
+
+ for (const listener_ctx of [...listeners.values()]) {
+ try {
+ listener_ctx.port.postMessage([channel_name, value]);
+ } catch (e) {
+ console.error(e);
+ remove_broadcast_listener(listener_ctx);
+ }
+ }
+}
+
+function remove_broadcast_sender(sender_ctx)
+{
+ sender_ctx.prepared_broadcasts.forEach(nv => broadcast(...nv));
+}
+
+function start_broadcast_broker()
+{
+ listen_for_connection(CONNECTION_TYPE.BROADCAST_SEND, new_broadcast_sender);
+ listen_for_connection(CONNECTION_TYPE.BROADCAST_LISTEN,
+ new_broadcast_listener);
+}
+
+/*
+ * EXPORTS_START
+ * EXPORT start_broadcast_broker
+ * EXPORTS_END
+ */