@dronedeploy/rocos-js-sdk 4.4.5 → 4.4.7

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 (49) hide show
  1. package/cjs/api/StreamRegister.js +4 -4
  2. package/cjs/api/streams/caller/CallerStreamAbstract.js +2 -1
  3. package/cjs/api/streams/command/CommandStreamAbstract.js +2 -1
  4. package/cjs/api/streams/control/ControlStreamAbstract.js +2 -1
  5. package/cjs/api/streams/fileAccessor/FileAccessorStreamAbstract.js +2 -1
  6. package/cjs/api/streams/search/SearchStreamAbstract.js +2 -1
  7. package/cjs/api/streams/telemetry/TelemetryStream.js +1 -0
  8. package/cjs/api/streams/telemetry/TelemetryStreamAbstract.js +130 -25
  9. package/cjs/api/streams/telemetry/TelemetryStreamConnect.js +1 -0
  10. package/cjs/api/streams/telemetry/TelemetryStreamNode.js +1 -0
  11. package/cjs/api/streams/webRTCSignalling/WebRTCSignallingStreamAbstract.js +2 -1
  12. package/cjs/models/maps/Map.d.ts +1 -1
  13. package/cjs/models/robot-status/RobotStatus.d.ts +23 -0
  14. package/cjs/models/robot-status/RobotStatusEnums.d.ts +18 -6
  15. package/cjs/models/robot-status/RobotStatusEnums.js +18 -6
  16. package/cjs/services/RobotStatusService.d.ts +20 -1
  17. package/cjs/services/RobotStatusService.js +5 -0
  18. package/cjs/services/SearchService.js +2 -7
  19. package/cjs/services/TelemetryService.d.ts +4 -1
  20. package/cjs/services/TelemetryService.js +17 -12
  21. package/cjs/services/robot-status/connectivity.d.ts +16 -12
  22. package/cjs/services/robot-status/connectivity.js +26 -15
  23. package/cjs/services/robot-status/reportedStatus.d.ts +14 -1
  24. package/cjs/services/robot-status/reportedStatus.js +36 -1
  25. package/esm/api/StreamRegister.js +4 -4
  26. package/esm/api/streams/caller/CallerStreamAbstract.js +2 -1
  27. package/esm/api/streams/command/CommandStreamAbstract.js +2 -1
  28. package/esm/api/streams/control/ControlStreamAbstract.js +2 -1
  29. package/esm/api/streams/fileAccessor/FileAccessorStreamAbstract.js +2 -1
  30. package/esm/api/streams/search/SearchStreamAbstract.js +2 -1
  31. package/esm/api/streams/telemetry/TelemetryStream.js +1 -0
  32. package/esm/api/streams/telemetry/TelemetryStreamAbstract.js +131 -26
  33. package/esm/api/streams/telemetry/TelemetryStreamConnect.js +1 -0
  34. package/esm/api/streams/telemetry/TelemetryStreamNode.js +1 -0
  35. package/esm/api/streams/webRTCSignalling/WebRTCSignallingStreamAbstract.js +2 -1
  36. package/esm/models/maps/Map.d.ts +1 -1
  37. package/esm/models/robot-status/RobotStatus.d.ts +23 -0
  38. package/esm/models/robot-status/RobotStatusEnums.d.ts +18 -6
  39. package/esm/models/robot-status/RobotStatusEnums.js +18 -6
  40. package/esm/services/RobotStatusService.d.ts +20 -1
  41. package/esm/services/RobotStatusService.js +6 -1
  42. package/esm/services/SearchService.js +2 -7
  43. package/esm/services/TelemetryService.d.ts +4 -1
  44. package/esm/services/TelemetryService.js +17 -12
  45. package/esm/services/robot-status/connectivity.d.ts +16 -12
  46. package/esm/services/robot-status/connectivity.js +26 -15
  47. package/esm/services/robot-status/reportedStatus.d.ts +14 -1
  48. package/esm/services/robot-status/reportedStatus.js +35 -1
  49. package/package.json +7 -2
