@loadmill/executer 0.1.37 → 0.1.38

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/ws.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws.js","sourceRoot":"","sources":["../src/ws.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loadmill/executer",
3
- "version": "0.1.37",
3
+ "version": "0.1.38",
4
4
  "description": "Loadmill executer library",
5
5
  "scripts": {
6
6
  "ts-watch": "rimraf dist && tsc -w -p tsconfig.json",
@@ -14,8 +14,8 @@
14
14
  },
15
15
  "license": "Apache-2.0",
16
16
  "dependencies": {
17
- "@loadmill/core": "0.3.36",
18
- "@loadmill/universal": "0.3.29",
17
+ "@loadmill/core": "0.3.37",
18
+ "@loadmill/universal": "0.3.30",
19
19
  "@types/estree": "^0.0.50",
20
20
  "acorn": "^8.4.1",
21
21
  "agentkeepalive": "^4.1.3",
@@ -25,6 +25,6 @@
25
25
  "randomstring": "^1.1.5",
26
26
  "superagent": "^6.1.0",
27
27
  "urijs": "^1.18.1",
28
- "vm2": "^3.9.3"
28
+ "vm2": "^3.9.4"
29
29
  }
30
30
  }
package/src/errors.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { Histogram } from './failures';
2
+
3
+ export class RequestFailuresError extends Error {
4
+ constructor(message: string, public histogram: Histogram = { [message]: 1 }) {
5
+ super(message);
6
+
7
+ // Workaround suggested in: https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
8
+ Object.setPrototypeOf(this, RequestFailuresError.prototype);
9
+ }
10
+ }
@@ -5,12 +5,14 @@ import {
5
5
  CheerioExtractor,
6
6
  JsonPathExtractor,
7
7
  ExpressionExtractor,
8
- ParametrizedExtractor
8
+ ParametrizedExtractor,
9
+ WsExtractor,
9
10
  } from '@loadmill/core/dist/parameters/extractors';
10
11
  import { Parameters } from '@loadmill/core/dist/parameters';
11
12
  import { Extraction } from '@loadmill/core/dist/request';
12
13
  import log from '@loadmill/universal/dist/log';
13
14
  import ednParser from 'jsedn';
15
+ import { WSExtractionData } from '@loadmill/core/dist/parameters/extractors/ws-extractor';
14
16
 
