@dronedeploy/rocos-js-sdk 4.4.1 → 4.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/cjs/api/StreamRegister.js +9 -3
  2. package/cjs/models/ConfigGroupResponse.d.ts +20 -0
  3. package/cjs/models/ConfigGroupResponse.js +2 -0
  4. package/cjs/models/command/CommandV2.d.ts +28 -0
  5. package/cjs/models/command/CommandV2.js +2 -0
  6. package/cjs/models/command/index.d.ts +1 -0
  7. package/cjs/models/command/index.js +1 -0
  8. package/cjs/models/gamepad/GamepadConfig.d.ts +3 -16
  9. package/cjs/models/index.d.ts +1 -0
  10. package/cjs/models/index.js +1 -0
  11. package/cjs/services/BaseStreamService.d.ts +1 -1
  12. package/cjs/services/BaseStreamService.js +21 -2
  13. package/cjs/services/ProfileService.d.ts +3 -2
  14. package/cjs/services/ProfileService.js +0 -4
  15. package/cjs/services/RobotService.d.ts +3 -3
  16. package/cjs/services/RobotService.js +0 -4
  17. package/cjs/services/TelemetryService.js +21 -14
  18. package/esm/api/StreamRegister.js +9 -3
  19. package/esm/models/ConfigGroupResponse.d.ts +20 -0
  20. package/esm/models/ConfigGroupResponse.js +1 -0
  21. package/esm/models/command/CommandV2.d.ts +28 -0
  22. package/esm/models/command/CommandV2.js +1 -0
  23. package/esm/models/command/index.d.ts +1 -0
  24. package/esm/models/command/index.js +1 -0
  25. package/esm/models/gamepad/GamepadConfig.d.ts +3 -16
  26. package/esm/models/index.d.ts +1 -0
  27. package/esm/models/index.js +1 -0
  28. package/esm/services/BaseStreamService.d.ts +1 -1
  29. package/esm/services/BaseStreamService.js +21 -2
  30. package/esm/services/ProfileService.d.ts +3 -2
  31. package/esm/services/ProfileService.js +0 -4
  32. package/esm/services/RobotService.d.ts +3 -3
  33. package/esm/services/RobotService.js +0 -4
  34. package/esm/services/TelemetryService.js +22 -15
  35. package/package.json +1 -1
@@ -19,8 +19,10 @@ class StreamRegister {
19
19
  }