@@ -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 = {}));
@@ -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,4 +1,3 @@
1
- import { SubscriberStatusEnum, } from '../models';
2
1
  import { Subject } from 'rxjs';
3
2
  import { BaseStreamService } from './BaseStreamService';
4
3
  import { IDENTIFIER_NAME_SEARCH } from '../constants/identifier';
@@ -44,12 +43,8 @@ export class SearchService extends BaseStreamService {
44
43
  port: this.config.port,
45
44
  insecure: this.config.insecure,
46
45
  });
47
- if (newStream.isNew) {
48
- newStream.stream.statusStream$.subscribe((msg) => {
49
- this.status = msg === SubscriberStatusEnum.STOPPED || msg === SubscriberStatusEnum.ALIVE;
50
- this.status$.next(msg);
51
- });
52
- }
46
+ // The status forwarder is wired once per stream by BaseStreamService.acquireStream's factory
47
+ // (and released now that the stream completes statusStream$ on stop), so no per-service one here.
53
48
  return newStream.stream;
54
49
  }
55
50
  getStream(config) {
@@ -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);
@@ -29,6 +28,10 @@ export declare class TelemetryService extends BaseStreamService<ITelemetryStream
29
28
  getStatusChanges(): Observable<IStreamStatusMessage>;
30
29
  /**
31
30
  * Subscribes to the heartbeat telemetry and checks we receive one at least every 5s
31
+ *
32
+ * @deprecated Superseded by {@link RobotStatusService.getRobotStatusChanges}, which reports the
33
+ * normalised robot status (reported health/readiness plus platform connectivity) rather than only
34
+ * heartbeat-derived online/offline liveness.
32
35
  * @param projectId
33
36
  * @param callsign
34
37
  * @param heartbeatTimeoutMs optionally override the deadline for heartbeats (in ms). Default 5000ms
@@ -64,7 +64,9 @@ export class TelemetryService extends BaseStreamService {
64
64
  const { stream, isNew, ready, release } = this.acquireStream(IDENTIFIER_NAME_TELEMETRY, this.buildStreamConfig(params.projectId, callsignsLookup, sources, scope));
65
65
  try {
66
66
  stream.addSubscription(subscriptionParams);
67
- if (!isNew) {
67
+ // Forward the stream's status onto the per-scope subject once, at creation; it ends when the
68
+ // stream stops (statusStream$ completes), so it is not re-created or leaked across subscribers.
69
+ if (isNew) {
68
70
  stream.statusStream$.subscribe((msg) => {
69
71
  this.streamStatusSubject$.next({
70
72
  scope,
@@ -78,20 +80,19 @@ export class TelemetryService extends BaseStreamService {
78
80
  release();
79
81
  throw e;
80
82
  }
81
- const messages$ = ready.then(() => {
82
- if (!this.statusSubscription) {
83
- this.statusSubscription = stream.statusStream$.subscribe((msg) => {
84
- this.status = msg === SubscriberStatusEnum.STOPPED || msg === SubscriberStatusEnum.ALIVE;
85
- this.status$.next(msg);
86
- });
87
- }
88
- return from(stream.messageStream$);
89
- });
83
+ // status$ / getStatus() are driven by the acquireStream factory (BaseStreamService), not here.
84
+ const messages$ = ready.then(() => from(stream.messageStream$));
90
85
  return from(messages$).pipe(mergeAll(), filter((message) => {
91
86
  return callsignsLookup.includesCallsign(message.callsign) && sources.includes(message.source);
92
87
  }), finalize(() => {
93
- void stream.removeSubscription(subscriptionParams, params.terminateReceiverGroupOnUnsubscribe);
94
- release();
88
+ if (params.terminateReceiverGroupOnUnsubscribe) {
89
+ const removal = stream.removeSubscription(subscriptionParams, true);
90
+ void Promise.resolve(removal).finally(release);
91
+ }
92
+ else {
93
+ release();
94
+ void stream.removeSubscription(subscriptionParams);
95
+ }
95
96
  }));
96
97
  });
97
98
  }
@@ -113,6 +114,10 @@ export class TelemetryService extends BaseStreamService {
113
114
  }
114
115
  /**
115
116
  * Subscribes to the heartbeat telemetry and checks we receive one at least every 5s
117
+ *
118
+ * @deprecated Superseded by {@link RobotStatusService.getRobotStatusChanges}, which reports the
119
+ * normalised robot status (reported health/readiness plus platform connectivity) rather than only
120
+ * heartbeat-derived online/offline liveness.
116
121
  * @param projectId
117
122
  * @param callsign
118
123
  * @param heartbeatTimeoutMs optionally override the deadline for heartbeats (in ms). Default 5000ms
@@ -36,18 +36,19 @@ export declare const buildConnectionsUrl: (serviceId: string, baseUrl: string, p
36
36
  export declare const mapConnectionStatus: (connection?: IConnectedCallsign | null) => ServiceConnection;
37
37
  /**
38
38
  * Aggregates per-service connection states into an overall verdict:
39
- * - `ONLINE` — at least one service connected and none disconnected.
40
- * - `DEGRADED` — a genuine mix: some connected, some disconnected.
39
+ * - `DEGRADED` — any service reports an explicit `FAILED` fault, or a genuine mix (some connected,
40
+ * some disconnected).
41
+ * - `ONLINE` — at least one service connected and none disconnected or failed.
41
42
  * - `OFFLINE` — nothing connected.
42
43
  *
43
- * `UNKNOWN` services (e.g. not yet polled) never force `DEGRADED`, so startup
44
- * settles on `OFFLINE` (gray) rather than a spurious flashing-red degraded state.
44
+ * A `FAILED` service forces `DEGRADED` even when nothing else is connected, so a fault (e.g. a failed
45
+ * deployment) raises attention rather than reading as a plain, switched-off `OFFLINE`.
45
46
  *
46
- * Note: this optimism also means a service held `UNKNOWN` indefinitely (e.g. an
47
- * endpoint whose polls keep failing) does not by itself pull the aggregate below
48
- * `ONLINE` — a deliberate v1 tradeoff to keep transient errors from flapping the
49
- * indicator. Surfacing a long-lived failure distinctly (likely via health rather
50
- * than connectivity) is left as a future refinement.
47
+ * `UNKNOWN` services (e.g. not yet polled) never force `DEGRADED`, so startup settles on `OFFLINE`
48
+ * (gray) rather than a spurious flashing-red degraded state. This optimism also means a service held
49
+ * `UNKNOWN` indefinitely (e.g. an endpoint whose polls keep failing) does not by itself pull the
50
+ * aggregate below `ONLINE` — a deliberate tradeoff to keep transient errors from flapping the
51
+ * indicator; a service that wants to be surfaced reports `FAILED` explicitly.
51
52
  */
