ajax-hooker 1.2.6 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -295,6 +295,7 @@ The response object received by the response callback contains the following pro
295
295
  | `arrayBuffer` | `ArrayBuffer` | Read-only | Fetch only. Response ArrayBuffer |
296
296
  | `blob` | `Blob` | Read-only | Fetch only. Response Blob |
297
297
  | `formData` | `FormData` | Read-only | Fetch only. Response FormData |
298
+ | `mockError` | `Error \| string` | **Writable** | Fetch only. Rejects the fetch promise after the real request has been sent |
298
299
 
299
300
  > **Note:** For Fetch responses, `json`, `text`, `arrayBuffer`, `blob`, and `formData` are automatically parsed by the interceptor and available as properties. No need to call `.json()` or similar methods. If parsing fails, the corresponding property is `null`.
300
301
 
@@ -319,6 +320,7 @@ interface AjaxResponse {
319
320
  arrayBuffer?: ArrayBuffer;
320
321
  blob?: Blob;
321
322
  formData?: FormData;
323
+ mockError?: Error | string;
322
324
  }
323
325
  ```
324
326
 
@@ -406,6 +408,21 @@ interceptor.hook((request) => {
406
408
  }, 'xhr');
407
409
  ```
408
410
 
411
+ ### Simulate Fetch Network Errors
412
+
413
+ ```typescript
414
+ interceptor.hook((request) => {
415
+ if (request.url.includes('/api/fail')) {
416
+ request.response = (response) => {
417
+ response.mockError = new TypeError('Failed to fetch');
418
+ };
419
+ }
420
+ return request;
421
+ }, 'fetch');
422
+ ```
423
+
424
+ > `mockError` is designed for **Fetch only**. For XHR failure simulation, prefer setting `request.timeout` and using a slow or hanging endpoint to trigger native timeout behavior.
425
+
409
426
  ### Intercept Streaming Responses
410
427
 
