@dronedeploy/rocos-js-sdk 4.4.4 → 4.4.6

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 (72) hide show
  1. package/LICENSE +3 -0
  2. package/cjs/api/StreamRegister.d.ts +18 -3
  3. package/cjs/api/StreamRegister.js +71 -38
  4. package/cjs/api/atoms/StreamHeartbeat.js +2 -2
  5. package/cjs/api/streams/telemetry/TelemetryStreamAbstract.d.ts +8 -1
  6. package/cjs/api/streams/telemetry/TelemetryStreamAbstract.js +87 -40
  7. package/cjs/api/streams/webRTCSignalling/WebRTCSignallingStreamAbstract.js +1 -1
  8. package/cjs/constants/api.d.ts +3 -2
  9. package/cjs/constants/api.js +5 -4
  10. package/cjs/constants/identifier.d.ts +1 -0
  11. package/cjs/constants/identifier.js +2 -1
  12. package/cjs/helpers/getUniqueId.js +2 -1
  13. package/cjs/models/callsigns/CallsignsLookup.d.ts +4 -0
  14. package/cjs/models/callsigns/CallsignsLookup.js +19 -0
  15. package/cjs/models/robot-status/RobotStatus.d.ts +23 -0
  16. package/cjs/models/robot-status/RobotStatusEnums.d.ts +18 -6
  17. package/cjs/models/robot-status/RobotStatusEnums.js +18 -6
  18. package/cjs/services/AssetStorageService.js +6 -1
  19. package/cjs/services/BaseServiceAbstract.js +1 -1
  20. package/cjs/services/BaseStreamService.d.ts +12 -1
  21. package/cjs/services/BaseStreamService.js +19 -30
  22. package/cjs/services/EnvironmentService.d.ts +4 -0
  23. package/cjs/services/EnvironmentService.js +5 -1
  24. package/cjs/services/MapService.d.ts +15 -3
  25. package/cjs/services/MapService.js +25 -13
  26. package/cjs/services/RobotStatusService.d.ts +20 -1
  27. package/cjs/services/RobotStatusService.js +5 -0
  28. package/cjs/services/SearchService.js +1 -1
  29. package/cjs/services/TargetService.js +2 -2
  30. package/cjs/services/TelemetryService.d.ts +1 -2
  31. package/cjs/services/TelemetryService.js +30 -28
  32. package/cjs/services/WebRTCSignallingService.js +7 -9
  33. package/cjs/services/robot-status/connectivity.d.ts +16 -12
  34. package/cjs/services/robot-status/connectivity.js +26 -15
  35. package/cjs/services/robot-status/reportedStatus.d.ts +14 -1
  36. package/cjs/services/robot-status/reportedStatus.js +36 -1
  37. package/esm/api/StreamRegister.d.ts +18 -3
  38. package/esm/api/StreamRegister.js +71 -38
  39. package/esm/api/atoms/StreamHeartbeat.js +2 -2
  40. package/esm/api/streams/telemetry/TelemetryStreamAbstract.d.ts +8 -1
  41. package/esm/api/streams/telemetry/TelemetryStreamAbstract.js +88 -41
  42. package/esm/api/streams/webRTCSignalling/WebRTCSignallingStreamAbstract.js +2 -2
  43. package/esm/constants/api.d.ts +3 -2
  44. package/esm/constants/api.js +3 -2
  45. package/esm/constants/identifier.d.ts +1 -0
  46. package/esm/constants/identifier.js +1 -0
  47. package/esm/helpers/getUniqueId.js +2 -1
  48. package/esm/models/callsigns/CallsignsLookup.d.ts +4 -0
  49. package/esm/models/callsigns/CallsignsLookup.js +19 -0
  50. package/esm/models/robot-status/RobotStatus.d.ts +23 -0
  51. package/esm/models/robot-status/RobotStatusEnums.d.ts +18 -6
  52. package/esm/models/robot-status/RobotStatusEnums.js +18 -6
  53. package/esm/services/AssetStorageService.js +6 -1
  54. package/esm/services/BaseServiceAbstract.js +1 -1
  55. package/esm/services/BaseStreamService.d.ts +12 -1
  56. package/esm/services/BaseStreamService.js +19 -30
  57. package/esm/services/EnvironmentService.d.ts +4 -0
  58. package/esm/services/EnvironmentService.js +5 -1
  59. package/esm/services/MapService.d.ts +15 -3
  60. package/esm/services/MapService.js +26 -14
  61. package/esm/services/RobotStatusService.d.ts +20 -1
  62. package/esm/services/RobotStatusService.js +6 -1
  63. package/esm/services/SearchService.js +2 -2
  64. package/esm/services/TargetService.js +3 -3
  65. package/esm/services/TelemetryService.d.ts +1 -2
  66. package/esm/services/TelemetryService.js +31 -29
  67. package/esm/services/WebRTCSignallingService.js +8 -10
  68. package/esm/services/robot-status/connectivity.d.ts +16 -12
  69. package/esm/services/robot-status/connectivity.js +26 -15
  70. package/esm/services/robot-status/reportedStatus.d.ts +14 -1
  71. package/esm/services/robot-status/reportedStatus.js +35 -1
  72. package/package.json +7 -2
@@ -36,7 +36,7 @@ export declare const API_PROJECT_ASSET_INTEGRATION_PROVIDERS_PATH_URL = "https:/
36
36
  export declare const API_PROJECT_ASSET_INTEGRATIONS_PATH_URL = "https://{url}/projects/{projectId}/integrations";
37
37
  export declare const API_PROJECT_ASSET_INTEGRATION_PATH_URL = "https://{url}/projects/{projectId}/integrations/{integrationId}";