52
53
  export declare const aggregateConnectivity: (services: Record<string, ServiceConnection>) => RobotConnectivity;
53
54
  /**
@@ -66,11 +67,14 @@ export interface ConnectivityDeps {
66
67
  insecure?: boolean;
67
68
  /** The telemetry-liveness signal (from `getTelemetryLiveness`), folded in as one service. */
68
69
  telemetryLiveness$: Observable<ServiceConnection>;
70
+ /** Caller-injected connectivity signals, keyed by service id, folded in on equal footing. */
71
+ extraServices?: Record<string, Observable<ServiceConnection>>;
69
72
  }
70
73
  /**
71
74
  * Emits the robot's {@link RobotConnectivityStatus} — the per-service breakdown
72
- * plus the aggregate verdict — for the configured `expectedServices`. HTTP
73
- * services are polled; `telemetry-liveness` uses the supplied observable; any
74
- * unrecognised service id resolves to `UNKNOWN`.
75
+ * plus the aggregate verdict — for the configured `expectedServices`, plus any
76
+ * caller-injected `extraServices` (folded in on equal footing). HTTP services
77
+ * are polled; `telemetry-liveness` and injected services use their supplied
78
+ * observable; any unrecognised service id resolves to `UNKNOWN`.
75
79
  */
76
80
  export declare const getConnectivityChanges: (deps: ConnectivityDeps, projectId: string, callsign: string, expectedServices?: string[], pollMs?: number) => Observable<RobotConnectivityStatus>;
