@zimic/interceptor 1.2.3 → 1.2.4-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.
Files changed (34) hide show
  1. package/dist/{chunk-XCYZ5L2M.mjs → chunk-EIYQEPK2.mjs} +111 -85
  2. package/dist/chunk-EIYQEPK2.mjs.map +1 -0
  3. package/dist/{chunk-ZU6IGW27.js → chunk-OKZEX5DQ.js} +111 -85
  4. package/dist/chunk-OKZEX5DQ.js.map +1 -0
  5. package/dist/cli.js +18 -18
  6. package/dist/cli.js.map +1 -1
  7. package/dist/cli.mjs +2 -2
  8. package/dist/cli.mjs.map +1 -1
  9. package/dist/http.js +112 -110
  10. package/dist/http.js.map +1 -1
  11. package/dist/http.mjs +112 -110
  12. package/dist/http.mjs.map +1 -1
  13. package/dist/server.js +6 -6
  14. package/dist/server.mjs +1 -1
  15. package/package.json +1 -1
  16. package/src/http/interceptor/HttpInterceptorClient.ts +1 -1
  17. package/src/http/interceptor/HttpInterceptorStore.ts +6 -3
  18. package/src/http/interceptor/RemoteHttpInterceptor.ts +1 -1
  19. package/src/http/interceptor/errors/UnknownHttpInterceptorPlatformError.ts +1 -1
  20. package/src/http/interceptorWorker/HttpInterceptorWorker.ts +3 -5
  21. package/src/http/interceptorWorker/LocalHttpInterceptorWorker.ts +16 -12
  22. package/src/http/interceptorWorker/RemoteHttpInterceptorWorker.ts +20 -43
  23. package/src/server/InterceptorServer.ts +39 -21
  24. package/src/server/types/schema.ts +1 -1
  25. package/src/utils/http.ts +2 -2
  26. package/src/utils/webSocket.ts +3 -3
  27. package/src/webSocket/WebSocketClient.ts +4 -4
  28. package/src/webSocket/WebSocketHandler.ts +119 -69
  29. package/src/webSocket/WebSocketServer.ts +2 -3
  30. package/src/webSocket/errors/InvalidWebSocketMessageError.ts +8 -0
  31. package/src/webSocket/types.ts +9 -14
  32. package/dist/chunk-XCYZ5L2M.mjs.map +0 -1
  33. package/dist/chunk-ZU6IGW27.js.map +0 -1
  34. package/src/webSocket/errors/InvalidWebSocketMessage.ts +0 -8
@@ -12,7 +12,7 @@ import {
12
12
  } from '@/utils/webSocket';
13
13
 
14
14
  import { WEB_SOCKET_CONTROL_MESSAGES, WebSocketControlMessage } from './constants';
15
- import InvalidWebSocketMessage from './errors/InvalidWebSocketMessage';
15
+ import InvalidWebSocketMessageError from './errors/InvalidWebSocketMessageError';
16
16
  import NotRunningWebSocketHandlerError from './errors/NotRunningWebSocketHandlerError';
