aboutsummaryrefslogtreecommitdiff
path: root/background/script_injector.mjs
blob: 3298a43816ea620352c6125856375b85433c2ce2 (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
/**
* Myext script injector
* 
* Copyright (C) 2021 Wojtek Kosior
*
* Dual-licensed under:
*   - 0BSD license
*   - GPLv3 or (at your option) any later version
*/

import {TYPE_PREFIX} from '/common/stored_types.mjs';
import sha256 from './sha256.mjs';
import {url_item} from './ResponseProcessor.mjs';
import get_storage from './storage.mjs';

"use strict";

var storage;

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);
    }
}

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);
    }
}

// TODO: parallelize script fetching
// TODO: guard against infinite recursion

async function inject_scripts_rec(components, doc)
{
    for (let [prefix, name] of components) {
	if (prefix === TYPE_PREFIX.BUNDLE) {
	    var bundle = storage.get(TYPE_PREFIX.BUNDLE, name);

	    if (bundle === undefined) {
		console.log(`no bundle in storage for key ${elem_key}`);
		continue;
	    }
	    await inject_scripts_rec(bundle, doc);
	} else {
	    let script_text = await get_script_text(name,);
	    if (script_text === undefined)
		continue;

	    let script = doc.createElement("script");
	    script.textContent = script_text;
	    doc.body.appendChild(script);
	}
    }
}

async function inject_scripts(url, doc)
{
    storage = await get_storage();

    url = url_item(url);

    let components = storage.get(TYPE_PREFIX.PAGE, url);

    if (components === undefined) {
	console.log(`got nothing for ${url}`);
	return
    } else {
	console.log(`got ${components.length} component(s) for ${url}`);
    }

    await inject_scripts_rec(components, doc);
}

export default inject_scripts;