38
38
  export declare const API_PROJECT_ROBOT_ASSET_PATH_URL = "https://{url}/projects/{projectId}/robots/{callsign}/assets";
39
- export declare const API_PROJECT_FLOW_ASSET_PATH_URL = "https://{url}/projects/{projectId}/flows/{callsign}/assets";
39
+ export declare const API_PROJECT_FLOW_ASSET_PATH_URL = "https://{url}/projects/{projectId}/flows/{flowId}/instances/{flowInstance}/assets";
40
40
  export declare const API_PROJECT_MISSION_ASSETS_PATH_URL = "https://{url}/projects/{projectId}/missionassets";
41
41
  export declare const API_PROJECT_MISSION_ASSET_PATH_URL = "https://{url}/projects/{projectId}/missionassets/{assetId}";
42
42
  export declare const API_PROJECT_MAPPED_ASSETS_PATH_URL = "https://{url}/projects/{projectId}/mappedassets";
@@ -126,7 +126,7 @@ export declare const API_DD_INTEGRATION_LEVELS_URL = "https://{url}/projects/{pr
126
126
  export declare const API_GRAPHS_MAPS_URL = "https://{url}/graphs/projects/{projectId}/maps";
127
127
  export declare const API_GRAPHS_MAPS_NEW_URL = "https://{url}/graphs/projects/{projectId}/maps/new";
128
128
  export declare const API_GRAPHS_MAPS_DEPLOYED_URL = "https://{url}/graphs/projects/{projectId}/maps/deployed?callsign={callsign}";
129
- export declare const API_GRAPHS_MAPS_MERGE_URL = "https://{url}/graphs/projects/{projectId}/maps/merge";
129
+ export declare const API_GRAPHS_MAPS_UNION_URL = "https://{url}/graphs/projects/{projectId}/maps/union";
130
130
  export declare const API_GRAPHS_MAP_ID_URL = "https://{url}/graphs/projects/{projectId}/maps/{mapId}";
131
131
  export declare const API_GRAPHS_MAP_CONTENT_URL = "https://{url}/graphs/projects/{projectId}/maps/{mapId}/content";
132
132
  export declare const API_GRAPHS_MAP_METADATA_URL = "https://{url}/graphs/projects/{projectId}/maps/{mapId}/metadata";
@@ -152,6 +152,7 @@ export declare const API_GRAPHS_TARGET_UPLOAD_URL = "https://{url}/graphs/projec
152
152
  export declare const API_GRAPHS_TARGETS_URL = "https://{url}/graphs/projects/{projectId}/targets";
153
153
  export declare const API_GRAPHS_ASSETS_UPLOAD_URL = "https://{url}/graphs/projects/{projectId}/assets/upload";
154
154
  export declare const API_GRAPHS_ASSETS_URL = "https://{url}/graphs/projects/{projectId}/assets";
155
+ export declare const API_GRAPHS_TARGET_MEDIA_URL = "https://{url}/graphs/projects/{projectId}/targets/{targetId}/media";
155
156
  export declare const API_PROJECT_WORKFLOW_URL = "https://{url}/projects/{projectId}/automate/flows";
156
157
  export declare const API_PROJECT_WORKFLOW_ID_URL = "https://{url}/projects/{projectId}/automate/flows/{workflowId}";
157
158
  export declare const API_PROJECT_WORKFLOW_ASSET_URL = "https://{url}/projects/{projectId}/automate/flows/{workflowId}/assets";
@@ -36,7 +36,7 @@ export const API_PROJECT_ASSET_INTEGRATION_PROVIDERS_PATH_URL = 'https://{url}/p
36
36
  export const API_PROJECT_ASSET_INTEGRATIONS_PATH_URL = 'https://{url}/projects/{projectId}/integrations';
37
37
  export const API_PROJECT_ASSET_INTEGRATION_PATH_URL = 'https://{url}/projects/{projectId}/integrations/{integrationId}';
38
38
  export const API_PROJECT_ROBOT_ASSET_PATH_URL = 'https://{url}/projects/{projectId}/robots/{callsign}/assets';
39
- export const API_PROJECT_FLOW_ASSET_PATH_URL = 'https://{url}/projects/{projectId}/flows/{callsign}/assets';
39
+ export const API_PROJECT_FLOW_ASSET_PATH_URL = 'https://{url}/projects/{projectId}/flows/{flowId}/instances/{flowInstance}/assets';
40
40
  export const API_PROJECT_MISSION_ASSETS_PATH_URL = 'https://{url}/projects/{projectId}/missionassets';
41
41
  export const API_PROJECT_MISSION_ASSET_PATH_URL = 'https://{url}/projects/{projectId}/missionassets/{assetId}';
42
42
  export const API_PROJECT_MAPPED_ASSETS_PATH_URL = 'https://{url}/projects/{projectId}/mappedassets';
@@ -128,7 +128,7 @@ export const API_DD_INTEGRATION_LEVELS_URL = 'https://{url}/projects/{projectId}
128
128
  export const API_GRAPHS_MAPS_URL = 'https://{url}/graphs/projects/{projectId}/maps';
129
129
  export const API_GRAPHS_MAPS_NEW_URL = 'https://{url}/graphs/projects/{projectId}/maps/new';
130
130
  export const API_GRAPHS_MAPS_DEPLOYED_URL = 'https://{url}/graphs/projects/{projectId}/maps/deployed?callsign={callsign}';
131
- export const API_GRAPHS_MAPS_MERGE_URL = 'https://{url}/graphs/projects/{projectId}/maps/merge';
131
+ export const API_GRAPHS_MAPS_UNION_URL = 'https://{url}/graphs/projects/{projectId}/maps/union';
132
132
  export const API_GRAPHS_MAP_ID_URL = 'https://{url}/graphs/projects/{projectId}/maps/{mapId}';