17
17
  import {
18
18
  WebSocketEventMessageListener,
@@ -26,6 +26,10 @@ import {
26
26
  WebSocketMessage,
27
27
  } from './types';
28
28
 
29
+ interface WebSocketRequestAbortOptions<Schema extends WebSocketSchema> {
30
+ shouldAbortRequest?: (request: WebSocketEventMessage<Schema>) => boolean;
31
+ }
32
+
29
33
  abstract class WebSocketHandler<Schema extends WebSocketSchema> {
30
34
  private sockets = new Set<ClientSocket>();
31
35
 
@@ -40,7 +44,7 @@ abstract class WebSocketHandler<Schema extends WebSocketSchema> {
40
44
  } = {};
41
45
 
42
46
  private socketListeners = {
43
- messageAbort: new Map<ClientSocket, Set<(error: unknown) => void>>(),
47
+ abortRequests: new Map<ClientSocket, Set<(options: WebSocketRequestAbortOptions<Schema>) => void>>(),
44
48
  };
45
49
 
46
50
  protected constructor(options: { socketTimeout?: number; messageTimeout?: number }) {
@@ -71,11 +75,14 @@ abstract class WebSocketHandler<Schema extends WebSocketSchema> {
71
75
  socket.addEventListener('error', handleSocketError);
72
76
 
73
77
  const handleSocketClose = () => {
78
+ this.sockets.delete(socket);
79
+
80
+ this.emitSocket('abortRequests', socket);
81
+ this.socketListeners.abortRequests.delete(socket);
82
+
74
83
  socket.removeEventListener('message', handleSocketMessage);
75
84
  socket.removeEventListener('close', handleSocketClose);
76
85
  socket.removeEventListener('error', handleSocketError);
77
-
78
- this.removeSocket(socket);
79
86
  };
80
87
 
81
88
  socket.addEventListener('close', handleSocketClose);
@@ -109,7 +116,7 @@ abstract class WebSocketHandler<Schema extends WebSocketSchema> {
109
116
  if (typeof data === 'string') {
110
117
  return data;
111
118
  } else {
112
- throw new InvalidWebSocketMessage(data);
119
+ throw new InvalidWebSocketMessageError(data);
113
120
  }
114
121
  }
115
122
 
@@ -119,11 +126,11 @@ abstract class WebSocketHandler<Schema extends WebSocketSchema> {
119
126
  try {
120
127
  parsedMessage = JSON.parse(stringifiedMessage) as unknown;
121
128
  } catch {
122
- throw new InvalidWebSocketMessage(stringifiedMessage);
129
+ throw new InvalidWebSocketMessageError(stringifiedMessage);
123
130
  }
124
131
 
125
132
  if (!this.isMessage(parsedMessage)) {
126
- throw new InvalidWebSocketMessage(stringifiedMessage);
133
+ throw new InvalidWebSocketMessageError(stringifiedMessage);
127
134
  }
128
135
 
129
136
  if (this.isReplyMessage(parsedMessage)) {
@@ -154,6 +161,13 @@ abstract class WebSocketHandler<Schema extends WebSocketSchema> {
154
161
  );
155
162
  }
156
163
 
164
+ isChannelEvent<Channel extends WebSocketChannel<Schema>>(
165
+ event: WebSocketEventMessage<Schema>,
166
+ channel: Channel,
167
+ ): event is WebSocketEventMessage<Schema, Channel> {
168
+ return event.channel === channel;
169
+ }
170
+
157
171
  private async notifyListeners(message: WebSocketMessage<Schema>, socket: ClientSocket) {
158
172
  if (this.isReplyMessage(message)) {
159
173
  await this.notifyReplyListeners(message, socket);
@@ -193,11 +207,6 @@ abstract class WebSocketHandler<Schema extends WebSocketSchema> {
193
207
  await Promise.all(closingPromises);
194
208
  }
195
209
 
196
- private removeSocket(socket: ClientSocket) {
197
- this.abortSocketMessages([socket]);
198
- this.sockets.delete(socket);
199
- }
200
-
201
210
  private async createEventMessage<Channel extends WebSocketChannel<Schema>>(
202
211
  channel: Channel,
203
212
  eventData: WebSocketEventMessage<Schema, Channel>['data'],
@@ -233,43 +242,67 @@ abstract class WebSocketHandler<Schema extends WebSocketSchema> {
233
242
  const request = await this.createEventMessage(channel, requestData);
234
243
  this.sendMessage(request, options.sockets);
235
244
 
236
- const response = await this.waitForReply(channel, request.id, options.sockets);
245
+ const response = await this.waitForReply(channel, request, options.sockets);
237
246
  return response.data;
238
247
  }
239
248
 
240
249
  async waitForReply<Channel extends WebSocketChannelWithReply<Schema>>(
241
250
  channel: Channel,
242
- requestId: WebSocketEventMessage<Schema, Channel>['id'],
251
+ request: WebSocketEventMessage<Schema, Channel>,
243
252
  sockets: Collection<ClientSocket> = this.sockets,
244
253
  ) {
245
254
  return new Promise<WebSocketReplyMessage<Schema, Channel>>((resolve, reject) => {
246
255
  const replyTimeout = setTimeout(() => {
247
- this.offReply(channel, replyListener); // eslint-disable-line @typescript-eslint/no-use-before-define
248
- this.offAbortSocketMessages(sockets, abortListener); // eslint-disable-line @typescript-eslint/no-use-before-define
256
+ this.offChannel('reply', channel, replyListener); // eslint-disable-line @typescript-eslint/no-use-before-define
257
+
258
+ for (const socket of sockets) {
259
+ this.offSocket('abortRequests', socket, abortRequestsHandler); // eslint-disable-line @typescript-eslint/no-use-before-define
260
+ }
249
261
 
250
262
  const timeoutError = new WebSocketMessageTimeoutError(this.messageTimeout);
251
263
  reject(timeoutError);
252
264
  }, this.messageTimeout);
253
265
 
254
- const abortListener = this.onAbortSocketMessages(sockets, (error) => {
266
+ const replyListener = this.onChannel('reply', channel, (message) => {
267
+ if (message.requestId !== request.id) {
268
+ return;
269
+ }
270
+
255
271
  clearTimeout(replyTimeout);
256
272
 
257
- this.offReply(channel, replyListener); // eslint-disable-line @typescript-eslint/no-use-before-define
258
- this.offAbortSocketMessages(sockets, abortListener);
273
+ this.offChannel('reply', channel, replyListener);
274
+
275
+ for (const socket of sockets) {
276
+ this.offSocket('abortRequests', socket, abortRequestsHandler); // eslint-disable-line @typescript-eslint/no-use-before-define
277
+ }
259
278
 
260
- reject(error);
279
+ resolve(message);
261
280
  });
262
281
 
263
- const replyListener = this.onReply(channel, (message) => {
264
- if (message.requestId === requestId) {
265
- clearTimeout(replyTimeout);
282
+ const abortRequestsHandler = (options: WebSocketRequestAbortOptions<Schema>) => {
283
+ const shouldAbortRequest = options.shouldAbortRequest === undefined || options.shouldAbortRequest(request);
284
+
285
+ /* istanbul ignore if -- @preserve
286
+ * Aborting requests is highly non-deterministic because it depends on specific timing of socket events. */
287
+ if (!shouldAbortRequest) {
288
+ return;
289
+ }
290
+
291
+ clearTimeout(replyTimeout);
266
292
 
267
- this.offReply(channel, replyListener);
268
- this.offAbortSocketMessages(sockets, abortListener);
293
+ this.offChannel('reply', channel, replyListener);
269
294
 
270
- resolve(message);
295
+ for (const socket of sockets) {
296
+ this.offSocket('abortRequests', socket, abortRequestsHandler);
271
297
  }
272
- });
298
+
299
+ const abortError = new WebSocketMessageAbortError();
300
+ reject(abortError);
301
+ };
302
+
303
+ for (const socket of sockets) {
304
+ this.onSocket('abortRequests', socket, abortRequestsHandler);
305
+ }
273
306
  });
274
307
  }
275
308
 
@@ -322,12 +355,22 @@ abstract class WebSocketHandler<Schema extends WebSocketSchema> {
322
355
  }
323
356
  }
324
357
 
325
- onEvent<Channel extends WebSocketChannel<Schema>, Listener extends WebSocketEventMessageListener<Schema, Channel>>(
358
+ onChannel<Channel extends WebSocketChannel<Schema>, Listener extends WebSocketEventMessageListener<Schema, Channel>>(
359
+ type: 'event',
326
360
  channel: Channel,
327
- listener: Listener,
328
- ): Listener {
361
+ eventListener: Listener,
362
+ ): Listener;
363
+ onChannel<Channel extends WebSocketChannel<Schema>, Listener extends WebSocketReplyMessageListener<Schema, Channel>>(
364
+ type: 'reply',
365
+ channel: Channel,
366
+ replyListener: Listener,
367
+ ): Listener;
368
+ onChannel<
369
+ Channel extends WebSocketChannel<Schema>,
370
+ Listener extends WebSocketEventMessageListener<Schema, Channel> & WebSocketReplyMessageListener<Schema, Channel>,
371
+ >(type: 'event' | 'reply', channel: Channel, listener: Listener): Listener {
329
372
  const listeners = this.getOrCreateChannelListeners<Channel>(channel);
330
- listeners.event.add(listener);
373
+ listeners[type].add(listener);
331
374
  return listener;
332
375
  }
333
376
 
@@ -344,59 +387,66 @@ abstract class WebSocketHandler<Schema extends WebSocketSchema> {
344
387
  return listeners;
345
388
  }
346
389
 
347
- onReply<
348
- Channel extends WebSocketChannelWithReply<Schema>,
349
- Listener extends WebSocketReplyMessageListener<Schema, Channel>,
350
- >(channel: Channel, listener: Listener): Listener {
351
- const listeners = this.getOrCreateChannelListeners<Channel>(channel);
352
- listeners.reply.add(listener);
353
- return listener;
354
- }
355
-
356
- offEvent<Channel extends WebSocketChannel<Schema>>(
390
+ offChannel<Channel extends WebSocketChannel<Schema>>(
391
+ type: 'event',
357
392
  channel: Channel,
358
- listener: WebSocketEventMessageListener<Schema, Channel>,
359
- ) {
360
- this.channelListeners[channel]?.event.delete(listener);
361
- }
362
-
363
- offReply<Channel extends WebSocketChannelWithReply<Schema>>(
393
+ eventListener: WebSocketEventMessageListener<Schema, Channel>,
394
+ ): void;
395
+ offChannel<Channel extends WebSocketChannel<Schema>>(
396
+ type: 'reply',
397
+ channel: Channel,
398
+ replyListener: WebSocketReplyMessageListener<Schema, Channel>,
399
+ ): void;
400
+ offChannel<Channel extends WebSocketChannel<Schema>>(
401
+ type: 'event' | 'reply',
364
402
  channel: Channel,
365
- listener: WebSocketReplyMessageListener<Schema, Channel>,
403
+ listener: WebSocketEventMessageListener<Schema, Channel> & WebSocketReplyMessageListener<Schema, Channel>,
366
404
  ) {
367
- this.channelListeners[channel]?.reply.delete(listener);
405
+ const listeners = this.channelListeners[channel];
406
+ listeners?.[type].delete(listener);
368
407
  }
369
408
 
370
- removeAllChannelListeners() {
371
- this.channelListeners = {};
409
+ onSocket<Listener extends (options: WebSocketRequestAbortOptions<Schema>) => void>(
410
+ type: 'abortRequests',
411
+ socket: ClientSocket,
412
+ listener: Listener,
413
+ ): Listener {
414
+ const listeners = this.getOrCreateSocketListeners(type, socket);
415
+ listeners.add(listener);
416
+ return listener;
372
417
  }
373
418
 
374
- private onAbortSocketMessages(sockets: Collection<ClientSocket>, listener: (error: unknown) => void) {
375
- for (const socket of sockets) {
376
- let listeners = this.socketListeners.messageAbort.get(socket);
377
- if (!listeners) {
378
- listeners = new Set();
379
- this.socketListeners.messageAbort.set(socket, listeners);
380
- }
381
- listeners.add(listener);
419
+ private getOrCreateSocketListeners(type: 'abortRequests', socket: ClientSocket) {
420
+ const listeners = this.socketListeners[type].get(socket) ?? new Set();
421
+
422
+ if (!this.socketListeners[type].has(socket)) {
423
+ this.socketListeners[type].set(socket, listeners);
382
424
  }
383
425
 
384
- return listener;
426
+ return listeners;
385
427
  }
386
428
 
387
- private offAbortSocketMessages(sockets: Collection<ClientSocket>, listener: (error: unknown) => void) {
388
- for (const socket of sockets) {
389
- this.socketListeners.messageAbort.get(socket)?.delete(listener);
429
+ offSocket<Listener extends (options: WebSocketRequestAbortOptions<Schema>) => void>(
430
+ type: 'abortRequests',
431
+ socket: ClientSocket,
432
+ listener: Listener,
433
+ ) {
434
+ const listeners = this.socketListeners[type].get(socket);
435
+ listeners?.delete(listener);
436
+ }
437
+
438
+ emitSocket(type: 'abortRequests', socket: ClientSocket, options: WebSocketRequestAbortOptions<Schema> = {}) {
439
+ for (const listener of this.socketListeners[type].get(socket) ?? []) {
440
+ listener(options);
390
441
  }
391
442
  }
392
443
 
393
- abortSocketMessages(sockets: Collection<ClientSocket> = this.sockets) {
394
- const abortError = new WebSocketMessageAbortError();
444
+ offAny() {
445
+ this.channelListeners = {};
395
446
 
396
- for (const socket of sockets) {
397
- const listeners = this.socketListeners.messageAbort.get(socket) ?? [];
398
- for (const listener of listeners) {
399
- listener(abortError);
447
+ for (const listenersBySocket of Object.values(this.socketListeners)) {
448
+ for (const listeners of listenersBySocket.values()) {
449
+ listeners.clear();
400
450
  }
401
451
  }
402
452
  }
@@ -79,12 +79,11 @@ class WebSocketServer<Schema extends WebSocketSchema> extends WebSocketHandler<S
79
79
  return;
80
80
  }
81
81
 
82
- super.removeAllChannelListeners();
83
- super.abortSocketMessages();
82
+ super.offAny();
84
83
  await super.closeClientSockets();
85
84
 
86
85
  await closeServerSocket(this.webSocketServer, { timeout: this.socketTimeout });
87
- this.webSocketServer.removeAllListeners();
86
+
88
87
  this.webSocketServer = undefined;
89
88
  }
90
89
  }
@@ -0,0 +1,8 @@
1
+ class InvalidWebSocketMessageError extends Error {
2
+ constructor(message: unknown) {
3
+ super(`Web socket message is invalid and could not be parsed: ${message}`);
4
+ this.name = 'InvalidWebSocketMessageError';
5
+ }
6
+ }
7
+
8
+ export default InvalidWebSocketMessageError;
@@ -1,8 +1,8 @@
1
- import { JSONSerialized, JSONValue } from '@zimic/http';
1
+ import { JSONValue } from '@zimic/http';
2
2
  import { PossiblePromise } from '@zimic/utils/types';
3
3
  import type { WebSocket as ClientSocket } from 'isomorphic-ws';
4
4
 
5
- export interface WebSocketChannelData<Channel extends string> {
5
+ export interface WebSocketChannelFrame<Channel extends string> {
6
6
  id: string;
7
7
  channel: Channel;
8
8
  }
@@ -10,14 +10,14 @@ export interface WebSocketChannelData<Channel extends string> {
10
10
  export interface WebSocketEventMessage<
11
11
  Schema extends WebSocketSchema,
12
12
  Channel extends WebSocketChannel<Schema> = WebSocketChannel<Schema>,
13
- > extends WebSocketChannelData<Channel> {
13
+ > extends WebSocketChannelFrame<Channel> {
14
14
  data: Schema[Channel]['event'];
15
15
  }
16
16
 
17
17
  export interface WebSocketReplyMessage<
18
18
  Schema extends WebSocketSchema,
19
19
  Channel extends WebSocketChannel<Schema> = WebSocketChannel<Schema>,
20
- > extends WebSocketChannelData<Channel> {
20
+ > extends WebSocketChannelFrame<Channel> {
21
21
  data: Schema[Channel]['reply'];
22
22
  requestId: string;
23
23
  }
@@ -34,16 +34,7 @@ interface BaseWebSocketSchema {
34
34
  };
35
35
  }
36
36
 
37
- export type WebSocketSchema<Schema extends BaseWebSocketSchema = BaseWebSocketSchema> =
38
- WebSocketSchema.ConvertToStrict<Schema>;
39
-
40
- export namespace WebSocketSchema {
41
- export type ConvertToStrict<Schema extends BaseWebSocketSchema> = {
42
- [Channel in keyof Schema]: {
43
- [Key in keyof Schema[Channel]]: JSONSerialized<Schema[Channel][Key]>;
44
- };
45
- };
46
- }
37
+ export type WebSocketSchema<Schema extends BaseWebSocketSchema = BaseWebSocketSchema> = Schema;
47
38
 
48
39
  export type WebSocketChannel<Schema extends WebSocketSchema> = keyof Schema & string;
49
40
 
@@ -65,3 +56,7 @@ export type WebSocketReplyMessageListener<Schema extends WebSocketSchema, Channe
65
56
  message: WebSocketReplyMessage<Schema, Channel>,
66
57
  socket: ClientSocket,
67
58
  ) => PossiblePromise<void>;
59
+
60
+ export type WebSocketMessageListener<Schema extends WebSocketSchema, Channel extends WebSocketChannel<Schema>> =
61
+ | WebSocketEventMessageListener<Schema, Channel>
62
+ | WebSocketReplyMessageListener<Schema, Channel>;