/** * 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; })();