Skip to main content
Module

x/fetch_event_adapter/mod.ts

Dispatches global fetch events using Deno's native http server.
Latest
File
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. // Instead of copying the request, we return a proxy that returns a custom headers object: const headers = new Headers([ ...this.#event.request.headers, ...hostname ? [['x-forwarded-for', hostname]] : [], ]);
this.#request = new Proxy(this.#event.request, { get(target, prop) { return prop === 'headers' ? headers : Reflect.get(target, prop); }, }); } 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), }); }
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 Typesdeclare 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