importmap_mocha-rails 0.3.1 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1146 @@
1
+ // src/interceptors/fetch/index.ts
2
+ import { invariant as invariant2 } from "outvariant";
3
+ import { DeferredPromise as DeferredPromise2 } from "@open-draft/deferred-promise";
4
+ import { until } from "@open-draft/until";
5
+
6
+ // src/glossary.ts
7
+ var IS_PATCHED_MODULE = Symbol("isPatchedModule");
8
+
9
+ // src/Interceptor.ts
10
+ import { Logger } from "@open-draft/logger";
11
+ import { Emitter } from "strict-event-emitter";
12
+ function getGlobalSymbol(symbol) {
13
+ return (
14
+ // @ts-ignore https://github.com/Microsoft/TypeScript/issues/24587
15
+ globalThis[symbol] || void 0
16
+ );
17
+ }
18
+ function setGlobalSymbol(symbol, value) {
19
+ globalThis[symbol] = value;
20
+ }
21
+ function deleteGlobalSymbol(symbol) {
22
+ delete globalThis[symbol];
23
+ }
24
+ var Interceptor = class {
25
+ constructor(symbol) {
26
+ this.symbol = symbol;
27
+ this.readyState = "INACTIVE" /* INACTIVE */;
28
+ this.emitter = new Emitter();
29
+ this.subscriptions = [];
30
+ this.logger = new Logger(symbol.description);
31
+ this.emitter.setMaxListeners(0);
32
+ this.logger.info("constructing the interceptor...");
33
+ }
34
+ /**
35
+ * Determine if this interceptor can be applied
36
+ * in the current environment.
37
+ */
38
+ checkEnvironment() {
39
+ return true;
40
+ }
41
+ /**
42
+ * Apply this interceptor to the current process.
43
+ * Returns an already running interceptor instance if it's present.
44
+ */
45
+ apply() {
46
+ const logger = this.logger.extend("apply");
47
+ logger.info("applying the interceptor...");
48
+ if (this.readyState === "APPLIED" /* APPLIED */) {
49
+ logger.info("intercepted already applied!");
50
+ return;
51
+ }
52
+ const shouldApply = this.checkEnvironment();
53
+ if (!shouldApply) {
54
+ logger.info("the interceptor cannot be applied in this environment!");
55
+ return;
56
+ }
57
+ this.readyState = "APPLYING" /* APPLYING */;
58
+ const runningInstance = this.getInstance();
59
+ if (runningInstance) {
60
+ logger.info("found a running instance, reusing...");
61
+ this.on = (event, listener) => {
62
+ logger.info('proxying the "%s" listener', event);
63
+ runningInstance.emitter.addListener(event, listener);
64
+ this.subscriptions.push(() => {
65
+ runningInstance.emitter.removeListener(event, listener);
66
+ logger.info('removed proxied "%s" listener!', event);
67
+ });
68
+ return this;
69
+ };
70
+ this.readyState = "APPLIED" /* APPLIED */;
71
+ return;
72
+ }
73
+ logger.info("no running instance found, setting up a new instance...");
74
+ this.setup();
75
+ this.setInstance();
76
+ this.readyState = "APPLIED" /* APPLIED */;
77
+ }
78
+ /**
79
+ * Setup the module augments and stubs necessary for this interceptor.
80
+ * This method is not run if there's a running interceptor instance
81
+ * to prevent instantiating an interceptor multiple times.
82
+ */
83
+ setup() {
84
+ }
85
+ /**
86
+ * Listen to the interceptor's public events.
87
+ */
88
+ on(event, listener) {
89
+ const logger = this.logger.extend("on");
90
+ if (this.readyState === "DISPOSING" /* DISPOSING */ || this.readyState === "DISPOSED" /* DISPOSED */) {
91
+ logger.info("cannot listen to events, already disposed!");
92
+ return this;
93
+ }
94
+ logger.info('adding "%s" event listener:', event, listener);
95
+ this.emitter.on(event, listener);
96
+ return this;
97
+ }
98
+ once(event, listener) {
99
+ this.emitter.once(event, listener);
100
+ return this;
101
+ }
102
+ off(event, listener) {
103
+ this.emitter.off(event, listener);
104
+ return this;
105
+ }
106
+ removeAllListeners(event) {
107
+ this.emitter.removeAllListeners(event);
108
+ return this;
109
+ }
110
+ /**
111
+ * Disposes of any side-effects this interceptor has introduced.
112
+ */
113
+ dispose() {
114
+ const logger = this.logger.extend("dispose");
115
+ if (this.readyState === "DISPOSED" /* DISPOSED */) {
116
+ logger.info("cannot dispose, already disposed!");
117
+ return;
118
+ }
119
+ logger.info("disposing the interceptor...");
120
+ this.readyState = "DISPOSING" /* DISPOSING */;
121
+ if (!this.getInstance()) {
122
+ logger.info("no interceptors running, skipping dispose...");
123
+ return;
124
+ }
125
+ this.clearInstance();
126
+ logger.info("global symbol deleted:", getGlobalSymbol(this.symbol));
127
+ if (this.subscriptions.length > 0) {
128
+ logger.info("disposing of %d subscriptions...", this.subscriptions.length);
129
+ for (const dispose of this.subscriptions) {
130
+ dispose();
131
+ }
132
+ this.subscriptions = [];
133
+ logger.info("disposed of all subscriptions!", this.subscriptions.length);
134
+ }
135
+ this.emitter.removeAllListeners();
136
+ logger.info("destroyed the listener!");
137
+ this.readyState = "DISPOSED" /* DISPOSED */;
138
+ }
139
+ getInstance() {
140
+ var _a;
141
+ const instance = getGlobalSymbol(this.symbol);
142
+ this.logger.info("retrieved global instance:", (_a = instance == null ? void 0 : instance.constructor) == null ? void 0 : _a.name);
143
+ return instance;
144
+ }
145
+ setInstance() {
146
+ setGlobalSymbol(this.symbol, this);
147
+ this.logger.info("set global instance!", this.symbol.description);
148
+ }
149
+ clearInstance() {
150
+ deleteGlobalSymbol(this.symbol);
151
+ this.logger.info("cleared global instance!", this.symbol.description);
152
+ }
153
+ };
154
+
155
+ // src/utils/uuid.ts
156
+ function uuidv4() {
157
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
158
+ const r = Math.random() * 16 | 0;
159
+ const v = c == "x" ? r : r & 3 | 8;
160
+ return v.toString(16);
161
+ });
162
+ }
163
+
164
+ // src/utils/RequestController.ts
165
+ import { invariant } from "outvariant";
166
+ import { DeferredPromise } from "@open-draft/deferred-promise";
167
+ var RequestController = class {
168
+ constructor(request) {
169
+ this.request = request;
170
+ this.responsePromise = new DeferredPromise();
171
+ }
172
+ respondWith(response) {
173
+ invariant(
174
+ this.responsePromise.state === "pending",
175
+ 'Failed to respond to "%s %s" request: the "request" event has already been responded to.',
176
+ this.request.method,
177
+ this.request.url
178
+ );
179
+ this.responsePromise.resolve(response);
180
+ }
181
+ };
182
+
183
+ // src/utils/toInteractiveRequest.ts
184
+ function toInteractiveRequest(request) {
185
+ const requestController = new RequestController(request);
186
+ Reflect.set(
187
+ request,
188
+ "respondWith",
189
+ requestController.respondWith.bind(requestController)
190
+ );
191
+ return {
192
+ interactiveRequest: request,
193
+ requestController
194
+ };
195
+ }
196
+
197
+ // src/utils/emitAsync.ts
198
+ async function emitAsync(emitter, eventName, ...data) {
199
+ const listners = emitter.listeners(eventName);
200
+ if (listners.length === 0) {
201
+ return;
202
+ }
203
+ for (const listener of listners) {
204
+ await listener.apply(emitter, data);
205
+ }
206
+ }
207
+
208
+ // src/utils/isPropertyAccessible.ts
209
+ function isPropertyAccessible(obj, key) {
210
+ try {
211
+ obj[key];
212
+ return true;
213
+ } catch (e) {
214
+ return false;
215
+ }
216
+ }
217
+
218
+ // src/interceptors/fetch/index.ts
219
+ var _FetchInterceptor = class extends Interceptor {
220
+ constructor() {
221
+ super(_FetchInterceptor.symbol);
222
+ }
223
+ checkEnvironment() {
224
+ return typeof globalThis !== "undefined" && typeof globalThis.fetch !== "undefined";
225
+ }
226
+ setup() {
227
+ const pureFetch = globalThis.fetch;
228
+ invariant2(
229
+ !pureFetch[IS_PATCHED_MODULE],
230
+ 'Failed to patch the "fetch" module: already patched.'
231
+ );
232
+ globalThis.fetch = async (input, init) => {
233
+ var _a;
234
+ const requestId = uuidv4();
235
+ const request = new Request(input, init);
236
+ this.logger.info("[%s] %s", request.method, request.url);
237
+ const { interactiveRequest, requestController } = toInteractiveRequest(request);
238
+ this.logger.info(
239
+ 'emitting the "request" event for %d listener(s)...',
240
+ this.emitter.listenerCount("request")
241
+ );
242
+ this.emitter.once("request", ({ requestId: pendingRequestId }) => {
243
+ if (pendingRequestId !== requestId) {
244
+ return;
245
+ }
246
+ if (requestController.responsePromise.state === "pending") {
247
+ requestController.responsePromise.resolve(void 0);
248
+ }
249
+ });
250
+ this.logger.info("awaiting for the mocked response...");
251
+ const signal = interactiveRequest.signal;
252
+ const requestAborted = new DeferredPromise2();
253
+ signal.addEventListener(
254
+ "abort",
255
+ () => {
256
+ requestAborted.reject(signal.reason);
257
+ },
258
+ { once: true }
259
+ );
260
+ const resolverResult = await until(async () => {
261
+ const listenersFinished = emitAsync(this.emitter, "request", {
262
+ request: interactiveRequest,
263
+ requestId
264
+ });
265
+ await Promise.race([
266
+ requestAborted,
267
+ // Put the listeners invocation Promise in the same race condition
268
+ // with the request abort Promise because otherwise awaiting the listeners
269
+ // would always yield some response (or undefined).
270
+ listenersFinished,
271
+ requestController.responsePromise
272
+ ]);
273
+ this.logger.info("all request listeners have been resolved!");
274
+ const mockedResponse2 = await requestController.responsePromise;
275
+ this.logger.info("event.respondWith called with:", mockedResponse2);
276
+ return mockedResponse2;
277
+ });
278
+ if (requestAborted.state === "rejected") {
279
+ return Promise.reject(requestAborted.rejectionReason);
280
+ }
281
+ if (resolverResult.error) {
282
+ return Promise.reject(createNetworkError(resolverResult.error));
283
+ }
284
+ const mockedResponse = resolverResult.data;
285
+ if (mockedResponse && !((_a = request.signal) == null ? void 0 : _a.aborted)) {
286
+ this.logger.info("received mocked response:", mockedResponse);
287
+ if (isPropertyAccessible(mockedResponse, "type") && mockedResponse.type === "error") {
288
+ this.logger.info(
289
+ "received a network error response, rejecting the request promise..."
290
+ );
291
+ return Promise.reject(createNetworkError(mockedResponse));
292
+ }
293
+ const responseClone = mockedResponse.clone();
294
+ this.emitter.emit("response", {
295
+ response: responseClone,
296
+ isMockedResponse: true,
297
+ request: interactiveRequest,
298
+ requestId
299
+ });
300
+ const response = new Response(mockedResponse.body, mockedResponse);
301
+ Object.defineProperty(response, "url", {
302
+ writable: false,
303
+ enumerable: true,
304
+ configurable: false,
305
+ value: request.url
306
+ });
307
+ return response;
308
+ }
309
+ this.logger.info("no mocked response received!");
310
+ return pureFetch(request).then((response) => {
311
+ const responseClone = response.clone();
312
+ this.logger.info("original fetch performed", responseClone);
313
+ this.emitter.emit("response", {
314
+ response: responseClone,
315
+ isMockedResponse: false,
316
+ request: interactiveRequest,
317
+ requestId
318
+ });
319
+ return response;
320
+ });
321
+ };
322
+ Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {
323
+ enumerable: true,
324
+ configurable: true,
325
+ value: true
326
+ });
327
+ this.subscriptions.push(() => {
328
+ Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {
329
+ value: void 0
330
+ });
331
+ globalThis.fetch = pureFetch;
332
+ this.logger.info(
333
+ 'restored native "globalThis.fetch"!',
334
+ globalThis.fetch.name
335
+ );
336
+ });
337
+ }
338
+ };
339
+ var FetchInterceptor = _FetchInterceptor;
340
+ FetchInterceptor.symbol = Symbol("fetch");
341
+ function createNetworkError(cause) {
342
+ return Object.assign(new TypeError("Failed to fetch"), {
343
+ cause
344
+ });
345
+ }
346
+
347
+ // src/interceptors/XMLHttpRequest/index.ts
348
+ import { invariant as invariant4 } from "outvariant";
349
+
350
+ // src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts
351
+ import { until as until2 } from "@open-draft/until";
352
+
353
+ // src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts
354
+ import { invariant as invariant3 } from "outvariant";
355
+ import { isNodeProcess } from "is-node-process";
356
+
357
+ // src/interceptors/XMLHttpRequest/utils/concatArrayBuffer.ts
358
+ function concatArrayBuffer(left, right) {
359
+ const result = new Uint8Array(left.byteLength + right.byteLength);
360
+ result.set(left, 0);
361
+ result.set(right, left.byteLength);
362
+ return result;
363
+ }
364
+
365
+ // src/interceptors/XMLHttpRequest/polyfills/EventPolyfill.ts
366
+ var EventPolyfill = class {
367
+ constructor(type, options) {
368
+ this.AT_TARGET = 0;
369
+ this.BUBBLING_PHASE = 0;
370
+ this.CAPTURING_PHASE = 0;
371
+ this.NONE = 0;
372
+ this.type = "";
373
+ this.srcElement = null;
374
+ this.currentTarget = null;
375
+ this.eventPhase = 0;
376
+ this.isTrusted = true;
377
+ this.composed = false;
378
+ this.cancelable = true;
379
+ this.defaultPrevented = false;
380
+ this.bubbles = true;
381
+ this.lengthComputable = true;
382
+ this.loaded = 0;
383
+ this.total = 0;
384
+ this.cancelBubble = false;
385
+ this.returnValue = true;
386
+ this.type = type;
387
+ this.target = (options == null ? void 0 : options.target) || null;
388
+ this.currentTarget = (options == null ? void 0 : options.currentTarget) || null;
389
+ this.timeStamp = Date.now();
390
+ }
391
+ composedPath() {
392
+ return [];
393
+ }
394
+ initEvent(type, bubbles, cancelable) {
395
+ this.type = type;
396
+ this.bubbles = !!bubbles;
397
+ this.cancelable = !!cancelable;
398
+ }
399
+ preventDefault() {
400
+ this.defaultPrevented = true;
401
+ }
402
+ stopPropagation() {
403
+ }
404
+ stopImmediatePropagation() {
405
+ }
406
+ };
407
+
408
+ // src/interceptors/XMLHttpRequest/polyfills/ProgressEventPolyfill.ts
409
+ var ProgressEventPolyfill = class extends EventPolyfill {
410
+ constructor(type, init) {
411
+ super(type);
412
+ this.lengthComputable = (init == null ? void 0 : init.lengthComputable) || false;
413
+ this.composed = (init == null ? void 0 : init.composed) || false;
414
+ this.loaded = (init == null ? void 0 : init.loaded) || 0;
415
+ this.total = (init == null ? void 0 : init.total) || 0;
416
+ }
417
+ };
418
+
419
+ // src/interceptors/XMLHttpRequest/utils/createEvent.ts
420
+ var SUPPORTS_PROGRESS_EVENT = typeof ProgressEvent !== "undefined";
421
+ function createEvent(target, type, init) {
422
+ const progressEvents = [
423
+ "error",
424
+ "progress",
425
+ "loadstart",
426
+ "loadend",
427
+ "load",
428
+ "timeout",
429
+ "abort"
430
+ ];
431
+ const ProgressEventClass = SUPPORTS_PROGRESS_EVENT ? ProgressEvent : ProgressEventPolyfill;
432
+ const event = progressEvents.includes(type) ? new ProgressEventClass(type, {
433
+ lengthComputable: true,
434
+ loaded: (init == null ? void 0 : init.loaded) || 0,
435
+ total: (init == null ? void 0 : init.total) || 0
436
+ }) : new EventPolyfill(type, {
437
+ target,
438
+ currentTarget: target
439
+ });
440
+ return event;
441
+ }
442
+
443
+ // src/utils/bufferUtils.ts
444
+ var encoder = new TextEncoder();
445
+ function encodeBuffer(text) {
446
+ return encoder.encode(text);
447
+ }
448
+ function decodeBuffer(buffer, encoding) {
449
+ const decoder = new TextDecoder(encoding);
450
+ return decoder.decode(buffer);
451
+ }
452
+ function toArrayBuffer(array) {
453
+ return array.buffer.slice(
454
+ array.byteOffset,
455
+ array.byteOffset + array.byteLength
456
+ );
457
+ }
458
+
459
+ // src/utils/findPropertySource.ts
460
+ function findPropertySource(target, propertyName) {
461
+ if (!(propertyName in target)) {
462
+ return null;
463
+ }
464
+ const hasProperty = Object.prototype.hasOwnProperty.call(target, propertyName);
465
+ if (hasProperty) {
466
+ return target;
467
+ }
468
+ const prototype = Reflect.getPrototypeOf(target);
469
+ return prototype ? findPropertySource(prototype, propertyName) : null;
470
+ }
471
+
472
+ // src/utils/createProxy.ts
473
+ function createProxy(target, options) {
474
+ const proxy = new Proxy(target, optionsToProxyHandler(options));
475
+ return proxy;
476
+ }
477
+ function optionsToProxyHandler(options) {
478
+ const { constructorCall, methodCall, getProperty, setProperty } = options;
479
+ const handler = {};
480
+ if (typeof constructorCall !== "undefined") {
481
+ handler.construct = function(target, args, newTarget) {
482
+ const next = Reflect.construct.bind(null, target, args, newTarget);
483
+ return constructorCall.call(newTarget, args, next);
484
+ };
485
+ }
486
+ handler.set = function(target, propertyName, nextValue) {
487
+ const next = () => {
488
+ const propertySource = findPropertySource(target, propertyName) || target;
489
+ const ownDescriptors = Reflect.getOwnPropertyDescriptor(
490
+ propertySource,
491
+ propertyName
492
+ );
493
+ if (typeof (ownDescriptors == null ? void 0 : ownDescriptors.set) !== "undefined") {
494
+ ownDescriptors.set.apply(target, [nextValue]);
495
+ return true;
496
+ }
497
+ return Reflect.defineProperty(propertySource, propertyName, {
498
+ writable: true,
499
+ enumerable: true,
500
+ configurable: true,
501
+ value: nextValue
502
+ });
503
+ };
504
+ if (typeof setProperty !== "undefined") {
505
+ return setProperty.call(target, [propertyName, nextValue], next);
506
+ }
507
+ return next();
508
+ };
509
+ handler.get = function(target, propertyName, receiver) {
510
+ const next = () => target[propertyName];
511
+ const value = typeof getProperty !== "undefined" ? getProperty.call(target, [propertyName, receiver], next) : next();
512
+ if (typeof value === "function") {
513
+ return (...args) => {
514
+ const next2 = value.bind(target, ...args);
515
+ if (typeof methodCall !== "undefined") {
516
+ return methodCall.call(target, [propertyName, args], next2);
517
+ }
518
+ return next2();
519
+ };
520
+ }
521
+ return value;
522
+ };
523
+ return handler;
524
+ }
525
+
526
+ // src/interceptors/XMLHttpRequest/utils/isDomParserSupportedType.ts
527
+ function isDomParserSupportedType(type) {
528
+ const supportedTypes = [
529
+ "application/xhtml+xml",
530
+ "application/xml",
531
+ "image/svg+xml",
532
+ "text/html",
533
+ "text/xml"
534
+ ];
535
+ return supportedTypes.some((supportedType) => {
536
+ return type.startsWith(supportedType);
537
+ });
538
+ }
539
+
540
+ // src/utils/parseJson.ts
541
+ function parseJson(data) {
542
+ try {
543
+ const json = JSON.parse(data);
544
+ return json;
545
+ } catch (_) {
546
+ return null;
547
+ }
548
+ }
549
+
550
+ // src/utils/responseUtils.ts
551
+ var RESPONSE_STATUS_CODES_WITHOUT_BODY = /* @__PURE__ */ new Set([
552
+ 101,
553
+ 103,
554
+ 204,
555
+ 205,
556
+ 304
557
+ ]);
558
+ function isResponseWithoutBody(status) {
559
+ return RESPONSE_STATUS_CODES_WITHOUT_BODY.has(status);
560
+ }
561
+
562
+ // src/interceptors/XMLHttpRequest/utils/createResponse.ts
563
+ function createResponse(request, body) {
564
+ const responseBodyOrNull = isResponseWithoutBody(request.status) ? null : body;
565
+ return new Response(responseBodyOrNull, {
566
+ status: request.status,
567
+ statusText: request.statusText,
568
+ headers: createHeadersFromXMLHttpReqestHeaders(
569
+ request.getAllResponseHeaders()
570
+ )
571
+ });
572
+ }
573
+ function createHeadersFromXMLHttpReqestHeaders(headersString) {
574
+ const headers = new Headers();
575
+ const lines = headersString.split(/[\r\n]+/);
576
+ for (const line of lines) {
577
+ if (line.trim() === "") {
578
+ continue;
579
+ }
580
+ const [name, ...parts] = line.split(": ");
581
+ const value = parts.join(": ");
582
+ headers.append(name, value);
583
+ }
584
+ return headers;
585
+ }
586
+
587
+ // src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts
588
+ var IS_MOCKED_RESPONSE = Symbol("isMockedResponse");
589
+ var IS_NODE = isNodeProcess();
590
+ var XMLHttpRequestController = class {
591
+ constructor(initialRequest, logger) {
592
+ this.initialRequest = initialRequest;
593
+ this.logger = logger;
594
+ this.method = "GET";
595
+ this.url = null;
596
+ this.events = /* @__PURE__ */ new Map();
597
+ this.requestId = uuidv4();
598
+ this.requestHeaders = new Headers();
599
+ this.responseBuffer = new Uint8Array();
600
+ this.request = createProxy(initialRequest, {
601
+ setProperty: ([propertyName, nextValue], invoke) => {
602
+ switch (propertyName) {
603
+ case "ontimeout": {
604
+ const eventName = propertyName.slice(
605
+ 2
606
+ );
607
+ this.request.addEventListener(eventName, nextValue);
608
+ return invoke();
609
+ }
610
+ default: {
611
+ return invoke();
612
+ }
613
+ }
614
+ },
615
+ methodCall: ([methodName, args], invoke) => {
616
+ var _a;
617
+ switch (methodName) {
618
+ case "open": {
619
+ const [method, url] = args;
620
+ if (typeof url === "undefined") {
621
+ this.method = "GET";
622
+ this.url = toAbsoluteUrl(method);
623
+ } else {
624
+ this.method = method;
625
+ this.url = toAbsoluteUrl(url);
626
+ }
627
+ this.logger = this.logger.extend(`${this.method} ${this.url.href}`);
628
+ this.logger.info("open", this.method, this.url.href);
629
+ return invoke();
630
+ }
631
+ case "addEventListener": {
632
+ const [eventName, listener] = args;
633
+ this.registerEvent(eventName, listener);
634
+ this.logger.info("addEventListener", eventName, listener);
635
+ return invoke();
636
+ }
637
+ case "setRequestHeader": {
638
+ const [name, value] = args;
639
+ this.requestHeaders.set(name, value);
640
+ this.logger.info("setRequestHeader", name, value);
641
+ return invoke();
642
+ }
643
+ case "send": {
644
+ const [body] = args;
645
+ if (body != null) {
646
+ this.requestBody = typeof body === "string" ? encodeBuffer(body) : body;
647
+ }
648
+ this.request.addEventListener("load", () => {
649
+ if (typeof this.onResponse !== "undefined") {
650
+ const fetchResponse = createResponse(
651
+ this.request,
652
+ /**
653
+ * The `response` property is the right way to read
654
+ * the ambiguous response body, as the request's "responseType" may differ.
655
+ * @see https://xhr.spec.whatwg.org/#the-response-attribute
656
+ */
657
+ this.request.response
658
+ );
659
+ this.onResponse.call(this, {
660
+ response: fetchResponse,
661
+ isMockedResponse: IS_MOCKED_RESPONSE in this.request,
662
+ request: fetchRequest,
663
+ requestId: this.requestId
664
+ });
665
+ }
666
+ });
667
+ const fetchRequest = this.toFetchApiRequest();
668
+ const onceRequestSettled = ((_a = this.onRequest) == null ? void 0 : _a.call(this, {
669
+ request: fetchRequest,
670
+ requestId: this.requestId
671
+ })) || Promise.resolve();
672
+ onceRequestSettled.finally(() => {
673
+ if (this.request.readyState < this.request.LOADING) {
674
+ this.logger.info(
675
+ "request callback settled but request has not been handled (readystate %d), performing as-is...",
676
+ this.request.readyState
677
+ );
678
+ if (IS_NODE) {
679
+ this.request.setRequestHeader("X-Request-Id", this.requestId);
680
+ }
681
+ return invoke();
682
+ }
683
+ });
684
+ break;
685
+ }
686
+ default: {
687
+ return invoke();
688
+ }
689
+ }
690
+ }
691
+ });
692
+ }
693
+ registerEvent(eventName, listener) {
694
+ const prevEvents = this.events.get(eventName) || [];
695
+ const nextEvents = prevEvents.concat(listener);
696
+ this.events.set(eventName, nextEvents);
697
+ this.logger.info('registered event "%s"', eventName, listener);
698
+ }
699
+ /**
700
+ * Responds to the current request with the given
701
+ * Fetch API `Response` instance.
702
+ */
703
+ respondWith(response) {
704
+ this.logger.info(
705
+ "responding with a mocked response: %d %s",
706
+ response.status,
707
+ response.statusText
708
+ );
709
+ define(this.request, IS_MOCKED_RESPONSE, true);
710
+ define(this.request, "status", response.status);
711
+ define(this.request, "statusText", response.statusText);
712
+ define(this.request, "responseURL", this.url.href);
713
+ this.request.getResponseHeader = new Proxy(this.request.getResponseHeader, {
714
+ apply: (_, __, args) => {
715
+ this.logger.info("getResponseHeader", args[0]);
716
+ if (this.request.readyState < this.request.HEADERS_RECEIVED) {
717
+ this.logger.info("headers not received yet, returning null");
718
+ return null;
719
+ }
720
+ const headerValue = response.headers.get(args[0]);
721
+ this.logger.info(
722
+ 'resolved response header "%s" to',
723
+ args[0],
724
+ headerValue
725
+ );
726
+ return headerValue;
727
+ }
728
+ });
729
+ this.request.getAllResponseHeaders = new Proxy(
730
+ this.request.getAllResponseHeaders,
731
+ {
732
+ apply: () => {
733
+ this.logger.info("getAllResponseHeaders");
734
+ if (this.request.readyState < this.request.HEADERS_RECEIVED) {
735
+ this.logger.info("headers not received yet, returning empty string");
736
+ return "";
737
+ }
738
+ const headersList = Array.from(response.headers.entries());
739
+ const allHeaders = headersList.map(([headerName, headerValue]) => {
740
+ return `${headerName}: ${headerValue}`;
741
+ }).join("\r\n");
742
+ this.logger.info("resolved all response headers to", allHeaders);
743
+ return allHeaders;
744
+ }
745
+ }
746
+ );
747
+ Object.defineProperties(this.request, {
748
+ response: {
749
+ enumerable: true,
750
+ configurable: false,
751
+ get: () => this.response
752
+ },
753
+ responseText: {
754
+ enumerable: true,
755
+ configurable: false,
756
+ get: () => this.responseText
757
+ },
758
+ responseXML: {
759
+ enumerable: true,
760
+ configurable: false,
761
+ get: () => this.responseXML
762
+ }
763
+ });
764
+ const totalResponseBodyLength = response.headers.has("Content-Length") ? Number(response.headers.get("Content-Length")) : (
765
+ /**
766
+ * @todo Infer the response body length from the response body.
767
+ */
768
+ void 0
769
+ );
770
+ this.logger.info("calculated response body length", totalResponseBodyLength);
771
+ this.trigger("loadstart", {
772
+ loaded: 0,
773
+ total: totalResponseBodyLength
774
+ });
775
+ this.setReadyState(this.request.HEADERS_RECEIVED);
776
+ this.setReadyState(this.request.LOADING);
777
+ const finalizeResponse = () => {
778
+ this.logger.info("finalizing the mocked response...");
779
+ this.setReadyState(this.request.DONE);
780
+ this.trigger("load", {
781
+ loaded: this.responseBuffer.byteLength,
782
+ total: totalResponseBodyLength
783
+ });
784
+ this.trigger("loadend", {
785
+ loaded: this.responseBuffer.byteLength,
786
+ total: totalResponseBodyLength
787
+ });
788
+ };
789
+ if (response.body) {
790
+ this.logger.info("mocked response has body, streaming...");
791
+ const reader = response.body.getReader();
792
+ const readNextResponseBodyChunk = async () => {
793
+ const { value, done } = await reader.read();
794
+ if (done) {
795
+ this.logger.info("response body stream done!");
796
+ finalizeResponse();
797
+ return;
798
+ }
799
+ if (value) {
800
+ this.logger.info("read response body chunk:", value);
801
+ this.responseBuffer = concatArrayBuffer(this.responseBuffer, value);
802
+ this.trigger("progress", {
803
+ loaded: this.responseBuffer.byteLength,
804
+ total: totalResponseBodyLength
805
+ });
806
+ }
807
+ readNextResponseBodyChunk();
808
+ };
809
+ readNextResponseBodyChunk();
810
+ } else {
811
+ finalizeResponse();
812
+ }
813
+ }
814
+ responseBufferToText() {
815
+ return decodeBuffer(this.responseBuffer);
816
+ }
817
+ get response() {
818
+ this.logger.info(
819
+ "getResponse (responseType: %s)",
820
+ this.request.responseType
821
+ );
822
+ if (this.request.readyState !== this.request.DONE) {
823
+ return null;
824
+ }
825
+ switch (this.request.responseType) {
826
+ case "json": {
827
+ const responseJson = parseJson(this.responseBufferToText());
828
+ this.logger.info("resolved response JSON", responseJson);
829
+ return responseJson;
830
+ }
831
+ case "arraybuffer": {
832
+ const arrayBuffer = toArrayBuffer(this.responseBuffer);
833
+ this.logger.info("resolved response ArrayBuffer", arrayBuffer);
834
+ return arrayBuffer;
835
+ }
836
+ case "blob": {
837
+ const mimeType = this.request.getResponseHeader("Content-Type") || "text/plain";
838
+ const responseBlob = new Blob([this.responseBufferToText()], {
839
+ type: mimeType
840
+ });
841
+ this.logger.info(
842
+ "resolved response Blob (mime type: %s)",
843
+ responseBlob,
844
+ mimeType
845
+ );
846
+ return responseBlob;
847
+ }
848
+ default: {
849
+ const responseText = this.responseBufferToText();
850
+ this.logger.info(
851
+ 'resolving "%s" response type as text',
852
+ this.request.responseType,
853
+ responseText
854
+ );
855
+ return responseText;
856
+ }
857
+ }
858
+ }
859
+ get responseText() {
860
+ invariant3(
861
+ this.request.responseType === "" || this.request.responseType === "text",
862
+ "InvalidStateError: The object is in invalid state."
863
+ );
864
+ if (this.request.readyState !== this.request.LOADING && this.request.readyState !== this.request.DONE) {
865
+ return "";
866
+ }
867
+ const responseText = this.responseBufferToText();
868
+ this.logger.info('getResponseText: "%s"', responseText);
869
+ return responseText;
870
+ }
871
+ get responseXML() {
872
+ invariant3(
873
+ this.request.responseType === "" || this.request.responseType === "document",
874
+ "InvalidStateError: The object is in invalid state."
875
+ );
876
+ if (this.request.readyState !== this.request.DONE) {
877
+ return null;
878
+ }
879
+ const contentType = this.request.getResponseHeader("Content-Type") || "";
880
+ if (typeof DOMParser === "undefined") {
881
+ console.warn(
882
+ "Cannot retrieve XMLHttpRequest response body as XML: DOMParser is not defined. You are likely using an environment that is not browser or does not polyfill browser globals correctly."
883
+ );
884
+ return null;
885
+ }
886
+ if (isDomParserSupportedType(contentType)) {
887
+ return new DOMParser().parseFromString(
888
+ this.responseBufferToText(),
889
+ contentType
890
+ );
891
+ }
892
+ return null;
893
+ }
894
+ errorWith(error) {
895
+ this.logger.info("responding with an error");
896
+ this.setReadyState(this.request.DONE);
897
+ this.trigger("error");
898
+ this.trigger("loadend");
899
+ }
900
+ /**
901
+ * Transitions this request's `readyState` to the given one.
902
+ */
903
+ setReadyState(nextReadyState) {
904
+ this.logger.info(
905
+ "setReadyState: %d -> %d",
906
+ this.request.readyState,
907
+ nextReadyState
908
+ );
909
+ if (this.request.readyState === nextReadyState) {
910
+ this.logger.info("ready state identical, skipping transition...");
911
+ return;
912
+ }
913
+ define(this.request, "readyState", nextReadyState);
914
+ this.logger.info("set readyState to: %d", nextReadyState);
915
+ if (nextReadyState !== this.request.UNSENT) {
916
+ this.logger.info('triggerring "readystatechange" event...');
917
+ this.trigger("readystatechange");
918
+ }
919
+ }
920
+ /**
921
+ * Triggers given event on the `XMLHttpRequest` instance.
922
+ */
923
+ trigger(eventName, options) {
924
+ const callback = this.request[`on${eventName}`];
925
+ const event = createEvent(this.request, eventName, options);
926
+ this.logger.info('trigger "%s"', eventName, options || "");
927
+ if (typeof callback === "function") {
928
+ this.logger.info('found a direct "%s" callback, calling...', eventName);
929
+ callback.call(this.request, event);
930
+ }
931
+ for (const [registeredEventName, listeners] of this.events) {
932
+ if (registeredEventName === eventName) {
933
+ this.logger.info(
934
+ 'found %d listener(s) for "%s" event, calling...',
935
+ listeners.length,
936
+ eventName
937
+ );
938
+ listeners.forEach((listener) => listener.call(this.request, event));
939
+ }
940
+ }
941
+ }
942
+ /**
943
+ * Converts this `XMLHttpRequest` instance into a Fetch API `Request` instance.
944
+ */
945
+ toFetchApiRequest() {
946
+ this.logger.info("converting request to a Fetch API Request...");
947
+ const fetchRequest = new Request(this.url.href, {
948
+ method: this.method,
949
+ headers: this.requestHeaders,
950
+ /**
951
+ * @see https://xhr.spec.whatwg.org/#cross-origin-credentials
952
+ */
953
+ credentials: this.request.withCredentials ? "include" : "same-origin",
954
+ body: ["GET", "HEAD"].includes(this.method) ? null : this.requestBody
955
+ });
956
+ const proxyHeaders = createProxy(fetchRequest.headers, {
957
+ methodCall: ([methodName, args], invoke) => {
958
+ switch (methodName) {
959
+ case "append":
960
+ case "set": {
961
+ const [headerName, headerValue] = args;
962
+ this.request.setRequestHeader(headerName, headerValue);
963
+ break;
964
+ }
965
+ case "delete": {
966
+ const [headerName] = args;
967
+ console.warn(
968
+ `XMLHttpRequest: Cannot remove a "${headerName}" header from the Fetch API representation of the "${fetchRequest.method} ${fetchRequest.url}" request. XMLHttpRequest headers cannot be removed.`
969
+ );
970
+ break;
971
+ }
972
+ }
973
+ return invoke();
974
+ }
975
+ });
976
+ define(fetchRequest, "headers", proxyHeaders);
977
+ this.logger.info("converted request to a Fetch API Request!", fetchRequest);
978
+ return fetchRequest;
979
+ }
980
+ };
981
+ function toAbsoluteUrl(url) {
982
+ if (typeof location === "undefined") {
983
+ return new URL(url);
984
+ }
985
+ return new URL(url.toString(), location.href);
986
+ }
987
+ function define(target, property, value) {
988
+ Reflect.defineProperty(target, property, {
989
+ // Ensure writable properties to allow redefining readonly properties.
990
+ writable: true,
991
+ enumerable: true,
992
+ value
993
+ });
994
+ }
995
+
996
+ // src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts
997
+ function createXMLHttpRequestProxy({
998
+ emitter,
999
+ logger
1000
+ }) {
1001
+ const XMLHttpRequestProxy = new Proxy(globalThis.XMLHttpRequest, {
1002
+ construct(target, args, newTarget) {
1003
+ logger.info("constructed new XMLHttpRequest");
1004
+ const originalRequest = Reflect.construct(target, args, newTarget);
1005
+ const prototypeDescriptors = Object.getOwnPropertyDescriptors(
1006
+ target.prototype
1007
+ );
1008
+ for (const propertyName in prototypeDescriptors) {
1009
+ Reflect.defineProperty(
1010
+ originalRequest,
1011
+ propertyName,
1012
+ prototypeDescriptors[propertyName]
1013
+ );
1014
+ }
1015
+ const xhrRequestController = new XMLHttpRequestController(
1016
+ originalRequest,
1017
+ logger
1018
+ );
1019
+ xhrRequestController.onRequest = async function({ request, requestId }) {
1020
+ const { interactiveRequest, requestController } = toInteractiveRequest(request);
1021
+ this.logger.info("awaiting mocked response...");
1022
+ emitter.once("request", ({ requestId: pendingRequestId }) => {
1023
+ if (pendingRequestId !== requestId) {
1024
+ return;
1025
+ }
1026
+ if (requestController.responsePromise.state === "pending") {
1027
+ requestController.respondWith(void 0);
1028
+ }
1029
+ });
1030
+ const resolverResult = await until2(async () => {
1031
+ this.logger.info(
1032
+ 'emitting the "request" event for %s listener(s)...',
1033
+ emitter.listenerCount("request")
1034
+ );
1035
+ await emitAsync(emitter, "request", {
1036
+ request: interactiveRequest,
1037
+ requestId
1038
+ });
1039
+ this.logger.info('all "request" listeners settled!');
1040
+ const mockedResponse2 = await requestController.responsePromise;
1041
+ this.logger.info("event.respondWith called with:", mockedResponse2);
1042
+ return mockedResponse2;
1043
+ });
1044
+ if (resolverResult.error) {
1045
+ this.logger.info(
1046
+ "request listener threw an exception, aborting request...",
1047
+ resolverResult.error
1048
+ );
1049
+ xhrRequestController.errorWith(resolverResult.error);
1050
+ return;
1051
+ }
1052
+ const mockedResponse = resolverResult.data;
1053
+ if (typeof mockedResponse !== "undefined") {
1054
+ this.logger.info(
1055
+ "received mocked response: %d %s",
1056
+ mockedResponse.status,
1057
+ mockedResponse.statusText
1058
+ );
1059
+ if (mockedResponse.type === "error") {
1060
+ this.logger.info(
1061
+ "received a network error response, rejecting the request promise..."
1062
+ );
1063
+ xhrRequestController.errorWith(new TypeError("Network error"));
1064
+ return;
1065
+ }
1066
+ return xhrRequestController.respondWith(mockedResponse);
1067
+ }
1068
+ this.logger.info(
1069
+ "no mocked response received, performing request as-is..."
1070
+ );
1071
+ };
1072
+ xhrRequestController.onResponse = async function({
1073
+ response,
1074
+ isMockedResponse,
1075
+ request,
1076
+ requestId
1077
+ }) {
1078
+ this.logger.info(
1079
+ 'emitting the "response" event for %s listener(s)...',
1080
+ emitter.listenerCount("response")
1081
+ );
1082
+ emitter.emit("response", {
1083
+ response,
1084
+ isMockedResponse,
1085
+ request,
1086
+ requestId
1087
+ });
1088
+ };
1089
+ return xhrRequestController.request;
1090
+ }
1091
+ });
1092
+ return XMLHttpRequestProxy;
1093
+ }
1094
+
1095
+ // src/interceptors/XMLHttpRequest/index.ts
1096
+ var _XMLHttpRequestInterceptor = class extends Interceptor {
1097
+ constructor() {
1098
+ super(_XMLHttpRequestInterceptor.interceptorSymbol);
1099
+ }
1100
+ checkEnvironment() {
1101
+ return typeof globalThis.XMLHttpRequest !== "undefined";
1102
+ }
1103
+ setup() {
1104
+ const logger = this.logger.extend("setup");
1105
+ logger.info('patching "XMLHttpRequest" module...');
1106
+ const PureXMLHttpRequest = globalThis.XMLHttpRequest;
1107
+ invariant4(
1108
+ !PureXMLHttpRequest[IS_PATCHED_MODULE],
1109
+ 'Failed to patch the "XMLHttpRequest" module: already patched.'
1110
+ );
1111
+ globalThis.XMLHttpRequest = createXMLHttpRequestProxy({
1112
+ emitter: this.emitter,
1113
+ logger: this.logger
1114
+ });
1115
+ logger.info(
1116
+ 'native "XMLHttpRequest" module patched!',
1117
+ globalThis.XMLHttpRequest.name
1118
+ );
1119
+ Object.defineProperty(globalThis.XMLHttpRequest, IS_PATCHED_MODULE, {
1120
+ enumerable: true,
1121
+ configurable: true,
1122
+ value: true
1123
+ });
1124
+ this.subscriptions.push(() => {
1125
+ Object.defineProperty(globalThis.XMLHttpRequest, IS_PATCHED_MODULE, {
1126
+ value: void 0
1127
+ });
1128
+ globalThis.XMLHttpRequest = PureXMLHttpRequest;
1129
+ logger.info(
1130
+ 'native "XMLHttpRequest" module restored!',
1131
+ globalThis.XMLHttpRequest.name
1132
+ );
1133
+ });
1134
+ }
1135
+ };
1136
+ var XMLHttpRequestInterceptor = _XMLHttpRequestInterceptor;
1137
+ XMLHttpRequestInterceptor.interceptorSymbol = Symbol("xhr");
1138
+
1139
+ // src/presets/browser.ts
1140
+ var browser_default = [
1141
+ new FetchInterceptor(),
1142
+ new XMLHttpRequestInterceptor()
1143
+ ];
1144
+ export {
1145
+ browser_default as default
1146
+ };