15
17
  export class ExtractionCombiner {
16
18
  headerExtractor: HeaderExtractor;
@@ -19,19 +21,19 @@ export class ExtractionCombiner {
19
21
  ednPathExtractor: JsonPathExtractor;
20
22
  expressionExtractor: ExpressionExtractor;
21
23
 
22
- constructor(private contextParameters: Parameters, private res: any) {
24
+ constructor(private contextParameters: Parameters, private res: any, private wsExtractionData: WSExtractionData) {
23
25
  this.headerExtractor = new HeaderExtractor(res, this.contextParameters);
24
26
  this.expressionExtractor = new ExpressionExtractor(this.contextParameters);
25
27
  }
26
28
 
27
- combine(extraction: Extraction) {
29
+ async combine(extraction: Extraction) {
28
30
  let query: string | string[], extractor!: ParametrizedExtractor;
29
31
 
30
32
  if (typeof extraction === 'string') {
31
33
  query = extraction;
32
34
  extractor = this.expressionExtractor;
33
35
  } else {
34
- const { header, jQuery, jsonPath, edn, regex } = extraction;
36
+ const { header, jQuery, jsonPath, edn, regex, ws } = extraction;
35
37
 
36
38
  if (header != null) {
37
39
  query = header;
@@ -88,6 +90,15 @@ export class ExtractionCombiner {
88
90
 
89
91
  query = regex;
90
92
  }
93
+
94
+ if (ws != null) {
95
+ extractor = new WsExtractor(
96
+ this.wsExtractionData,
97
+ this.contextParameters
98
+ );
99
+
100
+ query = ws;
101
+ }
91
102
  }
92
103
 
93
104
  return () => extractor.extract(query);
@@ -1,6 +1,6 @@
1
1
  export const millVersion = {
2
2
  major: 9,
3
- minor: 43,
3
+ minor: 44,
4
4
  patch: 0,
5
5
  };
6
6
 
package/src/sequence.ts CHANGED
@@ -10,7 +10,6 @@ import clamp from 'lodash/clamp';
10
10
  import find from 'lodash/find';
11
11
  import map from 'lodash/map';
12
12
  import filter from 'lodash/filter';
13
- import forEach from 'lodash/forEach';
14
13
 
15
14
  import { ResolvedRequest } from './request-sequence-result';
16
15
  import { Asserter } from './asserter';
@@ -52,8 +51,6 @@ const {
52
51
  DEFAULT_REQUEST_DELAY,
53
52
  } = confDefaults;
54
53
  const {
55
- MAX_API_REQUEST_BODY_LENGTH,
56
- MAX_LOAD_REQUEST_BODY_LENGTH,
57
54
  MAX_RESPONSE_BYTES,
58
55
  MAX_RESPONSE_COLLECT,
59
56
  MIN_REQUEST_DELAY,
@@ -71,10 +68,15 @@ import {
71
68
  ALLOWED_RESPONSE_STATUSES,
72
69
  LoopConf,
73
70
  DEFAULT_REQUEST_TIMEOUT,
74
- CachePenetrationModes
71
+ CachePenetrationModes,
72
+ RequestPostData
75
73
  } from '@loadmill/core/dist/request';
76
74
 
77
75
  import { testRunEventsEmitter } from './test-run-event-emitter';
76
+ import { WSRequest, WSRequestArguments, WSSequenceHandler } from './ws';
77
+ import { RequestFailuresError } from './errors';
78
+ import { getMaxRequestBodySize } from './utils';
79
+ import { WSExtractionData } from '@loadmill/core/dist/parameters/extractors/ws-extractor';
78
80
 
79
81
  export const reqIdParamName = 'loadmill-request-id';
80
82
 
@@ -127,6 +129,7 @@ class SequenceExecutor {
127
129
  resolvedRequests: ResolvedRequest[] = [];
128
130
  keepaliveHTTPSAgent;
129
131
  keepaliveHTTPAgent;
132
+ wsHandler: WSSequenceHandler;
130
133
 
131
134
  constructor(
132
135
  private httpAgent,
@@ -143,6 +146,7 @@ class SequenceExecutor {
143
146
  freeSocketTimeout: SOCKET_TIMEOUT / 2
144
147
  });
145
148
  this.postScriptRunner = new PostScriptRunner();
149
+ this.wsHandler = new WSSequenceHandler();
146
150
  }
147
151
 
148
152
  startAndPass(requests: LoadmillRequest[]) {
@@ -213,9 +217,11 @@ class SequenceExecutor {
213
217
  log.info('Unexpected error info:', unexpectedError);
214
218
  }
215
219
 
220
+ this.wsHandler.closeAllConnections();
216
221
  throw error;
217
222
  }
218
223
  }
224
+ this.wsHandler.closeAllConnections();
219
225
  }
220
226
 
221
227
  shouldStop(request: LoadmillRequest) {
@@ -309,12 +315,14 @@ class SequenceExecutor {
309
315
  let failedAssertionsHistogram, resTime;
310
316
  let loopIteration = 0;
311
317
  const maxIterations = getLoopIterations(requestConf.loop);
318
+ this.wsHandler.clearMessages();
312
319
 
313
320
  while (loopIteration < maxIterations) {
314
321
  loopIteration++;
315
322
  const request = this.prepareRequest(requestConf, index);
316
323
 
317
324
  const res = await this.sendRequest(request, index);
325
+
318
326
  // Setting now to avoid possible user overwrite:
319
327
  resTime = Number(this.parameters.__responseTime);
320
328
 
@@ -325,7 +333,9 @@ class SequenceExecutor {
325
333
  );
326
334
  }
327
335
 
328
- failedAssertionsHistogram = this.processSuccessfulResponse(
336
+ res.wsExtractionData = { messages: this.wsHandler.messages, timeLimit: timeout - resTime } as WSExtractionData;
337
+
338
+ failedAssertionsHistogram = await this.processSuccessfulResponse(
329
339
  index,
330
340
  requestConf,
331
341
  res
@@ -501,8 +511,18 @@ class SequenceExecutor {
501
511
  }
502
512
 
503
513
  prepareRequest(requestConf: LoadmillRequest, reqIndex: number) {
514
+ if (this.isWSRequest(requestConf) && !envUtils.isBrowser()) {
515
+ return this.prepareWSRequest(requestConf, reqIndex);
516
+ }
517
+ return this.prepareHttpRequest(requestConf, reqIndex);
518
+ }
519
+ private isWSRequest({ url }: LoadmillRequest) {
520
+ const resolvedUrl = this.resolve(url, (e) => setParameterErrorHistogram(e, 'Failed to compute URL - '));
521
+ return resolvedUrl.startsWith('ws://') || resolvedUrl.startsWith('wss://');
522
+ }
523
+ private prepareHttpRequest(requestConf: LoadmillRequest, reqIndex: number) {
504
524
  const urlObj = new URI(
505
- resolveUrl(requestConf.url, this.parameters, (err) =>
525
+ resolveUrl(requestConf.url, this.parameters, (err: Error) =>
506
526
  setParameterErrorHistogram(err, 'Failed to compute URL - ')
507
527
  )
508
528
  );
@@ -654,6 +674,66 @@ class SequenceExecutor {
654
674
  return request;
655
675
  }
656
676
 
677
+ prepareWSRequest(requestConf: LoadmillRequest, reqIndex: number) {
678
+ const wsRequestArgs = this.resolveAndSetWSReqData(requestConf, reqIndex);
679
+
680
+ return new WSRequest(
681
+ wsRequestArgs,
682
+ this.wsHandler,
683
+ (e: Error) => this.setSingleFailure(reqIndex, 'Websocket error ' + e.message),
684
+ );
685
+ }
686
+
687
+ resolveAndSetWSReqData = (
688
+ { url, headers, postData, timeout = DEFAULT_REQUEST_TIMEOUT, expectedStatus = 'SUCCESS' }: LoadmillRequest,
689
+ reqIndex: number
690
+ ): WSRequestArguments => {
691
+ const preparedUrl = this.prepareWsUrl(url, reqIndex);
692
+ const preparedHeaders = this.prepareWsHeaders(headers, reqIndex);
693
+ const preparedMessage = this.prepareWsMessage(postData, reqIndex);
694
+ return {
695
+ expectedStatus,
696
+ headers: preparedHeaders,
697
+ message: preparedMessage,
698
+ timeout,
699
+ url: preparedUrl,
700
+ };
701
+ };
702
+
703
+ prepareWsUrl = (url: string, reqIndex: number) => {
704
+ const resolvedUrl = this.resolve(url, (e: Error) => setParameterErrorHistogram(e, 'Failed to compute URL - '));
705
+ this.resolvedRequests[reqIndex].url = resolvedUrl;
706
+ return resolvedUrl;
707
+ };
708
+
709
+ prepareWsHeaders = (headers: LoadmillHeaders[] | undefined, reqIndex: number) => {
710
+ const resolvedHeadersObj: LoadmillHeaders = {};
711
+ const resolvedHeaders: LoadmillHeaders[] = [];
712
+ if (headers && !isEmpty(headers)) {
713
+ resolvedHeaders.push(...this.resolveHeaders(headers));
714
+ resolvedHeaders.forEach(({ name, value }) => resolvedHeadersObj[name] = value);
715
+ }
716
+ this.resolvedRequests[reqIndex].headers = resolvedHeaders;
717
+ return resolvedHeadersObj;
718
+ };
719
+
720
+ prepareWsMessage = (postData: RequestPostData | undefined, reqIndex: number) => {
721
+ let resolvedMessage: string = '';
722
+ if (postData) {
723
+ resolvedMessage = this.resolve(postData.text, (err: Error) =>
724
+ setParameterErrorHistogram(err, 'Failed to compute Websocket message - ')
725
+ );
726
+ if (resolvedMessage && resolvedMessage.length > getMaxRequestBodySize()) {
727
+ throw new RequestFailuresError('Websocket message size is too large');
728
+ }
729
+ this.resolvedRequests[reqIndex].postData = {
730
+ mimeType: postData.mimeType,
731
+ text: resolvedMessage,
732
+ };
733
+ }
734
+ return resolvedMessage;
735
+ };
736
+
657
737
  checkProgressEvent = (
658
738
  requestIndex: number,
659
739
  request,
@@ -715,9 +795,9 @@ class SequenceExecutor {
715
795
  )
716
796
  );
717
797
 
718
- processSuccessfulResponse(reqIndex, requestConf: LoadmillRequest, res) {
798
+ async processSuccessfulResponse(reqIndex, requestConf: LoadmillRequest, res) {
719
799
  // modifies parameters:
720
- this.handleExtractions(requestConf, res);
800
+ await this.handleExtractions(requestConf, res);
721
801
 
722
802
  if (!envUtils.isBrowser()) {
723
803
  this.handlePostScript(requestConf, res);
@@ -745,33 +825,33 @@ class SequenceExecutor {
745
825
  );
746
826
  }
747
827
 
748
- handleExtractions(requestConf: LoadmillRequest, res) {
749
- requestConf.extract?.forEach((extractions: Extractions) =>
750
- this.extractInScope(res, extractions)
751
- );
828
+ async handleExtractions(requestConf: LoadmillRequest, res) {
829
+ for (const extractions of (requestConf.extract || [])) {
830
+ await this.extractInScope(res, extractions);
831
+ }
752
832
  }
753
833
 
754
- extractInScope(res, extractions: Extractions) {
834
+ async extractInScope(res, extractions: Extractions) {
755
835
  const contextParameters = Object.assign({}, this.parameters);
756
- const extractionCombiner = new ExtractionCombiner(contextParameters, res);
836
+ const extractionCombiner = new ExtractionCombiner(contextParameters, res, res.wsExtractionData);
757
837
 
758
- forEach(extractions, (extraction: Extraction, name: string) =>
759
- this.extract(name, extraction, extractionCombiner)
760
- );
838
+ for (const [name, extraction] of Object.entries(extractions)) {
839
+ await this.extract(name, extraction, extractionCombiner);
840
+ }
761
841
  }
762
842
 
763
- extract(
843
+ async extract(
764
844
  parameterName: string,
765
845
  extraction: Extraction,
766
846
  extractionCombiner: ExtractionCombiner
767
847
  ) {
768
848
  log.trace('Parameter extraction start: ', { parameterName, extraction });
769
849
 
770
- const combinedExtractor = extractionCombiner.combine(extraction);
850
+ const combinedExtractor = await extractionCombiner.combine(extraction);
771
851
  let result;
772
852
 
773
853
  try {
774
- result = combinedExtractor();
854
+ result = await combinedExtractor();
775
855
  } catch (error) {
776
856
  const genericMessage = `Failed to extract value for parameter "${parameterName}"`;
777
857
  log.debug(genericMessage, error);
@@ -893,9 +973,6 @@ function isSimpleRequest(headers) {
893
973
  );
894
974
  }
895
975
 
896
- const getMaxRequestBodySize = () =>
897
- envUtils.isBrowser() ? MAX_LOAD_REQUEST_BODY_LENGTH : MAX_API_REQUEST_BODY_LENGTH;
898
-
899
976
  const getLoopIterations = (LoopConf?: LoopConf) => {
900
977
  const declared = (LoopConf && LoopConf.iterations) || 1;
901
978
  return Math.min(MAX_REQUEST_LOOPS_ITERATIONS, declared);
@@ -913,7 +990,7 @@ const extendResponseHeaders = (headers, redirectHeaders) => {
913
990
 
914
991
  const isExpectedStatus = ({ expectedStatus, url }, status: number) => {
915
992
  if (expectedStatus === ALLOWED_RESPONSE_STATUSES.SUCCESS) {
916
- return 200 <= status && status < 400;
993
+ return (200 <= status && status < 400) || status === 101;
917
994
  }
918
995
  else if (expectedStatus === ALLOWED_RESPONSE_STATUSES.ERROR) {
919
996
  log.debug('user asked to fail this request', url);
@@ -949,12 +1026,3 @@ const setTCPReuse = (request, agent, sslAgent) => {
949
1026
  };
950
1027
 
951
1028
  };
952
-
953
- class RequestFailuresError extends Error {
954
- constructor(message: string, public histogram: Histogram = { [message]: 1 }) {
955
- super(message);
956
-
957
- // Workaround suggested in: https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
958
- Object.setPrototypeOf(this, RequestFailuresError.prototype);
959
- }
960
- }
package/src/utils.ts ADDED
@@ -0,0 +1,8 @@
1
+ import {
2
+ MAX_API_REQUEST_BODY_LENGTH,
3
+ MAX_LOAD_REQUEST_BODY_LENGTH
4
+ } from '@loadmill/core/dist/conf/extrema';
5
+ import * as envUtils from '@loadmill/universal/dist/env-utils';
6
+
7
+ export const getMaxRequestBodySize = () =>
8
+ envUtils.isBrowser() ? MAX_LOAD_REQUEST_BODY_LENGTH : MAX_API_REQUEST_BODY_LENGTH;
package/src/ws.ts ADDED
@@ -0,0 +1,233 @@
1
+ import WebSocket from 'ws'; // todo should be picked with other option (socketio or non in browser)
2
+ import { delay } from '@loadmill/universal/dist/promise-utils';
3
+ import log from '@loadmill/universal/dist/log';
4
+ import { HttpResponseStatus, LoadmillHeaders } from '@loadmill/core/dist/request';
5
+ import { RequestFailuresError } from './errors';
6
+
7
+ enum WSState {
8
+ CONNECTING,
9
+ OPEN,
10
+ CLOSING,
11
+ CLOSED,
12
+ }
13
+
14
+ const SWITCHING_PROTOCOLS = 'Switching Protocols';
15
+ const WS_CONNECTION_TIMEOUT_MS = 10000;
16
+
17
+ export class WSRequest {
18
+ private hasErrorOccured?: boolean; // need this otherwise we have race condition between verifyConnectedAndOpen and onError
19
+ private ws: WebSocket;
20
+ public url: string; // need this for isExpectedStatus function
21
+ public expectedStatus: HttpResponseStatus; // need this for isExpectedStatus function
22
+
23
+ constructor(
24
+ private readonly wsRequestArgs: WSRequestArguments,
25
+ private readonly wsHandler: WSSequenceHandler,
26
+ private readonly onError: (e: Error) => void,
27
+ ) {
28
+ this.url = wsRequestArgs.url;
29
+ this.expectedStatus = wsRequestArgs.expectedStatus;
30
+ }
31
+
32
+ /**
33
+ * This function is executed when we are ready to send the ws request
34
+ * @param cb This callback is being executed after we successfully connected to ws and sent a ws message if there was any
35
+ */
36
+ async ok(cb: (response: WSResponse) => boolean) {
37
+ const response = {
38
+ status: 101,
39
+ res: {
40
+ statusMessage: SWITCHING_PROTOCOLS,
41
+ },
42
+ header: {},
43
+ req: {},
44
+ };
45
+
46
+ const existingWS = this.wsHandler.getConnection(this.wsRequestArgs.url);
47
+
48
+ log.debug(`Connection state ${getConnectionState(existingWS)}`);
49
+ if (!existingWS || (existingWS && existingWS.readyState !== WSState.OPEN)) {
50
+ await this.addConnection(response);
51
+ } else {
52
+ this.ws = existingWS;
53
+ log.debug('Reusing existing ws connection', this.ws.url);
54
+ }
55
+
56
+ try {
57
+ this.sendMessage();
58
+ } catch (e) {
59
+ log.error('Failed to send a ws message', e);
60
+ }
61
+ cb(response);
62
+ return { };
63
+ }
64
+
65
+ private async addConnection(response: WSResponse) {
66
+ this.ws = new WebSocket(this.wsRequestArgs.url, undefined, { headers: this.wsRequestArgs.headers });
67
+ this.addEventListeners(response);
68
+ !this.hasErrorOccured && await this.verifyOpen();
69
+ this.wsHandler.storeConnection(this.wsRequestArgs.url, this.ws);
70
+ // todo add some debug details for the user to know we created a new connection in this request
71
+ }
72
+
73
+ private async addEventListeners(response: WSResponse) {
74
+ this.addOnErrorListener();
75
+ this.addOnUpgradeListener(response);
76
+ this.addOnOpenListener();
77
+ this.addOnMessageListener();
78
+ }
79
+
80
+ private addOnErrorListener() {
81
+ this.ws.on('error', (e) => {
82
+ try {
83
+ log.debug('inside on error', e);
84
+ this.onError(e);
85
+ this.hasErrorOccured = true;
86
+ } catch (e) {
87
+ log.warn('Failed to handle a ws error', e);
88
+ }
89
+ try {
90
+ log.debug('closing ws');
91
+ this.ws.close();
92
+ } catch (e) {
93
+ log.warn('Failed to close a ws connection', e);
94
+ }
95
+ });
96
+ }
97
+
98
+ private addOnUpgradeListener(response: WSResponse) {
99
+ this.ws.on('upgrade', (event) => {
100
+ try {
101
+ const { statusCode, statusMessage, headers } = event;
102
+ log.debug('inside upgrade event', { statusCode, statusMessage, headers });
103
+ response.status = statusCode || 101;
104
+ response.res.statusMessage = statusMessage || SWITCHING_PROTOCOLS;
105
+ response.header = headers as LoadmillHeaders;
106
+ } catch (e) {
107
+ log.debug('upgrade event err', e);
108
+ }
109
+ });
110
+ }
111
+
112
+ private addOnOpenListener() {
113
+ this.ws.on('open', () => {
114
+ log.debug('inside on open, currently doing nothing here.');
115
+ });
116
+ }
117
+
118
+ private addOnMessageListener() {
119
+ this.ws.on('message', (message: string) => {
120
+ try {
121
+ log.debug('got incoming message', message);
122
+ this.wsHandler.addMessage(message);
123
+ } catch (e) {
124
+ log.debug('error getting message', e);
125
+ }
126
+ });
127
+ }
128
+
129
+ async verifyOpen() {
130
+ const readyState = await this.waitForConnectedState(this.wsRequestArgs.timeout);
131
+ log.debug(`Connection state ${WSState[readyState]}`);
132
+ if (readyState !== WebSocket.OPEN && !this.hasErrorOccured) {
133
+ log.error('Could not connect to WebSocket address', { connectionState: WSState[readyState], url: this.ws.url });
134
+ throw new RequestFailuresError('Could not connect to WebSocket address');
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Waiting for WS connection to go out of CONNECTING state
140
+ * @param socket The ws connection object
141
+ * @param timeout The timeout in milliseconds for the waiting procedure
142
+ * @returns WSState
143
+ */
144
+ waitForConnectedState = async (timeout: number = WS_CONNECTION_TIMEOUT_MS): Promise<WSState> => {
145
+ const WS_CONNECTION_INTERVAL_MS = 100;
146
+ if (this.ws.readyState !== WebSocket.CONNECTING) {
147
+ return this.ws.readyState;
148
+ }
149
+ else {
150
+ const maxIterations = timeout / WS_CONNECTION_INTERVAL_MS;
151
+ let i = 0;
152
+ while (this.ws.readyState === WebSocket.CONNECTING && i < maxIterations) {
153
+ await delay(WS_CONNECTION_INTERVAL_MS);
154
+ i++;
155
+ }
156
+ return this.ws.readyState;
157
+ }
158
+ };
159
+
160
+ private sendMessage() {
161
+ log.debug('about to send message', { readyState: WSState[this.ws.readyState] } );
162
+ if (this.ws.readyState === WSState.OPEN && this.wsRequestArgs.message) {
163
+ log.debug('sending message', this.wsRequestArgs.message);
164
+ this.ws.send(this.wsRequestArgs.message);
165
+ }
166
+ }
167
+
168
+ // need this because the SequenceExecutor expectes a request obj with on func prop
169
+ on(_eventName: string, _cb: Function) {
170
+ //do nothing
171
+ }
172
+ }
173
+
174
+ export class WSSequenceHandler {
175
+ private _connections: { [url: string]: WebSocket };
176
+ messages: string[];
177
+ constructor() {
178
+ this._connections = {};
179
+ this.clearMessages();
180
+ }
181
+
182
+ getConnection(url: string): WebSocket | undefined {
183
+ return this._connections[url];
184
+ }
185
+
186
+ storeConnection(url: string, wsConnection: WebSocket) {
187
+ this._connections[url] = wsConnection;
188
+ }
189
+
190
+ async closeAllConnections() {
191
+ try {
192
+ log.debug('Closing all ws connections');
193
+ for (const connection of Object.values(this._connections)) {
194
+ connection.close();
195
+ }
196
+ } catch (e) {
197
+ log.warn('Failed to close all connections', e, { connections: this._connections });
198
+ }
199
+ }
200
+
201
+ getMessages() {
202
+ return this.messages;
203
+ }
204
+
205
+ addMessage(message: string) {
206
+ this.messages.push(message);
207
+ }
208
+
209
+ clearMessages() {
210
+ this.messages = [];
211
+ }
212
+ }
213
+
214
+ function getConnectionState(existingWS?: WebSocket) {
215
+ return existingWS ? WSState[existingWS.readyState] : null;
216
+ }
217
+
218
+ export type WSRequestArguments = {
219
+ expectedStatus: HttpResponseStatus;
220
+ headers: LoadmillHeaders
221
+ message: string;
222
+ timeout: number;
223
+ url: string;
224
+ };
225
+
226
+ export type WSResponse = {
227
+ status: number;
228
+ res: {
229
+ statusMessage: string;
230
+ };
231
+ header: LoadmillHeaders;
232
+ req: {};
233
+ }