File size: 2,959 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
import { ScramjetClient } from "../client";
import { type MessageC2W } from "../../worker";
import { flagEnabled } from "../../shared";
import { rewriteUrl } from "../../shared/rewriters/url";

// we need a late order because we're mangling with addEventListener at a higher level
export const order = 2;

export const enabled = (client: ScramjetClient) =>
	flagEnabled("serviceworkers", client.url);

export function disabled(_client: ScramjetClient, _self: Self) {
	Reflect.deleteProperty(Navigator.prototype, "serviceWorker");
}

type FakeRegistrationState = {
	scope: string;
	active: ServiceWorker;
};

export default function (client: ScramjetClient, _self: Self) {
	const registrationmap: WeakMap<
		ServiceWorkerRegistration,
		FakeRegistrationState
	> = new WeakMap();
	client.Proxy("EventTarget.prototype.addEventListener", {
		apply(ctx) {
			if (registrationmap.get(ctx.this)) {
				// do nothing
				ctx.return(undefined);
			}
		},
	});

	client.Proxy("EventTarget.prototype.removeEventListener", {
		apply(ctx) {
			if (registrationmap.get(ctx.this)) {
				// do nothing
				ctx.return(undefined);
			}
		},
	});

	client.Proxy("ServiceWorkerContainer.prototype.getRegistration", {
		apply(ctx) {
			ctx.return(new Promise((resolve) => resolve(registration)));
		},
	});

	client.Proxy("ServiceWorkerContainer.prototype.getRegistrations", {
		apply(ctx) {
			ctx.return(new Promise((resolve) => resolve([registration])));
		},
	});

	client.Trap("ServiceWorkerContainer.prototype.ready", {
		get(_ctx) {
			return new Promise((resolve) => resolve(registration));
		},
	});

	client.Trap("ServiceWorkerContainer.prototype.controller", {
		get(ctx) {
			return registration?.active;
		},
	});

	client.Proxy("ServiceWorkerContainer.prototype.register", {
		apply(ctx) {
			const fakeRegistration = new EventTarget() as ServiceWorkerRegistration;
			Object.setPrototypeOf(
				fakeRegistration,
				self.ServiceWorkerRegistration.prototype
			);
			fakeRegistration.constructor = ctx.fn;
			let url = rewriteUrl(ctx.args[0], client.meta) + "?dest=serviceworker";
			if (ctx.args[1] && ctx.args[1].type === "module") {
				url += "&type=module";
			}

			const worker = client.natives.construct("SharedWorker", url);
			const handle = worker.port;
			const state: FakeRegistrationState = {
				scope: ctx.args[0],
				active: handle as ServiceWorker,
			};
			const controller = client.descriptors.get(
				"ServiceWorkerContainer.prototype.controller",
				client.serviceWorker
			);

			client.natives.call(
				"ServiceWorker.prototype.postMessage",
				controller,
				{
					scramjet$type: "registerServiceWorker",
					port: handle,
					origin: client.url.origin,
				} as MessageC2W,
				[handle]
			);

			registrationmap.set(fakeRegistration, state);
			ctx.return(new Promise((resolve) => resolve(fakeRegistration)));
		},
	});
}