@opentap/runner-client 4.1.0 → 4.3.0-beta.3.32232370336

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.
@@ -52,6 +52,28 @@ var nats_ws_1 = require("nats.ws");
52
52
  var DTOs_1 = require("./DTOs");
53
53
  var events_1 = require("events");
54
54
  var uuid_1 = require("uuid");
55
+ // A browser extension like CrowdStrike may have already replaced globalThis.WebSocket before this module loads,
56
+ // so reading the global is unreliable. A freshly created same-origin iframe exposes the browser's
57
+ // pristine, un-patched WebSocket; NATS traffic is pinned to it to bypass any extension shim.
58
+ // The iframe is kept attached because a detached iframe's realm becomes unusable in some browsers.
59
+ function getNativeWebSocket() {
60
+ var _a, _b, _c;
61
+ var globalWebSocket = typeof globalThis !== 'undefined' ? globalThis.WebSocket : undefined;
62
+ var doc = globalThis.document;
63
+ if (!doc || typeof doc.createElement !== 'function') {
64
+ return globalWebSocket;
65
+ }
66
+ try {
67
+ var iframe = doc.createElement('iframe');
68
+ iframe.style.display = 'none';
69
+ ((_a = doc.body) !== null && _a !== void 0 ? _a : doc.documentElement).appendChild(iframe);
70
+ return (_c = (_b = iframe.contentWindow) === null || _b === void 0 ? void 0 : _b.WebSocket) !== null && _c !== void 0 ? _c : globalWebSocket;
71
+ }
72
+ catch (_d) {
73
+ return globalWebSocket;
74
+ }
75
+ }
76
+ var OriginalWebSocket = getNativeWebSocket();
55
77
  var DEFAULT_TIMEOUT = 40000; // default timeout of 40 seconds
56
78
  var Events;
