@opentap/runner-client 4.2.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", {
@@ -157,7 +188,14 @@ var BaseClient = /** @class */ (function () {
157
188
  };
158
189
  subscription = this.connection.subscribe(replySubject);
159
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.
160
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;
161
199
  subscription.callback = function (error, message) {
162
200
  var _a, _b, _c, _d;
163
201
  if (error) {
@@ -173,44 +211,50 @@ var BaseClient = /** @class */ (function () {
173
211
  subscription.unsubscribe();
174
212
  return resolve({ byteArray: message.data, isErrorResponse: ((_c = message.headers) === null || _c === void 0 ? void 0 : _c.get('OpenTapNatsError')) === 'true' });
175
213
  }
176
- // Put all the response chunks in an array in the order they are received
177
- messages.push(message);
178
- // If the chunk has a size equal to the chunkSize, we should expect another message
179
- 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) {
180
225
  return;
181
226
  }
182
- // If the chunk has a length smaller than the chunkSize, the message is complete
183
- // If the final message was received, we can safely unsubscribe
227
+ // All expected chunks received; we can safely unsubscribe.
184
228
  subscription.unsubscribe();
185
- // Check if the number of the final message is equal to the number of
186
- // messages we received. If this is not the case, we dropped a chunk,
187
- // likely due to a network error. In this case, the entire message is invalid.
188
- if (parseInt(chunkNumber) !== messages.length) {
189
- return reject(Error("Expected {finalMessageNumber} chunks, but received ".concat(messages.length, ". ") +
190
- "The connection may have been interrupted."));
191
- }
192
- // Concatenate the payloads
193
- // When there are many chunks, doing a single allocation
194
- // is significantly faster than concatenating arrays in sequence
195
- 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;
196
232
  var flattenedArray = new Uint8Array(flattenedSize);
197
- var k = 0;
198
- messages.forEach(function (m) {
199
- for (var i = 0; i < m.data.length; i++) {
200
- 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."));
201
240
  }
202
- });
241
+ flattenedArray.set(chunk_1, i * chunkSize);
242
+ }
203
243
  return resolve({ byteArray: flattenedArray, isErrorResponse: ((_d = message.headers) === null || _d === void 0 ? void 0 : _d.get('OpenTapNatsError')) === 'true' });
204
244
  };
205
245
  }).then(function (_a) {
206
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
+ }
207
251
  if (byteArray.length === 0) {
208
252
  return Promise.resolve(undefined);
209
253
  }
210
- var jsonCodec = (0, nats_ws_1.JSONCodec)();
211
- // If a raw response is requested, we should avoid decoding the bytes.
212
- var response = (options === null || options === void 0 ? void 0 : options.rawResponse) ? byteArray : jsonCodec.decode(byteArray);
213
- 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));
214
258
  });
215
259
  chunk = getChunk(0);
216
260
  this.connection.publish(subject, chunk, opts);
@@ -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,6 +69,13 @@ 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
81
  withTimeout(promise, timeout, subject) {
@@ -109,7 +138,14 @@ export class BaseClient {
109
138
  // The response from the session will arrive in this promise after we have finished
110
139
  // publishing chunks
111
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.
112
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;
113
149
  subscription.callback = (error, message) => {
114
150
  var _a, _b, _c, _d;
115
151
  if (error) {
@@ -125,43 +161,49 @@ export class BaseClient {
125
161
  subscription.unsubscribe();
126
162
  return resolve({ byteArray: message.data, isErrorResponse: ((_c = message.headers) === null || _c === void 0 ? void 0 : _c.get('OpenTapNatsError')) === 'true' });
127
163
  }
128
- // Put all the response chunks in an array in the order they are received
129
- messages.push(message);
130
- // If the chunk has a size equal to the chunkSize, we should expect another message
131
- 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) {
132
175
  return;
133
176
  }
134
- // If the chunk has a length smaller than the chunkSize, the message is complete
135
- // If the final message was received, we can safely unsubscribe
177
+ // All expected chunks received; we can safely unsubscribe.
136
178
  subscription.unsubscribe();
137
- // Check if the number of the final message is equal to the number of
138
- // messages we received. If this is not the case, we dropped a chunk,
139
- // likely due to a network error. In this case, the entire message is invalid.
140
- if (parseInt(chunkNumber) !== messages.length) {
141
- return reject(Error(`Expected {finalMessageNumber} chunks, but received ${messages.length}. ` +
142
- `The connection may have been interrupted.`));
143
- }
144
- // Concatenate the payloads
145
- // When there are many chunks, doing a single allocation
146
- // is significantly faster than concatenating arrays in sequence
147
- 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;
148
182
  const flattenedArray = new Uint8Array(flattenedSize);
149
- let k = 0;
150
- messages.forEach(m => {
151
- for (let i = 0; i < m.data.length; i++) {
152
- 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.`));
153
190
  }
154
- });
191
+ flattenedArray.set(chunk, i * chunkSize);
192
+ }
155
193
  return resolve({ byteArray: flattenedArray, isErrorResponse: ((_d = message.headers) === null || _d === void 0 ? void 0 : _d.get('OpenTapNatsError')) === 'true' });
156
194
  };
157
195
  }).then(({ byteArray, isErrorResponse }) => {
196
+ const jsonCodec = JSONCodec();
197
+ if (isErrorResponse) {
198
+ return Promise.reject(ErrorResponse.fromJS(jsonCodec.decode(byteArray)));
199
+ }
158
200
  if (byteArray.length === 0) {
159
201
  return Promise.resolve(undefined);
160
202
  }
161
- const jsonCodec = JSONCodec();
162
- // If a raw response is requested, we should avoid decoding the bytes.
163
- const response = (options === null || options === void 0 ? void 0 : options.rawResponse) ? byteArray : jsonCodec.decode(byteArray);
164
- 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));
165
207
  });
166
208
  // 'Manually' publish the first chunk. This is to ensure that **at least** one chunk is published.
167
209
  // This is a special case for empty payloads (which are common)
@@ -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.2.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",