aboutsummaryrefslogtreecommitdiff
path: root/common/storage_client.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'common/storage_client.mjs')
-rw-r--r--common/storage_client.mjs186
1 files changed, 186 insertions, 0 deletions
diff --git a/common/storage_client.mjs b/common/storage_client.mjs
new file mode 100644
index 0000000..8260ad7
--- /dev/null
+++ b/common/storage_client.mjs
@@ -0,0 +1,186 @@
+/**
+* Myext storage through connection (client side)
+*
+* Copyright (C) 2021 Wojtek Kosior
+*
+* Dual-licensed under:
+* - 0BSD license
+* - GPLv3 or (at your option) any later version
+*/
+
+"use strict";
+
+import CONNECTION_TYPE from './connection_types.mjs';
+import {TYPE_PREFIX, list_prefixes} from '/common/stored_types.mjs';
+import make_once from './once.mjs';
+import browser from '/common/browser.mjs';
+
+var call_id = 0;
+var port;
+var calls_waiting = new Map();
+
+function set_call_callback(resolve, reject, func, args)
+{
+ port.postMessage([call_id, func, args]);
+ calls_waiting.set(call_id++, [resolve, reject]);
+}
+
+async function remote_call(func, args)
+{
+ return new Promise((resolve, reject) =>
+ set_call_callback(resolve, reject, func, args));
+}
+
+function handle_message(message)
+{
+ let callbacks = calls_waiting.get(message.call_id);
+ if (callbacks === undefined) {
+ handle_change(message);
+ return;
+ }
+
+ let [resolve, reject] = callbacks;
+ calls_waiting.delete(message.call_id);
+ if (message.error !== undefined)
+ setTimeout(reject, 0, message.error);
+ else
+ setTimeout(resolve, 0, message.result);
+}
+
+function list(name, prefix)
+{
+ return {prefix, name, listeners : new Set()};
+}
+
+var scripts = list("scripts", TYPE_PREFIX.SCRIPT);
+var bundles = list("bundles", TYPE_PREFIX.BUNDLE);
+var pages = list("pages", TYPE_PREFIX.PAGE);
+
+const list_by_prefix = {
+ [TYPE_PREFIX.SCRIPT] : scripts,
+ [TYPE_PREFIX.BUNDLE] : bundles,
+ [TYPE_PREFIX.PAGE] : pages
+};
+
+var resolve_init;
+
+function handle_first_message(message)
+{
+ for (let prefix of Object.keys(message))
+ list_by_prefix[prefix].map = new Map(message[prefix]);
+
+ port.onMessage.removeListener(handle_first_message);
+ port.onMessage.addListener(handle_message);
+
+ resolve_init();
+}
+
+function handle_change(change)
+{
+ let list = list_by_prefix[change.prefix];
+
+ if (change.new_val === undefined)
+ list.map.delete(change.item);
+ else
+ list.map.set(change.item, change.new_val);
+
+ for (let listener_callback of list.listeners)
+ listener_callback(change);
+}
+
+var exports = {};
+
+function start_connection(resolve)
+{
+ resolve_init = resolve;
+ port = browser.runtime.connect({name : CONNECTION_TYPE.REMOTE_STORAGE});
+ port.onMessage.addListener(handle_first_message);
+}
+
+async function init() {
+ await new Promise((resolve, reject) => start_connection(resolve));
+ return exports;
+}
+
+for (let call_name of ["set", "remove", "replace", "clear"])
+ exports [call_name] = (...args) => remote_call(call_name, args);
+
+// TODO: Much of the code below is copy-pasted from /background/storage.mjs.
+// This should later be refactored into a separate module
+// to avoid duplication.
+
+/*
+ * Facilitate listening to changes
+ */
+
+exports.add_change_listener = function (cb, prefixes=list_prefixes)
+{
+ if (typeof(prefixes) === "string")
+ prefixes = [prefixes];
+
+ for (let prefix of prefixes)
+ list_by_prefix[prefix].listeners.add(cb);
+}
+
+exports.remove_change_listener = function (cb, prefixes=list_prefixes)
+{
+ if (typeof(prefixes) === "string")
+ prefixes = [prefixes];
+
+ for (let prefix of prefixes)
+ list_by_prefix[prefix].listeners.delete(cb);
+}
+
+/* Prepare some hepler functions to get elements of a list */
+
+function list_items_it(list, with_values=false)
+{
+ return with_values ? list.map.entries() : list.map.keys();
+}
+
+function list_entries_it(list)
+{
+ return list_items_it(list, true);
+}
+
+function list_items(list, with_values=false)
+{
+ let array = [];
+
+ for (let item of list_items_it(list, with_values))
+ array.push(item);
+
+ return array;
+}
+
+function list_entries(list)
+{
+ return list_items(list, true);
+}
+
+exports.get = function (prefix, item)
+{
+ return list_by_prefix[prefix].map.get(item);
+}
+
+exports.get_all_names = function (prefix)
+{
+ return list_items(list_by_prefix[prefix]);
+}
+
+exports.get_all_names_it = function (prefix)
+{
+ return list_items_it(list_by_prefix[prefix]);
+}
+
+exports.get_all = function (prefix)
+{
+ return list_entries(list_by_prefix[prefix]);
+}
+
+exports.get_all_it = function (prefix)
+{
+ return list_entries_it(list_by_prefix[prefix]);
+}
+
+export default make_once(init);