mirror of
https://github.com/MercuryWorkshop/scramjet.git
synced 2025-05-13 22:40:01 -04:00
refactor service worker to handle blobs properly
This commit is contained in:
parent
23b2ba9928
commit
4dfa47a2bf
6 changed files with 99 additions and 112 deletions
|
@ -154,7 +154,6 @@ export default function (client: ScramjetClient, self: Self) {
|
||||||
key.startsWith("on") &&
|
key.startsWith("on") &&
|
||||||
handlers[key.slice(2)]
|
handlers[key.slice(2)]
|
||||||
) {
|
) {
|
||||||
console.log(key);
|
|
||||||
const descriptor = nativeGetOwnPropertyDescriptor(target, key);
|
const descriptor = nativeGetOwnPropertyDescriptor(target, key);
|
||||||
if (!descriptor.get || !descriptor.set || !descriptor.configurable)
|
if (!descriptor.get || !descriptor.set || !descriptor.configurable)
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -13,28 +13,11 @@ export default function (client: ScramjetClient, self: typeof globalThis) {
|
||||||
const handler = {
|
const handler = {
|
||||||
construct({ args, call }) {
|
construct({ args, call }) {
|
||||||
if (args[0] instanceof URL) args[0] = args[0].href;
|
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;
|
args[0] = encodeUrl(args[0], client.meta) + "?dest=worker";
|
||||||
if (args[1] && args[1].type === "module") {
|
|
||||||
args[0] += "&type=module";
|
|
||||||
}
|
|
||||||
|
|
||||||
args[0] += "&origin=" + encodeURIComponent(client.url.origin);
|
if (args[1] && args[1].type === "module") {
|
||||||
|
args[0] += "&type=module";
|
||||||
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";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const worker = call();
|
const worker = call();
|
||||||
|
@ -81,14 +64,3 @@ export default function (client: ScramjetClient, self: typeof globalThis) {
|
||||||
client.Proxy("SharedWorker", handler);
|
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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ export class ScramjetController {
|
||||||
syncxhr: false,
|
syncxhr: false,
|
||||||
cleanerrors: false,
|
cleanerrors: false,
|
||||||
scramitize: false,
|
scramitize: false,
|
||||||
sourcemaps: true,
|
sourcemaps: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,11 @@ export function encodeUrl(url: string | URL, meta: URLMeta) {
|
||||||
|
|
||||||
if (url.startsWith("javascript:")) {
|
if (url.startsWith("javascript:")) {
|
||||||
return "javascript:" + rewriteJs(url.slice("javascript:".length), meta);
|
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
|
// TODO this regex is jank but i'm not fixing it
|
||||||
return url;
|
return url;
|
||||||
} else {
|
} else {
|
||||||
|
@ -51,6 +55,7 @@ export function decodeUrl(url: string | URL) {
|
||||||
return new URL(new URL(url).searchParams.get("origin")).href;
|
return new URL(new URL(url).searchParams.get("origin")).href;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: unrewrite rewritten blobs
|
||||||
if (/^(#|about|data|mailto|javascript)/.test(url)) {
|
if (/^(#|about|data|mailto|javascript)/.test(url)) {
|
||||||
return url;
|
return url;
|
||||||
} else if (tryCanParseURL(url)) {
|
} else if (tryCanParseURL(url)) {
|
||||||
|
|
|
@ -46,6 +46,51 @@ export async function swfetch(
|
||||||
if (requesturl.searchParams.has("dest")) {
|
if (requesturl.searchParams.has("dest")) {
|
||||||
requesturl.searchParams.delete("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 url = new URL(decodeUrl(requesturl));
|
||||||
|
|
||||||
const activeWorker: FakeServiceWorker | null = this.serviceWorkers.find(
|
const activeWorker: FakeServiceWorker | null = this.serviceWorkers.find(
|
||||||
|
@ -136,7 +181,7 @@ async function handleResponse(
|
||||||
client: Client,
|
client: Client,
|
||||||
swtarget: ScramjetServiceWorker
|
swtarget: ScramjetServiceWorker
|
||||||
): Promise<Response> {
|
): Promise<Response> {
|
||||||
let responseBody: string | ArrayBuffer | ReadableStream;
|
let responseBody: BodyType;
|
||||||
const responseHeaders = rewriteHeaders(response.rawHeaders, newmeta(url));
|
const responseHeaders = rewriteHeaders(response.rawHeaders, newmeta(url));
|
||||||
|
|
||||||
const maybeHeaders = responseHeaders["set-cookie"] || [];
|
const maybeHeaders = responseHeaders["set-cookie"] || [];
|
||||||
|
@ -161,41 +206,15 @@ async function handleResponse(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.body) {
|
if (response.body) {
|
||||||
switch (destination) {
|
responseBody = await rewriteBody(
|
||||||
case "iframe":
|
response,
|
||||||
case "document":
|
newmeta(url),
|
||||||
if (responseHeaders["content-type"]?.startsWith("text/html")) {
|
destination,
|
||||||
responseBody = rewriteHtml(
|
workertype,
|
||||||
await response.text(),
|
cookieStore
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// downloads
|
// downloads
|
||||||
if (["document", "iframe"].includes(destination)) {
|
if (["document", "iframe"].includes(destination)) {
|
||||||
const header = responseHeaders["content-disposition"];
|
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<BodyType> {
|
||||||
|
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<any>;
|
||||||
|
|
||||||
export class ScramjetHandleResponseEvent extends Event {
|
export class ScramjetHandleResponseEvent extends Event {
|
||||||
public responseHeaders: Record<string, string>;
|
public responseHeaders: Record<string, string>;
|
||||||
public responseBody: string | ArrayBuffer | ReadableStream;
|
public responseBody: BodyType;
|
||||||
public status: number;
|
public status: number;
|
||||||
public statusText: string;
|
public statusText: string;
|
||||||
public destination: string;
|
public destination: string;
|
||||||
|
|
|
@ -125,48 +125,6 @@ export class ScramjetServiceWorker extends EventTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetch({ request, clientId }: FetchEvent) {
|
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<string>((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);
|
const client = await self.clients.get(clientId);
|
||||||
|
|
||||||
return swfetch.call(this, request, client);
|
return swfetch.call(this, request, client);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue