ajax-hooker 1.2.5 → 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 +20 -2
- package/dist/esm/index.js +20 -2
- package/dist/iife/index.js +20 -2
- 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
|
@@ -31,7 +31,7 @@ Object.getOwnPropertyDescriptor.bind(Object);
|
|
|
31
31
|
|
|
32
32
|
const getType = Object.prototype.toString.call.bind(Object.prototype.toString);
|
|
33
33
|
|
|
34
|
-
const resolveUrl = (url = "") => new URL(url, window.location.
|
|
34
|
+
const resolveUrl = (url = "") => new URL(url, window.location.href).toString();
|
|
35
35
|
|
|
36
36
|
const getProxyValue = (target, prop) => {
|
|
37
37
|
const value = Reflect.get(target, prop);
|
|
@@ -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,6 +104,7 @@ class XhrInterceptor {
|
|
|
97
104
|
console.warn("[AjaxInterceptor] Error in xhr request hooks:", error);
|
|
98
105
|
}
|
|
99
106
|
hooker.req = newRequest;
|
|
107
|
+
if (target.readyState !== self.xhrContructorKeys.OPENED && target.readyState !== self.xhrContructorKeys.UNSENT) return;
|
|
100
108
|
const needReopen = oldRequest.method !== newRequest.method || oldRequest.url !== newRequest.url;
|
|
101
109
|
const headersChanged = !self.headersEqual(oldRequest.headers, newRequest.headers);
|
|
102
110
|
const shouldReopen = needReopen || headersChanged;
|
|
@@ -354,7 +362,8 @@ class FetchInterceptor {
|
|
|
354
362
|
};
|
|
355
363
|
}
|
|
356
364
|
resolveRequest(req, newRequest, sourceMap) {
|
|
357
|
-
const
|
|
365
|
+
const resolvedOriginalUrl = typeof req === "string" ? resolveUrl(req) : req instanceof URL ? req.href : req.url;
|
|
366
|
+
const urlChanged = newRequest.url !== resolvedOriginalUrl;
|
|
358
367
|
if (typeof req === "string") return urlChanged ? newRequest.url : req;
|
|
359
368
|
if (req instanceof URL) return urlChanged ? new URL(newRequest.url) : req;
|
|
360
369
|
if (req instanceof Request) {
|
|
@@ -404,6 +413,13 @@ class FetchInterceptor {
|
|
|
404
413
|
if (headers instanceof Headers) return headers;
|
|
405
414
|
return new Headers(headers);
|
|
406
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
|
+
}
|
|
407
423
|
isWasmRequest(url) {
|
|
408
424
|
try {
|
|
409
425
|
return new URL(url).pathname.endsWith(".wasm");
|
|
@@ -468,6 +484,7 @@ class FetchInterceptor {
|
|
|
468
484
|
} catch (error) {
|
|
469
485
|
console.warn("[AjaxInterceptor] Error in fetch stream response callback:", error);
|
|
470
486
|
}
|
|
487
|
+
self.throwIfMockError(hooker.resp.mockError);
|
|
471
488
|
let chunkIndex = 0;
|
|
472
489
|
const {readable: readable, writable: writable} = new TransformStream({
|
|
473
490
|
async transform(chunk, controller) {
|
|
@@ -520,6 +537,7 @@ class FetchInterceptor {
|
|
|
520
537
|
} catch (error) {
|
|
521
538
|
console.warn("[AjaxInterceptor] Error in fetch response callback:", error);
|
|
522
539
|
}
|
|
540
|
+
self.throwIfMockError(hooker.resp.mockError);
|
|
523
541
|
}
|
|
524
542
|
interceptedResponse[CYCLE_SCHEDULER] = hooker;
|
|
525
543
|
const proxyFh = new Proxy(interceptedResponse, {
|
package/dist/esm/index.js
CHANGED
|
@@ -25,7 +25,7 @@ Object.getOwnPropertyDescriptor.bind(Object);
|
|
|
25
25
|
|
|
26
26
|
const getType = Object.prototype.toString.call.bind(Object.prototype.toString);
|
|
27
27
|
|
|
28
|
-
const resolveUrl = (url = "") => new URL(url, window.location.
|
|
28
|
+
const resolveUrl = (url = "") => new URL(url, window.location.href).toString();
|
|
29
29
|
|
|
30
30
|
const getProxyValue = (target, prop) => {
|
|
31
31
|
const value = Reflect.get(target, prop);
|
|
@@ -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,6 +98,7 @@ class XhrInterceptor {
|
|
|
91
98
|
console.warn("[AjaxInterceptor] Error in xhr request hooks:", error);
|
|
92
99
|
}
|
|
93
100
|
hooker.req = newRequest;
|
|
101
|
+
if (target.readyState !== self.xhrContructorKeys.OPENED && target.readyState !== self.xhrContructorKeys.UNSENT) return;
|
|
94
102
|
const needReopen = oldRequest.method !== newRequest.method || oldRequest.url !== newRequest.url;
|
|
95
103
|
const headersChanged = !self.headersEqual(oldRequest.headers, newRequest.headers);
|
|
96
104
|
const shouldReopen = needReopen || headersChanged;
|
|
@@ -348,7 +356,8 @@ class FetchInterceptor {
|
|
|
348
356
|
};
|
|
349
357
|
}
|
|
350
358
|
resolveRequest(req, newRequest, sourceMap) {
|
|
351
|
-
const
|
|
359
|
+
const resolvedOriginalUrl = typeof req === "string" ? resolveUrl(req) : req instanceof URL ? req.href : req.url;
|
|
360
|
+
const urlChanged = newRequest.url !== resolvedOriginalUrl;
|
|
352
361
|
if (typeof req === "string") return urlChanged ? newRequest.url : req;
|
|
353
362
|
if (req instanceof URL) return urlChanged ? new URL(newRequest.url) : req;
|
|
354
363
|
if (req instanceof Request) {
|
|
@@ -398,6 +407,13 @@ class FetchInterceptor {
|
|
|
398
407
|
if (headers instanceof Headers) return headers;
|
|
399
408
|
return new Headers(headers);
|
|
400
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
|
+
}
|
|
401
417
|
isWasmRequest(url) {
|
|
402
418
|
try {
|
|
403
419
|
return new URL(url).pathname.endsWith(".wasm");
|
|
@@ -462,6 +478,7 @@ class FetchInterceptor {
|
|
|
462
478
|
} catch (error) {
|
|
463
479
|
console.warn("[AjaxInterceptor] Error in fetch stream response callback:", error);
|
|
464
480
|
}
|
|
481
|
+
self.throwIfMockError(hooker.resp.mockError);
|
|
465
482
|
let chunkIndex = 0;
|
|
466
483
|
const {readable: readable, writable: writable} = new TransformStream({
|
|
467
484
|
async transform(chunk, controller) {
|
|
@@ -514,6 +531,7 @@ class FetchInterceptor {
|
|
|
514
531
|
} catch (error) {
|
|
515
532
|
console.warn("[AjaxInterceptor] Error in fetch response callback:", error);
|
|
516
533
|
}
|
|
534
|
+
self.throwIfMockError(hooker.resp.mockError);
|
|
517
535
|
}
|
|
518
536
|
interceptedResponse[CYCLE_SCHEDULER] = hooker;
|
|
519
537
|
const proxyFh = new Proxy(interceptedResponse, {
|
package/dist/iife/index.js
CHANGED
|
@@ -22,7 +22,7 @@ var AjaxHooker = function(exports) {
|
|
|
22
22
|
}
|
|
23
23
|
Object.getOwnPropertyDescriptor.bind(Object);
|
|
24
24
|
const getType = Object.prototype.toString.call.bind(Object.prototype.toString);
|
|
25
|
-
const resolveUrl = (url = "") => new URL(url, window.location.
|
|
25
|
+
const resolveUrl = (url = "") => new URL(url, window.location.href).toString();
|
|
26
26
|
const getProxyValue = (target, prop) => {
|
|
27
27
|
const value = Reflect.get(target, prop);
|
|
28
28
|
if (typeof value !== "function") return value;
|
|
@@ -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,6 +92,7 @@ var AjaxHooker = function(exports) {
|
|
|
85
92
|
console.warn("[AjaxInterceptor] Error in xhr request hooks:", error);
|
|
86
93
|
}
|
|
87
94
|
hooker.req = newRequest;
|
|
95
|
+
if (target.readyState !== self.xhrContructorKeys.OPENED && target.readyState !== self.xhrContructorKeys.UNSENT) return;
|
|
88
96
|
const needReopen = oldRequest.method !== newRequest.method || oldRequest.url !== newRequest.url;
|
|
89
97
|
const headersChanged = !self.headersEqual(oldRequest.headers, newRequest.headers);
|
|
90
98
|
const shouldReopen = needReopen || headersChanged;
|
|
@@ -339,7 +347,8 @@ var AjaxHooker = function(exports) {
|
|
|
339
347
|
};
|
|
340
348
|
}
|
|
341
349
|
resolveRequest(req, newRequest, sourceMap) {
|
|
342
|
-
const
|
|
350
|
+
const resolvedOriginalUrl = typeof req === "string" ? resolveUrl(req) : req instanceof URL ? req.href : req.url;
|
|
351
|
+
const urlChanged = newRequest.url !== resolvedOriginalUrl;
|
|
343
352
|
if (typeof req === "string") return urlChanged ? newRequest.url : req;
|
|
344
353
|
if (req instanceof URL) return urlChanged ? new URL(newRequest.url) : req;
|
|
345
354
|
if (req instanceof Request) {
|
|
@@ -389,6 +398,13 @@ var AjaxHooker = function(exports) {
|
|
|
389
398
|
if (headers instanceof Headers) return headers;
|
|
390
399
|
return new Headers(headers);
|
|
391
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
|
+
}
|
|
392
408
|
isWasmRequest(url) {
|
|
393
409
|
try {
|
|
394
410
|
return new URL(url).pathname.endsWith(".wasm");
|
|
@@ -453,6 +469,7 @@ var AjaxHooker = function(exports) {
|
|
|
453
469
|
} catch (error) {
|
|
454
470
|
console.warn("[AjaxInterceptor] Error in fetch stream response callback:", error);
|
|
455
471
|
}
|
|
472
|
+
self.throwIfMockError(hooker.resp.mockError);
|
|
456
473
|
let chunkIndex = 0;
|
|
457
474
|
const {readable: readable, writable: writable} = new TransformStream({
|
|
458
475
|
async transform(chunk, controller) {
|
|
@@ -505,6 +522,7 @@ var AjaxHooker = function(exports) {
|
|
|
505
522
|
} catch (error) {
|
|
506
523
|
console.warn("[AjaxInterceptor] Error in fetch response callback:", error);
|
|
507
524
|
}
|
|
525
|
+
self.throwIfMockError(hooker.resp.mockError);
|
|
508
526
|
}
|
|
509
527
|
interceptedResponse[CYCLE_SCHEDULER] = hooker;
|
|
510
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",
|