diff options
Diffstat (limited to 'background')
-rw-r--r-- | background/broadcast_broker.js | 184 |
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 + */ |