File size: 3,627 Bytes
bee6636 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
import { config, flagEnabled } from "../../../shared";
import { rewriteUrl, unrewriteUrl } from "../../../shared/rewriters/url";
import { ScramjetClient } from "../../client";
export default function (client: ScramjetClient, self: Self) {
let worker;
if (self.Worker && flagEnabled("syncxhr", client.url)) {
worker = client.natives.construct("Worker", config.files.sync);
}
const ARGS = Symbol("xhr original args");
const HEADERS = Symbol("xhr headers");
client.Proxy("XMLHttpRequest.prototype.open", {
apply(ctx) {
if (ctx.args[1]) ctx.args[1] = rewriteUrl(ctx.args[1], client.meta);
if (ctx.args[2] === undefined) ctx.args[2] = true;
ctx.this[ARGS] = ctx.args;
},
});
client.Proxy("XMLHttpRequest.prototype.setRequestHeader", {
apply(ctx) {
const headers = ctx.this[HEADERS] || (ctx.this[HEADERS] = {});
headers[ctx.args[0]] = ctx.args[1];
},
});
client.Proxy("XMLHttpRequest.prototype.send", {
apply(ctx) {
const args = ctx.this[ARGS];
if (!args || args[2]) return;
if (!flagEnabled("syncxhr", client.url)) {
console.warn("ignoring request - sync xhr disabled in flags");
return ctx.return(undefined);
}
// it's a sync request
// sync xhr to service worker is not supported
// there's a nice way of polyfilling this though, we can spin on an atomic using sharedarraybuffer. this will maintain the sync behavior
//@ts-ignore
const sab = new SharedArrayBuffer(1024, { maxByteLength: 2147483647 });
const view = new DataView(sab);
client.natives.call("Worker.prototype.postMessage", worker, {
sab,
args,
headers: ctx.this[HEADERS],
body: ctx.args[0],
});
const now = performance.now();
while (view.getUint8(0) === 0) {
if (performance.now() - now > 1000) {
throw new Error("xhr timeout");
}
/* spin */
}
const status = view.getUint16(1);
const headersLength = view.getUint32(3);
const headersab = new Uint8Array(headersLength);
headersab.set(new Uint8Array(sab.slice(7, 7 + headersLength)));
const headers = new TextDecoder().decode(headersab);
const bodyLength = view.getUint32(7 + headersLength);
const bodyab = new Uint8Array(bodyLength);
bodyab.set(
new Uint8Array(
sab.slice(11 + headersLength, 11 + headersLength + bodyLength)
)
);
const body = new TextDecoder().decode(bodyab);
// these should be using proxies to not leak scram strings but who cares
client.RawTrap(ctx.this, "status", {
get() {
return status;
},
});
client.RawTrap(ctx.this, "responseText", {
get() {
return body;
},
});
client.RawTrap(ctx.this, "response", {
get() {
if (ctx.this.responseType === "arraybuffer") return bodyab.buffer;
return body;
},
});
client.RawTrap(ctx.this, "responseXML", {
get() {
const parser = new DOMParser();
return parser.parseFromString(body, "text/xml");
},
});
client.RawTrap(ctx.this, "getAllResponseHeaders", {
get() {
return () => headers;
},
});
client.RawTrap(ctx.this, "getResponseHeader", {
get() {
return (header: string) => {
const re = new RegExp(`^${header}: (.*)$`, "m");
const match = re.exec(headers);
return match ? match[1] : null;
};
},
});
// send has no return value right
ctx.return(undefined);
},
});
client.Trap("XMLHttpRequest.prototype.responseURL", {
get(ctx) {
return unrewriteUrl(ctx.get() as string);
},
});
}
|