English | 中文
A lightweight AJAX request interceptor that supports intercepting and modifying both XMLHttpRequest and Fetch requests.
- Works with both XMLHttpRequest and Fetch API
- Intercepts and modifies request parameters (URL, Method, Headers, Body)
- Captures response data
- Supports streaming response interception (SSE, NDJSON, streaming JSON, etc.)
- Chain multiple hook functions
- Singleton pattern ensures a single global instance
- Full TypeScript type support
npm install ajax-hookerimport AjaxInterceptor from 'ajax-hooker';
// Get the interceptor instance
const interceptor = AjaxInterceptor.getInstance();
// Inject the interceptor
interceptor.inject();
// Add a hook
interceptor.hook((request) => {
// Modify the request
request.headers.set('Authorization', 'Bearer token');
// Capture the response
request.response = async (response) => {
console.log('Status:', response.status);
console.log('Data:', response.json);
};
return request;
});Get the singleton interceptor instance.
const interceptor = AjaxInterceptor.getInstance();Inject the interceptor and start intercepting requests.
Parameters:
type: Optional. Specify'xhr'or'fetch'to inject only one type. If omitted, both are injected.
// Inject all
interceptor.inject();
// Only XHR
interceptor.inject('xhr');
// Only Fetch
interceptor.inject('fetch');Remove the interceptor and restore native XMLHttpRequest and Fetch.
Parameters:
type: Optional. Specify'xhr'or'fetch'to remove only one type. If omitted, both are removed.
// Remove all
interceptor.uninject();
// Only remove XHR
interceptor.uninject('xhr');Add a hook function.
Parameters:
fn: Hook function that receives a request object and returns the modified request (can also return nothing, in which case the original request is kept unchanged)type: Optional. Specify'xhr'or'fetch'to intercept only one type. If omitted, both are intercepted.
// Intercept all requests
interceptor.hook((request) => {
console.log('Request:', request.url);
return request;
});
// Only XHR
interceptor.hook((request) => {
console.log('XHR:', request.url);
return request;
}, 'xhr');
// Only Fetch
interceptor.hook((request) => {
console.log('Fetch:', request.url);
return request;
}, 'fetch');The request object received by hook functions contains the following properties:
| Property | Type | Access | Description |
|---|---|---|---|
type |
'xhr' | 'fetch' |
Read-only | Request type, identifies the request source |
method |
string |
Writable | HTTP method (GET, POST, etc.) |
url |
string |
Writable | Request URL |
headers |
Headers |
Writable | Request headers, standard Headers object |
data |
any |
Writable | Request body |
response |
(response: AjaxResponse) => void | Promise<void> |
Writable | Response callback, invoked when the response is received |
onStreamChunk |
(chunk: StreamChunk) => string | void | Promise<string | void> |
Writable | Streaming response hook (optional), used to intercept each chunk of a streaming response |
responseType |
XMLHttpRequestResponseType |
Writable | XHR only. Corresponds to xhr.responseType |
withCredentials |
boolean |
Writable | XHR only. Corresponds to xhr.withCredentials |
timeout |
number |
Writable | XHR only. Corresponds to xhr.timeout |
interface AjaxInterceptorRequest {
type: 'xhr' | 'fetch';
method: string;
url: string;
headers: Headers;
data: any;
response: (response: AjaxResponse) => void | Promise<void>;
onStreamChunk?: (chunk: StreamChunk) => string | void | Promise<string | void>;
// XHR-specific properties
responseType?: XMLHttpRequestResponseType;
withCredentials?: boolean;
timeout?: number;
}The response object received by the response callback contains the following properties:
| Property | Type | Access | Description |
|---|---|---|---|
status |
number |
Writable | HTTP status code |
statusText |
string |
Writable | HTTP status text |
headers |
Headers |
Read-only | Response headers |
finalUrl |
string |
Read-only | Final URL (after redirects) |
response |
any |
Writable | XHR only. Corresponds to xhr.response |
responseText |
string |
Writable | XHR only. Corresponds to xhr.responseText |
responseXML |
Document | null |
Writable | XHR only. Corresponds to xhr.responseXML |
ok |
boolean |
Read-only | Fetch only. Whether the request was successful (status 200-299) |
redirected |
boolean |
Read-only | Fetch only. Whether the request was redirected |
json |
any |
Read-only | Fetch only. Parsed JSON data |
text |
string |
Read-only | Fetch only. Response text |
arrayBuffer |
ArrayBuffer |
Read-only | Fetch only. Response ArrayBuffer |
blob |
Blob |
Read-only | Fetch only. Response Blob |
formData |
FormData |
Read-only | Fetch only. Response FormData |
Note: For Fetch responses,
json,text,arrayBuffer,blob, andformDataare automatically parsed by the interceptor and available as properties. No need to call.json()or similar methods. If parsing fails, the corresponding property isnull.
interface AjaxResponse {
// Common properties
status: number; // Writable
statusText: string; // Writable
headers: Headers; // Read-only
finalUrl: string;
// XHR-specific (Writable)
response?: any;
responseText?: string;
responseXML?: Document | null;
// Fetch-specific (Read-only, auto-parsed)
ok?: boolean;
redirected?: boolean;
json?: any;
text?: string;
arrayBuffer?: ArrayBuffer;
blob?: Blob;
formData?: FormData;
}The chunk object received by the onStreamChunk hook:
| Property | Type | Description |
|---|---|---|
text |
string |
Decoded text content |
raw |
Uint8Array |
Raw byte data |
index |
number |
Chunk index (starting from 0) |
timestamp |
number |
Receive timestamp |
interface StreamChunk {
text: string;
raw: Uint8Array;
index: number;
timestamp: number;
}The interceptor automatically detects streaming responses based on the Content-Type response header. The following types are recognized as streaming responses:
text/event-stream(SSE)application/stream+jsonapplication/x-ndjsonapplication/jsonlapplication/json-seq
When a streaming response is detected:
- The
responsecallback is invoked immediately (containing onlystatus,statusText,ok,headers,finalUrl,redirected— no body data) - Stream data is passed chunk by chunk via the
onStreamChunkhook - Returning a
stringfromonStreamChunkmodifies the chunk content; returningvoidor nothing keeps the original content
interceptor.hook((request) => {
if (request.url.includes('/api/v1/')) {
request.url = request.url.replace('/api/v1/', '/api/v2/');
}
return request;
});interceptor.hook((request) => {
request.headers.set('Authorization', `Bearer ${getToken()}`);
return request;
});interceptor.hook((request) => {
request.response = async (response) => {
console.log('Status:', response.status);
// XHR uses response.response, Fetch uses response.json
console.log('Data:', response.json || response.response);
};
return request;
});interceptor.hook((request) => {
// Change response type
request.responseType = 'json';
// Set timeout
request.timeout = 5000;
// Send credentials
request.withCredentials = true;
return request;
}, 'xhr');interceptor.hook((request) => {
// Response headers are available immediately when the stream starts
request.response = async (response) => {
console.log('Stream started, status:', response.status);
};
// Intercept each chunk of the streaming response
request.onStreamChunk = async (chunk) => {
console.log('Chunk:', chunk.text);
console.log('Raw data:', chunk.raw);
console.log('Index:', chunk.index);
console.log('Timestamp:', chunk.timestamp);
// Return modified text to replace the chunk content
return chunk.text.replace('old', 'new');
// Return void or nothing to keep the original content
};
return request;
});// First hook: add token
interceptor.hook((request) => {
request.headers.set('Authorization', 'Bearer token');
return request;
});
// Second hook: add timestamp
interceptor.hook((request) => {
request.headers.set('X-Timestamp', Date.now().toString());
return request;
});
// Third hook: log (no return value, keeps original request)
interceptor.hook((request) => {
console.log(`${request.method} ${request.url}`);
});# Install dependencies
pnpm install
# Dev mode
pnpm dev
# Build
pnpm build
# Test
pnpm test
# Test coverage
pnpm test:coverageMIT