@opentap/runner-client 2.3.2-alpha.1 → 2.3.2-alpha.1.4

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.
@@ -1,5 +1,11 @@
1
1
  import { ComponentSettingsBase, ComponentSettingsIdentifier, ComponentSettingsListItem, DataGridControl, ErrorResponse, FileParameter, FileResponse, ListItemType, ProfileGroup, RepositoryPackageReference, RepositorySettingsPackageDefinition, SettingsTapPackage } from './DTOs';
2
- import { ConnectionOptions, RequestOptions, Subscription, SubscriptionOptions } from 'nats.ws';
2
+ import { ConnectionOptions, Subscription, SubscriptionOptions, PublishOptions } from 'nats.ws';
3
+ interface BaseClientRequestOptions {
4
+ options?: PublishOptions;
5
+ rawResponse?: boolean;
6
+ fullSubject?: boolean;
7
+ timeout?: number;
8
+ }
3
9
  export declare class BaseClient {
4
10
  private connection;
5
11
  private baseSubject;
@@ -9,7 +15,6 @@ export declare class BaseClient {
9
15
  private _accessToken;
10
16
  private _headers;
11
17
  private _timeout;
12
- private _chunkSize;
13
18
  /** Get request access token */
14
19
  get accessToken(): string;
15
20
  /** Set request access token */
@@ -23,6 +28,7 @@ export declare class BaseClient {
23
28
  /** Set timeout in milliseconds. Default is 40000 milliseconds */
24
29
  set timeout(value: number);
25
30
  constructor(baseSubject: string, options: ConnectionOptions);
31
+ private withTimeout;
26
32
  /**
27
33
  * Send a request to the nats server.
28
34
  * @param subject The subject to request
@@ -30,7 +36,7 @@ export declare class BaseClient {
30
36
  * @param options (optional)
31
37
  * @returns Promise of an object
32
38
  */
33
- protected request<T>(subject: string, payload?: any, options?: RequestOptions, rawResponse?: boolean, fullSubject?: boolean): Promise<T>;
39
+ protected request<T>(subject: string, payload?: any, options?: BaseClientRequestOptions): Promise<T>;
34
40
  /**
35
41
  * Handle the error
36
42
  * @param error
@@ -50,14 +56,6 @@ export declare class BaseClient {
50
56
  * @returns Subscription object
51
57
  */
52
58
  protected subscribe(subject: string, options: SubscriptionOptions): Subscription;
53
- /**
54
- * Receive a chunked file specified by a request response
55
- * @param requestResponse Contains a reply subject and a file descriptor which can be used to download chunks
56
- * @param rawResponse If true, the response should not be decoded
57
- * @param opts Request options
58
- * @returns
59
- */
60
- private downloadChunkedRequest;
61
59
  protected encode(payload: any): Uint8Array;
62
60
  /**
63
61
  * Check if the the response is an error from the server.
@@ -246,3 +244,4 @@ export declare class BaseClient {
246
244
  */
247
245
  downloadSettingsPackage(settingsTapPackage: SettingsTapPackage): Promise<FileResponse>;
248
246
  }
247
+ export {};
package/lib/BaseClient.js CHANGED
@@ -45,17 +45,8 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
45
45
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
46
46
  }
47
47
  };
48
- var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
49
- if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
50
- if (ar || !(i in from)) {
51
- if (!ar) ar = Array.prototype.slice.call(from, 0, i);
52
- ar[i] = from[i];
53
- }
54
- }
55
- return to.concat(ar || Array.prototype.slice.call(from));
56
- };
57
48
  import { ComponentSettingsBase, ComponentSettingsIdentifier, ComponentSettingsListItem, DataGridControl, ErrorResponse, FileDescriptor, ListItemType, ProfileGroup, } from './DTOs';
58
- import { Empty, ErrorCode, JSONCodec, NatsError, StringCodec, connect, headers, } from 'nats.ws';
49
+ import { Empty, ErrorCode, JSONCodec, StringCodec, connect, headers, } from 'nats.ws';
59
50
  import { EventEmitter } from 'events';
60
51
  var DEFAULT_TIMEOUT = 40000; // default timeout of 40 seconds
61
52
  var Events;
