@mswjs/interceptors 0.19.5 → 0.20.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/lib/interceptors/XMLHttpRequest/XMLHttpRequestController.d.ts +44 -0
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestController.js +413 -0
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestController.js.map +1 -0
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestProxy.d.ts +20 -0
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestProxy.js +75 -0
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestProxy.js.map +1 -0
- package/lib/interceptors/XMLHttpRequest/index.js +9 -11
- package/lib/interceptors/XMLHttpRequest/index.js.map +1 -1
- package/lib/utils/createProxy.d.ts +8 -0
- package/lib/utils/createProxy.js +45 -0
- package/lib/utils/createProxy.js.map +1 -0
- package/package.json +1 -1
- package/src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts +578 -0
- package/src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts +111 -0
- package/src/interceptors/XMLHttpRequest/index.ts +12 -13
- package/src/utils/createProxy.ts +78 -0
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.d.ts +0 -93
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.js +0 -442
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.js.map +0 -1
- package/src/interceptors/XMLHttpRequest/XMLHttpRequestOverride.ts +0 -684
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { Debugger } from 'debug'
|
|
2
|
+
import { until } from '@open-draft/until'
|
|
3
|
+
import { XMLHttpRequestEmitter } from '.'
|
|
4
|
+
import { toInteractiveRequest } from '../../utils/toInteractiveRequest'
|
|
5
|
+
import { XMLHttpRequestController } from './XMLHttpRequestController'
|
|
6
|
+
|
|
7
|
+
export interface XMLHttpRequestProxyOptions {
|
|
8
|
+
emitter: XMLHttpRequestEmitter
|
|
9
|
+
log: Debugger
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create a proxied `XMLHttpRequest` class.
|
|
14
|
+
* The proxied class establishes spies on certain methods,
|
|
15
|
+
* allowing us to intercept requests and respond to them.
|
|
16
|
+
*/
|
|
17
|
+
export function createXMLHttpRequestProxy({
|
|
18
|
+
emitter,
|
|
19
|
+
log,
|
|
20
|
+
}: XMLHttpRequestProxyOptions) {
|
|
21
|
+
const XMLHttpRequestProxy = new Proxy(globalThis.XMLHttpRequest, {
|
|
22
|
+
construct(target, args) {
|
|
23
|
+
log('constructed new XMLHttpRequest')
|
|
24
|
+
|
|
25
|
+
const originalRequest = Reflect.construct(target, args)
|
|
26
|
+
|
|
27
|
+
const requestController = new XMLHttpRequestController(
|
|
28
|
+
originalRequest,
|
|
29
|
+
log
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
requestController.onRequest = async function (request, requestId) {
|
|
33
|
+
// Notify the consumer about a new request.
|
|
34
|
+
const interactiveRequest = toInteractiveRequest(request)
|
|
35
|
+
|
|
36
|
+
this.log(
|
|
37
|
+
'emitting the "request" event for %s listener(s)...',
|
|
38
|
+
emitter.listenerCount('request')
|
|
39
|
+
)
|
|
40
|
+
emitter.emit('request', interactiveRequest, requestId)
|
|
41
|
+
|
|
42
|
+
this.log('awaiting mocked response...')
|
|
43
|
+
|
|
44
|
+
const [middlewareException, mockedResponse] = await until(async () => {
|
|
45
|
+
await emitter.untilIdle(
|
|
46
|
+
'request',
|
|
47
|
+
({ args: [, pendingRequestId] }) => {
|
|
48
|
+
return pendingRequestId === requestId
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
this.log('all "request" listeners settled!')
|
|
53
|
+
|
|
54
|
+
const [mockedResponse] =
|
|
55
|
+
await interactiveRequest.respondWith.invoked()
|
|
56
|
+
|
|
57
|
+
this.log('event.respondWith called with:', mockedResponse)
|
|
58
|
+
|
|
59
|
+
return mockedResponse
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
if (middlewareException) {
|
|
63
|
+
this.log(
|
|
64
|
+
'request listener threw an exception, aborting request...',
|
|
65
|
+
middlewareException
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @todo Consider forwarding this error to the stderr as well
|
|
70
|
+
* since not all consumers are expecting to handle errors.
|
|
71
|
+
* If they don't, this error will be swallowed.
|
|
72
|
+
*/
|
|
73
|
+
requestController.errorWith(middlewareException)
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (typeof mockedResponse !== 'undefined') {
|
|
78
|
+
this.log(
|
|
79
|
+
'received mocked response: %d %s',
|
|
80
|
+
mockedResponse.status,
|
|
81
|
+
mockedResponse.statusText
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return requestController.respondWith(mockedResponse)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.log('no mocked response received, performing request as-is...')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
requestController.onResponse = async function (
|
|
91
|
+
response,
|
|
92
|
+
request,
|
|
93
|
+
requestId
|
|
94
|
+
) {
|
|
95
|
+
this.log(
|
|
96
|
+
'emitting the "response" event for %s listener(s)...',
|
|
97
|
+
emitter.listenerCount('response')
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
emitter.emit('response', response, request, requestId)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Return the proxied request from the controller
|
|
104
|
+
// so that the controller can react to the consumer's interactions
|
|
105
|
+
// with this request (opening/sending/etc).
|
|
106
|
+
return requestController.request
|
|
107
|
+
},
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
return XMLHttpRequestProxy
|
|
111
|
+
}
|
|
@@ -3,7 +3,7 @@ import { HttpRequestEventMap, IS_PATCHED_MODULE } from '../../glossary'
|
|
|
3
3
|
import { InteractiveRequest } from '../../utils/toInteractiveRequest'
|
|
4
4
|
import { Interceptor } from '../../Interceptor'
|
|
5
5
|
import { AsyncEventEmitter } from '../../utils/AsyncEventEmitter'
|
|
6
|
-
import {
|
|
6
|
+
import { createXMLHttpRequestProxy } from './XMLHttpRequestProxy'
|
|
7
7
|
|
|
8
8
|
export type XMLHttpRequestEventListener = (
|
|
9
9
|
request: InteractiveRequest,
|
|
@@ -20,10 +20,7 @@ export class XMLHttpRequestInterceptor extends Interceptor<HttpRequestEventMap>
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
protected checkEnvironment() {
|
|
23
|
-
return
|
|
24
|
-
typeof window !== 'undefined' &&
|
|
25
|
-
typeof window.XMLHttpRequest !== 'undefined'
|
|
26
|
-
)
|
|
23
|
+
return typeof globalThis.XMLHttpRequest !== 'undefined'
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
protected setup() {
|
|
@@ -31,36 +28,38 @@ export class XMLHttpRequestInterceptor extends Interceptor<HttpRequestEventMap>
|
|
|
31
28
|
|
|
32
29
|
log('patching "XMLHttpRequest" module...')
|
|
33
30
|
|
|
34
|
-
const PureXMLHttpRequest =
|
|
31
|
+
const PureXMLHttpRequest = globalThis.XMLHttpRequest
|
|
35
32
|
|
|
36
33
|
invariant(
|
|
37
34
|
!(PureXMLHttpRequest as any)[IS_PATCHED_MODULE],
|
|
38
35
|
'Failed to patch the "XMLHttpRequest" module: already patched.'
|
|
39
36
|
)
|
|
40
37
|
|
|
41
|
-
|
|
42
|
-
XMLHttpRequest: PureXMLHttpRequest,
|
|
38
|
+
globalThis.XMLHttpRequest = createXMLHttpRequestProxy({
|
|
43
39
|
emitter: this.emitter,
|
|
44
40
|
log: this.log,
|
|
45
41
|
})
|
|
46
42
|
|
|
47
|
-
log(
|
|
43
|
+
log(
|
|
44
|
+
'native "XMLHttpRequest" module patched!',
|
|
45
|
+
globalThis.XMLHttpRequest.name
|
|
46
|
+
)
|
|
48
47
|
|
|
49
|
-
Object.defineProperty(
|
|
48
|
+
Object.defineProperty(globalThis.XMLHttpRequest, IS_PATCHED_MODULE, {
|
|
50
49
|
enumerable: true,
|
|
51
50
|
configurable: true,
|
|
52
51
|
value: true,
|
|
53
52
|
})
|
|
54
53
|
|
|
55
54
|
this.subscriptions.push(() => {
|
|
56
|
-
Object.defineProperty(
|
|
55
|
+
Object.defineProperty(globalThis.XMLHttpRequest, IS_PATCHED_MODULE, {
|
|
57
56
|
value: undefined,
|
|
58
57
|
})
|
|
59
58
|
|
|
60
|
-
|
|
59
|
+
globalThis.XMLHttpRequest = PureXMLHttpRequest
|
|
61
60
|
log(
|
|
62
61
|
'native "XMLHttpRequest" module restored!',
|
|
63
|
-
|
|
62
|
+
globalThis.XMLHttpRequest.name
|
|
64
63
|
)
|
|
65
64
|
})
|
|
66
65
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export interface ProxyOptions<Target extends Record<string, any>> {
|
|
2
|
+
constructorCall?(args: Array<unknown>, next: NextFunction<Target>): Target
|
|
3
|
+
|
|
4
|
+
methodCall?<F extends keyof Target>(
|
|
5
|
+
this: Target,
|
|
6
|
+
data: [methodName: F, args: Array<unknown>],
|
|
7
|
+
next: NextFunction<void>
|
|
8
|
+
): void
|
|
9
|
+
|
|
10
|
+
setProperty?(
|
|
11
|
+
data: [propertyName: string | symbol, nextValue: unknown],
|
|
12
|
+
next: NextFunction<void>
|
|
13
|
+
): void
|
|
14
|
+
|
|
15
|
+
getProperty?(
|
|
16
|
+
data: [propertyName: string | symbol, receiver: Target],
|
|
17
|
+
next: NextFunction<void>
|
|
18
|
+
): void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type NextFunction<ReturnType> = () => ReturnType
|
|
22
|
+
|
|
23
|
+
export function createProxy<Target extends object>(
|
|
24
|
+
target: Target,
|
|
25
|
+
options: ProxyOptions<Target>
|
|
26
|
+
): Target {
|
|
27
|
+
const proxy = new Proxy(target, optionsToProxyHandler(options))
|
|
28
|
+
return proxy
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function optionsToProxyHandler<T extends Record<string, any>>(
|
|
32
|
+
options: ProxyOptions<T>
|
|
33
|
+
): ProxyHandler<T> {
|
|
34
|
+
const { constructorCall, methodCall, getProperty, setProperty } = options
|
|
35
|
+
const handler: ProxyHandler<T> = {}
|
|
36
|
+
|
|
37
|
+
if (typeof constructorCall !== 'undefined') {
|
|
38
|
+
handler.construct = function (target, args, newTarget) {
|
|
39
|
+
const next = Reflect.construct.bind(null, target as any, args, newTarget)
|
|
40
|
+
return constructorCall.call(newTarget, args, next)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeof setProperty !== 'undefined') {
|
|
45
|
+
handler.set = function (target, propertyName, nextValue, receiver) {
|
|
46
|
+
const next = () => Reflect.set(target, propertyName, nextValue, receiver)
|
|
47
|
+
return setProperty.call(target, [propertyName, nextValue], next) as any
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
handler.get = function (target, propertyName, receiver) {
|
|
52
|
+
/**
|
|
53
|
+
* @note Using `Reflect.get()` here causes "TypeError: Illegal invocation".
|
|
54
|
+
*/
|
|
55
|
+
const next = () => target[propertyName as any]
|
|
56
|
+
|
|
57
|
+
const value =
|
|
58
|
+
typeof getProperty !== 'undefined'
|
|
59
|
+
? getProperty.call(target, [propertyName, receiver], next)
|
|
60
|
+
: next()
|
|
61
|
+
|
|
62
|
+
if (typeof value === 'function') {
|
|
63
|
+
return (...args: Array<any>) => {
|
|
64
|
+
const next = value.bind(target, ...args)
|
|
65
|
+
|
|
66
|
+
if (typeof methodCall !== 'undefined') {
|
|
67
|
+
return methodCall.call(target, [propertyName as any, args], next)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return next()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return value
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return handler
|
|
78
|
+
}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* XMLHttpRequest override class.
|
|
3
|
-
* Inspired by https://github.com/marvinhagemeister/xhr-mocklet.
|
|
4
|
-
*/
|
|
5
|
-
import type { Debugger } from 'debug';
|
|
6
|
-
import { Headers } from 'headers-polyfill';
|
|
7
|
-
import type { XMLHttpRequestEmitter } from '.';
|
|
8
|
-
declare type XMLHttpRequestEventHandler = (this: XMLHttpRequest, event: Event | ProgressEvent<any>) => void;
|
|
9
|
-
interface XMLHttpRequestEvent<EventMap extends any> {
|
|
10
|
-
name: keyof EventMap;
|
|
11
|
-
listener: XMLHttpRequestEventHandler;
|
|
12
|
-
}
|
|
13
|
-
interface CreateXMLHttpRequestOverrideOptions {
|
|
14
|
-
XMLHttpRequest: typeof window.XMLHttpRequest;
|
|
15
|
-
emitter: XMLHttpRequestEmitter;
|
|
16
|
-
log: Debugger;
|
|
17
|
-
}
|
|
18
|
-
interface InternalXMLHttpRequestEventTargetEventMap extends XMLHttpRequestEventTargetEventMap {
|
|
19
|
-
readystatechange: Event;
|
|
20
|
-
}
|
|
21
|
-
export declare type ExtractCallbacks<Key extends string> = Key extends 'abort' | `on${infer _CallbackName}` ? Key : never;
|
|
22
|
-
export declare const createXMLHttpRequestOverride: (options: CreateXMLHttpRequestOverrideOptions) => {
|
|
23
|
-
new (): {
|
|
24
|
-
_requestHeaders: Headers;
|
|
25
|
-
_responseHeaders: Headers;
|
|
26
|
-
_responseBuffer: Uint8Array;
|
|
27
|
-
_events: XMLHttpRequestEvent<InternalXMLHttpRequestEventTargetEventMap>[];
|
|
28
|
-
log: Debugger;
|
|
29
|
-
readonly UNSENT: 0;
|
|
30
|
-
readonly OPENED: 1;
|
|
31
|
-
readonly HEADERS_RECEIVED: 2;
|
|
32
|
-
readonly LOADING: 3;
|
|
33
|
-
readonly DONE: 4;
|
|
34
|
-
method: string;
|
|
35
|
-
url: string;
|
|
36
|
-
withCredentials: boolean;
|
|
37
|
-
status: number;
|
|
38
|
-
statusText: string;
|
|
39
|
-
user?: string | undefined;
|
|
40
|
-
password?: string | undefined;
|
|
41
|
-
async?: boolean | undefined;
|
|
42
|
-
responseType: XMLHttpRequestResponseType;
|
|
43
|
-
responseURL: string;
|
|
44
|
-
upload: XMLHttpRequestUpload;
|
|
45
|
-
readyState: number;
|
|
46
|
-
onreadystatechange: (this: XMLHttpRequest, ev: Event) => any;
|
|
47
|
-
timeout: number;
|
|
48
|
-
onabort: (this: XMLHttpRequestEventTarget, event: ProgressEvent) => any;
|
|
49
|
-
onerror: (this: XMLHttpRequestEventTarget, event: Event) => any;
|
|
50
|
-
onload: (this: XMLHttpRequestEventTarget, event: ProgressEvent) => any;
|
|
51
|
-
onloadend: (this: XMLHttpRequestEventTarget, event: ProgressEvent) => any;
|
|
52
|
-
onloadstart: (this: XMLHttpRequestEventTarget, event: ProgressEvent) => any;
|
|
53
|
-
onprogress: (this: XMLHttpRequestEventTarget, event: ProgressEvent) => any;
|
|
54
|
-
ontimeout: (this: XMLHttpRequestEventTarget, event: ProgressEvent) => any;
|
|
55
|
-
setReadyState(nextState: number): void;
|
|
56
|
-
/**
|
|
57
|
-
* Triggers both direct callback and attached event listeners
|
|
58
|
-
* for the given event.
|
|
59
|
-
*/
|
|
60
|
-
trigger<K extends "readystatechange" | keyof XMLHttpRequestEventTargetEventMap>(eventName: K, options?: ProgressEventInit | undefined): any;
|
|
61
|
-
reset(): void;
|
|
62
|
-
open(method: string, url: string, async?: boolean, user?: string | undefined, password?: string | undefined): Promise<void>;
|
|
63
|
-
send(data?: string | ArrayBuffer | undefined): void;
|
|
64
|
-
readonly responseText: string;
|
|
65
|
-
readonly response: unknown;
|
|
66
|
-
readonly responseXML: Document | null;
|
|
67
|
-
abort(): void;
|
|
68
|
-
dispatchEvent(): boolean;
|
|
69
|
-
setRequestHeader(name: string, value: string): void;
|
|
70
|
-
getResponseHeader(name: string): string | null;
|
|
71
|
-
getAllResponseHeaders(): string;
|
|
72
|
-
addEventListener<Event_1 extends keyof InternalXMLHttpRequestEventTargetEventMap>(event: Event_1, listener: XMLHttpRequestEventHandler): void;
|
|
73
|
-
removeEventListener<Event_2 extends keyof XMLHttpRequestEventMap>(event: Event_2, listener: (event?: XMLHttpRequestEventMap[Event_2] | undefined) => void): void;
|
|
74
|
-
overrideMimeType(): void;
|
|
75
|
-
/**
|
|
76
|
-
* Propagates mock XMLHttpRequest instance callbacks
|
|
77
|
-
* to the given XMLHttpRequest instance.
|
|
78
|
-
*/
|
|
79
|
-
propagateCallbacks(request: XMLHttpRequest): void;
|
|
80
|
-
/**
|
|
81
|
-
* Propagates the mock XMLHttpRequest instance listeners
|
|
82
|
-
* to the given XMLHttpRequest instance.
|
|
83
|
-
*/
|
|
84
|
-
propagateListeners(request: XMLHttpRequest): void;
|
|
85
|
-
propagateHeaders(request: XMLHttpRequest, headers: Headers): void;
|
|
86
|
-
};
|
|
87
|
-
readonly UNSENT: 0;
|
|
88
|
-
readonly OPENED: 1;
|
|
89
|
-
readonly HEADERS_RECEIVED: 2;
|
|
90
|
-
readonly LOADING: 3;
|
|
91
|
-
readonly DONE: 4;
|
|
92
|
-
};
|
|
93
|
-
export {};
|