123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228 |
- 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<unknown>[] = [];
- waitUntil(promise: Awaitable<any>) {
- 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<Response>;
- [__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<Response>) {
- 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__]));
- });
|