UNPKG

sandstone-proxy

Version:

An experimental web proxy utilizing sandboxed iframes and no service worker.

202 lines (178 loc) 5.3 kB
import * as rpc from "../rpc.mjs"; import * as util from "../util.mjs"; import * as network from "./network.mjs"; import { version } from "./index.mjs"; import { libcurl } from "libcurl.js/bundled"; //frame_js is a string, which is imported using webpack import frame_js from "../../dist/sandstone_frame.js"; let frame_url = null; let frame_html = ` <!DOCTYPE html> <head> <meta charset="utf-8"> <script>${frame_js}</script> <style> html { background-color: #222222; font-family: sans-serif; } * { color: #dddddd; } #error_div { display: none; } #version_text { font-size: 14px; } </style> </head> <body> <p id="loading_text">Loading...</p> <div id="error_div"> <h2>An unexpected error has occurred</h2> <pre id="error_msg"> </pre> <p><i> <span id="version_text"></span> </p></i> </div> </body> `; export const iframes = {}; export const persist_storage_key = "proxy_local_storage"; export let local_storage = {}; try { local_storage = JSON.parse(localStorage.getItem(persist_storage_key)) || {}; } catch {} function get_frame_bundle() { if (!frame_url) { let frame_blob = new Blob([frame_html], {type: "text/html"}); frame_url = URL.createObjectURL(frame_blob); } return frame_url; } export class ProxyFrame { constructor() { this.url = null; this.id = Math.random() + ""; this.iframe = document.createElement("iframe"); this.iframe.sandbox = "allow-scripts allow-forms allow-modals allow-pointer-lock"; this.iframe.allowFullscreen = true; this.iframe.setAttribute("frame-id", this.id); iframes[this.id] = this; this.rpc_target = new rpc.RPCTarget(); this.rpc_target.onmessage = rpc.message_listener; this.send_page = rpc.create_rpc_wrapper(this.rpc_target, "html"); this.get_favicon = rpc.create_rpc_wrapper(this.rpc_target, "favicon"); this.eval_js = rpc.create_rpc_wrapper(this.rpc_target, "eval"); this.on_navigate = () => {}; this.on_load = () => {}; this.on_url_change = () => {}; this.special_pages = {}; this.site_settings = []; this.default_settings = { allow_js: true }; } async wait_for_libcurl() { if (libcurl.ready) return; await libcurl.load_wasm(); } async navigate_to(url, form_data=null) { await this.wait_for_libcurl(); if (!util.is_valid_url(url)) { throw TypeError("Invalid URL"); } console.log("navigating to", url); this.url = new URL(url); this.iframe.style.backgroundColor = "#222222"; this.on_navigate(); this.iframe.src = get_frame_bundle(); network.clean_ws_connections(this.id); let html = null; let error = false; if (typeof this.special_pages[url] === "string") { html = this.special_pages[url]; } else { try { let options = {}; if (form_data) { console.log("placing post request:", form_data); options = { method: "POST", headers: {"Content-Type": form_data.enctype}, body: form_data.body } } let response = await network.session.fetch(url, options); html = await response.text(); url = response.url; } catch (e) { error = util.format_error(e); } } this.url = new URL(url); let settings = this.site_settings.find((item) => { return item.hostname.test(this.url.hostname); }) || {}; settings = {...this.default_settings, ...settings}; console.log("site settings:", settings); await rpc.wait_on_frame(this.iframe); let msg_channel = new MessageChannel(); this.rpc_target.set_target(msg_channel.port1); msg_channel.port1.start(); await rpc.set_host(this.iframe, msg_channel.port2); try { await this.send_page({ url: this.url.href, html: html, frame_id: this.id, error: error, settings: settings, default_settings: this.default_settings, local_storage: local_storage[this.url.origin], version: version }); } catch (e) { let error_msg = util.format_error(e); await this.send_page({ url: this.url.href, html: html, frame_id: this.id, error: error_msg, settings: settings, default_settings: this.default_settings, local_storage: undefined, version: version }); } this.iframe.style.backgroundColor = "unset"; this.on_load(); } } rpc.rpc_handlers["navigate"] = async (frame_id, url, reload=true, form_data=null) => { let frame = iframes[frame_id]; if (!frame) return; if (reload) { await frame.navigate_to(url, form_data); } else { frame.url = new URL(url); frame.on_url_change(); } } rpc.rpc_handlers["local_storage"] = async (frame_id, entries) => { let frame = iframes[frame_id]; if (!frame) return; local_storage[frame.url.origin] = entries; if (window.origin) { localStorage.setItem(persist_storage_key, JSON.stringify(local_storage)); } } //this is for getting navigation events from form submission handlers window.addEventListener("message", rpc.message_listener);