From 4e87195739f2a5d9a05451b48773c8afdc680765 Mon Sep 17 00:00:00 2001 From: akiyamn Date: Sun, 24 Sep 2023 23:22:21 +1000 Subject: Initial commit (by create-cloudflare CLI) --- .../wrangler/templates/middleware/loader-sw.ts | 228 +++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 node_modules/wrangler/templates/middleware/loader-sw.ts (limited to 'node_modules/wrangler/templates/middleware/loader-sw.ts') diff --git a/node_modules/wrangler/templates/middleware/loader-sw.ts b/node_modules/wrangler/templates/middleware/loader-sw.ts new file mode 100644 index 0000000..9e465f9 --- /dev/null +++ b/node_modules/wrangler/templates/middleware/loader-sw.ts @@ -0,0 +1,228 @@ +import { + Awaitable, + Dispatcher, + IncomingRequest, + Middleware, + __facade_invoke__, + __facade_register__, + __facade_registerInternal__, +} from "./common"; +export { __facade_register__, __facade_registerInternal__ }; + +// Miniflare 2's `EventTarget` follows the spec and doesn't allow exceptions to +// be caught by `dispatchEvent`. Instead it has a custom `ThrowingEventTarget` +// class that rethrows errors from event listeners in `dispatchEvent`. +// We'd like errors to be propagated to the top-level `addEventListener`, so +// we'd like to use `ThrowingEventTarget`. Unfortunately, `ThrowingEventTarget` +// isn't exposed on the global scope, but `WorkerGlobalScope` (which extends +// `ThrowingEventTarget`) is. Therefore, we get at it in this nasty way. +let __FACADE_EVENT_TARGET__: EventTarget; +if ((globalThis as any).MINIFLARE) { + __FACADE_EVENT_TARGET__ = new (Object.getPrototypeOf(WorkerGlobalScope))(); +} else { + __FACADE_EVENT_TARGET__ = new EventTarget(); +} + +function __facade_isSpecialEvent__( + type: string +): type is "fetch" | "scheduled" { + return type === "fetch" || type === "scheduled"; +} +const __facade__originalAddEventListener__ = globalThis.addEventListener; +const __facade__originalRemoveEventListener__ = globalThis.removeEventListener; +const __facade__originalDispatchEvent__ = globalThis.dispatchEvent; + +globalThis.addEventListener = function (type, listener, options) { + if (__facade_isSpecialEvent__(type)) { + __FACADE_EVENT_TARGET__.addEventListener( + type, + listener as EventListenerOrEventListenerObject, + options + ); + } else { + __facade__originalAddEventListener__(type, listener, options); + } +}; +globalThis.removeEventListener = function (type, listener, options) { + if (__facade_isSpecialEvent__(type)) { + __FACADE_EVENT_TARGET__.removeEventListener( + type, + listener as EventListenerOrEventListenerObject, + options + ); + } else { + __facade__originalRemoveEventListener__(type, listener, options); + } +}; +globalThis.dispatchEvent = function (event) { + if (__facade_isSpecialEvent__(event.type)) { + return __FACADE_EVENT_TARGET__.dispatchEvent(event); + } else { + return __facade__originalDispatchEvent__(event); + } +}; + +declare global { + var addMiddleware: typeof __facade_register__; + var addMiddlewareInternal: typeof __facade_registerInternal__; +} +globalThis.addMiddleware = __facade_register__; +globalThis.addMiddlewareInternal = __facade_registerInternal__; + +const __facade_waitUntil__ = Symbol("__facade_waitUntil__"); +const __facade_response__ = Symbol("__facade_response__"); +const __facade_dispatched__ = Symbol("__facade_dispatched__"); + +class __Facade_ExtendableEvent__ extends Event { + [__facade_waitUntil__]: Awaitable[] = []; + + waitUntil(promise: Awaitable) { + if (!(this instanceof __Facade_ExtendableEvent__)) { + throw new TypeError("Illegal invocation"); + } + this[__facade_waitUntil__].push(promise); + } +} + +interface FetchEventInit extends EventInit { + request: Request; + passThroughOnException: FetchEvent["passThroughOnException"]; +} + +class __Facade_FetchEvent__ extends __Facade_ExtendableEvent__ { + #request: Request; + #passThroughOnException: FetchEvent["passThroughOnException"]; + [__facade_response__]?: Awaitable; + [__facade_dispatched__] = false; + + constructor(type: "fetch", init: FetchEventInit) { + super(type); + this.#request = init.request; + this.#passThroughOnException = init.passThroughOnException; + } + + get request() { + return this.#request; + } + + respondWith(response: Awaitable) { + if (!(this instanceof __Facade_FetchEvent__)) { + throw new TypeError("Illegal invocation"); + } + if (this[__facade_response__] !== undefined) { + throw new DOMException( + "FetchEvent.respondWith() has already been called; it can only be called once.", + "InvalidStateError" + ); + } + if (this[__facade_dispatched__]) { + throw new DOMException( + "Too late to call FetchEvent.respondWith(). It must be called synchronously in the event handler.", + "InvalidStateError" + ); + } + this.stopImmediatePropagation(); + this[__facade_response__] = response; + } + + passThroughOnException() { + if (!(this instanceof __Facade_FetchEvent__)) { + throw new TypeError("Illegal invocation"); + } + // Need to call native method immediately in case uncaught error thrown + this.#passThroughOnException(); + } +} + +interface ScheduledEventInit extends EventInit { + scheduledTime: number; + cron: string; + noRetry: ScheduledEvent["noRetry"]; +} + +class __Facade_ScheduledEvent__ extends __Facade_ExtendableEvent__ { + #scheduledTime: number; + #cron: string; + #noRetry: ScheduledEvent["noRetry"]; + + constructor(type: "scheduled", init: ScheduledEventInit) { + super(type); + this.#scheduledTime = init.scheduledTime; + this.#cron = init.cron; + this.#noRetry = init.noRetry; + } + + get scheduledTime() { + return this.#scheduledTime; + } + + get cron() { + return this.#cron; + } + + noRetry() { + if (!(this instanceof __Facade_ScheduledEvent__)) { + throw new TypeError("Illegal invocation"); + } + // Need to call native method immediately in case uncaught error thrown + this.#noRetry(); + } +} + +__facade__originalAddEventListener__("fetch", (event) => { + const ctx: ExecutionContext = { + waitUntil: event.waitUntil.bind(event), + passThroughOnException: event.passThroughOnException.bind(event), + }; + + const __facade_sw_dispatch__: Dispatcher = function (type, init) { + if (type === "scheduled") { + const facadeEvent = new __Facade_ScheduledEvent__("scheduled", { + scheduledTime: Date.now(), + cron: init.cron ?? "", + noRetry() {}, + }); + + __FACADE_EVENT_TARGET__.dispatchEvent(facadeEvent); + event.waitUntil(Promise.all(facadeEvent[__facade_waitUntil__])); + } + }; + + const __facade_sw_fetch__: Middleware = function (request, _env, ctx) { + const facadeEvent = new __Facade_FetchEvent__("fetch", { + request, + passThroughOnException: ctx.passThroughOnException, + }); + + __FACADE_EVENT_TARGET__.dispatchEvent(facadeEvent); + facadeEvent[__facade_dispatched__] = true; + event.waitUntil(Promise.all(facadeEvent[__facade_waitUntil__])); + + const response = facadeEvent[__facade_response__]; + if (response === undefined) { + throw new Error("No response!"); // TODO: proper error message + } + return response; + }; + + event.respondWith( + __facade_invoke__( + event.request as IncomingRequest, + globalThis, + ctx, + __facade_sw_dispatch__, + __facade_sw_fetch__ + ) + ); +}); + +__facade__originalAddEventListener__("scheduled", (event) => { + const facadeEvent = new __Facade_ScheduledEvent__("scheduled", { + scheduledTime: event.scheduledTime, + cron: event.cron, + noRetry: event.noRetry.bind(event), + }); + + __FACADE_EVENT_TARGET__.dispatchEvent(facadeEvent); + event.waitUntil(Promise.all(facadeEvent[__facade_waitUntil__])); +}); -- cgit v1.2.3