aboutsummaryrefslogtreecommitdiff
path: root/background/page_actions_server.js
blob: 4146fcd8304636392283ffcd78e173c995a22b86 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
/**
 * Myext serving of page actions to content scripts
 *
 * Copyright (C) 2021 Wojtek Kosior
 *
 * This code is dual-licensed under:
 * - Asshole license 1.0,
 * - GPLv3 or (at your option) any later version
 *
 * "dual-licensed" means you can choose the license you prefer.
 *
 * This code is released under a permissive license because I disapprove of
 * copyright and wouldn't be willing to sue a violator. Despite not putting
 * this code under copyleft (which is also kind of copyright), I do not want
 * it to be made proprietary. Hence, the permissive alternative to GPL is the
 * Asshole license 1.0 that allows me to call you an asshole if you use it.
 * This means you're legally ok regardless of how you utilize this code but if
 * you make it into something nonfree, you're an asshole.
 *
 * You should have received a copy of both GPLv3 and Asshole license 1.0
 * together with this code. If not, please see:
 * - https://www.gnu.org/licenses/gpl-3.0.en.html
 * - https://koszko.org/asshole-license.txt
 */

"use strict";

(() => {
    const get_storage = window.get_storage;
    const TYPE_PREFIX = window.TYPE_PREFIX;
    const CONNECTION_TYPE = window.CONNECTION_TYPE;
    const browser = window.browser;
    const listen_for_connection = window.listen_for_connection;
    const sha256 = window.sha256;
    const get_query_best = window.get_query_best;

    var storage;
    var query_best;
    var handler;

    function send_scripts(url, port)
    {
	let [pattern, settings] = query_best(url);
	if (settings === undefined)
	    return;

	let components = settings.components;
	let processed_bags = new Set();

	if (components !== undefined)
	    send_scripts_rec([components], port, processed_bags);
    }

    // TODO: parallelize script fetching
    async function send_scripts_rec(components, port, processed_bags)
    {
	for (let [prefix, name] of components) {
	    if (prefix === TYPE_PREFIX.BAG) {
		if (processed_bags.has(name)) {
		    console.log(`preventing recursive inclusion of bag ${name}`);
		    continue;
		}

		var bag = storage.get(TYPE_PREFIX.BAG, name);

		if (bag === undefined) {
		    console.log(`no bag in storage for key ${name}`);
		    continue;
		}

		processed_bags.add(name);
		await send_scripts_rec(bag, port, processed_bags);
		processed_bags.delete(name);
	    } else {
		let script_text = await get_script_text(name);
		if (script_text === undefined)
		    continue;

		port.postMessage({inject : [script_text]});
	    }
	}
    }

    async function get_script_text(script_name)
    {
	try {
	    let script_data = storage.get(TYPE_PREFIX.SCRIPT, script_name);
	    if (script_data === undefined) {
		console.log(`missing data for ${script_name}`);
		return;
	    }
	    let script_text = script_data.text;
	    if (!script_text)
		script_text = await fetch_remote_script(script_data);
	    return script_text;
	} catch (e) {
	    console.log(e);
	}
    }

    function ajax_callback()
    {
	if (this.readyState == 4)
	    this.resolve_callback(this);
    }

    function initiate_ajax_request(resolve, method, url)
    {
	var xhttp = new XMLHttpRequest();
	xhttp.resolve_callback = resolve;
	xhttp.onreadystatechange = ajax_callback;
	xhttp.open(method, url, true);
	xhttp.send();
    }

    function make_ajax_request(method, url)
    {
	return new Promise((resolve, reject) =>
			   initiate_ajax_request(resolve, method, url));
    }

    async function fetch_remote_script(script_data)
    {
	try {
	    let xhttp = await make_ajax_request("GET", script_data.url);
	    if (xhttp.status === 200) {
		let computed_hash = sha256(xhttp.responseText);
		if (computed_hash !== script_data.hash) {
		    console.log(`Bad hash for ${script_data.url}\n    got ${computed_hash} instead of ${script_data.hash}`);
		    return;
		}
		return xhttp.responseText;
	    } else {
		console.log("script not fetched: " + script_data.url);
		return;
	    }
	} catch (e) {
	    console.log(e);
	}
    }

    function handle_message(port, message, handler)
    {
	port.onMessage.removeListener(handler[0]);
	let url = message.url;
	console.log({url});
	send_scripts(url, port);
    }

    function new_connection(port)
    {
	console.log("new page actions connection!");
	let handler = [];
	handler.push(m => handle_message(port, m, handler));
	port.onMessage.addListener(handler[0]);
    }

    async function start()
    {
	storage = await get_storage();
	query_best = await get_query_best();

	listen_for_connection(CONNECTION_TYPE.PAGE_ACTIONS, new_connection);
    }

    window.start_page_actions_server = start;
})();