UNPKG

sandstone-proxy

Version:

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

210 lines (185 loc) 4.95 kB
export let role = null; export const rpc_handlers = {}; export const rpc_requests = {}; export let host = null; export let on_attach = () => {}; //a flexible wrapper for message ports export class RPCTarget { constructor(target=null) { this.target = null; this.extra_targets = []; this.msg_callback = this.handle_msg.bind(this); this.onmessage = () => {}; if (target) this.set_target(target); } set_target(new_target) { if (this.target) this.target.removeEventListener("message", this.msg_callback); for (let target of this.extra_targets) { target.removeEventListener("message", this.msg_callback); } this.target = new_target; this.extra_targets = []; this.target.addEventListener("message", this.msg_callback); } add_extra_target(target) { target.addEventListener("message", this.msg_callback); this.extra_targets.push(target); } handle_msg(event) { this.onmessage(event, this); } postMessage(...args) { this.target.postMessage(...args) } } async function handle_procedure_call(msg) { if (!rpc_handlers[msg.procedure]) { return; } let output; try { output = { value: await rpc_handlers[msg.procedure](...msg.arguments), success: true }; } catch (e) { console.error(e); output = { value: e, success: false }; } return { type: "reply", id: msg.id, content: output }; } function handle_procedure_reply(msg) { if (!rpc_requests[msg.id]) { return; } rpc_requests[msg.id](msg.content); delete rpc_requests[msg.id]; } export async function message_listener(event, target) { let msg = event.data; let source = event.source || event.currentTarget; if (typeof msg.type === "undefined") return; console.log(`RPC ${role} got`, msg); if (msg.type === "procedure") { let output = await handle_procedure_call(msg); if (!source) return; if (output) { source.postMessage(output, {targetOrigin: "*"}); } } else if (msg.type === "reply") { handle_procedure_reply(msg); } else if (msg.type === "attach") { let msg_port = event.ports[0]; if (role === "frame") { host.postMessage(msg, {targetOrigin: "*", transfer: [msg_port]}); } else { target.add_extra_target(msg_port); msg_port.start(); } } } export async function call_procedure(target, procedure, args) { if (globalThis.HTMLIFrameElement && target instanceof globalThis.HTMLIFrameElement) { target = target.contentWindow || target; } let msg = { type: "procedure", id: Math.random() + "", procedure: procedure, arguments: args }; return await new Promise((resolve, reject) => { rpc_requests[msg.id] = (reply) => { if (reply.success) resolve(reply.value); else reject(reply.value); } console.log(`RPC ${role} sending`, msg); target.postMessage(msg, {targetOrigin: "*"}); }); } export function create_rpc_wrapper(target, procedure) { return function() { return call_procedure(target, procedure, [...arguments]); } } //attach an additional message port to the host export function attach_host(msg_port) { let msg = { type: "attach", id: Math.random() + "" }; host.postMessage(msg, {targetOrigin: "*", transfer: [msg_port]}); } //tell a child frame to set a message port as the host rpc target export function set_host(frame, msg_port) { let msg = { type: "set_host", id: Math.random() + "" }; let msg_target = frame.contentWindow || frame; msg_target.postMessage(msg, {targetOrigin: "*", transfer: [msg_port]}) } export async function wait_on_frame(frame) { let resolved = false; globalThis.addEventListener("message", (event) => { let msg = event.data; if (msg.type === "pong") resolved = true; }); while (!resolved) { let msg = { type: "ping", id: Math.random() + "" }; frame.contentWindow.postMessage(msg, "*"); await new Promise(r => setTimeout(r, 50)); } } //handle the initial set_host message from the host function host_set_handler(event) { let msg = event.data; let msg_port = event.ports[0]; if (msg.type === "ping") { let msg = { type: "pong", id: Math.random() + "" }; event.source.postMessage(msg, "*"); return; } if (msg.type === "set_host" && msg_port) { if (!host.target) { host.set_target(msg_port); on_attach(); } else { host.add_extra_target(msg_port); } msg_port.start(); } } export function set_role(value) { role = value; } export function set_on_attach(callback) { on_attach = callback; } //create an rpc target for the host if we are in a child frame if (self.parent !== globalThis || typeof globalThis.importScripts === "function") { host = new RPCTarget(); host.onmessage = message_listener; self.addEventListener("message", host_set_handler); }