@@ -66,7 +57,6 @@ var BaseClient = /** @class */ (function () {
66
57
  function BaseClient(baseSubject, options) {
67
58
  this.domainAccess = new Map();
68
59
  this._headers = new Headers();
69
- this._chunkSize = 512000;
70
60
  this.baseSubject = baseSubject;
71
61
  this.connectionOptions = __assign({}, options) || {};
72
62
  this.connectionOptions.timeout = (options === null || options === void 0 ? void 0 : options.timeout) || DEFAULT_TIMEOUT;
@@ -108,6 +98,9 @@ var BaseClient = /** @class */ (function () {
108
98
  enumerable: false,
109
99
  configurable: true
110
100
  });
101
+ BaseClient.prototype.withTimeout = function (promise, timeout) {
102
+ return Promise.race([promise, new Promise(function (_, reject) { return setTimeout(function () { return reject(new Error('Request timed out.')); }, timeout); })]);
103
+ };
111
104
  /**
112
105
  * Send a request to the nats server.
113
106
  * @param subject The subject to request
@@ -115,74 +108,95 @@ var BaseClient = /** @class */ (function () {
115
108
  * @param options (optional)
116
109
  * @returns Promise of an object
117
110
  */
118
- BaseClient.prototype.request = function (subject, payload, options, rawResponse, fullSubject) {
111
+ BaseClient.prototype.request = function (subject, payload, options) {
119
112
  return __awaiter(this, void 0, void 0, function () {
120
- var originalSubject, data, headers, timeout, opts, fileDescriptor, getChunk, requestResponse, dataSubject, i, result, error, ex_1;
113
+ var data, headers, replySubject, serverMaxPayload, requestId, opts, fileDescriptor, chunkNumber, subscription, getChunk, jsonCodec, responsePromise, chunk, i;
114
+ var _this = this;
121
115
  return __generator(this, function (_a) {
122
- switch (_a.label) {
123
- case 0:
124
- // Prepend the base subject if the given subject does not start with that
125
- if (!fullSubject) {
126
- subject = "".concat(this.baseSubject, ".Request.").concat(subject);
127
- }
128
- originalSubject = subject;
129
- if (!this.connection)
130
- return [2 /*return*/, Promise.reject("".concat(subject, ": Connection is down! Please try again!"))];
131
- if (this.connection.isClosed())
132
- return [2 /*return*/, Promise.reject("".concat(subject, ": Connection has been closed! Please reconnect!"))];
133
- data = this.encode(payload);
134
- headers = this.buildHeaders();
135
- headers.append('ChunkSize', this._chunkSize.toString());
136
- timeout = (options === null || options === void 0 ? void 0 : options.timeout) || this.timeout;
137
- opts = __assign(__assign({}, options), { timeout: timeout, headers: headers });
138
- fileDescriptor = new FileDescriptor(data.length, this._chunkSize);
139
- getChunk = function (chunk) {
140
- var offset = chunk * fileDescriptor.chunkSize;
141
- return data.slice(offset, offset + fileDescriptor.chunkSize);
142
- };
143
- _a.label = 1;
144
- case 1:
145
- _a.trys.push([1, 9, , 10]);
146
- requestResponse = void 0;
147
- dataSubject = subject;
148
- i = 0;
149
- _a.label = 2;
150
- case 2:
151
- if (!(i < fileDescriptor.numberOfChunks)) return [3 /*break*/, 5];
152
- return [4 /*yield*/, this.connection.request(dataSubject, getChunk(i), opts)];
153
- case 3:
154
- requestResponse = _a.sent();
155
- dataSubject = requestResponse.reply;
156
- _a.label = 4;
157
- case 4:
158
- i++;
159
- return [3 /*break*/, 2];
160
- case 5:
161
- if (!(fileDescriptor.fileSize % fileDescriptor.chunkSize === 0)) return [3 /*break*/, 7];
162
- return [4 /*yield*/, this.connection.request(dataSubject, Empty, opts)];
163
- case 6:
164
- requestResponse = _a.sent();
165
- _a.label = 7;
166
- case 7: return [4 /*yield*/, this.downloadChunkedRequest(requestResponse, rawResponse, opts)];
167
- case 8:
168
- result = _a.sent();
169
- if (this.isErrorResponse(result)) {
170
- error = ErrorResponse.fromJS(result);
171
- this.eventEmitter.emit(Events.ERROR, error);
172
- return [2 /*return*/, Promise.reject(error)];
173
- }
174
- return [2 /*return*/, result];
175
- case 9:
176
- ex_1 = _a.sent();
177
- if (ex_1 instanceof NatsError) {
178
- return [2 /*return*/, Promise.reject(this.natsErrorHandler(ex_1, originalSubject))];
116
+ // Prepend the base subject if the given subject does not start with that
117
+ if (!(options === null || options === void 0 ? void 0 : options.fullSubject)) {
118
+ subject = "".concat(this.baseSubject, ".Request.").concat(subject);
119
+ }
120
+ if (!this.connection)
121
+ return [2 /*return*/, Promise.reject("".concat(subject, ": Connection is down! Please try again!"))];
122
+ if (this.connection.isClosed())
123
+ return [2 /*return*/, Promise.reject("".concat(subject, ": Connection has been closed! Please reconnect!"))];
124
+ data = this.encode(payload);
125
+ headers = this.buildHeaders();
126
+ replySubject = crypto.randomUUID();
127
+ serverMaxPayload = 10;
128
+ headers.append('ChunkSize', serverMaxPayload.toString());
129
+ requestId = crypto.randomUUID();
130
+ headers.append('RequestId', requestId);
131
+ opts = __assign(__assign({}, options === null || options === void 0 ? void 0 : options.options), { headers: headers, reply: replySubject });
132
+ fileDescriptor = new FileDescriptor(data.length, serverMaxPayload);
133
+ chunkNumber = 1;
134
+ headers.set('ChunkNumber', chunkNumber.toString());
135
+ subscription = this.connection.subscribe(replySubject);
136
+ getChunk = function (chunk) {
137
+ var offset = chunk * fileDescriptor.chunkSize;
138
+ return data.slice(offset, offset + fileDescriptor.chunkSize);
139
+ };
140
+ jsonCodec = JSONCodec();
141
+ responsePromise = new Promise(function (resolve, reject) {
142
+ var messages = [];
143
+ subscription.callback = function (error, message) {
144
+ var _a;
145
+ if (error) {
146
+ reject(error);
179
147
  }
180
- else {
181
- return [2 /*return*/, Promise.reject(ex_1)];
148
+ messages.push(message);
149
+ if (message.data.length === 0 || message.data.length < serverMaxPayload) {
150
+ var finalMessage = messages[messages.length - 1];
151
+ var finalMessageNumber = (_a = finalMessage.headers) === null || _a === void 0 ? void 0 : _a.get('ChunkNumber');
152
+ if (!finalMessageNumber) {
153
+ return reject('Response is not a valid chunk.');
154
+ }
155
+ if (parseInt(finalMessageNumber) !== messages.length) {
156
+ return reject("Expected {finalMessageNumber} chunks, but received ".concat(messages.length, ". ") +
157
+ "The connection may have been interrupted.");
158
+ }
159
+ // Concatenate the payloads
160
+ var dataArrays = messages.map(function (m) { return m.data; });
161
+ var totalSize = dataArrays.reduce(function (total, current) { return total + current.length; }, 0);
162
+ var flattenedArray_1 = new Uint8Array(totalSize);
163
+ var k_1 = 0;
164
+ dataArrays.map(function (m) {
165
+ for (var i = 0; i < m.length; i++) {
166
+ flattenedArray_1[k_1++] = m[i];
167
+ }
168
+ });
169
+ return resolve(flattenedArray_1);
182
170
  }
183
- return [3 /*break*/, 10];
184
- case 10: return [2 /*return*/];
171
+ };
172
+ })
173
+ .then(function (byteArray) {
174
+ var response = (options === null || options === void 0 ? void 0 : options.rawResponse) ? byteArray : jsonCodec.decode(byteArray);
175
+ return _this.isErrorResponse(response) ? Promise.reject(ErrorResponse.fromJS(response)) : Promise.resolve(response);
176
+ })
177
+ .catch(function (err) {
178
+ return Promise.reject(_this.natsErrorHandler(err, subject));
179
+ });
180
+ chunk = getChunk(0);
181
+ this.connection.publish(subject, chunk, opts);
182
+ chunkNumber += 1;
183
+ for (i = 1; i < fileDescriptor.numberOfChunks; i++) {
184
+ headers.set('ChunkNumber', chunkNumber.toString());
185
+ chunk = getChunk(i);
186
+ this.connection.publish(subject, chunk, opts);
187
+ chunkNumber += 1;
188
+ }
189
+ // In the special case where the last published chunk was full, we need to publish
190
+ // an empty message
191
+ if (data.length > 0 && data.length % fileDescriptor.chunkSize === 0) {
192
+ headers.set('ChunkNumber', chunkNumber.toString());
193
+ this.connection.publish(subject, Empty, opts);
194
+ }
195
+ // Now that we have sent the terminating chunk, the result should arrive on our promise.
196
+ if (options === null || options === void 0 ? void 0 : options.timeout) {
197
+ return [2 /*return*/, this.withTimeout(responsePromise, options.timeout)];
185
198
  }
199
+ return [2 /*return*/, responsePromise];
186
200
  });
187
201
  });
188
202
  };
@@ -234,50 +248,6 @@ var BaseClient = /** @class */ (function () {
234
248
  var natsSubject = "".concat(this.baseSubject, ".").concat(subject);
235
249
  return this.connection.subscribe(natsSubject, options);
236
250
  };
237
- /**
238
- * Receive a chunked file specified by a request response
239
- * @param requestResponse Contains a reply subject and a file descriptor which can be used to download chunks
240
- * @param rawResponse If true, the response should not be decoded
241
- * @param opts Request options
242
- * @returns
243
- */
244
- BaseClient.prototype.downloadChunkedRequest = function (requestResponse, rawResponse, opts) {
245
- return __awaiter(this, void 0, void 0, function () {
246
- var result, resultCodec;
247
- return __generator(this, function (_a) {
248
- switch (_a.label) {
249
- case 0:
250
- if (!this.connection)
251
- return [2 /*return*/, Promise.reject('Chunking: Connection is down! Please try again!')];
252
- if (this.connection.isClosed())
253
- return [2 /*return*/, Promise.reject("Chunking: Connection has been closed! Please reconnect!")];
254
- result = new Uint8Array([]);
255
- _a.label = 1;
256
- case 1:
257
- result = new Uint8Array(__spreadArray(__spreadArray([], Array.from(result), true), Array.from(requestResponse.data), true));
258
- // Acknowledge that the final chunk was received
259
- if (requestResponse.data.length < this._chunkSize) {
260
- this.connection.publish(requestResponse.reply, Empty, opts);
261
- return [3 /*break*/, 4];
262
- }
263
- return [4 /*yield*/, this.connection.request(requestResponse.reply, Empty, opts)];
264
- case 2:
265
- requestResponse = _a.sent();
266
- _a.label = 3;
267
- case 3: return [3 /*break*/, 1];
268
- case 4:
269
- if (result.length !== 0) {
270
- // The runner skips the encoding step if the return type is a byte array, so we must also skip the decoding step in this case.
271
- if (rawResponse)
272
- return [2 /*return*/, Promise.resolve(result)];
273
- resultCodec = JSONCodec();
274
- return [2 /*return*/, resultCodec.decode(result)];
275
- }
276
- return [2 /*return*/];
277
- }
278
- });
279
- });
280
- };
281
251
  BaseClient.prototype.encode = function (payload) {
282
252
  if (!payload) {
283
253
  return Empty;
@@ -51,7 +51,7 @@ var RunnerClient = /** @class */ (function (_super) {
51
51
  */
52
52
  RunnerClient.prototype.resolveImage = function (images, timeout) {
53
53
  return (images === null || images === void 0 ? void 0 : images.length) > 0
54
- ? this.request('ResolveImage', images, { timeout: timeout || this.timeout })
54
+ ? this.request('ResolveImage', images, { timeout: timeout })
55
55
  .then(function (imageJs) { return Image.fromJS(imageJs); })
56
56
  .then(this.success())
57
57
  .catch(this.error())
@@ -218,7 +218,9 @@ var SessionClient = /** @class */ (function (_super) {
218
218
  return Promise.reject('The source of the provided resource is not a nats subject.');
219
219
  }
220
220
  var subject = resource.source.slice(runnerResourcePrefix.length);
221
- return this.request(subject, undefined, undefined, true, true).then(this.success()).catch(this.error());
221
+ return this.request(subject, undefined, { rawResponse: true, fullSubject: true })
222
+ .then(this.success())
223
+ .catch(this.error());
222
224
  };
223
225
  /**
224
226
  * Load test plan using a test plan TapPackage from a repository
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opentap/runner-client",
3
- "version": "2.3.2-alpha.1",
3
+ "version": "2.3.2-alpha.1.4",
4
4
  "description": "This is the web client for the OpenTAP Runner.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",