@zimic/interceptor 1.5.1 → 1.5.2-canary.1
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/dist/{chunk-3WCEMV5U.js → chunk-6YD4TBYJ.js} +13 -4
- package/dist/chunk-6YD4TBYJ.js.map +1 -0
- package/dist/{chunk-YQNDAJTN.mjs → chunk-MCLPYYCY.mjs} +13 -4
- package/dist/chunk-MCLPYYCY.mjs.map +1 -0
- package/dist/cli.js +16 -16
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +2 -2
- package/dist/cli.mjs.map +1 -1
- package/dist/http.d.ts +24 -8
- package/dist/http.js +368 -289
- package/dist/http.js.map +1 -1
- package/dist/http.mjs +367 -288
- package/dist/http.mjs.map +1 -1
- package/dist/server.js +6 -6
- package/dist/server.mjs +1 -1
- package/package.json +10 -10
- package/src/http/errors/HttpTimesCheckError.ts +9 -9
- package/src/http/index.ts +6 -1
- package/src/http/interceptor/HttpInterceptorImplementation.ts +74 -21
- package/src/http/interceptor/HttpInterceptorStore.ts +11 -13
- package/src/http/interceptor/LocalHttpInterceptor.ts +1 -10
- package/src/http/interceptor/RemoteHttpInterceptor.ts +16 -22
- package/src/http/interceptor/errors/UnknownHttpInterceptorPlatformError.ts +3 -1
- package/src/http/interceptor/factory.ts +3 -0
- package/src/http/interceptor/types/options.ts +1 -1
- package/src/http/interceptorWorker/HttpInterceptorWorker.ts +14 -3
- package/src/http/interceptorWorker/LocalHttpInterceptorWorker.ts +25 -93
- package/src/http/requestHandler/HttpRequestHandlerImplementation.ts +5 -9
- package/src/http/requestHandler/LocalHttpRequestHandler.ts +2 -5
- package/src/http/requestHandler/RemoteHttpRequestHandler.ts +17 -20
- package/src/http/requestHandler/types/public.ts +3 -8
- package/src/http/requestHandler/types/requests.ts +31 -3
- package/src/http/utils/logging.ts +54 -0
- package/src/interceptor/LocalMSWWorkerStore.ts +123 -0
- package/src/{http/interceptorWorker → interceptor}/types/msw.ts +1 -2
- package/src/server/utils/auth.ts +1 -1
- package/src/utils/fetch.ts +2 -2
- package/src/utils/logging.ts +0 -54
- package/src/utils/webSocket/WebSocketHandler.ts +1 -1
- package/dist/chunk-3WCEMV5U.js.map +0 -1
- package/dist/chunk-YQNDAJTN.mjs.map +0 -1
- /package/src/{http/interceptorWorker → interceptor}/errors/UnregisteredBrowserServiceWorkerError.ts +0 -0
package/dist/server.js
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunk6YD4TBYJ_js = require('./chunk-6YD4TBYJ.js');
|
|
4
4
|
require('./chunk-DGUM43GV.js');
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
Object.defineProperty(exports, "DEFAULT_ACCESS_CONTROL_HEADERS", {
|
|
9
9
|
enumerable: true,
|
|
10
|
-
get: function () { return
|
|
10
|
+
get: function () { return chunk6YD4TBYJ_js.DEFAULT_ACCESS_CONTROL_HEADERS; }
|
|
11
11
|
});
|
|
12
12
|
Object.defineProperty(exports, "DEFAULT_PREFLIGHT_STATUS_CODE", {
|
|
13
13
|
enumerable: true,
|
|
14
|
-
get: function () { return
|
|
14
|
+
get: function () { return chunk6YD4TBYJ_js.DEFAULT_PREFLIGHT_STATUS_CODE; }
|
|
15
15
|
});
|
|
16
16
|
Object.defineProperty(exports, "NotRunningInterceptorServerError", {
|
|
17
17
|
enumerable: true,
|
|
18
|
-
get: function () { return
|
|
18
|
+
get: function () { return chunk6YD4TBYJ_js.NotRunningInterceptorServerError_default; }
|
|
19
19
|
});
|
|
20
20
|
Object.defineProperty(exports, "RunningInterceptorServerError", {
|
|
21
21
|
enumerable: true,
|
|
22
|
-
get: function () { return
|
|
22
|
+
get: function () { return chunk6YD4TBYJ_js.RunningInterceptorServerError_default; }
|
|
23
23
|
});
|
|
24
24
|
Object.defineProperty(exports, "createInterceptorServer", {
|
|
25
25
|
enumerable: true,
|
|
26
|
-
get: function () { return
|
|
26
|
+
get: function () { return chunk6YD4TBYJ_js.createInterceptorServer; }
|
|
27
27
|
});
|
|
28
28
|
//# sourceMappingURL=server.js.map
|
|
29
29
|
//# sourceMappingURL=server.js.map
|
package/dist/server.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { DEFAULT_ACCESS_CONTROL_HEADERS, DEFAULT_PREFLIGHT_STATUS_CODE, NotRunningInterceptorServerError_default as NotRunningInterceptorServerError, RunningInterceptorServerError_default as RunningInterceptorServerError, createInterceptorServer } from './chunk-
|
|
1
|
+
export { DEFAULT_ACCESS_CONTROL_HEADERS, DEFAULT_PREFLIGHT_STATUS_CODE, NotRunningInterceptorServerError_default as NotRunningInterceptorServerError, RunningInterceptorServerError_default as RunningInterceptorServerError, createInterceptorServer } from './chunk-MCLPYYCY.mjs';
|
|
2
2
|
import './chunk-BJTO5JO5.mjs';
|
|
3
3
|
//# sourceMappingURL=server.mjs.map
|
|
4
4
|
//# sourceMappingURL=server.mjs.map
|
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"api",
|
|
15
15
|
"static"
|
|
16
16
|
],
|
|
17
|
-
"version": "1.5.1",
|
|
17
|
+
"version": "1.5.2-canary.1",
|
|
18
18
|
"homepage": "https://zimic.dev/docs/interceptor",
|
|
19
19
|
"repository": {
|
|
20
20
|
"type": "git",
|
|
@@ -100,29 +100,29 @@
|
|
|
100
100
|
"isomorphic-ws": "5.0.0",
|
|
101
101
|
"msw": "2.15.0",
|
|
102
102
|
"picocolors": "^1.1.1",
|
|
103
|
-
"ws": "8.21.
|
|
103
|
+
"ws": "8.21.3",
|
|
104
104
|
"yargs": "18.1.0",
|
|
105
|
-
"zod": "^4.
|
|
105
|
+
"zod": "^4.5.1"
|
|
106
106
|
},
|
|
107
107
|
"devDependencies": {
|
|
108
|
-
"@types/node": "^26.
|
|
108
|
+
"@types/node": "^26.4.0",
|
|
109
109
|
"@types/ws": "^8.18.1",
|
|
110
110
|
"@types/yargs": "^17.0.35",
|
|
111
111
|
"@typescript/native": "npm:typescript@^7.0.2",
|
|
112
|
-
"@vitest/browser": "^4.1.
|
|
113
|
-
"@vitest/browser-playwright": "^4.1.
|
|
114
|
-
"@vitest/coverage-istanbul": "^4.1.
|
|
112
|
+
"@vitest/browser": "^4.1.11",
|
|
113
|
+
"@vitest/browser-playwright": "^4.1.11",
|
|
114
|
+
"@vitest/coverage-istanbul": "^4.1.11",
|
|
115
115
|
"@zimic/eslint-config-node": "workspace:*",
|
|
116
116
|
"@zimic/lint-staged-config": "workspace:*",
|
|
117
117
|
"@zimic/tsconfig": "workspace:*",
|
|
118
118
|
"@zimic/utils": "workspace:*",
|
|
119
|
-
"concurrently": "^10.0.
|
|
119
|
+
"concurrently": "^10.0.5",
|
|
120
120
|
"dotenv-cli": "^11.0.0",
|
|
121
121
|
"eslint": "^9.39.5",
|
|
122
|
-
"playwright": "^1.62.
|
|
122
|
+
"playwright": "^1.62.1",
|
|
123
123
|
"tsup": "^8.5.1",
|
|
124
124
|
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
125
|
-
"vitest": "^4.1.
|
|
125
|
+
"vitest": "^4.1.11"
|
|
126
126
|
},
|
|
127
127
|
"peerDependencies": {
|
|
128
128
|
"@zimic/http": "workspace:^1.2.0",
|
|
@@ -3,7 +3,7 @@ import { Range } from '@zimic/utils/types';
|
|
|
3
3
|
import color from 'picocolors';
|
|
4
4
|
|
|
5
5
|
import { HttpInterceptorRequestSaving } from '@/http/interceptor/types/public';
|
|
6
|
-
import {
|
|
6
|
+
import { stringifyHttpValueToLog } from '@/http/utils/logging';
|
|
7
7
|
|
|
8
8
|
import { UnmatchedHttpInterceptorRequestGroup } from '../requestHandler/types/restrictions';
|
|
9
9
|
import HttpTimesDeclarationPointer from './HttpTimesDeclarationPointer';
|
|
@@ -66,8 +66,8 @@ function createMessageDiffs({ requestSaving, unmatchedRequestGroups }: HttpTimes
|
|
|
66
66
|
if (diff.computed) {
|
|
67
67
|
messageParts.push('Computed restriction:');
|
|
68
68
|
|
|
69
|
-
const stringifiedExpected =
|
|
70
|
-
const stringifiedReceived =
|
|
69
|
+
const stringifiedExpected = stringifyHttpValueToLog(diff.computed.expected);
|
|
70
|
+
const stringifiedReceived = stringifyHttpValueToLog(diff.computed.received);
|
|
71
71
|
|
|
72
72
|
messageParts.push(` ${color.green(`- return ${stringifiedExpected}`)}`);
|
|
73
73
|
messageParts.push(` ${color.red(`+ return ${stringifiedReceived}`)}`);
|
|
@@ -76,8 +76,8 @@ function createMessageDiffs({ requestSaving, unmatchedRequestGroups }: HttpTimes
|
|
|
76
76
|
if (diff.headers) {
|
|
77
77
|
messageParts.push('Headers:');
|
|
78
78
|
|
|
79
|
-
const stringifiedExpected =
|
|
80
|
-
const stringifiedReceived =
|
|
79
|
+
const stringifiedExpected = stringifyHttpValueToLog(diff.headers.expected);
|
|
80
|
+
const stringifiedReceived = stringifyHttpValueToLog(diff.headers.received);
|
|
81
81
|
|
|
82
82
|
messageParts.push(` ${color.green(`- ${stringifiedExpected}`)}`);
|
|
83
83
|
messageParts.push(` ${color.red(`+ ${stringifiedReceived}`)}`);
|
|
@@ -86,8 +86,8 @@ function createMessageDiffs({ requestSaving, unmatchedRequestGroups }: HttpTimes
|
|
|
86
86
|
if (diff.searchParams) {
|
|
87
87
|
messageParts.push('Search params:');
|
|
88
88
|
|
|
89
|
-
const stringifiedExpected =
|
|
90
|
-
const stringifiedReceived =
|
|
89
|
+
const stringifiedExpected = stringifyHttpValueToLog(diff.searchParams.expected);
|
|
90
|
+
const stringifiedReceived = stringifyHttpValueToLog(diff.searchParams.received);
|
|
91
91
|
|
|
92
92
|
messageParts.push(` ${color.green(`- ${stringifiedExpected}`)}`);
|
|
93
93
|
messageParts.push(` ${color.red(`+ ${stringifiedReceived}`)}`);
|
|
@@ -96,10 +96,10 @@ function createMessageDiffs({ requestSaving, unmatchedRequestGroups }: HttpTimes
|
|
|
96
96
|
if (diff.body) {
|
|
97
97
|
messageParts.push('Body:');
|
|
98
98
|
|
|
99
|
-
const stringifiedExpected =
|
|
99
|
+
const stringifiedExpected = stringifyHttpValueToLog(diff.body.expected, {
|
|
100
100
|
includeClassName: { searchParams: true },
|
|
101
101
|
});
|
|
102
|
-
const stringifiedReceived =
|
|
102
|
+
const stringifiedReceived = stringifyHttpValueToLog(diff.body.received, {
|
|
103
103
|
includeClassName: { searchParams: true },
|
|
104
104
|
});
|
|
105
105
|
|
package/src/http/index.ts
CHANGED
|
@@ -9,14 +9,18 @@ export { default as UnknownHttpInterceptorPlatformError } from './interceptor/er
|
|
|
9
9
|
export { default as UnknownHttpInterceptorTypeError } from './interceptor/errors/UnknownHttpInterceptorTypeError';
|
|
10
10
|
export { default as RequestSavingSafeLimitExceededError } from './interceptor/errors/RequestSavingSafeLimitExceededError';
|
|
11
11
|
|
|
12
|
-
export { default as UnregisteredBrowserServiceWorkerError } from '
|
|
12
|
+
export { default as UnregisteredBrowserServiceWorkerError } from '@/interceptor/errors/UnregisteredBrowserServiceWorkerError';
|
|
13
13
|
|
|
14
14
|
export { default as DisabledRequestSavingError } from './requestHandler/errors/DisabledRequestSavingError';
|
|
15
15
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
16
16
|
export { default as HttpTimesCheckError, TimesCheckError } from './errors/HttpTimesCheckError';
|
|
17
17
|
|
|
18
18
|
export type {
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
19
20
|
HttpRequestHandlerResponseDeclaration,
|
|
21
|
+
HttpRequestHandlerResponseStaticDeclaration,
|
|
22
|
+
HttpRequestHandlerResponseComputedDeclaration,
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
20
24
|
HttpRequestHandlerResponseDeclarationFactory,
|
|
21
25
|
HttpRequestHandlerResponseDelayFactory,
|
|
22
26
|
HttpInterceptorRequest,
|
|
@@ -43,6 +47,7 @@ export type {
|
|
|
43
47
|
export type {
|
|
44
48
|
HttpInterceptorType,
|
|
45
49
|
HttpInterceptorPlatform,
|
|
50
|
+
HttpInterceptorAuthOptions,
|
|
46
51
|
LocalHttpInterceptorOptions,
|
|
47
52
|
RemoteHttpInterceptorOptions,
|
|
48
53
|
HttpInterceptorOptions,
|
|
@@ -24,7 +24,7 @@ import { HttpInterceptorRequest } from '../requestHandler/types/requests';
|
|
|
24
24
|
import NotRunningHttpInterceptorError from './errors/NotRunningHttpInterceptorError';
|
|
25
25
|
import RequestSavingSafeLimitExceededError from './errors/RequestSavingSafeLimitExceededError';
|
|
26
26
|
import RunningHttpInterceptorError from './errors/RunningHttpInterceptorError';
|
|
27
|
-
import HttpInterceptorStore from './HttpInterceptorStore';
|
|
27
|
+
import type HttpInterceptorStore from './HttpInterceptorStore';
|
|
28
28
|
import { UnhandledRequestStrategy } from './types/options';
|
|
29
29
|
import { HttpInterceptorRequestSaving } from './types/public';
|
|
30
30
|
import { HttpInterceptorRequestContext } from './types/requests';
|
|
@@ -46,6 +46,10 @@ class HttpInterceptorImplementation<
|
|
|
46
46
|
private deleteWorker: () => void;
|
|
47
47
|
private worker?: HttpInterceptorWorker;
|
|
48
48
|
|
|
49
|
+
private startPromise?: Promise<void>;
|
|
50
|
+
private numberOfPendingStarts = 0;
|
|
51
|
+
private lifecycleQueue: Promise<void> = Promise.resolve();
|
|
52
|
+
|
|
49
53
|
requestSaving: HttpInterceptorRequestSaving;
|
|
50
54
|
private numberOfSavedRequests = 0;
|
|
51
55
|
|
|
@@ -104,7 +108,7 @@ class HttpInterceptorImplementation<
|
|
|
104
108
|
}
|
|
105
109
|
|
|
106
110
|
set baseURL(newBaseURL: URL) {
|
|
107
|
-
if (this.isRunning) {
|
|
111
|
+
if (this.isRunning || this.isStarting) {
|
|
108
112
|
throw new RunningHttpInterceptorError(
|
|
109
113
|
'Did you forget to call `await interceptor.stop()` before changing the base URL?',
|
|
110
114
|
);
|
|
@@ -134,35 +138,96 @@ class HttpInterceptorImplementation<
|
|
|
134
138
|
return this.worker?.platform ?? null;
|
|
135
139
|
}
|
|
136
140
|
|
|
137
|
-
|
|
141
|
+
start() {
|
|
142
|
+
if (this.startPromise) {
|
|
143
|
+
return this.startPromise;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
this.numberOfPendingStarts++;
|
|
147
|
+
|
|
148
|
+
const startPromise = this.enqueueLifecycleOperation(() => this.startOnce()).finally(() => {
|
|
149
|
+
this.numberOfPendingStarts--;
|
|
150
|
+
|
|
151
|
+
const isLastStartPromise = this.startPromise === startPromise;
|
|
152
|
+
|
|
153
|
+
if (isLastStartPromise) {
|
|
154
|
+
this.startPromise = undefined;
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
this.startPromise = startPromise;
|
|
159
|
+
|
|
160
|
+
return startPromise;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private async startOnce() {
|
|
164
|
+
if (this.isRunning) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
138
168
|
try {
|
|
139
169
|
this.worker = this.createWorker();
|
|
170
|
+
this.worker.registerRunningInterceptor(this);
|
|
140
171
|
|
|
141
172
|
await this.worker.start();
|
|
142
|
-
this.worker.registerRunningInterceptor(this);
|
|
143
173
|
|
|
144
174
|
this.markAsRunning(true);
|
|
145
175
|
} catch (error) {
|
|
146
|
-
await this.
|
|
176
|
+
await this.stopWorker();
|
|
147
177
|
throw error;
|
|
148
178
|
}
|
|
149
179
|
}
|
|
150
180
|
|
|
151
|
-
|
|
181
|
+
get isStarting() {
|
|
182
|
+
return this.numberOfPendingStarts > 0;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async stop(options: { beforeStop?: () => PossiblePromise<void> }) {
|
|
186
|
+
this.startPromise = undefined;
|
|
187
|
+
await this.enqueueLifecycleOperation(() => this.stopOnce(options));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private async stopOnce(options: { beforeStop?: () => PossiblePromise<void> }) {
|
|
191
|
+
if (!this.isRunning) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
await options.beforeStop?.();
|
|
196
|
+
|
|
197
|
+
await this.stopWorker();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private async stopWorker() {
|
|
152
201
|
this.worker?.unregisterRunningInterceptor(this);
|
|
153
202
|
|
|
154
|
-
|
|
155
|
-
const isLastRunningInterceptor = this.numberOfRunningInterceptors === 0 || this.numberOfRunningInterceptors === 1;
|
|
203
|
+
const isLastRunningInterceptor = this.worker?.numberOfRunningInterceptors === 0;
|
|
156
204
|
|
|
157
205
|
if (isLastRunningInterceptor) {
|
|
158
206
|
await this.worker?.stop();
|
|
159
|
-
|
|
207
|
+
|
|
208
|
+
// Stopping is asynchronous, so we need to check again if we are still the last before deleting the worker.
|
|
209
|
+
// Another interceptor might have started in the meantime and the worker should not be deleted if so.
|
|
210
|
+
const isStillLastRunningInterceptor = this.worker?.numberOfRunningInterceptors === 0;
|
|
211
|
+
|
|
212
|
+
if (isStillLastRunningInterceptor) {
|
|
213
|
+
this.deleteWorker();
|
|
214
|
+
}
|
|
160
215
|
}
|
|
161
216
|
|
|
162
217
|
this.markAsRunning(false);
|
|
163
218
|
this.worker = undefined;
|
|
164
219
|
}
|
|
165
220
|
|
|
221
|
+
private enqueueLifecycleOperation(operation: () => Promise<void>) {
|
|
222
|
+
const operationPromise = this.lifecycleQueue.then(operation);
|
|
223
|
+
|
|
224
|
+
this.lifecycleQueue = operationPromise
|
|
225
|
+
// Ensure the queue continues even if the operation fails
|
|
226
|
+
.catch(() => undefined);
|
|
227
|
+
|
|
228
|
+
return operationPromise;
|
|
229
|
+
}
|
|
230
|
+
|
|
166
231
|
private markAsRunning(isRunning: boolean) {
|
|
167
232
|
if (this.workerOrThrow.type === 'local') {
|
|
168
233
|
this.store.markLocalInterceptorAsRunning(this, isRunning);
|
|
@@ -172,18 +237,6 @@ class HttpInterceptorImplementation<
|
|
|
172
237
|
this.isRunning = isRunning;
|
|
173
238
|
}
|
|
174
239
|
|
|
175
|
-
get numberOfRunningInterceptors() {
|
|
176
|
-
if (!this.isRunning) {
|
|
177
|
-
return 0;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (this.workerOrThrow.type === 'local') {
|
|
181
|
-
return this.store.numberOfRunningLocalInterceptors;
|
|
182
|
-
} else {
|
|
183
|
-
return this.store.numberOfRunningRemoteInterceptors(this.baseURL);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
240
|
get(path: HttpSchemaPath<Schema, HttpSchemaMethod<Schema>>) {
|
|
188
241
|
return this.createHttpRequestHandler('GET' as HttpSchemaMethod<Schema>, path);
|
|
189
242
|
}
|
|
@@ -1,27 +1,25 @@
|
|
|
1
|
-
import { createHttpInterceptorWorker } from '../interceptorWorker/factory';
|
|
2
1
|
import LocalHttpInterceptorWorker from '../interceptorWorker/LocalHttpInterceptorWorker';
|
|
3
2
|
import RemoteHttpInterceptorWorker from '../interceptorWorker/RemoteHttpInterceptorWorker';
|
|
4
|
-
import {
|
|
3
|
+
import type {
|
|
5
4
|
LocalHttpInterceptorWorkerOptions,
|
|
6
5
|
RemoteHttpInterceptorWorkerOptions,
|
|
7
6
|
} from '../interceptorWorker/types/options';
|
|
8
|
-
import { AnyHttpInterceptorImplementation } from './HttpInterceptorImplementation';
|
|
7
|
+
import type { AnyHttpInterceptorImplementation } from './HttpInterceptorImplementation';
|
|
9
8
|
|
|
10
9
|
interface RemoteWorkerKeyOptions {
|
|
11
10
|
auth: RemoteHttpInterceptorWorkerOptions['auth'];
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
class HttpInterceptorStore {
|
|
15
|
-
private static
|
|
16
|
-
private static runningLocalInterceptors = new Set<AnyHttpInterceptorImplementation>();
|
|
17
|
-
|
|
14
|
+
private static localWorker?: LocalHttpInterceptorWorker;
|
|
18
15
|
private static remoteWorkers = new Map<string, RemoteHttpInterceptorWorker>();
|
|
16
|
+
private static runningLocalInterceptors = new Set<AnyHttpInterceptorImplementation>();
|
|
19
17
|
private static runningRemoteInterceptors = new Map<string, Set<AnyHttpInterceptorImplementation>>();
|
|
20
18
|
|
|
21
19
|
private class = HttpInterceptorStore;
|
|
22
20
|
|
|
23
21
|
get localWorker() {
|
|
24
|
-
return this.class.
|
|
22
|
+
return this.class.localWorker;
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
private getRemoteWorkerKey(baseURL: URL, options: RemoteWorkerKeyOptions) {
|
|
@@ -34,7 +32,7 @@ class HttpInterceptorStore {
|
|
|
34
32
|
return key.join(':');
|
|
35
33
|
}
|
|
36
34
|
|
|
37
|
-
|
|
35
|
+
getRemoteWorker(baseURL: URL, options: RemoteWorkerKeyOptions) {
|
|
38
36
|
const remoteWorkerKey = this.getRemoteWorkerKey(baseURL, options);
|
|
39
37
|
return this.class.remoteWorkers.get(remoteWorkerKey);
|
|
40
38
|
}
|
|
@@ -74,20 +72,20 @@ class HttpInterceptorStore {
|
|
|
74
72
|
}
|
|
75
73
|
|
|
76
74
|
getOrCreateLocalWorker(workerOptions: Omit<LocalHttpInterceptorWorkerOptions, 'type'>) {
|
|
77
|
-
const existingWorker = this.class.
|
|
75
|
+
const existingWorker = this.class.localWorker;
|
|
78
76
|
|
|
79
77
|
if (existingWorker) {
|
|
80
78
|
return existingWorker;
|
|
81
79
|
}
|
|
82
80
|
|
|
83
|
-
const createdWorker =
|
|
84
|
-
this.class.
|
|
81
|
+
const createdWorker = new LocalHttpInterceptorWorker({ ...workerOptions, type: 'local' });
|
|
82
|
+
this.class.localWorker = createdWorker;
|
|
85
83
|
|
|
86
84
|
return createdWorker;
|
|
87
85
|
}
|
|
88
86
|
|
|
89
87
|
deleteLocalWorker() {
|
|
90
|
-
this.class.
|
|
88
|
+
this.class.localWorker = undefined;
|
|
91
89
|
}
|
|
92
90
|
|
|
93
91
|
getOrCreateRemoteWorker(workerOptions: Omit<RemoteHttpInterceptorWorkerOptions, 'type'>) {
|
|
@@ -98,7 +96,7 @@ class HttpInterceptorStore {
|
|
|
98
96
|
return existingWorker;
|
|
99
97
|
}
|
|
100
98
|
|
|
101
|
-
const createdWorker =
|
|
99
|
+
const createdWorker = new RemoteHttpInterceptorWorker({ ...workerOptions, type: 'remote' });
|
|
102
100
|
this.class.remoteWorkers.set(remoteWorkerKey, createdWorker);
|
|
103
101
|
|
|
104
102
|
return createdWorker;
|
|
@@ -63,20 +63,11 @@ class LocalHttpInterceptor<Schema extends HttpSchema> implements PublicLocalHttp
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
async start() {
|
|
66
|
-
if (this.isRunning) {
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
66
|
await this.implementation.start();
|
|
71
67
|
}
|
|
72
68
|
|
|
73
69
|
async stop() {
|
|
74
|
-
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
this.clear();
|
|
79
|
-
await this.implementation.stop();
|
|
70
|
+
await this.implementation.stop({ beforeStop: () => this.clear() });
|
|
80
71
|
}
|
|
81
72
|
|
|
82
73
|
get = ((path: HttpSchemaPath<Schema, HttpSchemaMethod<Schema>>) => {
|
|
@@ -14,8 +14,6 @@ class RemoteHttpInterceptor<Schema extends HttpSchema> implements PublicRemoteHt
|
|
|
14
14
|
#auth?: RemoteHttpInterceptorOptions['auth'];
|
|
15
15
|
|
|
16
16
|
constructor(options: RemoteHttpInterceptorOptions) {
|
|
17
|
-
this.#auth = options.auth;
|
|
18
|
-
|
|
19
17
|
const baseURL = new URL(options.baseURL);
|
|
20
18
|
|
|
21
19
|
this.implementation = new HttpInterceptorImplementation<Schema, typeof RemoteHttpRequestHandler>({
|
|
@@ -23,17 +21,19 @@ class RemoteHttpInterceptor<Schema extends HttpSchema> implements PublicRemoteHt
|
|
|
23
21
|
baseURL,
|
|
24
22
|
createWorker: () => {
|
|
25
23
|
return this.store.getOrCreateRemoteWorker({
|
|
26
|
-
serverURL: baseURL,
|
|
24
|
+
serverURL: this.implementation.baseURL,
|
|
27
25
|
auth: this.#auth,
|
|
28
26
|
});
|
|
29
27
|
},
|
|
30
28
|
deleteWorker: () => {
|
|
31
|
-
this.store.deleteRemoteWorker(baseURL, { auth:
|
|
29
|
+
this.store.deleteRemoteWorker(this.implementation.baseURL, { auth: this.#auth });
|
|
32
30
|
},
|
|
33
31
|
Handler: RemoteHttpRequestHandler,
|
|
34
32
|
onUnhandledRequest: options.onUnhandledRequest,
|
|
35
33
|
requestSaving: options.requestSaving,
|
|
36
34
|
});
|
|
35
|
+
|
|
36
|
+
this.auth = options.auth;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
get type() {
|
|
@@ -64,7 +64,7 @@ class RemoteHttpInterceptor<Schema extends HttpSchema> implements PublicRemoteHt
|
|
|
64
64
|
const cannotChangeAuthWhileRunningMessage =
|
|
65
65
|
'Did you forget to call `await interceptor.stop()` before changing the authentication parameters?';
|
|
66
66
|
|
|
67
|
-
if (this.isRunning) {
|
|
67
|
+
if (this.isRunning || this.implementation.isStarting) {
|
|
68
68
|
throw new RunningHttpInterceptorError(cannotChangeAuthWhileRunningMessage);
|
|
69
69
|
}
|
|
70
70
|
|
|
@@ -73,14 +73,17 @@ class RemoteHttpInterceptor<Schema extends HttpSchema> implements PublicRemoteHt
|
|
|
73
73
|
return;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
this.#auth = new Proxy(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
this.#auth = new Proxy(
|
|
77
|
+
{ ...auth }, // Copy object to ensure it's not mutated later without going though the proxy.
|
|
78
|
+
{
|
|
79
|
+
set: (target, property, value) => {
|
|
80
|
+
if (this.isRunning || this.implementation.isStarting) {
|
|
81
|
+
throw new RunningHttpInterceptorError(cannotChangeAuthWhileRunningMessage);
|
|
82
|
+
}
|
|
83
|
+
return Reflect.set(target, property, value);
|
|
84
|
+
},
|
|
82
85
|
},
|
|
83
|
-
|
|
86
|
+
);
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
get onUnhandledRequest() {
|
|
@@ -100,20 +103,11 @@ class RemoteHttpInterceptor<Schema extends HttpSchema> implements PublicRemoteHt
|
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
async start() {
|
|
103
|
-
if (this.isRunning) {
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
106
|
await this.implementation.start();
|
|
108
107
|
}
|
|
109
108
|
|
|
110
109
|
async stop() {
|
|
111
|
-
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
await this.clear();
|
|
116
|
-
await this.implementation.stop();
|
|
110
|
+
await this.implementation.stop({ beforeStop: () => this.clear() });
|
|
117
111
|
}
|
|
118
112
|
|
|
119
113
|
get = ((path: HttpSchemaPath<Schema, HttpSchemaMethod<Schema>>) => {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { ValidationError } from '@zimic/utils/validation';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* An error thrown when an unknown interceptor platform is detected. Currently, the platforms `node` and `browser` are
|
|
3
5
|
* supported.
|
|
4
6
|
*
|
|
5
7
|
* @see {@link https://zimic.dev/docs/interceptor/api/http-interceptor#interceptorplatform `interceptor.platform` API reference}
|
|
6
8
|
*/
|
|
7
|
-
class UnknownHttpInterceptorPlatformError extends
|
|
9
|
+
class UnknownHttpInterceptorPlatformError extends ValidationError {
|
|
8
10
|
/* istanbul ignore next -- @preserve
|
|
9
11
|
* Ignoring because checking unknown platforms is currently not possible in our Vitest setup. */
|
|
10
12
|
constructor() {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HttpSchema } from '@zimic/http';
|
|
2
|
+
import { assertTypeOf } from '@zimic/utils/validation';
|
|
2
3
|
|
|
3
4
|
import UnknownHttpInterceptorTypeError from './errors/UnknownHttpInterceptorTypeError';
|
|
4
5
|
import LocalHttpInterceptor from './LocalHttpInterceptor';
|
|
@@ -30,6 +31,8 @@ export function createHttpInterceptor<Schema extends HttpSchema>(
|
|
|
30
31
|
export function createHttpInterceptor<Schema extends HttpSchema>(
|
|
31
32
|
options: HttpInterceptorOptions,
|
|
32
33
|
): PublicLocalHttpInterceptor<Schema> | PublicRemoteHttpInterceptor<Schema> {
|
|
34
|
+
assertTypeOf('options.baseURL', options.baseURL, 'string');
|
|
35
|
+
|
|
33
36
|
const type = options.type;
|
|
34
37
|
|
|
35
38
|
if (isLocalHttpInterceptorOptions(options)) {
|
|
@@ -77,7 +77,7 @@ export interface LocalHttpInterceptorOptions extends SharedHttpInterceptorOption
|
|
|
77
77
|
onUnhandledRequest?: UnhandledRequestStrategy.Local;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
interface HttpInterceptorAuthOptions {
|
|
80
|
+
export interface HttpInterceptorAuthOptions {
|
|
81
81
|
/** @see {@link https://zimic.dev/docs/interceptor/guides/http/remote-interceptors#interceptor-server-authentication Interceptor server authentication} */
|
|
82
82
|
token: string;
|
|
83
83
|
}
|
|
@@ -52,9 +52,17 @@ abstract class HttpInterceptorWorker {
|
|
|
52
52
|
|
|
53
53
|
private runningInterceptors: AnyHttpInterceptorImplementation[] = [];
|
|
54
54
|
|
|
55
|
+
get numberOfRunningInterceptors() {
|
|
56
|
+
return this.runningInterceptors.length;
|
|
57
|
+
}
|
|
58
|
+
|
|
55
59
|
abstract start(): Promise<void>;
|
|
56
60
|
|
|
57
61
|
protected async sharedStart(internalStart: () => Promise<void>) {
|
|
62
|
+
if (this.stoppingPromise) {
|
|
63
|
+
await this.stoppingPromise;
|
|
64
|
+
}
|
|
65
|
+
|
|
58
66
|
if (this.isRunning) {
|
|
59
67
|
return;
|
|
60
68
|
}
|
|
@@ -99,10 +107,13 @@ abstract class HttpInterceptorWorker {
|
|
|
99
107
|
* reliably reproduce in tests. */
|
|
100
108
|
if (stoppingResult instanceof Promise) {
|
|
101
109
|
this.stoppingPromise = stoppingResult;
|
|
102
|
-
await this.stoppingPromise;
|
|
103
|
-
}
|
|
104
110
|
|
|
105
|
-
|
|
111
|
+
try {
|
|
112
|
+
await this.stoppingPromise;
|
|
113
|
+
} finally {
|
|
114
|
+
this.stoppingPromise = undefined;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
106
117
|
}
|
|
107
118
|
|
|
108
119
|
abstract use<Schema extends HttpSchema>(
|