20
20
  addStream(stream) {
21
21
  try {
22
- if (this.teleStreams.has(stream.identifier)) {
23
- this.removeStream(stream);
22
+ // Stop any existing occupant of this key before overwriting it.
23
+ const existing = this.teleStreams.get(stream.identifier);
24
+ if (existing && existing !== stream) {
25
+ existing.stopStream();
24
26
  }
25
27
  this.teleStreams.set(stream.identifier, stream);
26
28
  }
@@ -40,7 +42,11 @@ class StreamRegister {
40
42
  removeStream(stream) {
41
43
  try {
42
44
  stream.stopStream();
43
- this.teleStreams.delete(stream.identifier);
45
+ // Only clear the entry if this instance still owns the key: a replacement may already hold it,
46
+ // and evicting that would leave subscribers forking duplicate streams for the scope.
47
+ if (this.teleStreams.get(stream.identifier) === stream) {
48
+ this.teleStreams.delete(stream.identifier);
49
+ }
44
50
  }
45
51
  catch (e) {
46
52
  this.logger.error(`Failed to remove stream from list. identifier: ${stream.identifier}`, e);
@@ -0,0 +1,20 @@
1
+ /** One stored item in a config group, scoped by whatever the group's `type` represents (e.g. one gamepad, one command). */
2
+ export interface ConfigGroupItem<T> {
3
+ id: string;
4
+ value: T;
5
+ /** Robot-scoped response only: whether this robot's config overrides its robot definition's. */
6
+ _isOverridden?: boolean;
7
+ /** Robot-scoped response only: the robot definition's item this one overrides, when it does. */
8
+ _globalItem?: ConfigGroupItem<T>;
9
+ }
10
+ /** Response shape shared by RobotService/ProfileService config-group endpoints (gamepads, commands-v2, ...). */
11
+ export interface ConfigGroupResponse<T> {
12
+ version?: string;
13
+ type: string;
14
+ items: Array<ConfigGroupItem<T>>;
15
+ /** Robot-scoped response only. */
16
+ robotDefinitionId?: string;
17
+ username?: string;
18
+ createdAt?: string;
19
+ updatedAt?: string;
20
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,28 @@
1
+ import { ConfigGroupItem, ConfigGroupResponse } from '../ConfigGroupResponse';
2
+ /** Declares one named parameter a command accepts. `default` is always a string, whatever `type` names. */
3
+ export interface CommandV2ParameterDeclaration {
4
+ type: string;
5
+ default: string;
6
+ }
7
+ export interface CommandV2Settings {
8
+ target: string;
9
+ /** Stored as a number on newer configs, a numeric string on older ones. Absent on some legacy configs. */
10
+ timeoutMs?: number | string;
11
+ requiresClockSync: boolean;
12
+ payloadTemplate: string;
13
+ /** Should be an object, but a known editor round-trip bug can persist it as a raw JSON string. */
14
+ payloadMeta?: Record<string, unknown> | string | null;
15
+ }
16
+ export interface CommandV2 {
17
+ schemaVersion: number;
18
+ id: string;
19
+ name: string;
20
+ type: string;
21
+ /** Absent for commands declaring no parameters, not an empty object. */
22
+ parameters?: Record<string, CommandV2ParameterDeclaration>;
23
+ settings: CommandV2Settings;
24
+ }
25
+ /** One stored command, scoped to a specific command id (CommandV2.id). */
26
+ export type CommandV2Item = ConfigGroupItem<CommandV2>;
27
+ /** Response shape of RobotService/ProfileService getCommandsV2 — one item per declared command. */
28
+ export type CommandV2Response = ConfigGroupResponse<CommandV2>;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,4 +1,5 @@
1
1
  export * from '../ResponseLevelEnum';
2
+ export * from './CommandV2';
2
3
  export * from './IRocosCommandMessageHeartbeat';
3
4
  export * from './IRocosCommandMessageResponse';
4
5
  export * from './RocosCommandResultStatus';
@@ -15,6 +15,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("../ResponseLevelEnum"), exports);
18
+ __exportStar(require("./CommandV2"), exports);
18
19
  __exportStar(require("./IRocosCommandMessageHeartbeat"), exports);
19
20
  __exportStar(require("./IRocosCommandMessageResponse"), exports);
20
21
  __exportStar(require("./RocosCommandResultStatus"), exports);
@@ -1,3 +1,4 @@
1
+ import { ConfigGroupItem, ConfigGroupResponse } from '../ConfigGroupResponse';
1
2
  export declare enum GamepadButtonType {
2
3
  UNKNOWN = 0,
3
4
  UP = 1,
@@ -81,20 +82,6 @@ export interface GamepadConfig {
81
82
  axisExpos?: GamepadAxisValueMap;
82
83
  }
83
84
  /** One stored gamepad config, scoped to a specific physical controller model (GamepadConfig.gamepad). */
84
- export interface GamepadConfigItem {
85
- id: string;
86
- value: GamepadConfig;
87
- /** Robot-scoped response only: whether this robot's config overrides its robot definition's. */
88
- _isOverridden?: boolean;
89
- }
85
+ export type GamepadConfigItem = ConfigGroupItem<GamepadConfig>;
90
86
  /** Response shape of RobotService/ProfileService getGamepads — one item per registered controller model. */
91
- export interface GamepadConfigResponse {
92
- version?: string;
93
- type: string;
94
- items: GamepadConfigItem[];
95
- /** Robot-scoped response only. */
96
- robotDefinitionId?: string;
97
- username?: string;
98
- createdAt?: string;
99
- updatedAt?: string;
100
- }
87
+ export type GamepadConfigResponse = ConfigGroupResponse<GamepadConfig>;
@@ -6,6 +6,7 @@ export * from './callsigns/CallsignsLookup';
6
6
  export * from './callsigns/CallsignsQuery';
7
7
  export * from './callsigns/CallsignsQueryPredicate';
8
8
  export * from './command';
9
+ export * from './ConfigGroupResponse';
9
10
  export * from './device-credentials/DeviceCredentials';
10
11
  export * from './Dashboard';
11
12
  export * from './ExportDataQuery';
@@ -22,6 +22,7 @@ __exportStar(require("./callsigns/CallsignsLookup"), exports);
22
22
  __exportStar(require("./callsigns/CallsignsQuery"), exports);
23
23
  __exportStar(require("./callsigns/CallsignsQueryPredicate"), exports);
24
24
  __exportStar(require("./command"), exports);
25
+ __exportStar(require("./ConfigGroupResponse"), exports);
25
26
  __exportStar(require("./device-credentials/DeviceCredentials"), exports);
26
27
  __exportStar(require("./Dashboard"), exports);
27
28
  __exportStar(require("./ExportDataQuery"), exports);
@@ -10,7 +10,7 @@ export declare abstract class BaseStreamService<T extends IBaseStream, C extends
10
10
  protected constructor(name: string, config: IRocosSDKConfig);
11
11
  getStatus(): boolean;
12
12
  protected initStream(stream: T): Promise<void>;
13
- protected createStreamFromConfig(identifier: string, config: C): Promise<{
13
+ protected createStreamFromConfig(identifier: string, config: C, onAcquire?: (stream: T) => void): Promise<{
14
14
  stream: T;
15
15
  isNew: boolean;
16
16
  }>;
@@ -32,7 +32,7 @@ class BaseStreamService {
32
32
  }
33
33
  authService.startTokenRefreshChecker();
34
34
  }
35
- async createStreamFromConfig(identifier, config) {
35
+ async createStreamFromConfig(identifier, config, onAcquire) {
36
36
  const identifierWithScope = StreamRegister_1.StreamRegister.getIdentifier(identifier, config.scope);
37
37
  const streamRegister = StreamRegister_1.StreamRegister.getInstance();
38
38
  let stream = streamRegister.getStream(identifierWithScope);
@@ -45,7 +45,26 @@ class BaseStreamService {
45
45
  this.status$.next(msg);
46
46
  });
47
47
  streamRegister.addStream(stream);
48
- await this.initStream(stream);
48
+ }
49
+ // Acquire synchronously with the lookup/registration above, before any await. A stream is
50
+ // stopped and deregistered the instant its refcount hits zero, so a deferred acquire can land on
51
+ // a dead instance — including a freshly-registered stream torn down during the init await below.
52
+ onAcquire?.(stream);
53
+ if (isNew) {
54
+ try {
55
+ await this.initStream(stream);
56
+ }
57
+ catch (e) {
58
+ // Evict eagerly, before the error reaches the subscriber, so a synchronous resubscribe
59
+ // can't re-acquire this uninitialised instance.
60
+ streamRegister.removeStream(stream);
61
+ throw e;
62
+ }
63
+ // If a concurrent teardown deregistered this stream during init, init() has opened a receiver
64
+ // on an instance nothing can reach; close it.
65
+ if (streamRegister.getStream(identifierWithScope) !== stream) {
66
+ stream.stopStream();
67
+ }
49
68
  }
50
69
  return { stream, isNew };
51
70
  }
@@ -1,4 +1,5 @@
1
1
  import { AgentSettingsRequestItem, AgentSettingsResponse } from '../models/IRobotSettings';
2
+ import { CommandV2Item, CommandV2Response } from '../models/command/CommandV2';
2
3
  import { CreateRobotDefinitionRequest, RobotProfile } from '../models/RobotProfile';
3
4
  import { GamepadConfigItem, GamepadConfigResponse } from '../models/gamepad/GamepadConfig';
4
5
  import { RocosError } from '../models/RocosError';
@@ -168,7 +169,7 @@ export declare class ProfileService extends BaseServiceAbstract implements IBase
168
169
  * @param definitionId - Robot definition Id
169
170
  * @description - Renamed from robotDefGetCommandsV2
170
171
  */
171
- getCommandsV2(projectId: string, definitionId: string): Promise<any>;
172
+ getCommandsV2(projectId: string, definitionId: string): Promise<CommandV2Response>;
172
173
  /**
173
174
  * Update command V2 for a robot definition
174
175
  *
@@ -177,7 +178,7 @@ export declare class ProfileService extends BaseServiceAbstract implements IBase
177
178
  * @param model - Payload
178
179
  * @description - Renamed from robotDefUpdateCommandsV2
179
180
  */
180
- updateCommandsV2(projectId: string, definitionId: string, model: any): Promise<any>;
181
+ updateCommandsV2(projectId: string, definitionId: string, model: CommandV2Item[]): Promise<void>;
181
182
  /**
182
183
  * Get command actions for a robot definition
183
184
  *
@@ -238,7 +238,6 @@ class ProfileService extends BaseServiceAbstract_1.BaseServiceAbstract {
238
238
  * @param definitionId - Robot definition Id
239
239
  * @description - Renamed from robotDefGetCommandsV2
240
240
  */
241
- // TODO: map the response object
242
241
  async getCommandsV2(projectId, definitionId) {
243
242
  return this.callGet((0, formatServiceUrl_1.formatServiceUrl)(api_1.API_PROJECT_DEFINITION_COMMAND2_URL, { url: this.config.url, projectId, definitionId }, this.config.insecure), `Failed to get command list for ${projectId}, definitionId ${definitionId}.`);
244
243
  }
@@ -250,9 +249,6 @@ class ProfileService extends BaseServiceAbstract_1.BaseServiceAbstract {
250
249
  * @param model - Payload
251
250
  * @description - Renamed from robotDefUpdateCommandsV2
252
251
  */
253
- // TODO: map the request model
254
- // TODO: map the response object
255
- // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
256
252
  async updateCommandsV2(projectId, definitionId, model) {
257
253
  return this.callPost((0, formatServiceUrl_1.formatServiceUrl)(api_1.API_PROJECT_DEFINITION_COMMAND2_URL, { url: this.config.url, projectId, definitionId }, this.config.insecure), model, `Failed to update command list for ${projectId}, definitionId ${definitionId}.`);
258
254
  }
@@ -1,4 +1,4 @@
1
- import { AgentSettingsResponse, GamepadConfigItem, GamepadConfigResponse, IBaseService, IConnectedCallsign, IRobot, IRobotListItem, IRobotSettings, IRobotTemplate, IRocosSDKConfig, RocosError } from '../models';
1
+ import { AgentSettingsResponse, CommandV2Item, CommandV2Response, GamepadConfigItem, GamepadConfigResponse, IBaseService, IConnectedCallsign, IRobot, IRobotListItem, IRobotSettings, IRobotTemplate, IRocosSDKConfig, RocosError } from '../models';
2
2
  import { BaseServiceAbstract } from './BaseServiceAbstract';
3
3
  export declare class RobotService extends BaseServiceAbstract implements IBaseService {
4
4
  constructor(config: IRocosSDKConfig);
@@ -227,7 +227,7 @@ export declare class RobotService extends BaseServiceAbstract implements IBaseSe
227
227
  * @param projectId - Project Id
228
228
  * @param callsign - Robot callsign
229
229
  */
230
- getCommandsV2(projectId: string, callsign: string): Promise<any>;
230
+ getCommandsV2(projectId: string, callsign: string): Promise<CommandV2Response>;
231
231
  /**
232
232
  * Update command V2 for a robot
233
233
  *
@@ -235,7 +235,7 @@ export declare class RobotService extends BaseServiceAbstract implements IBaseSe
235
235
  * @param callsign - Robot callsign
236
236
  * @param model - Payload
237
237
  */
238
- updateCommandsV2(projectId: string, callsign: string, model: any): Promise<any>;
238
+ updateCommandsV2(projectId: string, callsign: string, model: CommandV2Item[]): Promise<void>;
239
239
  /**
240
240
  * Get buttons for a robot
241
241
  *
@@ -380,7 +380,6 @@ class RobotService extends BaseServiceAbstract_1.BaseServiceAbstract {
380
380
  * @param projectId - Project Id
381
381
  * @param callsign - Robot callsign
382
382
  */
383
- // TODO: map the response object
384
383
  async getCommandsV2(projectId, callsign) {
385
384
  return this.callGet((0, formatServiceUrl_1.formatServiceUrl)(api_1.API_PROJECT_ROBOT_COMMAND2_URL, { url: this.config.url, projectId, callsign }, this.config.insecure), `Failed to get command list for ${projectId}, callsign ${callsign}.`);
386
385
  }
@@ -391,9 +390,6 @@ class RobotService extends BaseServiceAbstract_1.BaseServiceAbstract {
391
390
  * @param callsign - Robot callsign
392
391
  * @param model - Payload
393
392
  */
394
- // TODO: map the request model
395
- // TODO: map the response object
396
- // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
397
393
  async updateCommandsV2(projectId, callsign, model) {
398
394
  return this.callPost((0, formatServiceUrl_1.formatServiceUrl)(api_1.API_PROJECT_ROBOT_COMMAND2_URL, { url: this.config.url, projectId, callsign }, this.config.insecure), model, `Failed to update commands for ${projectId}, callsign ${callsign}.`);
399
395
  }
@@ -61,19 +61,26 @@ class TelemetryService extends BaseStreamService_1.BaseStreamService {
61
61
  callsigns: callsignsLookup?.lookupType === models_1.CallsignsLookupType.List ? callsignsLookup.lookupValue : [],
62
62
  sources,
63
63
  };
64
- const stream = this.createStream(params.projectId, callsignsLookup, sources, scope).then((stream) => {
65
- stream.addSubscription(subscriptionParams);
66
- if (!this.statusSubscription) {
67
- this.statusSubscription = stream.statusStream$.subscribe((msg) => {
68
- this.status = msg === models_1.SubscriberStatusEnum.STOPPED || msg === models_1.SubscriberStatusEnum.ALIVE;
69
- this.status$.next(msg);
70
- });
71
- }
72
- return (0, rxjs_1.from)(stream.messageStream$).pipe((0, operators_1.finalize)(() => stream?.removeSubscription(subscriptionParams, params.terminateReceiverGroupOnUnsubscribe)));
64
+ // `defer` acquires the stream only when subscribed; `finalize` releases it on unsubscribe, error
65
+ // and complete — so acquire and release stay balanced on every path.
66
+ return (0, rxjs_1.defer)(() => {
67
+ let acquired;
68
+ const stream$ = this.createStream(params.projectId, callsignsLookup, sources, scope, (stream) => {
69
+ acquired = stream;
70
+ stream.addSubscription(subscriptionParams);
71
+ }).then((stream) => {
72
+ if (!this.statusSubscription) {
73
+ this.statusSubscription = stream.statusStream$.subscribe((msg) => {
74
+ this.status = msg === models_1.SubscriberStatusEnum.STOPPED || msg === models_1.SubscriberStatusEnum.ALIVE;
75
+ this.status$.next(msg);
76
+ });
77
+ }
78
+ return (0, rxjs_1.from)(stream.messageStream$);
79
+ });
80
+ return (0, rxjs_1.from)(stream$).pipe((0, rxjs_1.mergeAll)(), (0, operators_1.filter)((message) => {
81
+ return callsigns.includes(message.callsign) && sources.includes(message.source);
82
+ }), (0, operators_1.finalize)(() => acquired?.removeSubscription(subscriptionParams, params.terminateReceiverGroupOnUnsubscribe)));
73
83
  });
74
- return (0, rxjs_1.from)(stream).pipe((0, rxjs_1.mergeAll)(), (0, operators_1.filter)((message) => {
75
- return callsigns.includes(message.callsign) && sources.includes(message.source);
76
- }));
77
84
  }
78
85
  /**
79
86
  * A method to keep track of the current subscriptions
@@ -152,7 +159,7 @@ class TelemetryService extends BaseStreamService_1.BaseStreamService {
152
159
  return `${params.projectId}-default-${(0, flattenCallsignsLookup_1.flattenCallsignsLookup)(callsignsLookup).join()}`;
153
160
  return `${params.projectId}-default`;
154
161
  }
155
- async createStream(projectId, callsignsLookup, sources, scope) {
162
+ async createStream(projectId, callsignsLookup, sources, scope, onAcquire) {
156
163
  const newStream = await this.createStreamFromConfig(identifier_1.IDENTIFIER_NAME_TELEMETRY, {
157
164
  url: this.config.url,
158
165
  projectId,
@@ -165,7 +172,7 @@ class TelemetryService extends BaseStreamService_1.BaseStreamService {
165
172
  insecure: this.config.insecure,
166
173
  transport: this.config.transport,
167
174
  fetch: this.config.fetch,
168
- });
175
+ }, onAcquire);
169
176
  if (!newStream.isNew) {
170
177
  newStream.stream.statusStream$.subscribe((msg) => {
171
178
  this.streamStatusSubject$.next({
@@ -16,8 +16,10 @@ export class StreamRegister {
16
16
  }
17
17
  addStream(stream) {
18
18
  try {
19
- if (this.teleStreams.has(stream.identifier)) {
20
- this.removeStream(stream);
19
+ // Stop any existing occupant of this key before overwriting it.
20
+ const existing = this.teleStreams.get(stream.identifier);
21
+ if (existing && existing !== stream) {
22
+ existing.stopStream();
21
23
  }
22
24
  this.teleStreams.set(stream.identifier, stream);
23
25
  }
@@ -37,7 +39,11 @@ export class StreamRegister {
37
39
  removeStream(stream) {
38
40
  try {
39
41
  stream.stopStream();
40
- this.teleStreams.delete(stream.identifier);
42
+ // Only clear the entry if this instance still owns the key: a replacement may already hold it,
43
+ // and evicting that would leave subscribers forking duplicate streams for the scope.
44
+ if (this.teleStreams.get(stream.identifier) === stream) {
45
+ this.teleStreams.delete(stream.identifier);
46
+ }
41
47
  }
42
48
  catch (e) {
43
49
  this.logger.error(`Failed to remove stream from list. identifier: ${stream.identifier}`, e);
@@ -0,0 +1,20 @@
1
+ /** One stored item in a config group, scoped by whatever the group's `type` represents (e.g. one gamepad, one command). */
2
+ export interface ConfigGroupItem<T> {
3
+ id: string;
4
+ value: T;
5
+ /** Robot-scoped response only: whether this robot's config overrides its robot definition's. */
6
+ _isOverridden?: boolean;
7
+ /** Robot-scoped response only: the robot definition's item this one overrides, when it does. */
8
+ _globalItem?: ConfigGroupItem<T>;
9
+ }
10
+ /** Response shape shared by RobotService/ProfileService config-group endpoints (gamepads, commands-v2, ...). */
11
+ export interface ConfigGroupResponse<T> {
12
+ version?: string;
13
+ type: string;
14
+ items: Array<ConfigGroupItem<T>>;
15
+ /** Robot-scoped response only. */
16
+ robotDefinitionId?: string;
17
+ username?: string;
18
+ createdAt?: string;
19
+ updatedAt?: string;
20
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,28 @@
1
+ import { ConfigGroupItem, ConfigGroupResponse } from '../ConfigGroupResponse';
2
+ /** Declares one named parameter a command accepts. `default` is always a string, whatever `type` names. */
3
+ export interface CommandV2ParameterDeclaration {
4
+ type: string;
5
+ default: string;
6
+ }
7
+ export interface CommandV2Settings {
8
+ target: string;
9
+ /** Stored as a number on newer configs, a numeric string on older ones. Absent on some legacy configs. */
10
+ timeoutMs?: number | string;
11
+ requiresClockSync: boolean;
12
+ payloadTemplate: string;
13
+ /** Should be an object, but a known editor round-trip bug can persist it as a raw JSON string. */
14
+ payloadMeta?: Record<string, unknown> | string | null;
15
+ }
16
+ export interface CommandV2 {
17
+ schemaVersion: number;
18
+ id: string;
19
+ name: string;
20
+ type: string;
21
+ /** Absent for commands declaring no parameters, not an empty object. */
22
+ parameters?: Record<string, CommandV2ParameterDeclaration>;
23
+ settings: CommandV2Settings;
24
+ }
25
+ /** One stored command, scoped to a specific command id (CommandV2.id). */
26
+ export type CommandV2Item = ConfigGroupItem<CommandV2>;
27
+ /** Response shape of RobotService/ProfileService getCommandsV2 — one item per declared command. */
28
+ export type CommandV2Response = ConfigGroupResponse<CommandV2>;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,5 @@
1
1
  export * from '../ResponseLevelEnum';
2
+ export * from './CommandV2';
2
3
  export * from './IRocosCommandMessageHeartbeat';
3
4
  export * from './IRocosCommandMessageResponse';
4
5
  export * from './RocosCommandResultStatus';
@@ -1,4 +1,5 @@
1
1
  export * from '../ResponseLevelEnum';
2
+ export * from './CommandV2';
2
3
  export * from './IRocosCommandMessageHeartbeat';
3
4
  export * from './IRocosCommandMessageResponse';
4
5
  export * from './RocosCommandResultStatus';
@@ -1,3 +1,4 @@
1
+ import { ConfigGroupItem, ConfigGroupResponse } from '../ConfigGroupResponse';
1
2
  export declare enum GamepadButtonType {
2
3
  UNKNOWN = 0,
3
4
  UP = 1,
@@ -81,20 +82,6 @@ export interface GamepadConfig {
81
82
  axisExpos?: GamepadAxisValueMap;
82
83
  }
83
84
  /** One stored gamepad config, scoped to a specific physical controller model (GamepadConfig.gamepad). */
84
- export interface GamepadConfigItem {
85
- id: string;
86
- value: GamepadConfig;
87
- /** Robot-scoped response only: whether this robot's config overrides its robot definition's. */
88
- _isOverridden?: boolean;
89
- }
85
+ export type GamepadConfigItem = ConfigGroupItem<GamepadConfig>;
90
86
  /** Response shape of RobotService/ProfileService getGamepads — one item per registered controller model. */
91
- export interface GamepadConfigResponse {
92
- version?: string;
93
- type: string;
94
- items: GamepadConfigItem[];
95
- /** Robot-scoped response only. */
96
- robotDefinitionId?: string;
97
- username?: string;
98
- createdAt?: string;
99
- updatedAt?: string;
100
- }
87
+ export type GamepadConfigResponse = ConfigGroupResponse<GamepadConfig>;
@@ -6,6 +6,7 @@ export * from './callsigns/CallsignsLookup';
6
6
  export * from './callsigns/CallsignsQuery';
7
7
  export * from './callsigns/CallsignsQueryPredicate';
8
8
  export * from './command';
9
+ export * from './ConfigGroupResponse';
9
10
  export * from './device-credentials/DeviceCredentials';
10
11
  export * from './Dashboard';
11
12
  export * from './ExportDataQuery';
@@ -6,6 +6,7 @@ export * from './callsigns/CallsignsLookup';
6
6
  export * from './callsigns/CallsignsQuery';
7
7
  export * from './callsigns/CallsignsQueryPredicate';
8
8
  export * from './command';
9
+ export * from './ConfigGroupResponse';
9
10
  export * from './device-credentials/DeviceCredentials';
10
11
  export * from './Dashboard';
11
12
  export * from './ExportDataQuery';
@@ -10,7 +10,7 @@ export declare abstract class BaseStreamService<T extends IBaseStream, C extends
10
10
  protected constructor(name: string, config: IRocosSDKConfig);
11
11
  getStatus(): boolean;
12
12
  protected initStream(stream: T): Promise<void>;
13
- protected createStreamFromConfig(identifier: string, config: C): Promise<{
13
+ protected createStreamFromConfig(identifier: string, config: C, onAcquire?: (stream: T) => void): Promise<{
14
14
  stream: T;
15
15
  isNew: boolean;
16
16
  }>;
@@ -29,7 +29,7 @@ export class BaseStreamService {
29
29
  }
30
30
  authService.startTokenRefreshChecker();
31
31
  }
32
- async createStreamFromConfig(identifier, config) {
32
+ async createStreamFromConfig(identifier, config, onAcquire) {
33
33
  const identifierWithScope = StreamRegister.getIdentifier(identifier, config.scope);
34
34
  const streamRegister = StreamRegister.getInstance();
35
35
  let stream = streamRegister.getStream(identifierWithScope);
@@ -42,7 +42,26 @@ export class BaseStreamService {
42
42
  this.status$.next(msg);
43
43
  });
44
44
  streamRegister.addStream(stream);
45
- await this.initStream(stream);
45
+ }
46
+ // Acquire synchronously with the lookup/registration above, before any await. A stream is
47
+ // stopped and deregistered the instant its refcount hits zero, so a deferred acquire can land on
48
+ // a dead instance — including a freshly-registered stream torn down during the init await below.
49
+ onAcquire?.(stream);
50
+ if (isNew) {
51
+ try {
52
+ await this.initStream(stream);
53
+ }
54
+ catch (e) {
55
+ // Evict eagerly, before the error reaches the subscriber, so a synchronous resubscribe
56
+ // can't re-acquire this uninitialised instance.
57
+ streamRegister.removeStream(stream);
58
+ throw e;
59
+ }
60
+ // If a concurrent teardown deregistered this stream during init, init() has opened a receiver
61
+ // on an instance nothing can reach; close it.
62
+ if (streamRegister.getStream(identifierWithScope) !== stream) {
63
+ stream.stopStream();
64
+ }
46
65
  }
47
66
  return { stream, isNew };
48
67
  }
@@ -1,4 +1,5 @@
1
1
  import { AgentSettingsRequestItem, AgentSettingsResponse } from '../models/IRobotSettings';
2
+ import { CommandV2Item, CommandV2Response } from '../models/command/CommandV2';
2
3
  import { CreateRobotDefinitionRequest, RobotProfile } from '../models/RobotProfile';
3
4
  import { GamepadConfigItem, GamepadConfigResponse } from '../models/gamepad/GamepadConfig';
4
5
  import { RocosError } from '../models/RocosError';
@@ -168,7 +169,7 @@ export declare class ProfileService extends BaseServiceAbstract implements IBase
168
169
  * @param definitionId - Robot definition Id
169
170
  * @description - Renamed from robotDefGetCommandsV2
170
171
  */
171
- getCommandsV2(projectId: string, definitionId: string): Promise<any>;
172
+ getCommandsV2(projectId: string, definitionId: string): Promise<CommandV2Response>;
172
173
  /**
173
174
  * Update command V2 for a robot definition
174
175
  *
@@ -177,7 +178,7 @@ export declare class ProfileService extends BaseServiceAbstract implements IBase
177
178
  * @param model - Payload
178
179
  * @description - Renamed from robotDefUpdateCommandsV2
179
180
  */
180
- updateCommandsV2(projectId: string, definitionId: string, model: any): Promise<any>;
181
+ updateCommandsV2(projectId: string, definitionId: string, model: CommandV2Item[]): Promise<void>;
181
182
  /**
182
183
  * Get command actions for a robot definition
183
184
  *
@@ -235,7 +235,6 @@ export class ProfileService extends BaseServiceAbstract {
235
235
  * @param definitionId - Robot definition Id
236
236
  * @description - Renamed from robotDefGetCommandsV2
237
237
  */
238
- // TODO: map the response object
239
238
  async getCommandsV2(projectId, definitionId) {
240
239
  return this.callGet(formatServiceUrl(API_PROJECT_DEFINITION_COMMAND2_URL, { url: this.config.url, projectId, definitionId }, this.config.insecure), `Failed to get command list for ${projectId}, definitionId ${definitionId}.`);
241
240
  }
@@ -247,9 +246,6 @@ export class ProfileService extends BaseServiceAbstract {
247
246
  * @param model - Payload
248
247
  * @description - Renamed from robotDefUpdateCommandsV2
249
248
  */
250
- // TODO: map the request model
251
- // TODO: map the response object
252
- // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
253
249
  async updateCommandsV2(projectId, definitionId, model) {
254
250
  return this.callPost(formatServiceUrl(API_PROJECT_DEFINITION_COMMAND2_URL, { url: this.config.url, projectId, definitionId }, this.config.insecure), model, `Failed to update command list for ${projectId}, definitionId ${definitionId}.`);
255
251
  }
@@ -1,4 +1,4 @@
1
- import { AgentSettingsResponse, GamepadConfigItem, GamepadConfigResponse, IBaseService, IConnectedCallsign, IRobot, IRobotListItem, IRobotSettings, IRobotTemplate, IRocosSDKConfig, RocosError } from '../models';
1
+ import { AgentSettingsResponse, CommandV2Item, CommandV2Response, GamepadConfigItem, GamepadConfigResponse, IBaseService, IConnectedCallsign, IRobot, IRobotListItem, IRobotSettings, IRobotTemplate, IRocosSDKConfig, RocosError } from '../models';
2
2
  import { BaseServiceAbstract } from './BaseServiceAbstract';
3
3
  export declare class RobotService extends BaseServiceAbstract implements IBaseService {
4
4
  constructor(config: IRocosSDKConfig);
@@ -227,7 +227,7 @@ export declare class RobotService extends BaseServiceAbstract implements IBaseSe
227
227
  * @param projectId - Project Id
228
228
  * @param callsign - Robot callsign
229
229
  */
230
- getCommandsV2(projectId: string, callsign: string): Promise<any>;
230
+ getCommandsV2(projectId: string, callsign: string): Promise<CommandV2Response>;
231
231
  /**
232
232
  * Update command V2 for a robot
233
233
  *
@@ -235,7 +235,7 @@ export declare class RobotService extends BaseServiceAbstract implements IBaseSe
235
235
  * @param callsign - Robot callsign
236
236
  * @param model - Payload
237
237
  */
238
- updateCommandsV2(projectId: string, callsign: string, model: any): Promise<any>;
238
+ updateCommandsV2(projectId: string, callsign: string, model: CommandV2Item[]): Promise<void>;
239
239
  /**
240
240
  * Get buttons for a robot
241
241
  *
@@ -377,7 +377,6 @@ export class RobotService extends BaseServiceAbstract {
377
377
  * @param projectId - Project Id
378
378
  * @param callsign - Robot callsign
379
379
  */
380
- // TODO: map the response object
381
380
  async getCommandsV2(projectId, callsign) {
382
381
  return this.callGet(formatServiceUrl(API_PROJECT_ROBOT_COMMAND2_URL, { url: this.config.url, projectId, callsign }, this.config.insecure), `Failed to get command list for ${projectId}, callsign ${callsign}.`);
383
382
  }
@@ -388,9 +387,6 @@ export class RobotService extends BaseServiceAbstract {
388
387
  * @param callsign - Robot callsign
389
388
  * @param model - Payload
390
389
  */
391
- // TODO: map the request model
392
- // TODO: map the response object
393
- // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
394
390
  async updateCommandsV2(projectId, callsign, model) {
395
391
  return this.callPost(formatServiceUrl(API_PROJECT_ROBOT_COMMAND2_URL, { url: this.config.url, projectId, callsign }, this.config.insecure), model, `Failed to update commands for ${projectId}, callsign ${callsign}.`);
396
392
  }
@@ -1,4 +1,4 @@
1
- import { BehaviorSubject, catchError, combineLatest, distinctUntilChanged, from, interval, map, mergeAll, of, startWith, } from 'rxjs';
1
+ import { BehaviorSubject, catchError, combineLatest, defer, distinctUntilChanged, from, interval, map, mergeAll, of, startWith, } from 'rxjs';
2
2
  import { CallsignStatus, CallsignsLookup, CallsignsLookupType, RocosError, SubscriberStatusEnum, TelemetryMonitorStatus, errorCodes, } from '../models';
3
3
  import { filter, finalize } from 'rxjs/operators';
4
4
  import { BaseStreamService } from './BaseStreamService';
@@ -58,19 +58,26 @@ export class TelemetryService extends BaseStreamService {
58
58
  callsigns: callsignsLookup?.lookupType === CallsignsLookupType.List ? callsignsLookup.lookupValue : [],
59
59
  sources,
60
60
  };
61
- const stream = this.createStream(params.projectId, callsignsLookup, sources, scope).then((stream) => {
62
- stream.addSubscription(subscriptionParams);
63
- if (!this.statusSubscription) {
64
- this.statusSubscription = stream.statusStream$.subscribe((msg) => {
65
- this.status = msg === SubscriberStatusEnum.STOPPED || msg === SubscriberStatusEnum.ALIVE;
66
- this.status$.next(msg);
67
- });
68
- }
69
- return from(stream.messageStream$).pipe(finalize(() => stream?.removeSubscription(subscriptionParams, params.terminateReceiverGroupOnUnsubscribe)));
61
+ // `defer` acquires the stream only when subscribed; `finalize` releases it on unsubscribe, error
62
+ // and complete — so acquire and release stay balanced on every path.
63
+ return defer(() => {
64
+ let acquired;
65
+ const stream$ = this.createStream(params.projectId, callsignsLookup, sources, scope, (stream) => {
66
+ acquired = stream;
67
+ stream.addSubscription(subscriptionParams);
68
+ }).then((stream) => {
69
+ if (!this.statusSubscription) {
70
+ this.statusSubscription = stream.statusStream$.subscribe((msg) => {
71
+ this.status = msg === SubscriberStatusEnum.STOPPED || msg === SubscriberStatusEnum.ALIVE;
72
+ this.status$.next(msg);
73
+ });
74
+ }
75
+ return from(stream.messageStream$);
76
+ });
77
+ return from(stream$).pipe(mergeAll(), filter((message) => {
78
+ return callsigns.includes(message.callsign) && sources.includes(message.source);
79
+ }), finalize(() => acquired?.removeSubscription(subscriptionParams, params.terminateReceiverGroupOnUnsubscribe)));
70
80
  });
71
- return from(stream).pipe(mergeAll(), filter((message) => {
72
- return callsigns.includes(message.callsign) && sources.includes(message.source);
73
- }));
74
81
  }
75
82
  /**
76
83
  * A method to keep track of the current subscriptions
@@ -149,7 +156,7 @@ export class TelemetryService extends BaseStreamService {
149
156
  return `${params.projectId}-default-${flattenCallsignsLookup(callsignsLookup).join()}`;
150
157
  return `${params.projectId}-default`;
151
158
  }
152
- async createStream(projectId, callsignsLookup, sources, scope) {
159
+ async createStream(projectId, callsignsLookup, sources, scope, onAcquire) {
153
160
  const newStream = await this.createStreamFromConfig(IDENTIFIER_NAME_TELEMETRY, {
154
161
  url: this.config.url,
155
162
  projectId,
@@ -162,7 +169,7 @@ export class TelemetryService extends BaseStreamService {
162
169
  insecure: this.config.insecure,
163
170
  transport: this.config.transport,
164
171
  fetch: this.config.fetch,
165
- });
172
+ }, onAcquire);
166
173
  if (!newStream.isNew) {
167
174
  newStream.stream.statusStream$.subscribe((msg) => {
168
175
  this.streamStatusSubject$.next({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dronedeploy/rocos-js-sdk",
3
- "version": "4.4.1",
3
+ "version": "4.4.3",
4
4
  "description": "Javascript SDK for rocos",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",