diff options
Diffstat (limited to 'common/storage_client.js')
-rw-r--r-- | common/storage_client.js | 189 |
1 files changed, 189 insertions, 0 deletions
diff --git a/common/storage_client.js b/common/storage_client.js new file mode 100644 index 0000000..39ece44 --- /dev/null +++ b/common/storage_client.js @@ -0,0 +1,189 @@ +/** +* 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"; + +(() => { + const CONNECTION_TYPE = window.CONNECTION_TYPE; + const TYPE_PREFIX = window.TYPE_PREFIX; + const list_prefixes = window.list_prefixes; + const make_once = window.make_once; + const browser = window.browser; + + 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]); + } + + window.get_storage = make_once(init); +})(); |