From 4dfa47a2bfbc99a94b89e60a8f10a7529a391d32 Mon Sep 17 00:00:00 2001 From: velzie Date: Sat, 12 Oct 2024 10:58:15 -0400 Subject: [PATCH] refactor service worker to handle blobs properly --- src/client/shared/event.ts | 1 - src/client/shared/worker.ts | 34 +--------- src/controller/index.ts | 2 +- src/shared/rewriters/url.ts | 7 +- src/worker/fetch.ts | 125 +++++++++++++++++++++++++----------- src/worker/index.ts | 42 ------------ 6 files changed, 99 insertions(+), 112 deletions(-) diff --git a/src/client/shared/event.ts b/src/client/shared/event.ts index 59147c3..d208d12 100644 --- a/src/client/shared/event.ts +++ b/src/client/shared/event.ts @@ -154,7 +154,6 @@ export default function (client: ScramjetClient, self: Self) { key.startsWith("on") && handlers[key.slice(2)] ) { - console.log(key); const descriptor = nativeGetOwnPropertyDescriptor(target, key); if (!descriptor.get || !descriptor.set || !descriptor.configurable) continue; diff --git a/src/client/shared/worker.ts b/src/client/shared/worker.ts index f348b5f..cc74b34 100644 --- a/src/client/shared/worker.ts +++ b/src/client/shared/worker.ts @@ -13,28 +13,11 @@ export default function (client: ScramjetClient, self: typeof globalThis) { const handler = { construct({ args, call }) { if (args[0] instanceof URL) args[0] = args[0].href; - if (args[0].startsWith("blob:") || args[0].startsWith("data:")) { - const data = syncfetch(client, args[0]); - const id = Math.random().toString(8).slice(5); - args[0] = "/scramjet/worker?id=" + id; - if (args[1] && args[1].type === "module") { - args[0] += "&type=module"; - } + args[0] = encodeUrl(args[0], client.meta) + "?dest=worker"; - args[0] += "&origin=" + encodeURIComponent(client.url.origin); - - client.serviceWorker.controller?.postMessage({ - scramjet$type: "dataworker", - data, - id, - } as MessageC2W); - } else { - args[0] = encodeUrl(args[0], client.meta) + "?dest=worker"; - - if (args[1] && args[1].type === "module") { - args[0] += "&type=module"; - } + if (args[1] && args[1].type === "module") { + args[0] += "&type=module"; } const worker = call(); @@ -81,14 +64,3 @@ export default function (client: ScramjetClient, self: typeof globalThis) { client.Proxy("SharedWorker", handler); } } - -function syncfetch(client: ScramjetClient, url: string) { - const xhr = new XMLHttpRequest(); - - const realOpen = client.natives["XMLHttpRequest.prototype.open"].bind(xhr); - - realOpen("GET", url, false); - xhr.send(); - - return xhr.responseText; -} diff --git a/src/controller/index.ts b/src/controller/index.ts index 6c6471a..60ad814 100644 --- a/src/controller/index.ts +++ b/src/controller/index.ts @@ -34,7 +34,7 @@ export class ScramjetController { syncxhr: false, cleanerrors: false, scramitize: false, - sourcemaps: true, + sourcemaps: false, }, }; diff --git a/src/shared/rewriters/url.ts b/src/shared/rewriters/url.ts index f5f0fb1..237ea0d 100644 --- a/src/shared/rewriters/url.ts +++ b/src/shared/rewriters/url.ts @@ -21,7 +21,11 @@ export function encodeUrl(url: string | URL, meta: URLMeta) { if (url.startsWith("javascript:")) { return "javascript:" + rewriteJs(url.slice("javascript:".length), meta); - } else if (/^(#|mailto|about|data|blob)/.test(url)) { + } else if (url.startsWith("blob:")) { + return location.origin + self.$scramjet.config.prefix + url; + } else if (url.startsWith("data:")) { + return location.origin + self.$scramjet.config.prefix + url; + } else if (/^(#|mailto|about)/.test(url)) { // TODO this regex is jank but i'm not fixing it return url; } else { @@ -51,6 +55,7 @@ export function decodeUrl(url: string | URL) { return new URL(new URL(url).searchParams.get("origin")).href; } + // TODO: unrewrite rewritten blobs if (/^(#|about|data|mailto|javascript)/.test(url)) { return url; } else if (tryCanParseURL(url)) { diff --git a/src/worker/fetch.ts b/src/worker/fetch.ts index 6a84a7a..9477a53 100644 --- a/src/worker/fetch.ts +++ b/src/worker/fetch.ts @@ -46,6 +46,51 @@ export async function swfetch( if (requesturl.searchParams.has("dest")) { requesturl.searchParams.delete("dest"); } + + if ( + requesturl.pathname.startsWith(this.config.prefix + "blob:") || + requesturl.pathname.startsWith(this.config.prefix + "data:") + ) { + let response: Response = await fetch( + requesturl.pathname.substring(this.config.prefix.length), + { + // this is extremely redundant but i don't care + // method: request.method, + // body: request.body, + // headers: request.headers, + // credentials: "omit", + // mode: request.mode === "cors" ? request.mode : "same-origin", + // cache: request.cache, + // redirect: "manual", + // //@ts-ignore + // duplex: "half", + } + ); + + let body: BodyType; + + if (response.body) { + body = await rewriteBody( + response, + { + base: new URL(new URL(client.url).origin), + origin: new URL(new URL(client.url).origin), + }, + request.destination, + workertype, + this.cookieStore + ); + } + let headers = Object.fromEntries(response.headers.entries()); + headers["Cross-Origin-Embedder-Policy"] = "require-corp"; + + return new Response(body, { + status: response.status, + statusText: response.statusText, + headers: headers, + }); + } + const url = new URL(decodeUrl(requesturl)); const activeWorker: FakeServiceWorker | null = this.serviceWorkers.find( @@ -136,7 +181,7 @@ async function handleResponse( client: Client, swtarget: ScramjetServiceWorker ): Promise { - let responseBody: string | ArrayBuffer | ReadableStream; + let responseBody: BodyType; const responseHeaders = rewriteHeaders(response.rawHeaders, newmeta(url)); const maybeHeaders = responseHeaders["set-cookie"] || []; @@ -161,41 +206,15 @@ async function handleResponse( } if (response.body) { - switch (destination) { - case "iframe": - case "document": - if (responseHeaders["content-type"]?.startsWith("text/html")) { - responseBody = rewriteHtml( - await response.text(), - cookieStore, - newmeta(url), - true - ); - } else { - responseBody = response.body; - } - break; - case "script": - responseBody = rewriteJs(await response.arrayBuffer(), newmeta(url)); - // Disable threading for now, it's causing issues. - // responseBody = await this.threadpool.rewriteJs(await responseBody.arrayBuffer(), url.toString()); - break; - case "style": - responseBody = rewriteCss(await response.text(), newmeta(url)); - break; - case "sharedworker": - case "worker": - responseBody = rewriteWorkers( - await response.arrayBuffer(), - workertype, - newmeta(url) - ); - break; - default: - responseBody = response.body; - break; - } + responseBody = await rewriteBody( + response, + newmeta(url), + destination, + workertype, + cookieStore + ); } + // downloads if (["document", "iframe"].includes(destination)) { const header = responseHeaders["content-disposition"]; @@ -255,9 +274,43 @@ async function handleResponse( }); } +async function rewriteBody( + response: Response, + meta: URLMeta, + destination: RequestDestination, + workertype: string, + cookieStore: CookieStore +): Promise { + switch (destination) { + case "iframe": + case "document": + if (response.headers.get("content-type")?.startsWith("text/html")) { + return rewriteHtml(await response.text(), cookieStore, meta, true); + } else { + return response.body; + } + break; + case "script": + return rewriteJs(await response.arrayBuffer(), meta); + // Disable threading for now, it's causing issues. + // responseBody = await this.threadpool.rewriteJs(await responseBody.arrayBuffer(), url.toString()); + break; + case "style": + return rewriteCss(await response.text(), meta); + break; + case "sharedworker": + case "worker": + return rewriteWorkers(await response.arrayBuffer(), workertype, meta); + default: + return response.body; + } +} + +type BodyType = string | ArrayBuffer | Blob | ReadableStream; + export class ScramjetHandleResponseEvent extends Event { public responseHeaders: Record; - public responseBody: string | ArrayBuffer | ReadableStream; + public responseBody: BodyType; public status: number; public statusText: string; public destination: string; diff --git a/src/worker/index.ts b/src/worker/index.ts index 6191191..4cfea4e 100644 --- a/src/worker/index.ts +++ b/src/worker/index.ts @@ -125,48 +125,6 @@ export class ScramjetServiceWorker extends EventTarget { } async fetch({ request, clientId }: FetchEvent) { - if (new URL(request.url).pathname.startsWith("/scramjet/worker")) { - const id = new URL(request.url).searchParams.get("id"); - const type = new URL(request.url).searchParams.get("type"); - - const origin = new URL( - decodeURIComponent(new URL(request.url).searchParams.get("origin")) - ); - - let promise = this.dataworkerpromises[id]; - if (!promise) { - let resolve: (v: string) => void; - promise = { - promise: new Promise((res) => (resolve = res)), - resolve, - }; - promise.resolve = resolve; - this.dataworkerpromises[id] = promise; - } - - const data = await promise.promise; - delete this.dataworkerpromises[id]; - - const rewritten = rewriteWorkers(data, type, { - origin: new URL(origin), - base: new URL(origin), - }); - - const headers = { - "Content-Type": "application/javascript", - }; - - // this is broken on firefox - if (crossOriginIsolated) { - headers["Cross-Origin-Opener-Policy"] = "same-origin"; - headers["Cross-Origin-Embedder-Policy"] = "require-corp"; - } - - return new Response(rewritten, { - headers, - }); - } - const client = await self.clients.get(clientId); return swfetch.call(this, request, client);