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 +17 -0
- package/dist/cjs/index.js +17 -1
- package/dist/esm/index.js +17 -1
- package/dist/iife/index.js +17 -1
- package/dist/types/fetch.d.ts +2 -0
- package/dist/types/type.d.ts +1 -0
- package/dist/types/xhr.d.ts +1 -0
- package/package.json +1 -1
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 !==
|
|
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 !==
|
|
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, {
|
package/dist/iife/index.js
CHANGED
|
@@ -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 !==
|
|
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, {
|
package/dist/types/fetch.d.ts
CHANGED
package/dist/types/type.d.ts
CHANGED
|
@@ -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
|
}
|
package/dist/types/xhr.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ajax-hooker",
|
|
3
|
-
"version": "1.
|
|
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",
|