summaryrefslogtreecommitdiff
path: root/background/page_actions_server.js
blob: c6800a01235462e8748e4348b6aa19d78c46949e (about) (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
/**
 * Hachette serving of page actions to content scripts
 *
 * Copyright (C) 2021 Wojtek Kosior
 * Redistribution terms are gathered in the `copyright' file.
 */

/*
 * IMPORTS_START
 * IMPORT get_storage
 * IMPORT TYPE_PREFIX
 * IMPORT CONNECTION_TYPE
 * IMPORT browser
 * IMPORT listen_for_connection
 * IMPORT sha256
 * IMPORT get_query_best
 * IMPORTS_END
 */

var storage;
var query_best;
var handler;

function send_actions(url, port)
{
    let [pattern, settings] = query_best(url);

    port.postMessage(["settings", [pattern, settings]]);

    if (settings === undefined)
	return;

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

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

// TODO: parallelize script fetching
async function send_scripts(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(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_actions(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_page_actions_server()
{
    storage = await get_storage();
    query_best = await get_query_best();

    listen_for_connection(CONNECTION_TYPE.PAGE_ACTIONS, new_connection);
}

/*
 * EXPORTS_START
 * EXPORT start_page_actions_server
 * EXPORTS_END
 */