UNPKG

4.9 kBPlain TextView Raw
1import * as flags from "https://deno.land/std/flags/mod.ts";
2
3class AdaptedFetchEvent extends Event implements FetchEvent {
4 #event: Deno.RequestEvent;
5 #request: Request;
6
7 constructor(type: string, eventInitDict?: FetchEventInit);
8 constructor(event: Deno.RequestEvent, hostname: string | null);
9 constructor(event: string | Deno.RequestEvent, hostname?: string | null | FetchEventInit) {
10 super('fetch');
11 if (typeof event === 'string' || typeof hostname === 'object') throw Error('Overload not implemented');
12 this.#event = event;
13
14 // Workaround for immutable headers.
15 // Instead of copying the request, we return a proxy that returns a custom headers object:
16 const headers = new Headers([
17 ...this.#event.request.headers,
18 ...hostname ? [['x-forwarded-for', hostname]] : [],
19 ]);
20
21 this.#request = new Proxy(this.#event.request, {
22 get(target, prop) {
23 return prop === 'headers' ? headers : Reflect.get(target, prop);
24 },
25 });
26 }
27 get request(): Request { return this.#request };
28 respondWith(r: Response | Promise<Response>): void {
29 this.#event.respondWith(r);
30 }
31 waitUntil(_f: any): void {
32 // Deno doesn't shut down the way Service Workers or CF Workers do, so this is a noop.
33 }
34
35 get clientId(): string { return '' };
36 get preloadResponse(): Promise<any> { return Promise.resolve() };
37 get replacesClientId(): string { return '' };
38 get resultingClientId(): string { return '' };
39}
40
41// TODO: Don't overwrite if already present?
42Object.defineProperty(self, 'FetchEvent', {
43 configurable: false,
44 enumerable: false,
45 writable: false,
46 value: AdaptedFetchEvent,
47});
48
49const NAME = 'Deno Fetch Event Adapter';
50
51(async () => {
52 let server: Deno.Listener;
53
54 if (!self.location) {
55 throw new Error(`${NAME}: When using Deno Fetch Event Adapter, a --location is required.`)
56 }
57
58 if (self.location.protocol === 'https:' || self.location.port === '433') {
59 const { cert: certFile, key: keyFile } = flags.parse(Deno.args, {
60 alias: {
61 cert: ['c', 'cert-file'],
62 key: ['k', 'key-file'],
63 }
64 });
65
66 if (!certFile || !keyFile) {
67 throw new Error(`${NAME}: When using HTTPS or port 443, a --cert and --key are required.`);
68 }
69
70 server = Deno.listenTls({
71 hostname: self.location.hostname,
72 port: Number(self.location.port || 443),
73 certFile,
74 keyFile,
75 });
76 } else {
77 server = Deno.listen({
78 hostname: self.location.hostname,
79 port: Number(self.location.port || 80),
80 });
81 }
82
83 for await (const conn of server) {
84 (async () => {
85 try {
86 for await (const event of Deno.serveHttp(conn)) {
87 const { hostname } = conn.remoteAddr as Deno.NetAddr;
88 self.dispatchEvent(new AdaptedFetchEvent(event, hostname));
89 }
90 } catch (error) {
91 self.dispatchEvent(new ErrorEvent('error', {
92 message: error?.message,
93 filename: import.meta.url,
94 error,
95 }));
96 }
97 })();
98 }
99})();
100
101//#region Global Types
102declare global {
103 /**
104 * Extends the lifetime of the install and activate events dispatched on the global scope as part of the
105 * service worker lifecycle. This ensures that any functional events (like FetchEvent) are not dispatched until it
106 * upgrades database schemas and deletes the outdated cache entries.
107 */
108 interface ExtendableEvent extends Event {
109 waitUntil(f: any): void;
110 }
111
112 interface ExtendableEventInit extends EventInit {
113 }
114
115 var ExtendableEvent: {
116 prototype: ExtendableEvent;
117 new(type: string, eventInitDict?: ExtendableEventInit): ExtendableEvent;
118 };
119
120 interface FetchEventInit extends ExtendableEventInit {
121 clientId?: string;
122 preloadResponse?: Promise<any>;
123 replacesClientId?: string;
124 request: Request;
125 resultingClientId?: string;
126 }
127
128 var FetchEvent: {
129 prototype: FetchEvent;
130 new(type: string, eventInitDict: FetchEventInit): FetchEvent;
131 };
132
133 /**
134 * This is the event type for fetch events dispatched on the service worker global scope.
135 * It contains information about the fetch, including the request and how the receiver will treat the response.
136 * It provides the event.respondWith() method, which allows us to provide a response to this fetch.
137 */
138 interface FetchEvent extends ExtendableEvent {
139 readonly clientId: string;
140 readonly preloadResponse: Promise<any>;
141 readonly replacesClientId: string;
142 readonly request: Request;
143 readonly resultingClientId: string;
144 respondWith(r: Response | Promise<Response>): void;
145 }
146
147 interface Window {
148 FetchEvent: new (type: string, eventInitDict: FetchEventInit) => FetchEvent;
149 }
150
151 function addEventListener(type: 'fetch', handler: (event: FetchEvent) => void): void;
152 function addEventListener(type: 'error', handler: (event: ErrorEvent) => void): void;
153}
154//#endregion