loader-sw.ts 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. import {
  2. Awaitable,
  3. Dispatcher,
  4. IncomingRequest,
  5. Middleware,
  6. __facade_invoke__,
  7. __facade_register__,
  8. __facade_registerInternal__,
  9. } from "./common";
  10. export { __facade_register__, __facade_registerInternal__ };
  11. // Miniflare 2's `EventTarget` follows the spec and doesn't allow exceptions to
  12. // be caught by `dispatchEvent`. Instead it has a custom `ThrowingEventTarget`
  13. // class that rethrows errors from event listeners in `dispatchEvent`.
  14. // We'd like errors to be propagated to the top-level `addEventListener`, so
  15. // we'd like to use `ThrowingEventTarget`. Unfortunately, `ThrowingEventTarget`
  16. // isn't exposed on the global scope, but `WorkerGlobalScope` (which extends
  17. // `ThrowingEventTarget`) is. Therefore, we get at it in this nasty way.
  18. let __FACADE_EVENT_TARGET__: EventTarget;
  19. if ((globalThis as any).MINIFLARE) {
  20. __FACADE_EVENT_TARGET__ = new (Object.getPrototypeOf(WorkerGlobalScope))();
  21. } else {
  22. __FACADE_EVENT_TARGET__ = new EventTarget();
  23. }
  24. function __facade_isSpecialEvent__(
  25. type: string
  26. ): type is "fetch" | "scheduled" {
  27. return type === "fetch" || type === "scheduled";
  28. }
  29. const __facade__originalAddEventListener__ = globalThis.addEventListener;
  30. const __facade__originalRemoveEventListener__ = globalThis.removeEventListener;
  31. const __facade__originalDispatchEvent__ = globalThis.dispatchEvent;
  32. globalThis.addEventListener = function (type, listener, options) {
  33. if (__facade_isSpecialEvent__(type)) {
  34. __FACADE_EVENT_TARGET__.addEventListener(
  35. type,
  36. listener as EventListenerOrEventListenerObject,
  37. options
  38. );
  39. } else {
  40. __facade__originalAddEventListener__(type, listener, options);
  41. }
  42. };
  43. globalThis.removeEventListener = function (type, listener, options) {
  44. if (__facade_isSpecialEvent__(type)) {
  45. __FACADE_EVENT_TARGET__.removeEventListener(
  46. type,
  47. listener as EventListenerOrEventListenerObject,
  48. options
  49. );
  50. } else {
  51. __facade__originalRemoveEventListener__(type, listener, options);
  52. }
  53. };
  54. globalThis.dispatchEvent = function (event) {
  55. if (__facade_isSpecialEvent__(event.type)) {
  56. return __FACADE_EVENT_TARGET__.dispatchEvent(event);
  57. } else {
  58. return __facade__originalDispatchEvent__(event);
  59. }
  60. };
  61. declare global {
  62. var addMiddleware: typeof __facade_register__;
  63. var addMiddlewareInternal: typeof __facade_registerInternal__;
  64. }
  65. globalThis.addMiddleware = __facade_register__;
  66. globalThis.addMiddlewareInternal = __facade_registerInternal__;
  67. const __facade_waitUntil__ = Symbol("__facade_waitUntil__");
  68. const __facade_response__ = Symbol("__facade_response__");
  69. const __facade_dispatched__ = Symbol("__facade_dispatched__");
  70. class __Facade_ExtendableEvent__ extends Event {
  71. [__facade_waitUntil__]: Awaitable<unknown>[] = [];
  72. waitUntil(promise: Awaitable<any>) {
  73. if (!(this instanceof __Facade_ExtendableEvent__)) {
  74. throw new TypeError("Illegal invocation");
  75. }
  76. this[__facade_waitUntil__].push(promise);
  77. }
  78. }
  79. interface FetchEventInit extends EventInit {
  80. request: Request;
  81. passThroughOnException: FetchEvent["passThroughOnException"];
  82. }
  83. class __Facade_FetchEvent__ extends __Facade_ExtendableEvent__ {
  84. #request: Request;
  85. #passThroughOnException: FetchEvent["passThroughOnException"];
  86. [__facade_response__]?: Awaitable<Response>;
  87. [__facade_dispatched__] = false;
  88. constructor(type: "fetch", init: FetchEventInit) {
  89. super(type);
  90. this.#request = init.request;
  91. this.#passThroughOnException = init.passThroughOnException;
  92. }
  93. get request() {
  94. return this.#request;
  95. }
  96. respondWith(response: Awaitable<Response>) {
  97. if (!(this instanceof __Facade_FetchEvent__)) {
  98. throw new TypeError("Illegal invocation");
  99. }
  100. if (this[__facade_response__] !== undefined) {
  101. throw new DOMException(
  102. "FetchEvent.respondWith() has already been called; it can only be called once.",
  103. "InvalidStateError"
  104. );
  105. }
  106. if (this[__facade_dispatched__]) {
  107. throw new DOMException(
  108. "Too late to call FetchEvent.respondWith(). It must be called synchronously in the event handler.",
  109. "InvalidStateError"
  110. );
  111. }
  112. this.stopImmediatePropagation();
  113. this[__facade_response__] = response;
  114. }
  115. passThroughOnException() {
  116. if (!(this instanceof __Facade_FetchEvent__)) {
  117. throw new TypeError("Illegal invocation");
  118. }
  119. // Need to call native method immediately in case uncaught error thrown
  120. this.#passThroughOnException();
  121. }
  122. }
  123. interface ScheduledEventInit extends EventInit {
  124. scheduledTime: number;
  125. cron: string;
  126. noRetry: ScheduledEvent["noRetry"];
  127. }
  128. class __Facade_ScheduledEvent__ extends __Facade_ExtendableEvent__ {
  129. #scheduledTime: number;
  130. #cron: string;
  131. #noRetry: ScheduledEvent["noRetry"];
  132. constructor(type: "scheduled", init: ScheduledEventInit) {
  133. super(type);
  134. this.#scheduledTime = init.scheduledTime;
  135. this.#cron = init.cron;
  136. this.#noRetry = init.noRetry;
  137. }
  138. get scheduledTime() {
  139. return this.#scheduledTime;
  140. }
  141. get cron() {
  142. return this.#cron;
  143. }
  144. noRetry() {
  145. if (!(this instanceof __Facade_ScheduledEvent__)) {
  146. throw new TypeError("Illegal invocation");
  147. }
  148. // Need to call native method immediately in case uncaught error thrown
  149. this.#noRetry();
  150. }
  151. }
  152. __facade__originalAddEventListener__("fetch", (event) => {
  153. const ctx: ExecutionContext = {
  154. waitUntil: event.waitUntil.bind(event),
  155. passThroughOnException: event.passThroughOnException.bind(event),
  156. };
  157. const __facade_sw_dispatch__: Dispatcher = function (type, init) {
  158. if (type === "scheduled") {
  159. const facadeEvent = new __Facade_ScheduledEvent__("scheduled", {
  160. scheduledTime: Date.now(),
  161. cron: init.cron ?? "",
  162. noRetry() {},
  163. });
  164. __FACADE_EVENT_TARGET__.dispatchEvent(facadeEvent);
  165. event.waitUntil(Promise.all(facadeEvent[__facade_waitUntil__]));
  166. }
  167. };
  168. const __facade_sw_fetch__: Middleware = function (request, _env, ctx) {
  169. const facadeEvent = new __Facade_FetchEvent__("fetch", {
  170. request,
  171. passThroughOnException: ctx.passThroughOnException,
  172. });
  173. __FACADE_EVENT_TARGET__.dispatchEvent(facadeEvent);
  174. facadeEvent[__facade_dispatched__] = true;
  175. event.waitUntil(Promise.all(facadeEvent[__facade_waitUntil__]));
  176. const response = facadeEvent[__facade_response__];
  177. if (response === undefined) {
  178. throw new Error("No response!"); // TODO: proper error message
  179. }
  180. return response;
  181. };
  182. event.respondWith(
  183. __facade_invoke__(
  184. event.request as IncomingRequest,
  185. globalThis,
  186. ctx,
  187. __facade_sw_dispatch__,
  188. __facade_sw_fetch__
  189. )
  190. );
  191. });
  192. __facade__originalAddEventListener__("scheduled", (event) => {
  193. const facadeEvent = new __Facade_ScheduledEvent__("scheduled", {
  194. scheduledTime: event.scheduledTime,
  195. cron: event.cron,
  196. noRetry: event.noRetry.bind(event),
  197. });
  198. __FACADE_EVENT_TARGET__.dispatchEvent(facadeEvent);
  199. event.waitUntil(Promise.all(facadeEvent[__facade_waitUntil__]));
  200. });