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);
		},
	});
}