411
428
  ```typescript
package/dist/cjs/index.js CHANGED
@@ -52,6 +52,13 @@ class XhrInterceptor {
52
52
  nativeXhr=window.XMLHttpRequest;
53
53
  nativeXhrPrototype=window.XMLHttpRequest.prototype;
54
54
  hooks=[];
55
+ xhrContructorKeys={
56
+ UNSENT: this.nativeXhr.UNSENT,
57
+ OPENED: this.nativeXhr.OPENED,
58
+ HEADERS_RECEIVED: this.nativeXhr.HEADERS_RECEIVED,
59
+ LOADING: this.nativeXhr.LOADING,
60
+ DONE: this.nativeXhr.DONE
61
+ };
55
62
  xhrResponseEvents=[ "readystatechange", "load", "loadend" ];
56
63
  xhrInstanceAttr=[ "response", "responseText", "responseXML", "status", "statusText" ];
57
64
  xhrInstanceAttrHandler=this.xhrInstanceAttr.reduce((acc, attr) => {
@@ -97,7 +104,7 @@ class XhrInterceptor {
97
104
  console.warn("[AjaxInterceptor] Error in xhr request hooks:", error);
98
105
  }
99
106
  hooker.req = newRequest;
100
- if (target.readyState !== XMLHttpRequest.OPENED) return;
107
+ if (target.readyState !== self.xhrContructorKeys.OPENED && target.readyState !== self.xhrContructorKeys.UNSENT) return;
101
108
  const needReopen = oldRequest.method !== newRequest.method || oldRequest.url !== newRequest.url;
102
109
  const headersChanged = !self.headersEqual(oldRequest.headers, newRequest.headers);
103
110
  const shouldReopen = needReopen || headersChanged;
@@ -406,6 +413,13 @@ class FetchInterceptor {
406
413
  if (headers instanceof Headers) return headers;
407
414
  return new Headers(headers);
408
415
  }
416
+ resolveFetchError(error) {
417
+ if (error instanceof Error) return error;
418
+ return new TypeError(error);
419
+ }
420
+ throwIfMockError(mockError) {
421
+ if (mockError) throw this.resolveFetchError(mockError);
422
+ }
409
423
  isWasmRequest(url) {
410
424
  try {
411
425
  return new URL(url).pathname.endsWith(".wasm");
@@ -470,6 +484,7 @@ class FetchInterceptor {
470
484
  } catch (error) {
471
485
  console.warn("[AjaxInterceptor] Error in fetch stream response callback:", error);
472
486
  }
487
+ self.throwIfMockError(hooker.resp.mockError);
473
488
  let chunkIndex = 0;
474
489
  const {readable: readable, writable: writable} = new TransformStream({
475
490
  async transform(chunk, controller) {
@@ -522,6 +537,7 @@ class FetchInterceptor {
522
537
  } catch (error) {
523
538
  console.warn("[AjaxInterceptor] Error in fetch response callback:", error);
524
539
  }
540
+ self.throwIfMockError(hooker.resp.mockError);
525
541
  }
526
542
  interceptedResponse[CYCLE_SCHEDULER] = hooker;
527
543
  const proxyFh = new Proxy(interceptedResponse, {
package/dist/esm/index.js CHANGED
@@ -46,6 +46,13 @@ class XhrInterceptor {
46
46
  nativeXhr=window.XMLHttpRequest;
47
47
  nativeXhrPrototype=window.XMLHttpRequest.prototype;
48
48
  hooks=[];
49
+ xhrContructorKeys={
50
+ UNSENT: this.nativeXhr.UNSENT,
51
+ OPENED: this.nativeXhr.OPENED,
52
+ HEADERS_RECEIVED: this.nativeXhr.HEADERS_RECEIVED,
53
+ LOADING: this.nativeXhr.LOADING,
54
+ DONE: this.nativeXhr.DONE
55
+ };
49
56
  xhrResponseEvents=[ "readystatechange", "load", "loadend" ];
50
57
  xhrInstanceAttr=[ "response", "responseText", "responseXML", "status", "statusText" ];
51
58
  xhrInstanceAttrHandler=this.xhrInstanceAttr.reduce((acc, attr) => {
@@ -91,7 +98,7 @@ class XhrInterceptor {
91
98
  console.warn("[AjaxInterceptor] Error in xhr request hooks:", error);
92
99
  }
93
100
  hooker.req = newRequest;
94
- if (target.readyState !== XMLHttpRequest.OPENED) return;
101
+ if (target.readyState !== self.xhrContructorKeys.OPENED && target.readyState !== self.xhrContructorKeys.UNSENT) return;
95
102
  const needReopen = oldRequest.method !== newRequest.method || oldRequest.url !== newRequest.url;
96
103
  const headersChanged = !self.headersEqual(oldRequest.headers, newRequest.headers);
97
104
  const shouldReopen = needReopen || headersChanged;
@@ -400,6 +407,13 @@ class FetchInterceptor {
400
407
  if (headers instanceof Headers) return headers;
401
408
  return new Headers(headers);
402
409
  }
410
+ resolveFetchError(error) {
411
+ if (error instanceof Error) return error;
412
+ return new TypeError(error);
413
+ }
414
+ throwIfMockError(mockError) {
415
+ if (mockError) throw this.resolveFetchError(mockError);
416
+ }
403
417
  isWasmRequest(url) {
404
418
  try {
405
419
  return new URL(url).pathname.endsWith(".wasm");
@@ -464,6 +478,7 @@ class FetchInterceptor {
464
478
  } catch (error) {
465
479
  console.warn("[AjaxInterceptor] Error in fetch stream response callback:", error);
466
480
  }
481
+ self.throwIfMockError(hooker.resp.mockError);
467
482
  let chunkIndex = 0;
468
483
  const {readable: readable, writable: writable} = new TransformStream({
469
484
  async transform(chunk, controller) {
@@ -516,6 +531,7 @@ class FetchInterceptor {
516
531
  } catch (error) {
517
532
  console.warn("[AjaxInterceptor] Error in fetch response callback:", error);
518
533
  }
534
+ self.throwIfMockError(hooker.resp.mockError);
519
535
  }
520
536
  interceptedResponse[CYCLE_SCHEDULER] = hooker;
521
537
  const proxyFh = new Proxy(interceptedResponse, {
@@ -40,6 +40,13 @@ var AjaxHooker = function(exports) {
40
40
  nativeXhr=window.XMLHttpRequest;
41
41
  nativeXhrPrototype=window.XMLHttpRequest.prototype;
42
42
  hooks=[];
43
+ xhrContructorKeys={
44
+ UNSENT: this.nativeXhr.UNSENT,
45
+ OPENED: this.nativeXhr.OPENED,
46
+ HEADERS_RECEIVED: this.nativeXhr.HEADERS_RECEIVED,
47
+ LOADING: this.nativeXhr.LOADING,
48
+ DONE: this.nativeXhr.DONE
49
+ };
43
50
  xhrResponseEvents=[ "readystatechange", "load", "loadend" ];
44
51
  xhrInstanceAttr=[ "response", "responseText", "responseXML", "status", "statusText" ];
45
52
  xhrInstanceAttrHandler=this.xhrInstanceAttr.reduce((acc, attr) => {
@@ -85,7 +92,7 @@ var AjaxHooker = function(exports) {
85
92
  console.warn("[AjaxInterceptor] Error in xhr request hooks:", error);
86
93
  }
87
94
  hooker.req = newRequest;
88
- if (target.readyState !== XMLHttpRequest.OPENED) return;
95
+ if (target.readyState !== self.xhrContructorKeys.OPENED && target.readyState !== self.xhrContructorKeys.UNSENT) return;
89
96
  const needReopen = oldRequest.method !== newRequest.method || oldRequest.url !== newRequest.url;
90
97
  const headersChanged = !self.headersEqual(oldRequest.headers, newRequest.headers);
91
98
  const shouldReopen = needReopen || headersChanged;
@@ -391,6 +398,13 @@ var AjaxHooker = function(exports) {
391
398
  if (headers instanceof Headers) return headers;
392
399
  return new Headers(headers);
393
400
  }
401
+ resolveFetchError(error) {
402
+ if (error instanceof Error) return error;
403
+ return new TypeError(error);
404
+ }
405
+ throwIfMockError(mockError) {
406
+ if (mockError) throw this.resolveFetchError(mockError);
407
+ }
394
408
  isWasmRequest(url) {
395
409
  try {
396
410
  return new URL(url).pathname.endsWith(".wasm");
@@ -455,6 +469,7 @@ var AjaxHooker = function(exports) {
455
469
  } catch (error) {
456
470
  console.warn("[AjaxInterceptor] Error in fetch stream response callback:", error);
457
471
  }
472
+ self.throwIfMockError(hooker.resp.mockError);
458
473
  let chunkIndex = 0;
459
474
  const {readable: readable, writable: writable} = new TransformStream({
460
475
  async transform(chunk, controller) {
@@ -507,6 +522,7 @@ var AjaxHooker = function(exports) {
507
522
  } catch (error) {
508
523
  console.warn("[AjaxInterceptor] Error in fetch response callback:", error);
509
524
  }
525
+ self.throwIfMockError(hooker.resp.mockError);
510
526
  }
511
527
  interceptedResponse[CYCLE_SCHEDULER] = hooker;
512
528
  const proxyFh = new Proxy(interceptedResponse, {
@@ -15,6 +15,8 @@ export declare class FetchInterceptor {
15
15
  private resolveRequest;
16
16
  private resolveOptions;
17
17
  private resolveHeaders;
18
+ private resolveFetchError;
19
+ private throwIfMockError;
18
20
  private isWasmRequest;
19
21
  private _generateProxyFetch;
20
22
  }
@@ -13,6 +13,7 @@ export interface FetchResponse extends Pick<Response, 'ok' | 'redirected'> {
13
13
  blob: Blob;
14
14
  formData: FormData;
15
15
  json: any;
16
+ mockError?: Error | string;
16
17
  }
17
18
  export interface AjaxResponse extends BaseResponse, Partial<XhrResponse & FetchResponse> {
18
19
  }
@@ -11,6 +11,7 @@ export declare class XhrInterceptor {
11
11
  };
12
12
  readonly nativeXhrPrototype: XMLHttpRequest;
13
13
  hooks: HookFunction[];
14
+ private readonly xhrContructorKeys;
14
15
  private xhrResponseEvents;
15
16
  private xhrInstanceAttr;
16
17
  private xhrInstanceAttrHandler;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ajax-hooker",
3
- "version": "1.2.6",
3
+ "version": "1.3.0",
4
4
  "description": "Browser AJAX interceptor for XMLHttpRequest and fetch with unified hooks, request/response mutation, and streaming response support.",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",