Project

General

Profile

Download (4.3 KB) Statistics
| Branch: | Tag: | Revision:

haketilo / common / storage_client.js @ b93f26bf

1
/**
2
 * Myext storage through connection (client side)
3
 *
4
 * Copyright (C) 2021 Wojtek Kosior
5
 * Redistribution terms are gathered in the `copyright' file.
6
 */
7

    
8
"use strict";
9

    
10
(() => {
11
    const CONNECTION_TYPE = window.CONNECTION_TYPE;
12
    const TYPE_PREFIX = window.TYPE_PREFIX;
13
    const list_prefixes = window.list_prefixes;
14
    const make_once = window.make_once;
15
    const browser = window.browser;
16

    
17
    var call_id = 0;
18
    var port;
19
    var calls_waiting = new Map();
20

    
21
    function set_call_callback(resolve, reject, func, args)
22
    {
23
	port.postMessage([call_id, func, args]);
24
	calls_waiting.set(call_id++, [resolve, reject]);
25
    }
26

    
27
    async function remote_call(func, args)
28
    {
29
	return new Promise((resolve, reject) =>
30
			   set_call_callback(resolve, reject, func, args));
31
    }
32

    
33
    function handle_message(message)
34
    {
35
	let callbacks = calls_waiting.get(message.call_id);
36
	if (callbacks === undefined) {
37
	    handle_change(message);
38
	    return;
39
	}
40

    
41
	let [resolve, reject] = callbacks;
42
	calls_waiting.delete(message.call_id);
43
	if (message.error !== undefined)
44
	    setTimeout(reject, 0, message.error);
45
	else
46
	    setTimeout(resolve, 0, message.result);
47
    }
48

    
49
    function list(name, prefix)
50
    {
51
	return {prefix, name, listeners : new Set()};
52
    }
53

    
54
    var scripts = list("scripts", TYPE_PREFIX.SCRIPT);
55
    var bags = list("bags", TYPE_PREFIX.BAG);
56
    var pages = list("pages", TYPE_PREFIX.PAGE);
57

    
58
    const list_by_prefix = {
59
	[TYPE_PREFIX.SCRIPT] : scripts,
60
	[TYPE_PREFIX.BAG] : bags,
61
	[TYPE_PREFIX.PAGE] : pages
62
    };
63

    
64
    var resolve_init;
65

    
66
    function handle_first_message(message)
67
    {
68
	for (let prefix of Object.keys(message))
69
	    list_by_prefix[prefix].map = new Map(message[prefix]);
70

    
71
	port.onMessage.removeListener(handle_first_message);
72
	port.onMessage.addListener(handle_message);
73

    
74
	resolve_init();
75
    }
76

    
77
    function handle_change(change)
78
    {
79
	let list = list_by_prefix[change.prefix];
80

    
81
	if (change.new_val === undefined)
82
	    list.map.delete(change.item);
83
	else
84
	    list.map.set(change.item, change.new_val);
85

    
86
	for (let listener_callback of list.listeners)
87
	    listener_callback(change);
88
    }
89

    
90
    var exports = {};
91

    
92
    function start_connection(resolve)
93
    {
94
	resolve_init = resolve;
95
	port = browser.runtime.connect({name : CONNECTION_TYPE.REMOTE_STORAGE});
96
	port.onMessage.addListener(handle_first_message);
97
    }
98

    
99
    async function init() {
100
	await new Promise((resolve, reject) => start_connection(resolve));
101
	return exports;
102
    }
103

    
104
    for (let call_name of ["set", "remove", "replace", "clear"])
105
	exports [call_name] = (...args) => remote_call(call_name, args);
106

    
107
    // TODO: Much of the code below is copy-pasted from /background/storage.mjs.
108
    //       This should later be refactored into a separate module
109
    //       to avoid duplication.
110

    
111
    /*
112
     * Facilitate listening to changes
113
     */
114

    
115
    exports.add_change_listener = function (cb, prefixes=list_prefixes)
116
    {
117
	if (typeof(prefixes) === "string")
118
	    prefixes = [prefixes];
119

    
120
	for (let prefix of prefixes)
121
	    list_by_prefix[prefix].listeners.add(cb);
122
    }
123

    
124
    exports.remove_change_listener = function (cb, prefixes=list_prefixes)
125
    {
126
	if (typeof(prefixes) === "string")
127
	    prefixes = [prefixes];
128

    
129
	for (let prefix of prefixes)
130
	    list_by_prefix[prefix].listeners.delete(cb);
131
    }
132

    
133
    /* Prepare some hepler functions to get elements of a list */
134

    
135
    function list_items_it(list, with_values=false)
136
    {
137
	return with_values ? list.map.entries() : list.map.keys();
138
    }
139

    
140
    function list_entries_it(list)
141
    {
142
	return list_items_it(list, true);
143
    }
144

    
145
    function list_items(list, with_values=false)
146
    {
147
	let array = [];
148

    
149
	for (let item of list_items_it(list, with_values))
150
	    array.push(item);
151

    
152
	return array;
153
    }
154

    
155
    function list_entries(list)
156
    {
157
	return list_items(list, true);
158
    }
159

    
160
    exports.get = function (prefix, item)
161
    {
162
	return list_by_prefix[prefix].map.get(item);
163
    }
164

    
165
    exports.get_all_names = function (prefix)
166
    {
167
	return list_items(list_by_prefix[prefix]);
168
    }
169

    
170
    exports.get_all_names_it = function (prefix)
171
    {
172
	return list_items_it(list_by_prefix[prefix]);
173
    }
174

    
175
    exports.get_all = function (prefix)
176
    {
177
	return list_entries(list_by_prefix[prefix]);
178
    }
179

    
180
    exports.get_all_it = function (prefix)
181
    {
182
	return list_entries_it(list_by_prefix[prefix]);
183
    }
184

    
185
    window.get_storage = make_once(init);
186
})();
(7-7/9)