133
133
  export const API_GRAPHS_MAP_CONTENT_URL = 'https://{url}/graphs/projects/{projectId}/maps/{mapId}/content';
134
134
  export const API_GRAPHS_MAP_METADATA_URL = 'https://{url}/graphs/projects/{projectId}/maps/{mapId}/metadata';
@@ -154,6 +154,7 @@ export const API_GRAPHS_TARGET_UPLOAD_URL = 'https://{url}/graphs/projects/{proj
154
154
  export const API_GRAPHS_TARGETS_URL = 'https://{url}/graphs/projects/{projectId}/targets';
155
155
  export const API_GRAPHS_ASSETS_UPLOAD_URL = 'https://{url}/graphs/projects/{projectId}/assets/upload';
156
156
  export const API_GRAPHS_ASSETS_URL = 'https://{url}/graphs/projects/{projectId}/assets';
157
+ export const API_GRAPHS_TARGET_MEDIA_URL = 'https://{url}/graphs/projects/{projectId}/targets/{targetId}/media';
157
158
  export const API_PROJECT_WORKFLOW_URL = 'https://{url}/projects/{projectId}/automate/flows';
158
159
  export const API_PROJECT_WORKFLOW_ID_URL = 'https://{url}/projects/{projectId}/automate/flows/{workflowId}';
159
160
  export const API_PROJECT_WORKFLOW_ASSET_URL = 'https://{url}/projects/{projectId}/automate/flows/{workflowId}/assets';
@@ -1,6 +1,7 @@
1
1
  export declare const IDENTIFIER_NAME_TELEMETRY = "telemetry";
2
2
  export declare const IDENTIFIER_NAME_CALLER = "caller";
3
3
  export declare const IDENTIFIER_NAME_COMMAND = "command";
4
+ export declare const IDENTIFIER_NAME_WEBRTC_SIGNALLING = "webRTCSignalling";
4
5
  export declare const IDENTIFIER_NAME_CONTROL = "control";
5
6
  export declare const IDENTIFIER_NAME_SEARCH = "search";
6
7
  export declare const IDENTIFIER_NAME_FILE_ACCESSOR = "fileAccessor";
@@ -1,6 +1,7 @@
1
1
  export const IDENTIFIER_NAME_TELEMETRY = 'telemetry';
2
2
  export const IDENTIFIER_NAME_CALLER = 'caller';
3
3
  export const IDENTIFIER_NAME_COMMAND = 'command';
4
+ export const IDENTIFIER_NAME_WEBRTC_SIGNALLING = 'webRTCSignalling';
4
5
  export const IDENTIFIER_NAME_CONTROL = 'control';
5
6
  export const IDENTIFIER_NAME_SEARCH = 'search';
6
7
  export const IDENTIFIER_NAME_FILE_ACCESSOR = 'fileAccessor';
@@ -7,5 +7,6 @@ import { flattenCallsignsLookup } from './flattenCallsignsLookup';
7
7
  * @param scope
8
8
  */
9
9
  export const getUniqueId = (projectId, callsignsLookup, sources, scope) => {
10
- return [projectId, ...flattenCallsignsLookup(callsignsLookup), ...sources.sort(), scope].join();
10
+ // Copy before sorting: the caller keeps this array, and filters incoming messages with it.
11
+ return [projectId, ...flattenCallsignsLookup(callsignsLookup), ...[...sources].sort(), scope].join();
11
12
  };
@@ -4,5 +4,9 @@ export declare class CallsignsLookup {
4
4
  lookupValue: CallsignsQuery | string[];
5
5
  lookupType: CallsignsLookupType;
6
6
  constructor(lookupValue: string[] | CallsignsQuery);
7
+ isQuery(): boolean;
8
+ getCallsigns(): string[];
9
+ getQuery(): CallsignsQuery | undefined;
10
+ includesCallsign(callsign: string): boolean;
7
11
  toID(): string[];
8
12
  }
@@ -19,6 +19,25 @@ export class CallsignsLookup {
19
19
  }
20
20
  }
21
21
  }