@@ -52,21 +52,24 @@ export const mapConnectionStatus = (connection) => {
52
52
  };
53
53
  /**
54
54
  * Aggregates per-service connection states into an overall verdict:
55
- * - `ONLINE` — at least one service connected and none disconnected.
56
- * - `DEGRADED` — a genuine mix: some connected, some disconnected.
55
+ * - `DEGRADED` — any service reports an explicit `FAILED` fault, or a genuine mix (some connected,
56
+ * some disconnected).
57
+ * - `ONLINE` — at least one service connected and none disconnected or failed.
57
58
  * - `OFFLINE` — nothing connected.
58
59
  *
59
- * `UNKNOWN` services (e.g. not yet polled) never force `DEGRADED`, so startup
60
- * settles on `OFFLINE` (gray) rather than a spurious flashing-red degraded state.
60
+ * A `FAILED` service forces `DEGRADED` even when nothing else is connected, so a fault (e.g. a failed
61
+ * deployment) raises attention rather than reading as a plain, switched-off `OFFLINE`.
61
62
  *
62
- * Note: this optimism also means a service held `UNKNOWN` indefinitely (e.g. an
63
- * endpoint whose polls keep failing) does not by itself pull the aggregate below
64
- * `ONLINE` — a deliberate v1 tradeoff to keep transient errors from flapping the
65
- * indicator. Surfacing a long-lived failure distinctly (likely via health rather
66
- * than connectivity) is left as a future refinement.
63
+ * `UNKNOWN` services (e.g. not yet polled) never force `DEGRADED`, so startup settles on `OFFLINE`
64
+ * (gray) rather than a spurious flashing-red degraded state. This optimism also means a service held
65
+ * `UNKNOWN` indefinitely (e.g. an endpoint whose polls keep failing) does not by itself pull the
66
+ * aggregate below `ONLINE` — a deliberate tradeoff to keep transient errors from flapping the
67
+ * indicator; a service that wants to be surfaced reports `FAILED` explicitly.
67
68
  */
