-
Notifications
You must be signed in to change notification settings - Fork 2
/
mod.ts
147 lines (127 loc) · 4.62 KB
/
mod.ts
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// deno-lint-ignore-file no-var no-explicit-any no-empty-interface
import * as flags from "https://deno.land/std/flags/mod.ts";
class AdaptedFetchEvent extends Event implements FetchEvent {
#event: Deno.RequestEvent;
#request: Request;
constructor(type: string, eventInitDict?: FetchEventInit);
constructor(event: Deno.RequestEvent, hostname: string | null);
constructor(event: string | Deno.RequestEvent, hostname?: string | null | FetchEventInit) {
super('fetch');
if (typeof event === 'string' || typeof hostname === 'object') throw Error('Overload not implemented');
this.#event = event;
// Workaround for immutable headers
const request = this.#request = new Request(event.request.url, event.request);
if (hostname) request.headers.set('x-forwarded-for', hostname);
}
get request(): Request { return this.#request }
respondWith(r: Response | Promise<Response>): void {
this.#event.respondWith(r);
}
waitUntil(_f: any): void {
// Deno doesn't shut down the way Service Workers or CF Workers do, so this is a noop.
}
get clientId(): string { return '' }
get preloadResponse(): Promise<any> { return Promise.resolve() }
get replacesClientId(): string { return '' }
get resultingClientId(): string { return '' }
}
// TODO: Don't overwrite if already present?
Object.defineProperty(self, 'FetchEvent', {
configurable: false,
enumerable: false,
writable: false,
value: AdaptedFetchEvent,
});
const NAME = 'Deno Fetch Event Adapter';
(async () => {
let server: Deno.Listener;
if (!self.location) {
throw new Error(`${NAME}: When using Deno Fetch Event Adapter, a --location is required.`)
}
if (self.location.protocol === 'https:' || self.location.port === '433') {
const { cert: certFile, key: keyFile } = flags.parse(Deno.args, {
alias: {
cert: ['c', 'cert-file'],
key: ['k', 'key-file'],
}
});
if (!certFile || !keyFile) {
throw new Error(`${NAME}: When using HTTPS or port 443, a --cert and --key are required.`);
}
server = Deno.listenTls({
hostname: self.location.hostname,
port: Number(self.location.port || 443),
certFile,
keyFile,
});
} else {
server = Deno.listen({
hostname: self.location.hostname,
port: Number(self.location.port || 80),
});
}
// TODO: missing try-catch?
for await (const conn of server) {
(async () => {
try {
for await (const event of Deno.serveHttp(conn)) {
const { hostname } = conn.remoteAddr as Deno.NetAddr;
self.dispatchEvent(new AdaptedFetchEvent(event, hostname));
}
} catch (error) {
self.dispatchEvent(new ErrorEvent('error', {
message: error?.message,
filename: import.meta.url,
error,
}));
}
})();
}
})();
//#region Global Types
declare global {
/**
* Extends the lifetime of the install and activate events dispatched on the global scope as part of the
* service worker lifecycle. This ensures that any functional events (like FetchEvent) are not dispatched until it
* upgrades database schemas and deletes the outdated cache entries.
*/
interface ExtendableEvent extends Event {
waitUntil(f: any): void;
}
interface ExtendableEventInit extends EventInit {
}
var ExtendableEvent: {
prototype: ExtendableEvent;
new(type: string, eventInitDict?: ExtendableEventInit): ExtendableEvent;
};
interface FetchEventInit extends ExtendableEventInit {
clientId?: string;
preloadResponse?: Promise<any>;
replacesClientId?: string;
request: Request;
resultingClientId?: string;
}
var FetchEvent: {
prototype: FetchEvent;
new(type: string, eventInitDict: FetchEventInit): FetchEvent;
};
/**
* This is the event type for fetch events dispatched on the service worker global scope.
* It contains information about the fetch, including the request and how the receiver will treat the response.
* It provides the event.respondWith() method, which allows us to provide a response to this fetch.
*/
interface FetchEvent extends ExtendableEvent {
readonly clientId: string;
readonly preloadResponse: Promise<any>;
readonly replacesClientId: string;
readonly request: Request;
readonly resultingClientId: string;
respondWith(r: Response | Promise<Response>): void;
}
interface Window {
FetchEvent: new (type: string, eventInitDict: FetchEventInit) => FetchEvent;
}
function addEventListener(type: 'fetch', handler: (event: FetchEvent) => void): void;
function addEventListener(type: 'error', handler: (event: ErrorEvent) => void): void;
}
//#endregion