@mswjs/interceptors 0.15.3 → 0.16.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/LICENSE.md +9 -0
- package/README.md +68 -49
- package/lib/BatchInterceptor.d.ts +18 -0
- package/lib/BatchInterceptor.js +79 -0
- package/lib/BatchInterceptor.js.map +1 -0
- package/lib/Interceptor.d.ts +49 -0
- package/lib/Interceptor.js +197 -0
- package/lib/Interceptor.js.map +1 -0
- package/lib/RemoteInterceptor.d.ts +24 -0
- package/lib/RemoteInterceptor.js +216 -0
- package/lib/RemoteInterceptor.js.map +1 -0
- package/lib/glossary.d.ts +32 -0
- package/lib/glossary.js +3 -0
- package/lib/glossary.js.map +1 -0
- package/lib/index.d.ts +2 -2
- package/lib/index.js +2 -2
- package/lib/index.js.map +1 -1
- package/lib/interceptors/ClientRequest/NodeClientRequest.d.ts +5 -5
- package/lib/interceptors/ClientRequest/NodeClientRequest.js +56 -15
- package/lib/interceptors/ClientRequest/NodeClientRequest.js.map +1 -1
- package/lib/interceptors/ClientRequest/http.get.d.ts +2 -3
- package/lib/interceptors/ClientRequest/http.get.js +2 -5
- package/lib/interceptors/ClientRequest/http.get.js.map +1 -1
- package/lib/interceptors/ClientRequest/http.request.d.ts +2 -3
- package/lib/interceptors/ClientRequest/http.request.js +3 -6
- package/lib/interceptors/ClientRequest/http.request.js.map +1 -1
- package/lib/interceptors/ClientRequest/index.d.ts +14 -4
- package/lib/interceptors/ClientRequest/index.js +59 -46
- package/lib/interceptors/ClientRequest/index.js.map +1 -1
- package/lib/interceptors/ClientRequest/utils/getIncomingMessageBody.js +7 -2
- package/lib/interceptors/ClientRequest/utils/getIncomingMessageBody.js.map +1 -1
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.d.ts +11 -4
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.js +110 -58
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.js.map +1 -1
- package/lib/interceptors/XMLHttpRequest/index.d.ts +11 -5
- package/lib/interceptors/XMLHttpRequest/index.js +43 -25
- package/lib/interceptors/XMLHttpRequest/index.js.map +1 -1
- package/lib/interceptors/fetch/index.d.ts +8 -2
- package/lib/interceptors/fetch/index.js +120 -68
- package/lib/interceptors/fetch/index.js.map +1 -1
- package/lib/presets/browser.d.ts +3 -1
- package/lib/presets/browser.js +2 -2
- package/lib/presets/browser.js.map +1 -1
- package/lib/presets/node.d.ts +3 -1
- package/lib/presets/node.js +1 -1
- package/lib/presets/node.js.map +1 -1
- package/lib/utils/AsyncEventEmitter.d.ts +29 -0
- package/lib/utils/AsyncEventEmitter.js +241 -0
- package/lib/utils/AsyncEventEmitter.js.map +1 -0
- package/lib/utils/createLazyCallback.d.ts +11 -0
- package/lib/utils/createLazyCallback.js +75 -0
- package/lib/utils/createLazyCallback.js.map +1 -0
- package/lib/utils/nextTick.d.ts +2 -0
- package/lib/utils/nextTick.js +16 -0
- package/lib/utils/nextTick.js.map +1 -0
- package/lib/utils/toIsoResponse.d.ts +1 -1
- package/package.json +6 -6
- package/src/BatchInterceptor.test.ts +113 -0
- package/src/BatchInterceptor.ts +60 -0
- package/src/Interceptor.test.ts +166 -0
- package/src/Interceptor.ts +226 -0
- package/src/RemoteInterceptor.ts +176 -0
- package/src/glossary.ts +42 -0
- package/src/index.ts +2 -2
- package/src/interceptors/ClientRequest/NodeClientRequest.test.ts +87 -70
- package/src/interceptors/ClientRequest/NodeClientRequest.ts +139 -100
- package/src/interceptors/ClientRequest/http.get.ts +7 -11
- package/src/interceptors/ClientRequest/http.request.ts +8 -12
- package/src/interceptors/ClientRequest/index.test.ts +43 -0
- package/src/interceptors/ClientRequest/index.ts +46 -46
- package/src/interceptors/ClientRequest/utils/getIncomingMessageBody.test.ts +9 -0
- package/src/interceptors/ClientRequest/utils/getIncomingMessageBody.ts +9 -2
- package/src/interceptors/XMLHttpRequest/XMLHttpRequestOverride.ts +215 -159
- package/src/interceptors/XMLHttpRequest/index.ts +41 -23
- package/src/interceptors/fetch/index.ts +81 -55
- package/src/presets/browser.ts +3 -3
- package/src/presets/node.ts +3 -3
- package/src/utils/AsyncEventEmitter.test.ts +68 -0
- package/src/utils/AsyncEventEmitter.ts +171 -0
- package/src/utils/createLazyCallback.ts +49 -0
- package/src/utils/nextTick.ts +11 -0
- package/src/utils/toIsoResponse.ts +1 -1
- package/lib/createInterceptor.d.ts +0 -54
- package/lib/createInterceptor.js +0 -27
- package/lib/createInterceptor.js.map +0 -1
- package/lib/remote.d.ts +0 -21
- package/lib/remote.js +0 -178
- package/lib/remote.js.map +0 -1
- package/src/createInterceptor.ts +0 -100
- package/src/remote.ts +0 -174
- package/src/utils/.DS_Store +0 -0
|
@@ -1,79 +1,105 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Headers,
|
|
3
|
-
headersToObject,
|
|
4
|
-
objectToHeaders,
|
|
5
3
|
flattenHeadersObject,
|
|
4
|
+
objectToHeaders,
|
|
5
|
+
headersToObject,
|
|
6
6
|
} from 'headers-polyfill'
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
import type {
|
|
8
|
+
HttpRequestEventMap,
|
|
9
|
+
InteractiveIsomorphicRequest,
|
|
10
10
|
IsomorphicResponse,
|
|
11
|
-
} from '../../
|
|
11
|
+
} from '../../glossary'
|
|
12
|
+
import { Interceptor } from '../../Interceptor'
|
|
13
|
+
import { createLazyCallback } from '../../utils/createLazyCallback'
|
|
12
14
|
import { toIsoResponse } from '../../utils/toIsoResponse'
|
|
13
15
|
import { uuidv4 } from '../../utils/uuid'
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
|
|
18
|
+
static symbol = Symbol('fetch')
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
constructor() {
|
|
21
|
+
super(FetchInterceptor.symbol)
|
|
22
|
+
}
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
protected checkEnvironment() {
|
|
25
|
+
return (
|
|
26
|
+
typeof globalThis !== 'undefined' &&
|
|
27
|
+
typeof globalThis.fetch !== 'undefined'
|
|
28
|
+
)
|
|
29
|
+
}
|
|
21
30
|
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
const url = typeof input === 'string' ? input : input.url
|
|
25
|
-
const method = request.method
|
|
31
|
+
protected setup() {
|
|
32
|
+
const pureFetch = globalThis.fetch
|
|
26
33
|
|
|
27
|
-
|
|
34
|
+
globalThis.fetch = async (input, init) => {
|
|
35
|
+
const request = new Request(input, init)
|
|
36
|
+
const url = typeof input === 'string' ? input : input.url
|
|
37
|
+
const method = request.method
|
|
28
38
|
|
|
29
|
-
|
|
30
|
-
id: uuidv4(),
|
|
31
|
-
url: new URL(url, location.origin),
|
|
32
|
-
method: method,
|
|
33
|
-
headers: new Headers(request.headers),
|
|
34
|
-
credentials: request.credentials,
|
|
35
|
-
body: await request.clone().text(),
|
|
36
|
-
}
|
|
37
|
-
debug('isomorphic request', isoRequest)
|
|
38
|
-
observer.emit('request', isoRequest)
|
|
39
|
+
this.log('[%s] %s', method, url)
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
const isomorphicRequest: InteractiveIsomorphicRequest = {
|
|
42
|
+
id: uuidv4(),
|
|
43
|
+
url: new URL(url, location.origin),
|
|
44
|
+
method: method,
|
|
45
|
+
headers: new Headers(request.headers),
|
|
46
|
+
credentials: request.credentials,
|
|
47
|
+
body: await request.clone().text(),
|
|
48
|
+
respondWith: createLazyCallback(),
|
|
49
|
+
}
|
|
43
50
|
|
|
44
|
-
|
|
45
|
-
const isomorphicResponse = toIsoResponse(response)
|
|
46
|
-
debug('derived isomorphic response', isomorphicResponse)
|
|
51
|
+
this.log('isomorphic request', isomorphicRequest)
|
|
47
52
|
|
|
48
|
-
|
|
53
|
+
this.log(
|
|
54
|
+
'emitting the "request" event for %d listener(s)...',
|
|
55
|
+
this.emitter.listenerCount('request')
|
|
56
|
+
)
|
|
57
|
+
this.emitter.emit('request', isomorphicRequest)
|
|
49
58
|
|
|
50
|
-
|
|
51
|
-
...isomorphicResponse,
|
|
52
|
-
// `Response.headers` cannot be instantiated with the `Headers` polyfill.
|
|
53
|
-
// Apparently, it halts if the `Headers` class contains unknown properties
|
|
54
|
-
// (i.e. the internal `Headers.map`).
|
|
55
|
-
headers: flattenHeadersObject(response.headers || {}),
|
|
56
|
-
})
|
|
57
|
-
}
|
|
59
|
+
this.log('awaiting for the mocked response...')
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
await this.emitter.untilIdle('request')
|
|
62
|
+
this.log('all request listeners have been resolved!')
|
|
60
63
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
debug('original fetch performed', cloneResponse)
|
|
64
|
+
const [mockedResponse] = await isomorphicRequest.respondWith.invoked()
|
|
65
|
+
this.log('event.respondWith called with:', mockedResponse)
|
|
64
66
|
|
|
65
|
-
|
|
66
|
-
'response',
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
if (mockedResponse) {
|
|
68
|
+
this.log('received mocked response:', mockedResponse)
|
|
69
|
+
|
|
70
|
+
const isomorphicResponse = toIsoResponse(mockedResponse)
|
|
71
|
+
this.log('derived isomorphic response:', isomorphicResponse)
|
|
72
|
+
|
|
73
|
+
this.emitter.emit('response', isomorphicRequest, isomorphicResponse)
|
|
74
|
+
|
|
75
|
+
return new Response(mockedResponse.body, {
|
|
76
|
+
...isomorphicResponse,
|
|
77
|
+
// `Response.headers` cannot be instantiated with the `Headers` polyfill.
|
|
78
|
+
// Apparently, it halts if the `Headers` class contains unknown properties
|
|
79
|
+
// (i.e. the internal `Headers.map`).
|
|
80
|
+
headers: flattenHeadersObject(mockedResponse.headers || {}),
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this.log('no mocked response received!')
|
|
73
85
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
86
|
+
return pureFetch(request).then(async (response) => {
|
|
87
|
+
const cloneResponse = response.clone()
|
|
88
|
+
this.log('original fetch performed', cloneResponse)
|
|
89
|
+
|
|
90
|
+
this.emitter.emit(
|
|
91
|
+
'response',
|
|
92
|
+
isomorphicRequest,
|
|
93
|
+
await normalizeFetchResponse(cloneResponse)
|
|
94
|
+
)
|
|
95
|
+
return response
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this.subscriptions.push(() => {
|
|
100
|
+
globalThis.fetch = pureFetch
|
|
101
|
+
this.log('restored native "globalThis.fetch"!', globalThis.fetch.name)
|
|
102
|
+
})
|
|
77
103
|
}
|
|
78
104
|
}
|
|
79
105
|
|
package/src/presets/browser.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { FetchInterceptor } from '../interceptors/fetch'
|
|
2
|
+
import { XMLHttpRequestInterceptor } from '../interceptors/XMLHttpRequest'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* The default preset provisions the interception of requests
|
|
6
6
|
* regardless of their type (fetch/XMLHttpRequest).
|
|
7
7
|
*/
|
|
8
|
-
export default [
|
|
8
|
+
export default [new FetchInterceptor(), new XMLHttpRequestInterceptor()]
|
package/src/presets/node.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { ClientRequestInterceptor } from '../interceptors/ClientRequest'
|
|
2
|
+
import { XMLHttpRequestInterceptor } from '../interceptors/XMLHttpRequest'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* The default preset provisions the interception of requests
|
|
6
6
|
* regardless of their type (http/https/XMLHttpRequest).
|
|
7
7
|
*/
|
|
8
|
-
export default [
|
|
8
|
+
export default [new ClientRequestInterceptor(), new XMLHttpRequestInterceptor()]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { AsyncEventEmitter } from './AsyncEventEmitter'
|
|
2
|
+
import { sleep } from '../../test/helpers'
|
|
3
|
+
|
|
4
|
+
it('emits and listens to events', () => {
|
|
5
|
+
const emitter = new AsyncEventEmitter<{ hello(name: string): void }>()
|
|
6
|
+
const listener = jest.fn()
|
|
7
|
+
emitter.on('hello', listener)
|
|
8
|
+
emitter.emit('hello', 'John')
|
|
9
|
+
|
|
10
|
+
expect(listener).toHaveBeenCalledTimes(1)
|
|
11
|
+
expect(listener).toHaveBeenCalledWith('John')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('resolves "untilIdle" when all the event listeners are done', async () => {
|
|
15
|
+
const emitter = new AsyncEventEmitter<{ speak(word: string): void }>()
|
|
16
|
+
|
|
17
|
+
const results: string[] = []
|
|
18
|
+
const firstListener = jest.fn(() => results.push('first'))
|
|
19
|
+
emitter.on('speak', firstListener)
|
|
20
|
+
|
|
21
|
+
const secondListener = jest.fn(async () => {
|
|
22
|
+
await sleep(150)
|
|
23
|
+
results.push('second')
|
|
24
|
+
})
|
|
25
|
+
emitter.on('speak', secondListener)
|
|
26
|
+
|
|
27
|
+
emitter.emit('speak', 'hi')
|
|
28
|
+
await emitter.untilIdle('speak')
|
|
29
|
+
|
|
30
|
+
// All listeners must be called.
|
|
31
|
+
expect(firstListener).toHaveBeenCalledTimes(1)
|
|
32
|
+
expect(secondListener).toHaveBeenCalledTimes(1)
|
|
33
|
+
|
|
34
|
+
// All promise listeners must be awaited.
|
|
35
|
+
expect(results).toEqual(['first', 'second'])
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('resolves "untilIdle" immediately if there are no pending listeners', async () => {
|
|
39
|
+
const emitter = new AsyncEventEmitter<{ ping(): void }>()
|
|
40
|
+
emitter.emit('ping')
|
|
41
|
+
|
|
42
|
+
await expect(emitter.untilIdle('ping')).resolves.toBeUndefined()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('propagates listener exceptions to "untilIdle" promise', async () => {
|
|
46
|
+
const emitter = new AsyncEventEmitter<{ ping(): void }>()
|
|
47
|
+
|
|
48
|
+
const error = new Error('oops')
|
|
49
|
+
const listener = jest.fn(() => {
|
|
50
|
+
throw error
|
|
51
|
+
})
|
|
52
|
+
emitter.on('ping', listener)
|
|
53
|
+
|
|
54
|
+
emitter.emit('ping')
|
|
55
|
+
await expect(emitter.untilIdle('ping')).rejects.toBe(error)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('does not emit events once the emitter was deactivated', () => {
|
|
59
|
+
const emitter = new AsyncEventEmitter<{ ping(): void }>()
|
|
60
|
+
|
|
61
|
+
const listener = jest.fn()
|
|
62
|
+
emitter.on('ping', listener)
|
|
63
|
+
emitter.deactivate()
|
|
64
|
+
|
|
65
|
+
emitter.emit('ping')
|
|
66
|
+
|
|
67
|
+
expect(listener).not.toHaveBeenCalled()
|
|
68
|
+
})
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { Debugger, debug } from 'debug'
|
|
2
|
+
import { StrictEventEmitter, EventMapType } from 'strict-event-emitter'
|
|
3
|
+
import { nextTick } from './nextTick'
|
|
4
|
+
|
|
5
|
+
export type QueueItem = Promise<void>
|
|
6
|
+
|
|
7
|
+
export enum AsyncEventEmitterReadyState {
|
|
8
|
+
ACTIVE = 'ACTIVE',
|
|
9
|
+
DEACTIVATED = 'DEACTIVATED',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class AsyncEventEmitter<
|
|
13
|
+
EventMap extends EventMapType
|
|
14
|
+
> extends StrictEventEmitter<EventMap> {
|
|
15
|
+
public readyState: AsyncEventEmitterReadyState
|
|
16
|
+
|
|
17
|
+
private log: Debugger
|
|
18
|
+
protected queue: Map<keyof EventMap, QueueItem[]>
|
|
19
|
+
|
|
20
|
+
constructor() {
|
|
21
|
+
super()
|
|
22
|
+
|
|
23
|
+
this.log = debug('async-event-emitter')
|
|
24
|
+
this.queue = new Map()
|
|
25
|
+
|
|
26
|
+
this.readyState = AsyncEventEmitterReadyState.ACTIVE
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public on<Event extends keyof EventMap>(
|
|
30
|
+
event: Event,
|
|
31
|
+
listener: EventMap[Event]
|
|
32
|
+
) {
|
|
33
|
+
const log = this.log.extend('on')
|
|
34
|
+
|
|
35
|
+
log('adding "%s" listener...', event)
|
|
36
|
+
|
|
37
|
+
if (this.readyState === AsyncEventEmitterReadyState.DEACTIVATED) {
|
|
38
|
+
log('the emitter is destroyed, skipping!')
|
|
39
|
+
return this
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return super.on(event, (async (...args: unknown[]) => {
|
|
43
|
+
// Event queue is always established when calling ".emit()".
|
|
44
|
+
const queue = this.openListenerQueue(event)
|
|
45
|
+
|
|
46
|
+
log('awaiting the "%s" listener...', event)
|
|
47
|
+
|
|
48
|
+
// Whenever a listener is called, create a new Promise
|
|
49
|
+
// that resolves when that listener function completes its execution.
|
|
50
|
+
queue.push(
|
|
51
|
+
new Promise<void>(async (resolve, reject) => {
|
|
52
|
+
try {
|
|
53
|
+
// Treat listeners as potentially asynchronous functions
|
|
54
|
+
// so they could be awaited.
|
|
55
|
+
await listener(...args)
|
|
56
|
+
resolve()
|
|
57
|
+
|
|
58
|
+
log('"%s" listener has resolved!', event)
|
|
59
|
+
} catch (error) {
|
|
60
|
+
log('"%s" listener has rejected!', error)
|
|
61
|
+
reject(error)
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
)
|
|
65
|
+
}) as EventMap[Event])
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public emit<Event extends keyof EventMap>(
|
|
69
|
+
event: Event,
|
|
70
|
+
...args: Parameters<EventMap[Event]>
|
|
71
|
+
): boolean {
|
|
72
|
+
const log = this.log.extend('emit')
|
|
73
|
+
|
|
74
|
+
log('emitting "%s" event...', event)
|
|
75
|
+
|
|
76
|
+
if (this.readyState === AsyncEventEmitterReadyState.DEACTIVATED) {
|
|
77
|
+
log('the emitter is destroyed, skipping!')
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Establish the Promise queue for this particular event.
|
|
82
|
+
this.openListenerQueue(event)
|
|
83
|
+
|
|
84
|
+
log('appending a one-time cleanup "%s" listener...', event)
|
|
85
|
+
|
|
86
|
+
// Append a one-time clean up listener.
|
|
87
|
+
this.once(event, (() => {
|
|
88
|
+
// Clear the Promise queue for this particular event
|
|
89
|
+
// in the next tick so the Promise in "untilIdle" has
|
|
90
|
+
// time to properly resolve.
|
|
91
|
+
nextTick(() => {
|
|
92
|
+
this.queue.delete(event)
|
|
93
|
+
log('cleaned up "%s" listeners queue!', event)
|
|
94
|
+
})
|
|
95
|
+
}) as EventMap[Event])
|
|
96
|
+
|
|
97
|
+
return super.emit(event, ...args)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Returns a promise that resolves when all the listeners for the given event
|
|
102
|
+
* has been called. Awaits asynchronous listeners.
|
|
103
|
+
* If the event has no listeners, resolves immediately.
|
|
104
|
+
*/
|
|
105
|
+
public async untilIdle<Event extends keyof EventMap>(
|
|
106
|
+
event: Event
|
|
107
|
+
): Promise<void> {
|
|
108
|
+
const listenersQueue = this.queue.get(event) || []
|
|
109
|
+
await Promise.all(listenersQueue).finally(() => {
|
|
110
|
+
// Clear the queue one the promise settles
|
|
111
|
+
// so that different events don't share the same queue.
|
|
112
|
+
this.queue.delete(event)
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private openListenerQueue<Event extends keyof EventMap>(
|
|
117
|
+
event: Event
|
|
118
|
+
): QueueItem[] {
|
|
119
|
+
const log = this.log.extend('openListenerQueue')
|
|
120
|
+
|
|
121
|
+
log('opening "%s" listeners queue...', event)
|
|
122
|
+
|
|
123
|
+
const queue = this.queue.get(event)
|
|
124
|
+
|
|
125
|
+
if (!queue) {
|
|
126
|
+
log('no queue found, creating one...')
|
|
127
|
+
|
|
128
|
+
this.queue.set(event, [])
|
|
129
|
+
return []
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
log('returning an exising queue:', queue)
|
|
133
|
+
return queue
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
public removeAllListeners<Event extends keyof EventMap>(event?: Event) {
|
|
137
|
+
const log = this.log.extend('removeAllListeners')
|
|
138
|
+
log('event:', event)
|
|
139
|
+
|
|
140
|
+
if (event) {
|
|
141
|
+
this.queue.delete(event)
|
|
142
|
+
log('cleared the "%s" listeners queue!', event, this.queue.get(event))
|
|
143
|
+
} else {
|
|
144
|
+
this.queue.clear()
|
|
145
|
+
log('cleared the listeners queue!', this.queue)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return super.removeAllListeners(event)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
public activate(): void {
|
|
152
|
+
const log = this.log.extend('activate')
|
|
153
|
+
this.readyState = AsyncEventEmitterReadyState.ACTIVE
|
|
154
|
+
log('set state to:', this.readyState)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Deactivate this event emitter.
|
|
159
|
+
* Deactivated emitter can no longer emit and listen to events
|
|
160
|
+
* and needs to be activated again in order to do so.
|
|
161
|
+
*/
|
|
162
|
+
public deactivate(): void {
|
|
163
|
+
const log = this.log.extend('deactivate')
|
|
164
|
+
|
|
165
|
+
log('removing all listeners...')
|
|
166
|
+
this.removeAllListeners()
|
|
167
|
+
|
|
168
|
+
this.readyState = AsyncEventEmitterReadyState.DEACTIVATED
|
|
169
|
+
log('set state to:', this.readyState)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export type AnyFunction = (...args: any[]) => any
|
|
2
|
+
|
|
3
|
+
export type LazyCallbackReturnType<FnType extends AnyFunction> =
|
|
4
|
+
| Parameters<FnType>
|
|
5
|
+
| []
|
|
6
|
+
|
|
7
|
+
export interface LazyCallback<FnType extends AnyFunction> {
|
|
8
|
+
(...args: Parameters<FnType>): void
|
|
9
|
+
invoked(): Promise<LazyCallbackReturnType<FnType>>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface LazyCallbackOptions {
|
|
13
|
+
maxCalls?: number
|
|
14
|
+
maxCallsCallback?(): void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createLazyCallback<FnType extends AnyFunction>(
|
|
18
|
+
options: LazyCallbackOptions = {}
|
|
19
|
+
): LazyCallback<FnType> {
|
|
20
|
+
let calledTimes = 0
|
|
21
|
+
let autoResolveTimeout: NodeJS.Timeout
|
|
22
|
+
let remoteResolve: (args: LazyCallbackReturnType<FnType>) => unknown
|
|
23
|
+
|
|
24
|
+
const callPromise = new Promise<LazyCallbackReturnType<FnType>>((resolve) => {
|
|
25
|
+
remoteResolve = resolve
|
|
26
|
+
}).finally(() => {
|
|
27
|
+
clearTimeout(autoResolveTimeout)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const fn: LazyCallback<FnType> = function (...args) {
|
|
31
|
+
if (options.maxCalls && calledTimes >= options.maxCalls) {
|
|
32
|
+
options.maxCallsCallback?.()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
remoteResolve(args)
|
|
36
|
+
calledTimes++
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fn.invoked = async () => {
|
|
40
|
+
// Immediately resolve the callback if it hasn't been called already.
|
|
41
|
+
autoResolveTimeout = setTimeout(() => {
|
|
42
|
+
remoteResolve([])
|
|
43
|
+
}, 0)
|
|
44
|
+
|
|
45
|
+
return callPromise
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return fn
|
|
49
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { objectToHeaders } from 'headers-polyfill'
|
|
2
|
-
import { IsomorphicResponse, MockedResponse } from '../
|
|
2
|
+
import { IsomorphicResponse, MockedResponse } from '../glossary'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Converts a given mocked response object into an isomorphic response.
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
import { IncomingMessage } from 'http';
|
|
3
|
-
import { HeadersObject, Headers } from 'headers-polyfill';
|
|
4
|
-
import { StrictEventEmitter } from 'strict-event-emitter';
|
|
5
|
-
export declare type Interceptor = (observer: Observer, resolver: Resolver) => InterceptorCleanupFn;
|
|
6
|
-
export declare type Observer = StrictEventEmitter<InterceptorEventsMap>;
|
|
7
|
-
/**
|
|
8
|
-
* A side-effect function to restore all the patched modules.
|
|
9
|
-
*/
|
|
10
|
-
export declare type InterceptorCleanupFn = () => void;
|
|
11
|
-
export declare type RequestCredentials = 'omit' | 'include' | 'same-origin';
|
|
12
|
-
export interface IsomorphicRequest {
|
|
13
|
-
id: string;
|
|
14
|
-
url: URL;
|
|
15
|
-
method: string;
|
|
16
|
-
headers: Headers;
|
|
17
|
-
/**
|
|
18
|
-
* The value of the request client's "credentials" option
|
|
19
|
-
* or a compatible alternative (i.e. `withCredentials` for `XMLHttpRequest`).
|
|
20
|
-
* Always equals to "omit" in Node.js.
|
|
21
|
-
*/
|
|
22
|
-
credentials: RequestCredentials;
|
|
23
|
-
body?: string;
|
|
24
|
-
}
|
|
25
|
-
export interface IsomorphicResponse {
|
|
26
|
-
status: number;
|
|
27
|
-
statusText: string;
|
|
28
|
-
headers: Headers;
|
|
29
|
-
body?: string;
|
|
30
|
-
}
|
|
31
|
-
export interface MockedResponse extends Omit<Partial<IsomorphicResponse>, 'headers'> {
|
|
32
|
-
headers?: HeadersObject;
|
|
33
|
-
}
|
|
34
|
-
export interface InterceptorEventsMap {
|
|
35
|
-
request(request: IsomorphicRequest): void;
|
|
36
|
-
response(request: IsomorphicRequest, response: IsomorphicResponse): void;
|
|
37
|
-
}
|
|
38
|
-
export declare type Resolver = (request: IsomorphicRequest, ref: IncomingMessage | XMLHttpRequest | Request) => MockedResponse | Promise<MockedResponse | void> | void;
|
|
39
|
-
export interface InterceptorOptions {
|
|
40
|
-
modules: Interceptor[];
|
|
41
|
-
resolver: Resolver;
|
|
42
|
-
}
|
|
43
|
-
export interface InterceptorApi {
|
|
44
|
-
/**
|
|
45
|
-
* Apply necessary module patches to provision the interception of requests.
|
|
46
|
-
*/
|
|
47
|
-
apply(): void;
|
|
48
|
-
on<Event extends keyof InterceptorEventsMap>(event: Event, listener: InterceptorEventsMap[Event]): void;
|
|
49
|
-
/**
|
|
50
|
-
* Restore all applied module patches and disable the interception.
|
|
51
|
-
*/
|
|
52
|
-
restore(): void;
|
|
53
|
-
}
|
|
54
|
-
export declare function createInterceptor(options: InterceptorOptions): InterceptorApi;
|
package/lib/createInterceptor.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createInterceptor = void 0;
|
|
4
|
-
var strict_event_emitter_1 = require("strict-event-emitter");
|
|
5
|
-
function createInterceptor(options) {
|
|
6
|
-
var observer = new strict_event_emitter_1.StrictEventEmitter();
|
|
7
|
-
var cleanupFns = [];
|
|
8
|
-
return {
|
|
9
|
-
apply: function () {
|
|
10
|
-
cleanupFns = options.modules.map(function (interceptor) {
|
|
11
|
-
return interceptor(observer, options.resolver);
|
|
12
|
-
});
|
|
13
|
-
},
|
|
14
|
-
on: function (event, listener) {
|
|
15
|
-
observer.addListener(event, listener);
|
|
16
|
-
},
|
|
17
|
-
restore: function () {
|
|
18
|
-
observer.removeAllListeners();
|
|
19
|
-
if (cleanupFns.length === 0) {
|
|
20
|
-
throw new Error("Failed to restore patched modules: no patches found. Did you forget to run \".apply()\"?");
|
|
21
|
-
}
|
|
22
|
-
cleanupFns.forEach(function (restore) { return restore(); });
|
|
23
|
-
},
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
exports.createInterceptor = createInterceptor;
|
|
27
|
-
//# sourceMappingURL=createInterceptor.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createInterceptor.js","sourceRoot":"","sources":["../src/createInterceptor.ts"],"names":[],"mappings":";;;AAEA,6DAAyD;AAwEzD,SAAgB,iBAAiB,CAAC,OAA2B;IAC3D,IAAM,QAAQ,GAAG,IAAI,yCAAkB,EAAwB,CAAA;IAC/D,IAAI,UAAU,GAA2B,EAAE,CAAA;IAE3C,OAAO;QACL,KAAK;YACH,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,UAAC,WAAW;gBAC3C,OAAO,WAAW,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;YAChD,CAAC,CAAC,CAAA;QACJ,CAAC;QACD,EAAE,YAAC,KAAK,EAAE,QAAQ;YAChB,QAAQ,CAAC,WAAW,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;QACvC,CAAC;QACD,OAAO;YACL,QAAQ,CAAC,kBAAkB,EAAE,CAAA;YAE7B,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE;gBAC3B,MAAM,IAAI,KAAK,CACb,0FAAwF,CACzF,CAAA;aACF;YAED,UAAU,CAAC,OAAO,CAAC,UAAC,OAAO,IAAK,OAAA,OAAO,EAAE,EAAT,CAAS,CAAC,CAAA;QAC5C,CAAC;KACF,CAAA;AACH,CAAC;AAzBD,8CAyBC"}
|
package/lib/remote.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
import { ChildProcess } from 'child_process';
|
|
3
|
-
import { InterceptorApi, InterceptorOptions, Resolver } from './createInterceptor';
|
|
4
|
-
export declare type CreateRemoteInterceptorOptions = Omit<InterceptorOptions, 'resolver'>;
|
|
5
|
-
export declare type RemoteResolverApi = Pick<InterceptorApi, 'on'>;
|
|
6
|
-
export interface CreateRemoteResolverOptions {
|
|
7
|
-
process: ChildProcess;
|
|
8
|
-
resolver: Resolver;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Creates a remote request interceptor that delegates
|
|
12
|
-
* the mocked response resolution to the parent process.
|
|
13
|
-
* The parent process must establish a remote resolver
|
|
14
|
-
* by calling `createRemoteResolver` function.
|
|
15
|
-
*/
|
|
16
|
-
export declare function createRemoteInterceptor(options: CreateRemoteInterceptorOptions): InterceptorApi;
|
|
17
|
-
/**
|
|
18
|
-
* Creates a response resolver function attached to the given `ChildProcess`.
|
|
19
|
-
* The child process must establish a remote interceptor by calling `createRemoteInterceptor` function.
|
|
20
|
-
*/
|
|
21
|
-
export declare function createRemoteResolver(options: CreateRemoteResolverOptions): RemoteResolverApi;
|