57
79
  (function (Events) {
@@ -64,6 +86,15 @@ var BaseClient = /** @class */ (function () {
64
86
  this.baseSubject = baseSubject;
65
87
  this.connectionOptions = __assign({}, options);
66
88
  this.connectionOptions.timeout = (options === null || options === void 0 ? void 0 : options.timeout) || DEFAULT_TIMEOUT;
89
+ // Force NATS to use the WebSocket captured at load time so an extension that later
90
+ // overrides globalThis.WebSocket cannot intercept NATS traffic. Respect a caller-provided factory.
91
+ // wsFactory is read by nats.ws's WsTransport at runtime but lives on WsConnectionOptions, which
92
+ // isn't re-exported, so it's absent from the public ConnectionOptions type and requires this cast.
93
+ if (OriginalWebSocket && !this.connectionOptions.wsFactory) {
94
+ this.connectionOptions.wsFactory = function (url) {
95
+ return Promise.resolve({ socket: new OriginalWebSocket(url), encrypted: url.indexOf('wss://') === 0 });
96
+ };
97
+ }
67
98
  this.eventEmitter = new events_1.EventEmitter();
68
99
  }
69
100
  Object.defineProperty(BaseClient.prototype, "accessToken", {
@@ -102,8 +133,13 @@ var BaseClient = /** @class */ (function () {
102
133
  enumerable: false,
103
134
  configurable: true
104
135
  });
105
- BaseClient.prototype.withTimeout = function (promise, timeout) {
106
- return Promise.race([promise, new Promise(function (_, reject) { return setTimeout(function () { return reject(new Error(nats_ws_1.ErrorCode.Timeout)); }, timeout); })]);
136
+ BaseClient.prototype.withTimeout = function (promise, timeout, subject) {
137
+ return Promise.race([
138
+ promise,
139
+ new Promise(function (_, reject) {
140
+ return setTimeout(function () { return reject(new Error("".concat(nats_ws_1.ErrorCode.Timeout, ": '").concat(subject, "' timed out after ").concat(timeout, "ms"))); }, timeout);
141
+ }),
142
+ ]);
107
143
  };
108
144
  /**
109
145
  * Send a request to the nats server.
@@ -152,7 +188,14 @@ var BaseClient = /** @class */ (function () {
152
188
  };
153
189
  subscription = this.connection.subscribe(replySubject);
154
190
  responsePromise = new Promise(function (resolve, reject) {
191
+ // Chunk payloads stored at their sequence index so order is preserved
192
+ // regardless of the order in which messages arrive.
155
193
  var messages = [];
194
+ var receivedCount = 0;
195
+ // Total number of chunks and the size of the final chunk, both known
196
+ // once the final (smaller) chunk arrives.
197
+ var expectedCount;
198
+ var finalChunkLength = 0;
156
199
  subscription.callback = function (error, message) {
157
200
  var _a, _b, _c, _d;
158
201
  if (error) {
@@ -168,44 +211,50 @@ var BaseClient = /** @class */ (function () {
168
211
  subscription.unsubscribe();
169
212
  return resolve({ byteArray: message.data, isErrorResponse: ((_c = message.headers) === null || _c === void 0 ? void 0 : _c.get('OpenTapNatsError')) === 'true' });
170
213
  }
171
- // Put all the response chunks in an array in the order they are received
172
- messages.push(message);
173
- // If the chunk has a size equal to the chunkSize, we should expect another message
174
- if (message.data.length === chunkSize) {
214
+ // Place each chunk at its sequence position (ChunkNumber is 1-based).
215
+ messages[parseInt(chunkNumber) - 1] = message.data;
216
+ receivedCount += 1;
217
+ // The final chunk is the only one smaller than chunkSize; its sequence
218
+ // number tells us how many chunks to expect and its length fixes the total size.
219
+ if (message.data.length < chunkSize) {
220
+ expectedCount = parseInt(chunkNumber);
221
+ finalChunkLength = message.data.length;
222
+ }
223
+ // Keep waiting until every expected chunk has been received.
224
+ if (expectedCount === undefined || receivedCount < expectedCount) {
175
225
  return;
176
226
  }
177
- // If the chunk has a length smaller than the chunkSize, the message is complete
178
- // If the final message was received, we can safely unsubscribe
227
+ // All expected chunks received; we can safely unsubscribe.
179
228
  subscription.unsubscribe();
180
- // Check if the number of the final message is equal to the number of
181
- // messages we received. If this is not the case, we dropped a chunk,
182
- // likely due to a network error. In this case, the entire message is invalid.
183
- if (parseInt(chunkNumber) !== messages.length) {
184
- return reject(Error("Expected {finalMessageNumber} chunks, but received ".concat(messages.length, ". ") +
185
- "The connection may have been interrupted."));
186
- }
187
- // Concatenate the payloads
188
- // When there are many chunks, doing a single allocation
189
- // is significantly faster than concatenating arrays in sequence
190
- var flattenedSize = messages.reduce(function (sum, array) { return sum + array.data.length; }, 0);
229
+ // Every chunk before the last is exactly chunkSize, so the total size is
230
+ // deterministic and each chunk's offset is simply its index * chunkSize.
231
+ var flattenedSize = (expectedCount - 1) * chunkSize + finalChunkLength;
191
232
  var flattenedArray = new Uint8Array(flattenedSize);
192
- var k = 0;
193
- messages.forEach(function (m) {
194
- for (var i = 0; i < m.data.length; i++) {
195
- flattenedArray[k++] = m.data[i];
233
+ for (var i = 0; i < expectedCount; i++) {
234
+ var chunk_1 = messages[i];
235
+ // A missing chunk means we dropped one, likely due to a network error,
236
+ // so the entire message is invalid.
237
+ if (!chunk_1) {
238
+ return reject(Error("Expected ".concat(expectedCount, " chunks, but chunk ").concat(i + 1, " is missing. ") +
239
+ "The connection may have been interrupted."));
196
240
  }
197
- });
241
+ flattenedArray.set(chunk_1, i * chunkSize);
242
+ }
198
243
  return resolve({ byteArray: flattenedArray, isErrorResponse: ((_d = message.headers) === null || _d === void 0 ? void 0 : _d.get('OpenTapNatsError')) === 'true' });
199
244
  };
200
245
  }).then(function (_a) {
201
246
  var byteArray = _a.byteArray, isErrorResponse = _a.isErrorResponse;
247
+ var jsonCodec = (0, nats_ws_1.JSONCodec)();
248
+ if (isErrorResponse) {
249
+ return Promise.reject(DTOs_1.ErrorResponse.fromJS(jsonCodec.decode(byteArray)));
250
+ }
202
251
  if (byteArray.length === 0) {
203
252
  return Promise.resolve(undefined);
204
253
  }
205
- var jsonCodec = (0, nats_ws_1.JSONCodec)();
206
- // If a raw response is requested, we should avoid decoding the bytes.
207
- var response = (options === null || options === void 0 ? void 0 : options.rawResponse) ? byteArray : jsonCodec.decode(byteArray);
208
- return isErrorResponse ? Promise.reject(DTOs_1.ErrorResponse.fromJS(response)) : Promise.resolve(response);
254
+ if (options === null || options === void 0 ? void 0 : options.rawResponse) {
255
+ return Promise.resolve(byteArray);
256
+ }
257
+ return Promise.resolve(jsonCodec.decode(byteArray));
209
258
  });
210
259
  chunk = getChunk(0);
211
260
  this.connection.publish(subject, chunk, opts);
@@ -223,7 +272,7 @@ var BaseClient = /** @class */ (function () {
223
272
  this.connection.publish(subject, nats_ws_1.Empty, opts);
224
273
  }
225
274
  // Now that we have sent the terminating chunk, the result should arrive on our promise.
226
- return [2 /*return*/, this.withTimeout(responsePromise, timeout).catch(function (err) {
275
+ return [2 /*return*/, this.withTimeout(responsePromise, timeout, subject).catch(function (err) {
227
276
  subscription.unsubscribe();
228
277
  return Promise.reject(_this.natsErrorHandler(err, subject));
229
278
  })];
@@ -876,7 +876,8 @@ export declare enum SessionState {
876
876
  Breaking = "Breaking",
877
877
  Aborting = "Aborting",
878
878
  WaitingForUserInput = "WaitingForUserInput",
879
- Loading = "Loading"
879
+ Loading = "Loading",
880
+ Restarting = "Restarting"
880
881
  }
881
882
  export declare class TestPlan implements ITestPlan {
882
883
  childTestSteps?: TestStep[] | undefined;
@@ -1207,7 +1208,9 @@ export declare enum SessionEventName {
1207
1208
  TestStepChanged = "TestStepChanged",
1208
1209
  Break = "Break",
1209
1210
  UserInputRequested = "UserInputRequested",
1210
- UserInputRequestCompleted = "UserInputRequestCompleted"
1211
+ UserInputRequestCompleted = "UserInputRequestCompleted",
1212
+ RestartInitiated = "RestartInitiated",
1213
+ RestartCompleted = "RestartCompleted"
1211
1214
  }
1212
1215
  export declare abstract class SessionEvent {
1213
1216
  readonly eventName: SessionEventName;
@@ -1315,6 +1318,21 @@ export declare class UserInputRequestCompletedEvent extends SessionEvent {
1315
1318
  static fromJS(data: any): UserInputRequestCompletedEvent;
1316
1319
  toJSON(data?: any): any;
1317
1320
  }
1321
+ export declare class RestartInitiatedEvent extends SessionEvent {
1322
+ oldProcessId?: number;
1323
+ constructor(data?: Partial<RestartInitiatedEvent>);
1324
+ init(_data?: any): void;
1325
+ static fromJS(data: any): RestartInitiatedEvent;
1326
+ toJSON(data?: any): any;
1327
+ }
1328
+ export declare class RestartCompletedEvent extends SessionEvent {
1329
+ success?: boolean;
1330
+ newProcessId?: number;
1331
+ constructor(data?: Partial<RestartCompletedEvent>);
1332
+ init(_data?: any): void;
1333
+ static fromJS(data: any): RestartCompletedEvent;
1334
+ toJSON(data?: any): any;
1335
+ }
1318
1336
  export type ISessionEvent = SessionEvent;
1319
1337
  export interface IOnTestPlanRun {
1320
1338
  status?: string;
package/dist/cjs/DTOs.js CHANGED
@@ -17,7 +17,7 @@ var __extends = (this && this.__extends) || (function () {
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.SettingsTapPackage = exports.SaveTestPlanToRepositoryResponse = exports.GetTestPlanReferenceResponse = exports.RepositorySettingsPackageDefinition = exports.RepositoryPackageDefinition = exports.RepositoryPackageReference = exports.ProfileGroup = exports.ListItemType = exports.ComponentSettings = exports.FileDescriptor = exports.Resource = exports.PluginTypeSelectorControl = exports.PictureControl = exports.PasswordControl = exports.MultiSelectControl = exports.DropdownControl = exports.EnabledControl = exports.DirectoryPathControl = exports.FilePathControl = exports.ComboBoxControl = exports.TextBoxControl = exports.DataGridControl = exports.DataGridReferenceControl = exports.CheckBoxControl = exports.AvailableValue = exports.ButtonsControl = exports.ButtonControl = exports.ExternalParameter = exports.MetaData = exports.DisplayAttribute = exports.VisualStatus = exports.UnitAttribute = exports.Icon = exports.ColumnDisplayName = exports.LayoutMode = exports.Layout = exports.Setting = exports.ComponentSettingsListItem = exports.AnnotatedObject = exports.ComponentSettingsList = exports.DisplayMode = exports.ComponentSettingsIdentifier = exports.ComponentSettingsBase = exports.Links = exports.Session = exports.ImageResolveErrorResponse = exports.NoResponderError = exports.ErrorResponse = exports.PackageSpecifier = exports.Image = void 0;
19
19
  exports.StartedEvent = exports.StartingEvent = exports.SessionTimeoutEvent = exports.TestPlanSettingsChangedEvent = exports.TypeCacheInvalidatedEvent = exports.SessionEvent = exports.SessionEventName = exports.MetricValue = exports.MetricsConfiguration = exports.SessionMetricInfo = exports.MetricsPollRate = exports.SessionMetricType = exports.SessionMetricKind = exports.ResultColumn = exports.Result = exports.LogEvent = exports.TestStepRun = exports.WatchDog = exports.Parameter = exports.BreakPoints = exports.InstalledFile = exports.Interaction = exports.CommonContext = exports.CommonSettings = exports.ValidationError = exports.TestStepValidationError = exports.TestStepCopy = exports.TestStepType = exports.TestStep = exports.TestPlan = exports.SessionState = exports.Verdict = exports.EditStatus = exports.RunStatus = exports.MissingLicenseResponse = exports.LogEntry = exports.LogList = exports.ApiException = exports.ImageCreationFailed = exports.ImageCreated = exports.ImageCreating = exports.ImageEventArgs = exports.SessionInactivityLimitHit = exports.SessionShutdownFailed = exports.SessionShutdown = exports.SessionShutdownInitiated = exports.SessionStartFailed = exports.SessionStarted = exports.SessionStartInitiated = exports.SessionEventArgs = void 0;
20
- exports.RunnerStatus = exports.StreamInfoStatus = exports.RemoteConnection = exports.SaveDefaultSettings = exports.AssetUpdatedEvent = exports.MetadataUpdatedEvent = exports.RunnerDeletedEvent = exports.RunnerUpdatedEvent = exports.RunnerRegisteredEvent = exports.RunnerEvent = exports.TestPlanRunCompletedEvent = exports.TestPlanRunStartEvent = exports.TestPlanRunEvent = exports.OnTestStepRun = exports.OnTestPlanRun = exports.UserInputRequestCompletedEvent = exports.UserInputRequestedEvent = exports.BreakEvent = exports.TestStepChangedEvent = exports.SettingsChangedEvent = exports.HeartbeatEvent = exports.SessionStateChangedEvent = exports.TestPlanReferenceChangedEvent = exports.TestPlanChangedEvent = exports.StoppedEvent = exports.StoppingEvent = void 0;
20
+ exports.RunnerStatus = exports.StreamInfoStatus = exports.RemoteConnection = exports.SaveDefaultSettings = exports.AssetUpdatedEvent = exports.MetadataUpdatedEvent = exports.RunnerDeletedEvent = exports.RunnerUpdatedEvent = exports.RunnerRegisteredEvent = exports.RunnerEvent = exports.TestPlanRunCompletedEvent = exports.TestPlanRunStartEvent = exports.TestPlanRunEvent = exports.OnTestStepRun = exports.OnTestPlanRun = exports.RestartCompletedEvent = exports.RestartInitiatedEvent = exports.UserInputRequestCompletedEvent = exports.UserInputRequestedEvent = exports.BreakEvent = exports.TestStepChangedEvent = exports.SettingsChangedEvent = exports.HeartbeatEvent = exports.SessionStateChangedEvent = exports.TestPlanReferenceChangedEvent = exports.TestPlanChangedEvent = exports.StoppedEvent = exports.StoppingEvent = void 0;
21
21
  var Image = /** @class */ (function () {
22
22
  function Image(data) {
23
23
  this.packages = [];
@@ -2444,6 +2444,7 @@ var SessionState;
2444
2444
  SessionState["Aborting"] = "Aborting";
2445
2445
  SessionState["WaitingForUserInput"] = "WaitingForUserInput";
2446
2446
  SessionState["Loading"] = "Loading";
2447
+ SessionState["Restarting"] = "Restarting";
2447
2448
  })(SessionState || (exports.SessionState = SessionState = {}));
2448
2449
  var TestPlan = /** @class */ (function () {
2449
2450
  function TestPlan(data) {
@@ -3326,6 +3327,8 @@ var SessionEventName;
3326
3327
  SessionEventName["Break"] = "Break";
3327
3328
  SessionEventName["UserInputRequested"] = "UserInputRequested";
3328
3329
  SessionEventName["UserInputRequestCompleted"] = "UserInputRequestCompleted";
3330
+ SessionEventName["RestartInitiated"] = "RestartInitiated";
3331
+ SessionEventName["RestartCompleted"] = "RestartCompleted";
3329
3332
  })(SessionEventName || (exports.SessionEventName = SessionEventName = {}));
3330
3333
  var SessionEvent = /** @class */ (function () {
3331
3334
  function SessionEvent(eventName) {
@@ -3659,6 +3662,60 @@ var UserInputRequestCompletedEvent = /** @class */ (function (_super) {
3659
3662
  return UserInputRequestCompletedEvent;
3660
3663
  }(SessionEvent));
3661
3664
  exports.UserInputRequestCompletedEvent = UserInputRequestCompletedEvent;
3665
+ var RestartInitiatedEvent = /** @class */ (function (_super) {
3666
+ __extends(RestartInitiatedEvent, _super);
3667
+ function RestartInitiatedEvent(data) {
3668
+ var _this = _super.call(this, SessionEventName.RestartInitiated) || this;
3669
+ if (data) {
3670
+ Object.assign(_this, data);
3671
+ }
3672
+ return _this;
3673
+ }
3674
+ RestartInitiatedEvent.prototype.init = function (_data) {
3675
+ var data = ensureObject(_data);
3676
+ this.oldProcessId = data['OldProcessId'];
3677
+ };
3678
+ RestartInitiatedEvent.fromJS = function (data) {
3679
+ var result = new RestartInitiatedEvent();
3680
+ result.init(data);
3681
+ return result;
3682
+ };
3683
+ RestartInitiatedEvent.prototype.toJSON = function (data) {
3684
+ var json = ensureObject(data);
3685
+ json['OldProcessId'] = this.oldProcessId;
3686
+ return json;
3687
+ };
3688
+ return RestartInitiatedEvent;
3689
+ }(SessionEvent));
3690
+ exports.RestartInitiatedEvent = RestartInitiatedEvent;
3691
+ var RestartCompletedEvent = /** @class */ (function (_super) {
3692
+ __extends(RestartCompletedEvent, _super);
3693
+ function RestartCompletedEvent(data) {
3694
+ var _this = _super.call(this, SessionEventName.RestartCompleted) || this;
3695
+ if (data) {
3696
+ Object.assign(_this, data);
3697
+ }
3698
+ return _this;
3699
+ }
3700
+ RestartCompletedEvent.prototype.init = function (_data) {
3701
+ var data = ensureObject(_data);
3702
+ this.success = data['Success'];
3703
+ this.newProcessId = data['NewProcessId'];
3704
+ };
3705
+ RestartCompletedEvent.fromJS = function (data) {
3706
+ var result = new RestartCompletedEvent();
3707
+ result.init(data);
3708
+ return result;
3709
+ };
3710
+ RestartCompletedEvent.prototype.toJSON = function (data) {
3711
+ var json = ensureObject(data);
3712
+ json['Success'] = this.success;
3713
+ json['NewProcessId'] = this.newProcessId;
3714
+ return json;
3715
+ };
3716
+ return RestartCompletedEvent;
3717
+ }(SessionEvent));
3718
+ exports.RestartCompletedEvent = RestartCompletedEvent;
3662
3719
  var OnTestPlanRun = /** @class */ (function () {
3663
3720
  function OnTestPlanRun(data) {
3664
3721
  if (data) {
@@ -58,6 +58,12 @@ export declare class RunnerClient extends BaseClient {
58
58
  * @returns {{Promise<void>}}
59
59
  */
60
60
  shutdownSession(sessionId: string, force?: boolean): Promise<void>;
61
+ /**
62
+ * Restart a session
63
+ * @param sessionId the ID of the session to restart
64
+ * @returns {{Promise<void>}}
65
+ */
66
+ restartSession(sessionId: string): Promise<void>;
61
67
  /**
62
68
  * Start a session
63
69
  * @returns {{Promise<Session>}}
@@ -232,6 +232,16 @@ var RunnerClient = /** @class */ (function (_super) {
232
232
  .catch(this.error())
233
233
  : Promise.reject('sessionId is not defined');
234
234
  };
235
+ /**
236
+ * Restart a session
237
+ * @param sessionId the ID of the session to restart
238
+ * @returns {{Promise<void>}}
239
+ */
240
+ RunnerClient.prototype.restartSession = function (sessionId) {
241
+ return (sessionId === null || sessionId === void 0 ? void 0 : sessionId.length) > 0
242
+ ? this.request('RestartSession', { SessionId: sessionId }).then(this.success()).catch(this.error())
243
+ : Promise.reject('sessionId is not defined');
244
+ };
235
245
  /**
236
246
  * Start a session
237
247
  * @returns {{Promise<Session>}}
@@ -847,6 +847,8 @@ var SessionClient = /** @class */ (function (_super) {
847
847
  _a[DTOs_1.SessionEventName.Break] = function (payload) { return DTOs_1.BreakEvent.fromJS(payload); },
848
848
  _a[DTOs_1.SessionEventName.UserInputRequested] = function (payload) { return DTOs_1.UserInputRequestedEvent.fromJS(payload); },
849
849
  _a[DTOs_1.SessionEventName.UserInputRequestCompleted] = function (payload) { return DTOs_1.UserInputRequestCompletedEvent.fromJS(payload); },
850
+ _a[DTOs_1.SessionEventName.RestartInitiated] = function (payload) { return DTOs_1.RestartInitiatedEvent.fromJS(payload); },
851
+ _a[DTOs_1.SessionEventName.RestartCompleted] = function (payload) { return DTOs_1.RestartCompletedEvent.fromJS(payload); },
850
852
  _a);
851
853
  return SessionClient;
852
854
  }(BaseClient_1.BaseClient));
@@ -11,6 +11,28 @@ import { AckPolicy, Empty, ErrorCode, JSONCodec, StringCodec, connect, headers,
11
11
  import { ComponentSettingsBase, ComponentSettingsIdentifier, ComponentSettingsListItem, DataGridControl, ErrorResponse, FileDescriptor, ListItemType, NoResponderError, ProfileGroup, } from './DTOs';
12
12
  import { EventEmitter } from 'events';
13
13
  import { v4 as uuidv4 } from 'uuid';
14
+ // A browser extension like CrowdStrike may have already replaced globalThis.WebSocket before this module loads,
15
+ // so reading the global is unreliable. A freshly created same-origin iframe exposes the browser's
16
+ // pristine, un-patched WebSocket; NATS traffic is pinned to it to bypass any extension shim.
17
+ // The iframe is kept attached because a detached iframe's realm becomes unusable in some browsers.
18
+ function getNativeWebSocket() {
19
+ var _a, _b, _c;
20
+ const globalWebSocket = typeof globalThis !== 'undefined' ? globalThis.WebSocket : undefined;
21
+ const doc = globalThis.document;
22
+ if (!doc || typeof doc.createElement !== 'function') {
23
+ return globalWebSocket;
24
+ }
25
+ try {
26
+ const iframe = doc.createElement('iframe');
27
+ iframe.style.display = 'none';
28
+ ((_a = doc.body) !== null && _a !== void 0 ? _a : doc.documentElement).appendChild(iframe);
29
+ return (_c = (_b = iframe.contentWindow) === null || _b === void 0 ? void 0 : _b.WebSocket) !== null && _c !== void 0 ? _c : globalWebSocket;
30
+ }
31
+ catch (_d) {
32
+ return globalWebSocket;
33
+ }
34
+ }
35
+ const OriginalWebSocket = getNativeWebSocket();
14
36
  const DEFAULT_TIMEOUT = 40000; // default timeout of 40 seconds
15
37
  var Events;
16
38
  (function (Events) {
@@ -47,10 +69,20 @@ export class BaseClient {
47
69
  this.baseSubject = baseSubject;
48
70
  this.connectionOptions = Object.assign({}, options);
49
71
  this.connectionOptions.timeout = (options === null || options === void 0 ? void 0 : options.timeout) || DEFAULT_TIMEOUT;
72
+ // Force NATS to use the WebSocket captured at load time so an extension that later
73
+ // overrides globalThis.WebSocket cannot intercept NATS traffic. Respect a caller-provided factory.
74
+ // wsFactory is read by nats.ws's WsTransport at runtime but lives on WsConnectionOptions, which
75
+ // isn't re-exported, so it's absent from the public ConnectionOptions type and requires this cast.
76
+ if (OriginalWebSocket && !this.connectionOptions.wsFactory) {
77
+ this.connectionOptions.wsFactory = (url) => Promise.resolve({ socket: new OriginalWebSocket(url), encrypted: url.indexOf('wss://') === 0 });
78
+ }
50
79
  this.eventEmitter = new EventEmitter();
51
80
  }
52
- withTimeout(promise, timeout) {
53
- return Promise.race([promise, new Promise((_, reject) => setTimeout(() => reject(new Error(ErrorCode.Timeout)), timeout))]);
81
+ withTimeout(promise, timeout, subject) {
82
+ return Promise.race([
83
+ promise,
84
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`${ErrorCode.Timeout}: '${subject}' timed out after ${timeout}ms`)), timeout)),
85
+ ]);
54
86
  }
55
87
  /**
56
88
  * Send a request to the nats server.
@@ -106,7 +138,14 @@ export class BaseClient {
106
138
  // The response from the session will arrive in this promise after we have finished
107
139
  // publishing chunks
108
140
  const responsePromise = new Promise((resolve, reject) => {
141
+ // Chunk payloads stored at their sequence index so order is preserved
142
+ // regardless of the order in which messages arrive.
109
143
  const messages = [];
144
+ let receivedCount = 0;
145
+ // Total number of chunks and the size of the final chunk, both known
146
+ // once the final (smaller) chunk arrives.
147
+ let expectedCount;
148
+ let finalChunkLength = 0;
110
149
  subscription.callback = (error, message) => {
111
150
  var _a, _b, _c, _d;
112
151
  if (error) {
@@ -122,43 +161,49 @@ export class BaseClient {
122
161
  subscription.unsubscribe();
123
162
  return resolve({ byteArray: message.data, isErrorResponse: ((_c = message.headers) === null || _c === void 0 ? void 0 : _c.get('OpenTapNatsError')) === 'true' });
124
163
  }
125
- // Put all the response chunks in an array in the order they are received
126
- messages.push(message);
127
- // If the chunk has a size equal to the chunkSize, we should expect another message
128
- if (message.data.length === chunkSize) {
164
+ // Place each chunk at its sequence position (ChunkNumber is 1-based).
165
+ messages[parseInt(chunkNumber) - 1] = message.data;
166
+ receivedCount += 1;
167
+ // The final chunk is the only one smaller than chunkSize; its sequence
168
+ // number tells us how many chunks to expect and its length fixes the total size.
169
+ if (message.data.length < chunkSize) {
170
+ expectedCount = parseInt(chunkNumber);
171
+ finalChunkLength = message.data.length;
172
+ }
173
+ // Keep waiting until every expected chunk has been received.
174
+ if (expectedCount === undefined || receivedCount < expectedCount) {
129
175
  return;
130
176
  }
131
- // If the chunk has a length smaller than the chunkSize, the message is complete
132
- // If the final message was received, we can safely unsubscribe
177
+ // All expected chunks received; we can safely unsubscribe.
133
178
  subscription.unsubscribe();
134
- // Check if the number of the final message is equal to the number of
135
- // messages we received. If this is not the case, we dropped a chunk,
136
- // likely due to a network error. In this case, the entire message is invalid.
137
- if (parseInt(chunkNumber) !== messages.length) {
138
- return reject(Error(`Expected {finalMessageNumber} chunks, but received ${messages.length}. ` +
139
- `The connection may have been interrupted.`));
140
- }
141
- // Concatenate the payloads
142
- // When there are many chunks, doing a single allocation
143
- // is significantly faster than concatenating arrays in sequence
144
- const flattenedSize = messages.reduce((sum, array) => sum + array.data.length, 0);
179
+ // Every chunk before the last is exactly chunkSize, so the total size is
180
+ // deterministic and each chunk's offset is simply its index * chunkSize.
181
+ const flattenedSize = (expectedCount - 1) * chunkSize + finalChunkLength;
145
182
  const flattenedArray = new Uint8Array(flattenedSize);
146
- let k = 0;
147
- messages.forEach(m => {
148
- for (let i = 0; i < m.data.length; i++) {
149
- flattenedArray[k++] = m.data[i];
183
+ for (let i = 0; i < expectedCount; i++) {
184
+ const chunk = messages[i];
185
+ // A missing chunk means we dropped one, likely due to a network error,
186
+ // so the entire message is invalid.
187
+ if (!chunk) {
188
+ return reject(Error(`Expected ${expectedCount} chunks, but chunk ${i + 1} is missing. ` +
189
+ `The connection may have been interrupted.`));
150
190
  }
151
- });
191
+ flattenedArray.set(chunk, i * chunkSize);
192
+ }
152
193
  return resolve({ byteArray: flattenedArray, isErrorResponse: ((_d = message.headers) === null || _d === void 0 ? void 0 : _d.get('OpenTapNatsError')) === 'true' });
153
194
  };
154
195
  }).then(({ byteArray, isErrorResponse }) => {
196
+ const jsonCodec = JSONCodec();
197
+ if (isErrorResponse) {
198
+ return Promise.reject(ErrorResponse.fromJS(jsonCodec.decode(byteArray)));
199
+ }
155
200
  if (byteArray.length === 0) {
156
201
  return Promise.resolve(undefined);
157
202
  }
158
- const jsonCodec = JSONCodec();
159
- // If a raw response is requested, we should avoid decoding the bytes.
160
- const response = (options === null || options === void 0 ? void 0 : options.rawResponse) ? byteArray : jsonCodec.decode(byteArray);
161
- return isErrorResponse ? Promise.reject(ErrorResponse.fromJS(response)) : Promise.resolve(response);
203
+ if (options === null || options === void 0 ? void 0 : options.rawResponse) {
204
+ return Promise.resolve(byteArray);
205
+ }
206
+ return Promise.resolve(jsonCodec.decode(byteArray));
162
207
  });
163
208
  // 'Manually' publish the first chunk. This is to ensure that **at least** one chunk is published.
164
209
  // This is a special case for empty payloads (which are common)
@@ -178,7 +223,7 @@ export class BaseClient {
178
223
  this.connection.publish(subject, Empty, opts);
179
224
  }
180
225
  // Now that we have sent the terminating chunk, the result should arrive on our promise.
181
- return this.withTimeout(responsePromise, timeout).catch(err => {
226
+ return this.withTimeout(responsePromise, timeout, subject).catch(err => {
182
227
  subscription.unsubscribe();
183
228
  return Promise.reject(this.natsErrorHandler(err, subject));
184
229
  });
@@ -876,7 +876,8 @@ export declare enum SessionState {
876
876
  Breaking = "Breaking",
877
877
  Aborting = "Aborting",
878
878
  WaitingForUserInput = "WaitingForUserInput",
879
- Loading = "Loading"
879
+ Loading = "Loading",
880
+ Restarting = "Restarting"
880
881
  }
881
882
  export declare class TestPlan implements ITestPlan {
882
883
  childTestSteps?: TestStep[] | undefined;
@@ -1207,7 +1208,9 @@ export declare enum SessionEventName {
1207
1208
  TestStepChanged = "TestStepChanged",
1208
1209
  Break = "Break",
1209
1210
  UserInputRequested = "UserInputRequested",
1210
- UserInputRequestCompleted = "UserInputRequestCompleted"
1211
+ UserInputRequestCompleted = "UserInputRequestCompleted",
1212
+ RestartInitiated = "RestartInitiated",
1213
+ RestartCompleted = "RestartCompleted"
1211
1214
  }
1212
1215
  export declare abstract class SessionEvent {
1213
1216
  readonly eventName: SessionEventName;
@@ -1315,6 +1318,21 @@ export declare class UserInputRequestCompletedEvent extends SessionEvent {
1315
1318
  static fromJS(data: any): UserInputRequestCompletedEvent;
1316
1319
  toJSON(data?: any): any;
1317
1320
  }
1321
+ export declare class RestartInitiatedEvent extends SessionEvent {
1322
+ oldProcessId?: number;
1323
+ constructor(data?: Partial<RestartInitiatedEvent>);
1324
+ init(_data?: any): void;
1325
+ static fromJS(data: any): RestartInitiatedEvent;
1326
+ toJSON(data?: any): any;
1327
+ }
1328
+ export declare class RestartCompletedEvent extends SessionEvent {
1329
+ success?: boolean;
1330
+ newProcessId?: number;
1331
+ constructor(data?: Partial<RestartCompletedEvent>);
1332
+ init(_data?: any): void;
1333
+ static fromJS(data: any): RestartCompletedEvent;
1334
+ toJSON(data?: any): any;
1335
+ }
1318
1336
  export type ISessionEvent = SessionEvent;
1319
1337
  export interface IOnTestPlanRun {
1320
1338
  status?: string;
package/dist/mjs/DTOs.js CHANGED
@@ -2143,6 +2143,7 @@ export var SessionState;
2143
2143
  SessionState["Aborting"] = "Aborting";
2144
2144
  SessionState["WaitingForUserInput"] = "WaitingForUserInput";
2145
2145
  SessionState["Loading"] = "Loading";
2146
+ SessionState["Restarting"] = "Restarting";
2146
2147
  })(SessionState || (SessionState = {}));
2147
2148
  export class TestPlan {
2148
2149
  constructor(data) {
@@ -2925,6 +2926,8 @@ export var SessionEventName;
2925
2926
  SessionEventName["Break"] = "Break";
2926
2927
  SessionEventName["UserInputRequested"] = "UserInputRequested";
2927
2928
  SessionEventName["UserInputRequestCompleted"] = "UserInputRequestCompleted";
2929
+ SessionEventName["RestartInitiated"] = "RestartInitiated";
2930
+ SessionEventName["RestartCompleted"] = "RestartCompleted";
2928
2931
  })(SessionEventName || (SessionEventName = {}));
2929
2932
  export class SessionEvent {
2930
2933
  constructor(eventName) {
@@ -3200,6 +3203,52 @@ export class UserInputRequestCompletedEvent extends SessionEvent {
3200
3203
  return json;
3201
3204
  }
3202
3205
  }
3206
+ export class RestartInitiatedEvent extends SessionEvent {
3207
+ constructor(data) {
3208
+ super(SessionEventName.RestartInitiated);
3209
+ if (data) {
3210
+ Object.assign(this, data);
3211
+ }
3212
+ }
3213
+ init(_data) {
3214
+ const data = ensureObject(_data);
3215
+ this.oldProcessId = data['OldProcessId'];
3216
+ }
3217
+ static fromJS(data) {
3218
+ const result = new RestartInitiatedEvent();
3219
+ result.init(data);
3220
+ return result;
3221
+ }
3222
+ toJSON(data) {
3223
+ const json = ensureObject(data);
3224
+ json['OldProcessId'] = this.oldProcessId;
3225
+ return json;
3226
+ }
3227
+ }
3228
+ export class RestartCompletedEvent extends SessionEvent {
3229
+ constructor(data) {
3230
+ super(SessionEventName.RestartCompleted);
3231
+ if (data) {
3232
+ Object.assign(this, data);
3233
+ }
3234
+ }
3235
+ init(_data) {
3236
+ const data = ensureObject(_data);
3237
+ this.success = data['Success'];
3238
+ this.newProcessId = data['NewProcessId'];
3239
+ }
3240
+ static fromJS(data) {
3241
+ const result = new RestartCompletedEvent();
3242
+ result.init(data);
3243
+ return result;
3244
+ }
3245
+ toJSON(data) {
3246
+ const json = ensureObject(data);
3247
+ json['Success'] = this.success;
3248
+ json['NewProcessId'] = this.newProcessId;
3249
+ return json;
3250
+ }
3251
+ }
3203
3252
  export class OnTestPlanRun {
3204
3253
  constructor(data) {
3205
3254
  if (data) {
@@ -58,6 +58,12 @@ export declare class RunnerClient extends BaseClient {
58
58
  * @returns {{Promise<void>}}
59
59
  */
60
60
  shutdownSession(sessionId: string, force?: boolean): Promise<void>;
61
+ /**
62
+ * Restart a session
63
+ * @param sessionId the ID of the session to restart
64
+ * @returns {{Promise<void>}}
65
+ */
66
+ restartSession(sessionId: string): Promise<void>;
61
67
  /**
62
68
  * Start a session
63
69
  * @returns {{Promise<Session>}}
@@ -171,6 +171,16 @@ export class RunnerClient extends BaseClient {
171
171
  .catch(this.error())
172
172
  : Promise.reject('sessionId is not defined');
173
173
  }
174
+ /**
175
+ * Restart a session
176
+ * @param sessionId the ID of the session to restart
177
+ * @returns {{Promise<void>}}
178
+ */
179
+ restartSession(sessionId) {
180
+ return (sessionId === null || sessionId === void 0 ? void 0 : sessionId.length) > 0
181
+ ? this.request('RestartSession', { SessionId: sessionId }).then(this.success()).catch(this.error())
182
+ : Promise.reject('sessionId is not defined');
183
+ }
174
184
  /**
175
185
  * Start a session
176
186
  * @returns {{Promise<Session>}}
@@ -1,4 +1,4 @@
1
- import { BreakEvent, BreakPoints, CommonContext, CommonSettings, DataGridControl, GetTestPlanReferenceResponse, HeartbeatEvent, Image, Interaction, ListItemType, LogList, MetricsConfiguration, OnTestPlanRun, OnTestStepRun, Result, RunStatus, SaveTestPlanToRepositoryResponse, SessionEventName, SessionStateChangedEvent, SessionTimeoutEvent, Setting, SettingsChangedEvent, StartingEvent, StartedEvent, StoppedEvent, StoppingEvent, TestPlan, TestPlanChangedEvent, TestPlanReferenceChangedEvent, TestPlanSettingsChangedEvent, TestStepChangedEvent, TestStepType, TestStepValidationError, TypeCacheInvalidatedEvent, UserInputRequestCompletedEvent, UserInputRequestedEvent, WatchDog, EditStatus, } from './DTOs';
1
+ import { BreakEvent, BreakPoints, CommonContext, CommonSettings, DataGridControl, GetTestPlanReferenceResponse, HeartbeatEvent, Image, Interaction, ListItemType, LogList, MetricsConfiguration, OnTestPlanRun, OnTestStepRun, Result, RunStatus, SaveTestPlanToRepositoryResponse, SessionEventName, SessionStateChangedEvent, SessionTimeoutEvent, Setting, SettingsChangedEvent, StartingEvent, StartedEvent, StoppedEvent, StoppingEvent, TestPlan, TestPlanChangedEvent, TestPlanReferenceChangedEvent, TestPlanSettingsChangedEvent, TestStepChangedEvent, TestStepType, TestStepValidationError, TypeCacheInvalidatedEvent, UserInputRequestCompletedEvent, UserInputRequestedEvent, RestartInitiatedEvent, RestartCompletedEvent, WatchDog, EditStatus, } from './DTOs';
2
2
  import { BaseClient } from './BaseClient';
3
3
  import { jsonCodec } from './encoders';
4
4
  import { NIL } from 'uuid';
@@ -808,4 +808,6 @@ SessionClient.sessionEventFactories = {
808
808
  [SessionEventName.Break]: payload => BreakEvent.fromJS(payload),
809
809
  [SessionEventName.UserInputRequested]: payload => UserInputRequestedEvent.fromJS(payload),
810
810
  [SessionEventName.UserInputRequestCompleted]: payload => UserInputRequestCompletedEvent.fromJS(payload),
811
+ [SessionEventName.RestartInitiated]: payload => RestartInitiatedEvent.fromJS(payload),
812
+ [SessionEventName.RestartCompleted]: payload => RestartCompletedEvent.fromJS(payload),
811
813
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opentap/runner-client",
3
- "version": "4.1.0",
3
+ "version": "4.3.0-beta.3.32232370336",
4
4
  "description": "This is the web client for the OpenTAP Runner.",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/mjs/index.js",