22
+ isQuery() {
23
+ return this.lookupType === CallsignsLookupType.Query;
24
+ }
25
+ getCallsigns() {
26
+ if (this.isQuery())
27
+ return [];
28
+ return this.lookupValue;
29
+ }
30
+ getQuery() {
31
+ if (!this.isQuery())
32
+ return undefined;
33
+ return this.lookupValue;
34
+ }
35
+ // A query resolves its callsigns server side, so there is no local list to match against.
36
+ includesCallsign(callsign) {
37
+ if (this.isQuery())
38
+ return true;
39
+ return this.getCallsigns().includes(callsign);
40
+ }
22
41
  toID() {
23
42
  if (this.lookupType === CallsignsLookupType.List) {
24
43
  return this.lookupValue.sort();
@@ -41,3 +41,26 @@ export interface RobotStatus {
41
41
  connectivity: RobotConnectivityStatus;
42
42
  colour: RobotStatusColour;
43
43
  }
44
+ /**
45
+ * One contributor to a reported-status axis, from the agent's
46
+ * `robot/status/contributors` topic. `status` is the contributor's own verdict
47
+ * for that axis — the integer wire value mapped onto the enum.
48
+ */
49
+ export interface RobotStatusContributor<TStatus = RobotHealth | RobotReadiness> {
50
+ /** Identifier of the contributor, e.g. `battery`. */
51
+ id: string;
52
+ /** The agent component that raised it, e.g. `dji`. */
53
+ component: string;
54
+ /** Human-readable explanation, e.g. `battery below 20%`; empty when none was given. */
55
+ reason: string;
56
+ /** The contributor's verdict for its axis. */
57
+ status: TStatus;
58
+ }
59
+ /**
60
+ * The per-contributor breakdown behind a robot's reported health and readiness,
61
+ * from the agent's `robot/status/contributors` topic.
62
+ */
63
+ export interface RobotStatusContributors {
64
+ health: RobotStatusContributor<RobotHealth>[];
65
+ readiness: RobotStatusContributor<RobotReadiness>[];
66
+ }
@@ -30,13 +30,22 @@ export declare enum RobotReadiness {
30
30
  BUSY = "busy"
31
31
  }
32
32
  /**
33
- * Whether the robot is connected to a single cloud service, as reported by that
34
- * service's `connections` endpoint (or, for `telemetry-liveness`, derived from
35
- * heartbeat freshness).
33
+ * Whether the robot is connected to a single cloud service reported by that
34
+ * service's `connections` endpoint, derived from heartbeat freshness (for
35
+ * `telemetry-liveness`), or supplied by a caller-injected signal
36
+ * (`RobotStatusOptions.extraServices`), the only source that can report {@link FAILED}.
36
37
  */
37
38
  export declare enum ServiceConnection {
38
39
  CONNECTED = "connected",
39
40
  DISCONNECTED = "disconnected",
41
+ /**
42
+ * The service reports an explicit fault — a stronger signal than {@link DISCONNECTED} (which just
43
+ * means "not connected", as expected of a robot that is simply off). A failed virtual-robot
44
+ * deployment is the motivating case. Any `FAILED` service forces the aggregate to
45
+ * {@link RobotConnectivity.DEGRADED}, so a fault raises attention even when nothing else is
46
+ * connected — see `aggregateConnectivity`.
47
+ */
48
+ FAILED = "failed",
40
49
  /** The service's connectivity has not yet been determined (e.g. not polled). */
41
50
  UNKNOWN = "unknown"
42
51
  }
@@ -45,10 +54,13 @@ export declare enum ServiceConnection {
45
54
  * expected services.
46
55
  */
47
56
  export declare enum RobotConnectivity {
48
- /** At least one expected service is connected and none is disconnected. */
57
+ /** At least one expected service is connected and none is disconnected or failed. */
49
58
  ONLINE = "online",
50
- /** Some, but not all, expected services report connected. */
59
+ /**
60
+ * Any service reports a fault ({@link ServiceConnection.FAILED}), or a genuine mix — some
61
+ * connected, some disconnected. A `FAILED` service forces this even when nothing is connected.
62
+ */
51
63
  DEGRADED = "degraded",
52
- /** No expected service reports connected. */
64
+ /** Nothing connected, and nothing failed. */
53
65
  OFFLINE = "offline"
54
66
  }
@@ -32,14 +32,23 @@ export var RobotReadiness;
32
32
  RobotReadiness["BUSY"] = "busy";
33
33
  })(RobotReadiness || (RobotReadiness = {}));
34
34
  /**
35
- * Whether the robot is connected to a single cloud service, as reported by that
36
- * service's `connections` endpoint (or, for `telemetry-liveness`, derived from
37
- * heartbeat freshness).
35
+ * Whether the robot is connected to a single cloud service reported by that
36
+ * service's `connections` endpoint, derived from heartbeat freshness (for
37
+ * `telemetry-liveness`), or supplied by a caller-injected signal
38
+ * (`RobotStatusOptions.extraServices`), the only source that can report {@link FAILED}.
38
39
  */
39
40
  export var ServiceConnection;
40
41
  (function (ServiceConnection) {
41
42
  ServiceConnection["CONNECTED"] = "connected";
42
43
  ServiceConnection["DISCONNECTED"] = "disconnected";
44
+ /**
45
+ * The service reports an explicit fault — a stronger signal than {@link DISCONNECTED} (which just
46
+ * means "not connected", as expected of a robot that is simply off). A failed virtual-robot
47
+ * deployment is the motivating case. Any `FAILED` service forces the aggregate to
48
+ * {@link RobotConnectivity.DEGRADED}, so a fault raises attention even when nothing else is
49
+ * connected — see `aggregateConnectivity`.
50
+ */
51
+ ServiceConnection["FAILED"] = "failed";
43
52
  /** The service's connectivity has not yet been determined (e.g. not polled). */
44
53
  ServiceConnection["UNKNOWN"] = "unknown";
45
54
  })(ServiceConnection || (ServiceConnection = {}));
@@ -49,10 +58,13 @@ export var ServiceConnection;
49
58
  */
50
59
  export var RobotConnectivity;
51
60
  (function (RobotConnectivity) {
52
- /** At least one expected service is connected and none is disconnected. */
61
+ /** At least one expected service is connected and none is disconnected or failed. */
53
62
  RobotConnectivity["ONLINE"] = "online";
54
- /** Some, but not all, expected services report connected. */
63
+ /**
64
+ * Any service reports a fault ({@link ServiceConnection.FAILED}), or a genuine mix — some
65
+ * connected, some disconnected. A `FAILED` service forces this even when nothing is connected.
66
+ */
55
67
  RobotConnectivity["DEGRADED"] = "degraded";
56
- /** No expected service reports connected. */
68
+ /** Nothing connected, and nothing failed. */
57
69
  RobotConnectivity["OFFLINE"] = "offline";
58
70
  })(RobotConnectivity || (RobotConnectivity = {}));
@@ -96,7 +96,12 @@ export class AssetStorageService extends BaseServiceAbstract {
96
96
  * @param signedURL
97
97
  */
98
98
  async listFlowAssets(projectId, flowId, flowInstance, parent = '/', signedURL = false) {
99
- return this.callGet(formatServiceUrl(API_PROJECT_FLOW_ASSET_PATH_URL, { url: this.config.url, projectId, flowId, flowInstance }, this.config.insecure), `Failed to get asset for ${projectId}, flow ${flowId}, flow instance ${flowInstance}.`, { parent, signedURL });
99
+ return this.callGet(formatServiceUrl(API_PROJECT_FLOW_ASSET_PATH_URL, {
100
+ url: this.config.url,
101
+ projectId,
102
+ flowId: encodeURIComponent(flowId),
103
+ flowInstance: encodeURIComponent(flowInstance),
104
+ }, this.config.insecure), `Failed to get asset for ${projectId}, flow ${flowId}, flow instance ${flowInstance}.`, { parent, signedURL });
100
105
  }
101
106
  /**
102
107
  * List assets for a mission
@@ -41,7 +41,7 @@ export class BaseServiceAbstract {
41
41
  requestConfig.body = JSON.stringify(payload);
42
42
  requestConfig.headers = { ...requestConfig.headers, 'Content-Type': 'application/json' };
43
43
  }
44
- return this.fetchOrThrow(formattedUrl, requestConfig, config?.responseType);
44
+ return await this.fetchOrThrow(formattedUrl, requestConfig, config?.responseType);
45
45
  }
46
46
  catch (e) {
47
47
  const message = errorMessage;
@@ -1,3 +1,4 @@
1
+ import { IAcquiredStream } from '../api/StreamRegister';
1
2
  import { IBaseService, IBaseStream, IRocosSDKConfig, IStreamConfig, SubscriberStatusEnum } from '../models';
2
3
  import { ISubscriberStatus } from '../models/ISubscriberStatus';
3
4
  import { Logger } from 'loglevel';
@@ -10,7 +11,17 @@ export declare abstract class BaseStreamService<T extends IBaseStream, C extends
10
11
  protected constructor(name: string, config: IRocosSDKConfig);
11
12
  getStatus(): boolean;
12
13
  protected initStream(stream: T): Promise<void>;
13
- protected createStreamFromConfig(identifier: string, config: C, onAcquire?: (stream: T) => void): Promise<{
14
+ /**
15
+ * Get-or-create the shared stream for this identifier/scope and take a refcount on it,
16
+ * synchronously. Holders that stop consuming must call the handle's release(); the registry
17
+ * tears the stream down when the last holder releases.
18
+ */
19
+ protected acquireStream(identifier: string, config: C): IAcquiredStream<T>;
20
+ /**
21
+ * Acquire-and-init without a paired release: callers of this path hold their refcount for the
22
+ * life of the process, so these streams are only torn down by removeAllStreams.
23
+ */
24
+ protected createStreamFromConfig(identifier: string, config: C): Promise<{
14
25
  stream: T;
15
26
  isNew: boolean;
16
27
  }>;
@@ -1,7 +1,7 @@
1
+ import { StreamRegister } from '../api/StreamRegister';
1
2
  import { SubscriberStatusEnum, Token } from '../models';
2
3
  import { RocosLogger } from '../logger/RocosLogger';
3
4
  import { RocosStore } from '../store/RocosStore';
4
- import { StreamRegister } from '../api/StreamRegister';
5
5
  import { Subject } from 'rxjs';
6
6
  export class BaseStreamService {
7
7
  constructor(name, config) {
@@ -29,40 +29,29 @@ export class BaseStreamService {
29
29
  }
30
30
  authService.startTokenRefreshChecker();
31
31
  }
32
- async createStreamFromConfig(identifier, config, onAcquire) {
32
+ /**
33
+ * Get-or-create the shared stream for this identifier/scope and take a refcount on it,
34
+ * synchronously. Holders that stop consuming must call the handle's release(); the registry
35
+ * tears the stream down when the last holder releases.
36
+ */
37
+ acquireStream(identifier, config) {
33
38
  const identifierWithScope = StreamRegister.getIdentifier(identifier, config.scope);
34
- const streamRegister = StreamRegister.getInstance();
35
- let stream = streamRegister.getStream(identifierWithScope);
36
- let isNew = false;
37
- if (!stream) {
38
- isNew = true;
39
- stream = this.getStream(config);
39
+ return StreamRegister.getInstance().acquireStream(identifierWithScope, () => {
40
+ const stream = this.getStream(config);
40
41
  stream.statusStream$.subscribe((msg) => {
41
42
  this.status = msg === SubscriberStatusEnum.STOPPED || msg === SubscriberStatusEnum.ALIVE;
42
43
  this.status$.next(msg);
43
44
  });
44
- streamRegister.addStream(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
- }
65
- }
45
+ return stream;
46
+ }, (stream) => this.initStream(stream));
47
+ }
48
+ /**
49
+ * Acquire-and-init without a paired release: callers of this path hold their refcount for the
50
+ * life of the process, so these streams are only torn down by removeAllStreams.
51
+ */
52
+ async createStreamFromConfig(identifier, config) {
53
+ const { stream, isNew, ready } = this.acquireStream(identifier, config);
54
+ await ready;
66
55
  return { stream, isNew };
67
56
  }
68
57
  }
@@ -21,6 +21,10 @@ export declare class EnvironmentService extends BaseServiceAbstract implements I
21
21
  * CAUTION: This will delete the existing environment and create
22
22
  * a new one. Think of this as a "reset" for the environment.
23
23
  * This will cause ALL edges to other objects (assets, panos, etc) to be removed.
24
+ *
25
+ * @deprecated Prefer {@link update}. The environment has no create endpoint -
26
+ * `PUT /environments` is the only full-replace write, so this method now
27
+ * issues the same request as `update`.
24
28
  */
25
29
  set(projectId: string, scene: unknown): Promise<void>;
26
30
  /**
@@ -33,9 +33,13 @@ export class EnvironmentService extends BaseServiceAbstract {
33
33
  * CAUTION: This will delete the existing environment and create
34
34
  * a new one. Think of this as a "reset" for the environment.
35
35
  * This will cause ALL edges to other objects (assets, panos, etc) to be removed.
36
+ *
37
+ * @deprecated Prefer {@link update}. The environment has no create endpoint -
38
+ * `PUT /environments` is the only full-replace write, so this method now
39
+ * issues the same request as `update`.
36
40
  */
37
41
  async set(projectId, scene) {
38
- return this.callPost(formatServiceUrl(API_PROJECT_ENVIRONMENT_URL, { url: this.config.url, projectId }, this.config.insecure), scene, `Failed to set environment for ${projectId}.`);
42
+ return this.callPut(formatServiceUrl(API_PROJECT_ENVIRONMENT_URL, { url: this.config.url, projectId }, this.config.insecure), scene, `Failed to set environment for ${projectId}.`);
39
43
  }
40
44
  /**
41
45
  * Update Environment
@@ -36,10 +36,22 @@ export declare class MapService extends BaseServiceAbstract implements IBaseServ
36
36
  */
37
37
  listDeployedMaps(projectId: string, callsign: string): Promise<Map[]>;
38
38
  /**
39
- * Creates a new map by merging two existing maps
39
+ * Creates a new map by folding two existing maps together.
40
+ *
41
+ * `options.name` is the mapId of the new target map and must not already
42
+ * exist. `options.callsign` is accepted for signature compatibility and is
43
+ * not sent: the union target is a cloud map, and deploying it to a robot is
44
+ * {@link deploy}'s job.
45
+ *
46
+ * `options.description` needs a second request - the union endpoint takes no
47
+ * metadata. That request is best-effort: a failure is logged and swallowed,
48
+ * because the map itself already exists by then and retrying `merge` would
49
+ * conflict on `targetMapId`. Call {@link updateMetadata} directly to retry
50
+ * the description.
51
+ *
40
52
  * @param projectId Project ID
41
- * @param maps Array of two map IDs to merge
42
- * @param options Options to create the new map
53
+ * @param maps Array of two source map IDs to fold together
54
+ * @param options `name` for the new map, optional `description`
43
55
  */
44
56
  merge(projectId: string, maps: [string, string], options: {
45
57
  name: string;
@@ -1,4 +1,4 @@
1
- import { API_GRAPHS_MAPS_COPY_URL, API_GRAPHS_MAPS_DEPLOYED_URL, API_GRAPHS_MAPS_DEPLOY_URL, API_GRAPHS_MAPS_GEOJSON_URL, API_GRAPHS_MAPS_MERGE_URL, API_GRAPHS_MAPS_NEW_URL, API_GRAPHS_MAPS_URL, API_GRAPHS_MAP_CONTENT_URL, API_GRAPHS_MAP_EDGES_ADD_TRANSFORM_URL, API_GRAPHS_MAP_EDGES_ADD_URL, API_GRAPHS_MAP_EDGE_URL, API_GRAPHS_MAP_ID_URL, API_GRAPHS_MAP_METADATA_URL, API_GRAPHS_MAP_NODES_ADD_TRANSFORMABLE_URL, API_GRAPHS_MAP_NODES_ADD_URL, API_GRAPHS_MAP_NODE_URL, API_GRAPHS_MAP_PATHS_URL, API_GRAPHS_MAP_PATH_APPEND_TRANSFORMABLE_URL, API_GRAPHS_MAP_PATH_APPEND_URL, API_GRAPHS_MAP_PATH_URL, API_GRAPHS_OBSERVATIONS_URL, API_GRAPHS_OBSERVATION_KEYS_URL, API_GRAPHS_PANORAMA, API_GRAPHS_PANORAMAS_URL, API_GRAPHS_PANORAMA_OBSERVATIONS_URL, } from '../constants/api';
1
+ import { API_GRAPHS_MAPS_COPY_URL, API_GRAPHS_MAPS_DEPLOYED_URL, API_GRAPHS_MAPS_DEPLOY_URL, API_GRAPHS_MAPS_GEOJSON_URL, API_GRAPHS_MAPS_NEW_URL, API_GRAPHS_MAPS_UNION_URL, API_GRAPHS_MAPS_URL, API_GRAPHS_MAP_CONTENT_URL, API_GRAPHS_MAP_EDGES_ADD_TRANSFORM_URL, API_GRAPHS_MAP_EDGES_ADD_URL, API_GRAPHS_MAP_EDGE_URL, API_GRAPHS_MAP_ID_URL, API_GRAPHS_MAP_METADATA_URL, API_GRAPHS_MAP_NODES_ADD_TRANSFORMABLE_URL, API_GRAPHS_MAP_NODES_ADD_URL, API_GRAPHS_MAP_NODE_URL, API_GRAPHS_MAP_PATHS_URL, API_GRAPHS_MAP_PATH_APPEND_TRANSFORMABLE_URL, API_GRAPHS_MAP_PATH_APPEND_URL, API_GRAPHS_MAP_PATH_URL, API_GRAPHS_OBSERVATIONS_URL, API_GRAPHS_OBSERVATION_KEYS_URL, API_GRAPHS_PANORAMA, API_GRAPHS_PANORAMAS_URL, API_GRAPHS_PANORAMA_OBSERVATIONS_URL, } from '../constants/api';
2
2
  import { RocosError, errorCodes } from '../models';
3
3
  import { BaseServiceAbstract } from './BaseServiceAbstract';
4
4
  import { RocosLogger } from '../logger/RocosLogger';
@@ -52,26 +52,38 @@ export class MapService extends BaseServiceAbstract {
52
52
  return resp.maps;
53
53
  }
54
54
  /**
55
- * Creates a new map by merging two existing maps
55
+ * Creates a new map by folding two existing maps together.
56
+ *
57
+ * `options.name` is the mapId of the new target map and must not already
58
+ * exist. `options.callsign` is accepted for signature compatibility and is
59
+ * not sent: the union target is a cloud map, and deploying it to a robot is
60
+ * {@link deploy}'s job.
61
+ *
62
+ * `options.description` needs a second request - the union endpoint takes no
63
+ * metadata. That request is best-effort: a failure is logged and swallowed,
64
+ * because the map itself already exists by then and retrying `merge` would
65
+ * conflict on `targetMapId`. Call {@link updateMetadata} directly to retry
66
+ * the description.
67
+ *
56
68
  * @param projectId Project ID
57
- * @param maps Array of two map IDs to merge
58
- * @param options Options to create the new map
69
+ * @param maps Array of two source map IDs to fold together
70
+ * @param options `name` for the new map, optional `description`
59
71
  */
60
72
  async merge(projectId, maps, options) {
61
73
  if (maps.length !== 2)
62
74
  throw new Error('You must provide two maps to merge.');
63
- const payload = {
64
- projectID: projectId,
65
- mapIDLeft: maps[0],
66
- mapIDRight: maps[1],
67
- name: options.name,
68
- description: options.description,
69
- callsign: options.callsign,
70
- };
71
- return this.callPost(formatServiceUrl(API_GRAPHS_MAPS_MERGE_URL, {
75
+ await this.callPost(formatServiceUrl(API_GRAPHS_MAPS_UNION_URL, {
72
76
  url: this.config.url,
73
77
  projectId,
74
- }, this.config.insecure), payload, 'Failed to merge maps.');
78
+ }, this.config.insecure), { targetMapId: options.name, sources: [maps[0], maps[1]] }, 'Failed to merge maps.');
79
+ if (options.description) {
80
+ try {
81
+ await this.updateMetadata(projectId, options.name, { description: options.description });
82
+ }
83
+ catch (e) {
84
+ this.logger.error(`Map ${options.name} was created but its description could not be set.`, e);
85
+ }
86
+ }
75
87
  }
76
88
  /** Returns a map in its canonical form
77
89
  *
@@ -1,4 +1,4 @@
1
- import { IBaseService, IRocosSDKConfig, RobotConnectivityStatus, RobotStatus, RocosError } from '../models';
1
+ import { IBaseService, IRocosSDKConfig, RobotConnectivityStatus, RobotStatus, RobotStatusContributors, RocosError, ServiceConnection } from '../models';
2
2
  import { Observable } from 'rxjs';
3
3
  import { ReportedRobotStatus } from './robot-status/reportedStatus';
4
4
  import { BaseServiceAbstract } from './BaseServiceAbstract';
@@ -10,6 +10,23 @@ export interface RobotStatusOptions {
10
10
  heartbeatTimeoutMs?: number;
11
11
  /** How often the connections endpoints are polled (ms, floored internally). */
12
12
  connectivityPollMs?: number;
13
+ /**
14
+ * Extra per-robot connectivity signals, keyed by service id, folded into
15
+ * {@link RobotConnectivityStatus.services} and the aggregate on equal footing with the built-in
16
+ * services. Lets a caller inject a signal the SDK can't derive itself (e.g. virtual-robot
17
+ * deployment status); emit {@link ServiceConnection.FAILED} to surface a fault as `DEGRADED`.
18
+ *
19
+ * A key that collides with a built-in service id (`telemetry-liveness`, `robot-configs`,
20
+ * `robot-services`) is ignored in favour of the built-in.
21
+ *
22
+ * An error from an injected observable is contained as {@link ServiceConnection.UNKNOWN} so it
23
+ * can't tear down the overall status stream — but it is *terminal* for that signal: unlike the
24
+ * built-in HTTP poller (which recovers on its next tick), the errored observable is not
25
+ * resubscribed, so the service stays `UNKNOWN` for the life of the stream (and `getConnectivity`
26
+ * then waits out its timeout). To report a recoverable fault, emit `FAILED` / `UNKNOWN` as a
27
+ * value rather than letting the observable error.
28
+ */
29
+ extraServices?: Record<string, Observable<ServiceConnection>>;
13
30
  }
14
31
  /**
15
32
  * The single source of truth for a robot's status. It composes the reported
@@ -22,6 +39,8 @@ export declare class RobotStatusService extends BaseServiceAbstract implements I
22
39
  getStatus(): boolean;
23
40
  /** Health + readiness as reported by the robot (new topic, legacy heartbeat fallback). */
24
41
  getReportedStatusChanges(projectId: string, callsign: string): Observable<ReportedRobotStatus>;
42
+ /** Per-contributor breakdown behind the reported health and readiness, with the reason for each. */
43
+ getStatusContributorsChanges(projectId: string, callsign: string): Observable<RobotStatusContributors>;
25
44
  /** Per-service connectivity plus the aggregate verdict, polled. */
26
45
  getConnectivityChanges(projectId: string, callsign: string, opts?: RobotStatusOptions): Observable<RobotConnectivityStatus>;
27
46
  /** One-shot connectivity snapshot: resolves once every expected service is known, or after a timeout. */
@@ -1,7 +1,7 @@
1
1
  import { getConnectivityChanges } from './robot-status/connectivity';
2
2
  import { RobotHealth, RobotReadiness, RocosError, ServiceConnection, errorCodes, } from '../models';
3
3
  import { combineLatest, distinctUntilChanged, first, firstValueFrom, map, of, shareReplay, startWith, take, tap, timeout, } from 'rxjs';
4
- import { getReportedStatusChanges, getTelemetryLiveness } from './robot-status/reportedStatus';
4
+ import { getReportedStatusChanges, getStatusContributorsChanges, getTelemetryLiveness, } from './robot-status/reportedStatus';
5
5
  import { BaseServiceAbstract } from './BaseServiceAbstract';
6
6
  import { RocosLogger } from '../logger/RocosLogger';
7
7
  import { RocosStore } from '../store/RocosStore';
@@ -39,6 +39,10 @@ export class RobotStatusService extends BaseServiceAbstract {
39
39
  getReportedStatusChanges(projectId, callsign) {
40
40
  return getReportedStatusChanges(this.telemetryService, projectId, callsign);
41
41
  }
42
+ /** Per-contributor breakdown behind the reported health and readiness, with the reason for each. */
43
+ getStatusContributorsChanges(projectId, callsign) {
44
+ return getStatusContributorsChanges(this.telemetryService, projectId, callsign);
45
+ }
42
46
  /** Per-service connectivity plus the aggregate verdict, polled. */
43
47
  getConnectivityChanges(projectId, callsign, opts) {
44
48
  return getConnectivityChanges(this.connectivityDeps(projectId, callsign, opts), projectId, callsign, opts?.expectedServices, opts?.connectivityPollMs);
@@ -85,6 +89,7 @@ export class RobotStatusService extends BaseServiceAbstract {
85
89
  baseUrl: this.config.url,
86
90
  insecure: this.config.insecure,
87
91
  telemetryLiveness$: getTelemetryLiveness(this.telemetryService, projectId, callsign, opts?.heartbeatTimeoutMs),
92
+ extraServices: opts?.extraServices,
88
93
  };
89
94
  }
90
95
  }
@@ -1,7 +1,7 @@
1
1
  import { SubscriberStatusEnum, } from '../models';
2
2
  import { Subject } from 'rxjs';
3
3
  import { BaseStreamService } from './BaseStreamService';
4
- import { IDENTIFIER_NAME_FILE_ACCESSOR } from '../constants/identifier';
4
+ import { IDENTIFIER_NAME_SEARCH } from '../constants/identifier';
5
5
  import { SearchStream } from '../api/streams/search/SearchStream';
6
6
  import { finalize } from 'rxjs/operators';
7
7
  export class SearchService extends BaseStreamService {
@@ -36,7 +36,7 @@ export class SearchService extends BaseStreamService {
36
36
  return stream.deleteProjectData(params);
37
37
  }
38
38
  async createStream() {
39
- const newStream = await this.createStreamFromConfig(IDENTIFIER_NAME_FILE_ACCESSOR, {
39
+ const newStream = await this.createStreamFromConfig(IDENTIFIER_NAME_SEARCH, {
40
40
  url: this.config.url,
41
41
  token: this.config.token,
42
42
  scope: 'default',
@@ -1,4 +1,4 @@
1
- import { API_GRAPHS_ASSETS_UPLOAD_URL, API_GRAPHS_ASSETS_URL, API_GRAPHS_TARGETS_URL, API_GRAPHS_TARGET_UPLOAD_URL, } from '../constants/api';
1
+ import { API_GRAPHS_ASSETS_UPLOAD_URL, API_GRAPHS_ASSETS_URL, API_GRAPHS_TARGETS_URL, API_GRAPHS_TARGET_MEDIA_URL, API_GRAPHS_TARGET_UPLOAD_URL, } from '../constants/api';
2
2
  import { RocosError, errorCodes } from '../models';
3
3
  import { BaseServiceAbstract } from './BaseServiceAbstract';
4
4
  import { RocosLogger } from '../logger/RocosLogger';
@@ -53,10 +53,10 @@ export class TargetService extends BaseServiceAbstract {
53
53
  });
54
54
  }
55
55
  async addMedia(projectId, targetId, media) {
56
- await this.callPost(formatServiceUrl(API_GRAPHS_TARGETS_URL, {
56
+ await this.callPut(formatServiceUrl(API_GRAPHS_TARGET_MEDIA_URL, {
57
57
  url: this.config.url,
58
58
  projectId,
59
- targetId,
59
+ targetId: encodeURIComponent(targetId),
60
60
  }, this.config.insecure), media, 'Failed to add media to a target');
61
61
  }
62
62
  }
@@ -3,7 +3,6 @@ import { CallsignStatus, IRocosSDKConfig, IRocosTelemetryMessage, IStreamStatusM
3
3
  import { BaseStreamService } from './BaseStreamService';
4
4
  export declare class TelemetryService extends BaseStreamService<ITelemetryStream, ITelemetryStreamConfig> {
5
5
  private static grpcWebDeprecationWarned;
6
- private statusSubscription;
7
6
  private changeStream$;
8
7
  private streamStatusSubject$;
9
8
  constructor(config: IRocosSDKConfig);
@@ -46,6 +45,6 @@ export declare class TelemetryService extends BaseStreamService<ITelemetryStream
46
45
  monitorTelemetryWithTimeout(projectId: string, callsign: string, source: string, heartbeatTimeoutMs?: number, intervalMs?: number): Observable<TelemetryMonitorStatus>;
47
46
  protected initStream(stream: ITelemetryStream): Promise<void>;
48
47
  private buildScope;
49
- private createStream;
48
+ private buildStreamConfig;
50
49
  protected getStream(config: ITelemetryStreamConfig): ITelemetryStream;
51
50
  }