68
69
  export const aggregateConnectivity = (services) => {
69
70
  const values = Object.values(services);
71
+ if (values.includes(ServiceConnection.FAILED))
72
+ return RobotConnectivity.DEGRADED;
70
73
  const hasConnected = values.includes(ServiceConnection.CONNECTED);
71
74
  const hasDisconnected = values.includes(ServiceConnection.DISCONNECTED);
72
75
  if (hasConnected && hasDisconnected)
@@ -99,24 +102,32 @@ const connectivityEquals = (a, b) => {
99
102
  };
100
103
  /**
101
104
  * Emits the robot's {@link RobotConnectivityStatus} — the per-service breakdown
102
- * plus the aggregate verdict — for the configured `expectedServices`. HTTP
103
- * services are polled; `telemetry-liveness` uses the supplied observable; any
104
- * unrecognised service id resolves to `UNKNOWN`.
105
+ * plus the aggregate verdict — for the configured `expectedServices`, plus any
106
+ * caller-injected `extraServices` (folded in on equal footing). HTTP services
107
+ * are polled; `telemetry-liveness` and injected services use their supplied
108
+ * observable; any unrecognised service id resolves to `UNKNOWN`.
105
109
  */
106
110
  export const getConnectivityChanges = (deps, projectId, callsign, expectedServices = DEFAULT_EXPECTED_SERVICES, pollMs = DEFAULT_CONNECTIVITY_POLL_MS) => {
111
+ const extraServices = deps.extraServices ?? {};
112
+ const serviceIds = [...expectedServices, ...Object.keys(extraServices).filter((id) => !expectedServices.includes(id))];
107
113
  // combineLatest([]) completes without emitting; emit an explicit empty/offline status instead.
108
- if (expectedServices.length === 0) {
114
+ if (serviceIds.length === 0) {
109
115
  return of({ overall: RobotConnectivity.OFFLINE, services: {} });
110
116
  }
111
- const perService = expectedServices.map((serviceId) => {
117
+ const perService = serviceIds.map((serviceId) => {
112
118
  let source$;
113
119
  if (serviceId === TELEMETRY_LIVENESS_SERVICE) {
114
120
  source$ = deps.telemetryLiveness$;
115
121
  }
116
- else if (serviceId in CONNECTION_SERVICE_PREFIXES) {
122
+ else if (Object.prototype.hasOwnProperty.call(CONNECTION_SERVICE_PREFIXES, serviceId)) {
117
123
  const url = buildConnectionsUrl(serviceId, deps.baseUrl, projectId, callsign, deps.insecure);
118
124
  source$ = getServiceConnectionChanges(deps.httpGet, url, pollMs);
119
125
  }
126
+ else if (Object.prototype.hasOwnProperty.call(extraServices, serviceId)) {
127
+ // Contain an injected observable's errors as UNKNOWN, mirroring the built-in sources, so a
128
+ // faulty caller signal can't error out combineLatest and tear down the whole aggregate.
129
+ source$ = extraServices[serviceId].pipe(catchError(() => of(ServiceConnection.UNKNOWN)));
130
+ }
120
131
  else {
121
132
  source$ = of(ServiceConnection.UNKNOWN);
122
133
  }
@@ -1,10 +1,12 @@
1
1
  import { Observable } from 'rxjs';
2
- import { RobotHealth, RobotReadiness, ServiceConnection } from '../../models';
2
+ import { RobotHealth, RobotReadiness, RobotStatusContributors, ServiceConnection } from '../../models';
3
3
  import { TelemetryService } from '../TelemetryService';
4
4
  /** The new agent topic carrying health + readiness (replaces the heartbeat). */
5
5
  export declare const ROBOT_STATUS_SOURCE = "/diagnostics/robot/status";
6
6
  /** The legacy heartbeat topic — liveness only, no status payload. */
7
7
  export declare const LEGACY_HEARTBEAT_SOURCE = "/rocos/agent/telemetry/heartbeat";
8
+ /** The per-contributor breakdown behind the reported status (health + readiness). */
9
+ export declare const ROBOT_STATUS_CONTRIBUTORS_SOURCE = "/diagnostics/robot/status/contributors";
8
10
  export interface ReportedRobotStatus {
9
11
  health: RobotHealth;
10
12
  readiness: RobotReadiness;
@@ -28,6 +30,17 @@ export declare const parseRobotReadiness: (value: unknown) => RobotReadiness;
28
30
  * new topic.
29
31
  */
30
32
  export declare const getReportedStatusChanges: (telemetry: TelemetryService, projectId: string, callsign: string) => Observable<ReportedRobotStatus>;
33
+ /**
34
+ * Emits the per-contributor breakdown behind the robot's reported health and
35
+ * readiness, from the agent's `robot/status/contributors` topic. Each
36
+ * contributor's integer wire value is mapped onto {@link RobotHealth} /
37
+ * {@link RobotReadiness}; a missing `health`/`readiness` array becomes `[]`.
38
+ *
39
+ * Starts with an empty breakdown so consumers combining this with other streams
40
+ * emit immediately, and — like {@link getReportedStatusChanges} — a telemetry
41
+ * error falls back to an empty breakdown rather than terminating the stream.
42
+ */
43
+ export declare const getStatusContributorsChanges: (telemetry: TelemetryService, projectId: string, callsign: string) => Observable<RobotStatusContributors>;
31
44
  /**
32
45
  * Emits the `telemetry-liveness` connectivity signal: whether a heartbeat (from
33
46
  * either the new `robot/status` topic or the legacy heartbeat) has been received
@@ -1,9 +1,11 @@
1
1
  import { catchError, combineLatest, distinctUntilChanged, filter, interval, map, merge, of, scan, startWith, } from 'rxjs';
2
- import { RobotHealth, RobotReadiness, ServiceConnection } from '../../models';
2
+ import { RobotHealth, RobotReadiness, ServiceConnection, } from '../../models';
3
3
  /** The new agent topic carrying health + readiness (replaces the heartbeat). */
4
4
  export const ROBOT_STATUS_SOURCE = '/diagnostics/robot/status';
5
5
  /** The legacy heartbeat topic — liveness only, no status payload. */
6
6
  export const LEGACY_HEARTBEAT_SOURCE = '/rocos/agent/telemetry/heartbeat';
7
+ /** The per-contributor breakdown behind the reported status (health + readiness). */
8
+ export const ROBOT_STATUS_CONTRIBUTORS_SOURCE = `${ROBOT_STATUS_SOURCE}/contributors`;
7
9
  const DEFAULT_HEARTBEAT_TIMEOUT_MS = 5000;
8
10
  const DEFAULT_INTERVAL_MS = 2000;
9
11
  /**
@@ -78,6 +80,38 @@ export const getReportedStatusChanges = (telemetry, projectId, callsign) => {
78
80
  // UNKNOWN/UNKNOWN (matching getTelemetryLiveness) so subscribers get an honest value.
79
81
  catchError(() => of({ health: RobotHealth.UNKNOWN, readiness: RobotReadiness.UNKNOWN })));
80
82
  };
83
+ const toContributor = (raw, parse) => ({
84
+ id: raw.id ?? '',
85
+ component: raw.component ?? '',
86
+ reason: raw.reason ?? '',
87
+ status: parse(raw.value),
88
+ });
89
+ const EMPTY_CONTRIBUTORS = { health: [], readiness: [] };
90
+ /**
91
+ * Emits the per-contributor breakdown behind the robot's reported health and
92
+ * readiness, from the agent's `robot/status/contributors` topic. Each
93
+ * contributor's integer wire value is mapped onto {@link RobotHealth} /
94
+ * {@link RobotReadiness}; a missing `health`/`readiness` array becomes `[]`.
95
+ *
96
+ * Starts with an empty breakdown so consumers combining this with other streams
97
+ * emit immediately, and — like {@link getReportedStatusChanges} — a telemetry
98
+ * error falls back to an empty breakdown rather than terminating the stream.
99
+ */
100
+ export const getStatusContributorsChanges = (telemetry, projectId, callsign) => {
101
+ return telemetry
102
+ .subscribe({
103
+ projectId,
104
+ callsigns: [callsign],
105
+ sources: [ROBOT_STATUS_CONTRIBUTORS_SOURCE],
106
+ })
107
+ .pipe(map((message) => {
108
+ const payload = message.payload ?? {};
109
+ return {
110
+ health: (payload.health ?? []).map((c) => toContributor(c, parseRobotHealth)),
111
+ readiness: (payload.readiness ?? []).map((c) => toContributor(c, parseRobotReadiness)),
112
+ };
113
+ }), startWith(EMPTY_CONTRIBUTORS), distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)), catchError(() => of(EMPTY_CONTRIBUTORS)));
114
+ };
81
115
  /**
82
116
  * Emits the `telemetry-liveness` connectivity signal: whether a heartbeat (from
83
117
  * either the new `robot/status` topic or the legacy heartbeat) has been received
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dronedeploy/rocos-js-sdk",
3
- "version": "4.4.5",
3
+ "version": "4.4.7",
4
4
  "description": "Javascript SDK for rocos",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -42,5 +42,10 @@
42
42
  }
43
43
  },
44
44
  "repository": {},
45
- "private": false
45
+ "private": false,
46
+ "overrides": {
47
+ "openapi-typescript": {
48
+ "typescript": "$typescript"
49
+ }
50
+ }
46
51
  }