@dronedeploy/rocos-js-sdk 4.4.3 → 4.4.5

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 (58) 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 +83 -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/gamepad/GamepadConfig.d.ts +26 -0
  16. package/cjs/models/gamepad/GamepadConfig.js +11 -1
  17. package/cjs/services/AssetStorageService.js +6 -1
  18. package/cjs/services/BaseServiceAbstract.js +1 -1
  19. package/cjs/services/BaseStreamService.d.ts +12 -1
  20. package/cjs/services/BaseStreamService.js +19 -30
  21. package/cjs/services/EnvironmentService.d.ts +4 -0
  22. package/cjs/services/EnvironmentService.js +5 -1
  23. package/cjs/services/MapService.d.ts +15 -3
  24. package/cjs/services/MapService.js +25 -13
  25. package/cjs/services/SearchService.js +1 -1
  26. package/cjs/services/TargetService.js +2 -2
  27. package/cjs/services/TelemetryService.d.ts +1 -1
  28. package/cjs/services/TelemetryService.js +29 -22
  29. package/cjs/services/WebRTCSignallingService.js +7 -9
  30. package/esm/api/StreamRegister.d.ts +18 -3
  31. package/esm/api/StreamRegister.js +71 -38
  32. package/esm/api/atoms/StreamHeartbeat.js +2 -2
  33. package/esm/api/streams/telemetry/TelemetryStreamAbstract.d.ts +8 -1
  34. package/esm/api/streams/telemetry/TelemetryStreamAbstract.js +84 -41
  35. package/esm/api/streams/webRTCSignalling/WebRTCSignallingStreamAbstract.js +2 -2
  36. package/esm/constants/api.d.ts +3 -2
  37. package/esm/constants/api.js +3 -2
  38. package/esm/constants/identifier.d.ts +1 -0
  39. package/esm/constants/identifier.js +1 -0
  40. package/esm/helpers/getUniqueId.js +2 -1
  41. package/esm/models/callsigns/CallsignsLookup.d.ts +4 -0
  42. package/esm/models/callsigns/CallsignsLookup.js +19 -0
  43. package/esm/models/gamepad/GamepadConfig.d.ts +26 -0
  44. package/esm/models/gamepad/GamepadConfig.js +10 -0
  45. package/esm/services/AssetStorageService.js +6 -1
  46. package/esm/services/BaseServiceAbstract.js +1 -1
  47. package/esm/services/BaseStreamService.d.ts +12 -1
  48. package/esm/services/BaseStreamService.js +19 -30
  49. package/esm/services/EnvironmentService.d.ts +4 -0
  50. package/esm/services/EnvironmentService.js +5 -1
  51. package/esm/services/MapService.d.ts +15 -3
  52. package/esm/services/MapService.js +26 -14
  53. package/esm/services/SearchService.js +2 -2
  54. package/esm/services/TargetService.js +3 -3
  55. package/esm/services/TelemetryService.d.ts +1 -1
  56. package/esm/services/TelemetryService.js +30 -23
  57. package/esm/services/WebRTCSignallingService.js +8 -10
  58. package/package.json +8 -1
@@ -14,6 +14,7 @@ export declare abstract class TelemetryStreamAbstract implements ITelemetryStrea
14
14
  protected token?: string;
15
15
  private scope;
16
16
  protected url: string;
17
+ private telemetryActionQueue;
17
18
  private timerIntervalInSec;
18
19
  protected subscriberStatus: SubscriberStatusEnum;
19
20
  private checkerStartedAt?;
@@ -40,10 +41,17 @@ export declare abstract class TelemetryStreamAbstract implements ITelemetryStrea
40
41
  stopStream(): void;
41
42
  addSubscription(params: ITelemetryParams): void;
42
43
  removeSubscription(params: ITelemetryParams, terminateReceiverGroup?: boolean): Promise<void>;
44
+ private isQueryLookup;
45
+ private getSubscribedSources;
43
46
  sendAcknowledgment(uid: string, status: TelemetryAckStatus, noRetry: boolean): boolean;
44
47
  private getSubscriptions;
45
48
  protected onData(message: TelemetryStreamMessage, isStream: true): void;
46
49
  protected onData(message: TelemetryMessage, isStream: false): void;
50
+ /**
51
+ * Gateway subscription state is a set mutated by deltas, so ordering is correctness: a concurrent
52
+ * unsubscribe landing last strands a subscribe and nothing re-adds it.
53
+ */
54
+ private enqueueTelemetryAction;
47
55
  private takeTelemetryAction;
48
56
  protected listenMessagesAndRenew(): void;
49
57
  private addTerminateToAction;
@@ -52,7 +60,6 @@ export declare abstract class TelemetryStreamAbstract implements ITelemetryStrea
52
60
  * Auto resubscribe to reduce CPU usage.
53
61
  */
54
62
  private autoResubscribe;
55
- private isFoundCallsign;
56
63
  private isFoundSource;
57
64
  private isRegisteredMessage;
58
65
  private updateReceivedDataStatsWithMessage;
@@ -1,16 +1,18 @@
1
1
  import { BehaviorSubject, Subject } from 'rxjs';
2
- import { CallsignsLookup, CallsignsLookupType, RocosTelemetryMessage, StreamOptions, SubscriberStatusEnum, } from '../../../models';
2
+ import { CallsignsLookup, RocosTelemetryMessage, StreamOptions, SubscriberStatusEnum, } from '../../../models';
3
3
  import { GRPC_SOURCE_NOOP, GRPC_SOURCE_SUBSCRIBED } from '../../../constants/grpc';
4
4
  import { QueryOrPredicate, TelemetryAckStatus, TelemetryQueryRequest, TelemetryRequest, UnsubscribeOperation, } from '../../../grpc/teletubby_pb';
5
5
  import { IDENTIFIER_NAME_TELEMETRY } from '../../../constants/identifier';
6
6
  import { RocosStore } from '../../../store/RocosStore';
7
7
  import { StreamHeartbeat } from '../../atoms/StreamHeartbeat';
8
8
  import { StreamRegister } from '../../StreamRegister';
9
+ import { arrayRemove } from '../../../helpers/arrayRemove';
9
10
  import { arrayUnique } from '../../../helpers/arrayUnique';
10
11
  import { filter } from 'rxjs/operators';
11
12
  import { getSubscriptionsDifference } from '../../../helpers/getSubscriptionsDifference';
12
13
  export class TelemetryStreamAbstract {
13
14
  constructor(config) {
15
+ this.telemetryActionQueue = Promise.resolve();
14
16
  // /////////////////
15
17
  // Subscriber Check
16
18
  this.timerIntervalInSec = 1;
@@ -61,23 +63,36 @@ export class TelemetryStreamAbstract {
61
63
  addSubscription(params) {
62
64
  this.logger.info('Adding subscriptions from stream', params.uniqueId);
63
65
  if (!this.subscriptions.has(params.uniqueId)) {
66
+ const isQuery = this.isQueryLookup();
64
67
  // get subscriptions before change
65
- const before = this.getSubscriptions();
68
+ const before = isQuery ? {} : this.getSubscriptions();
66
69
  this.subscriptions.set(params.uniqueId, {
67
70
  ...params,
68
71
  count: 1, // set the count to one just in case it's set in param
69
72
  });
70
73
  // get subscriptions after change
71
- const after = this.getSubscriptions();
72
- // assign the current values from state after change
73
- this.callsignsLookup = new CallsignsLookup(Object.keys(after));
74
- this.sources = arrayUnique(Object.values(after).reduce((a, v) => a.concat(v), []));
75
- // compare before and after subscriptions and only add the difference
76
- const { toAdd } = getSubscriptionsDifference(before, after);
77
- // only subscribe if the subscriberId exists, otherwise the messages will be subscribed on lazy load
78
- if (toAdd.callsigns.length && toAdd.sources.length) {
79
- this.logger.debug('New subscriptions added, we need to refresh sources', { toAdd });
80
- void this.takeTelemetryAction('subscribe', new CallsignsLookup(toAdd.callsigns), toAdd.sources);
74
+ const after = isQuery ? {} : this.getSubscriptions();
75
+ if (isQuery) {
76
+ // A query stream selects its callsigns server-side, so the callsign-keyed view above holds
77
+ // nothing for it: keep the query and diff on sources alone.
78
+ const sourcesBefore = this.sources;
79
+ this.sources = this.getSubscribedSources();
80
+ const toAdd = arrayRemove(this.sources, sourcesBefore);
81
+ if (toAdd.length) {
82
+ this.logger.debug('New query subscriptions added, we need to refresh sources', { toAdd });
83
+ void this.enqueueTelemetryAction('subscribe', this.callsignsLookup, toAdd);
84
+ }
85
+ }
86
+ else {
87
+ // assign the current values from state after change
88
+ this.callsignsLookup = new CallsignsLookup(Object.keys(after));
89
+ this.sources = arrayUnique(Object.values(after).reduce((a, v) => a.concat(v), []));
90
+ // compare before and after subscriptions and only add the difference
91
+ const { toAdd } = getSubscriptionsDifference(before, after);
92
+ if (toAdd.callsigns.length && toAdd.sources.length) {
93
+ this.logger.debug('New subscriptions added, we need to refresh sources', { toAdd });
94
+ void this.enqueueTelemetryAction('subscribe', new CallsignsLookup(toAdd.callsigns), toAdd.sources);
95
+ }
81
96
  }
82
97
  }
83
98
  else {
@@ -97,16 +112,24 @@ export class TelemetryStreamAbstract {
97
112
  if (subs.count)
98
113
  return;
99
114
  this.logger.info('Removing subscriptions from stream registry', params.uniqueId);
100
- const before = this.getSubscriptions();
115
+ const isQuery = this.isQueryLookup();
116
+ const before = isQuery ? {} : this.getSubscriptions();
101
117
  this.subscriptions.delete(params.uniqueId);
102
- const after = this.getSubscriptions();
103
- // Do this before we send the telemetry request so that new subscriptions don't get this stream from the register while it is closing
104
- if (!this.subscriptions.size) {
105
- this.logger.info('No subscriptions remaining closing stream', params.uniqueId);
106
- this.stopStream();
107
- // self remove when no subscriptions are left
108
- StreamRegister.getInstance().removeStream(this);
109
- this.logger.info('Stream closed');
118
+ const after = isQuery ? {} : this.getSubscriptions();
119
+ if (isQuery) {
120
+ const sourcesBefore = this.sources;
121
+ this.sources = this.getSubscribedSources();
122
+ const toRemove = arrayRemove(sourcesBefore, this.sources);
123
+ if (toRemove.length) {
124
+ this.logger.debug('Query subscriptions removed, we need to refresh sources', { toRemove });
125
+ try {
126
+ await this.enqueueTelemetryAction('unsubscribe', this.callsignsLookup, toRemove, terminateReceiverGroup);
127
+ }
128
+ catch (err) {
129
+ this.logger.error(`Failed to unsubscribe: ${err}`);
130
+ }
131
+ }
132
+ return;
110
133
  }
111
134
  // assign the current values from state after change
112
135
  this.callsignsLookup = new CallsignsLookup(Object.keys(after));
@@ -115,13 +138,23 @@ export class TelemetryStreamAbstract {
115
138
  if (toRemove.callsigns.length && toRemove.sources.length) {
116
139
  this.logger.debug('Subscriptions removed, we need to refresh sources', { toRemove });
117
140
  try {
118
- await this.takeTelemetryAction('unsubscribe', new CallsignsLookup(toRemove.callsigns), toRemove.sources, terminateReceiverGroup);
141
+ await this.enqueueTelemetryAction('unsubscribe', new CallsignsLookup(toRemove.callsigns), toRemove.sources, terminateReceiverGroup);
119
142
  }
120
143
  catch (err) {
121
144
  this.logger.error(`Failed to unsubscribe: ${err}`);
122
145
  }
123
146
  }
124
147
  }
148
+ isQueryLookup() {
149
+ return this.callsignsLookup?.isQuery() ?? false;
150
+ }
151
+ getSubscribedSources() {
152
+ const sources = [];
153
+ this.subscriptions.forEach((data) => {
154
+ sources.push(...data.sources);
155
+ });
156
+ return arrayUnique(sources);
157
+ }
125
158
  sendAcknowledgment(uid, status, noRetry) {
126
159
  return this.sendAcknowledgmentInternal(uid, status, noRetry);
127
160
  }
@@ -171,8 +204,8 @@ export class TelemetryStreamAbstract {
171
204
  this.subscriberId = subscriberId;
172
205
  rocosMessage.subscriberId = this.subscriberId;
173
206
  this.logger.info('onData', `subscriberId has been updated - ${subscriberId}`, { subscriberId });
174
- void this.takeTelemetryAction('subscribe');
175
- this.heartbeat.start(this.sendHeartbeat, this.sendHeartbeatTime);
207
+ void this.enqueueTelemetryAction('subscribe');
208
+ this.heartbeat.start(() => this.sendHeartbeat(), this.sendHeartbeatTime);
176
209
  isRobotMessage = false;
177
210
  break;
178
211
  }
@@ -208,25 +241,40 @@ export class TelemetryStreamAbstract {
208
241
  });
209
242
  }
210
243
  }
244
+ /**
245
+ * Gateway subscription state is a set mutated by deltas, so ordering is correctness: a concurrent
246
+ * unsubscribe landing last strands a subscribe and nothing re-adds it.
247
+ */
248
+ enqueueTelemetryAction(actionType, callsignsLookup, sources, terminate) {
249
+ const takeAction = () => this.takeTelemetryAction(actionType, callsignsLookup, sources, terminate);
250
+ const run = this.telemetryActionQueue.then(takeAction);
251
+ // The tail only signals whose turn it is, so it must never reject, otherwise one failed op
252
+ // would wedge every op queued behind it. Callers still see the failure through `run`.
253
+ this.telemetryActionQueue = run.then(() => undefined, () => undefined);
254
+ return run;
255
+ }
211
256
  async takeTelemetryAction(actionType, callsignsLookup, sources, terminate) {
212
257
  this.logger.debug('takeTelemetryAction', actionType);
213
- if (!this.subscriberId || !this.projectId) {
214
- this.logger.debug('takeTelemetryAction', actionType, `${actionType} without subscriberId or projectId - message will not send out`, {
215
- subscriberId: this.subscriberId,
216
- projectId: this.projectId,
217
- });
258
+ // A config fault, not a race: this stream can never address a request.
259
+ if (!this.projectId) {
260
+ this.logger.warn('takeTelemetryAction', actionType, `${actionType} without a projectId - dropped`);
261
+ return Promise.resolve();
262
+ }
263
+ if (!this.subscriberId) {
264
+ // Deferred, not lost: `onData` re-asserts `this.sources` when the subscriberId arrives.
265
+ this.logger.debug('takeTelemetryAction', actionType, `${actionType} before the receiver was registered - deferred to the post-registration re-assert`);
218
266
  return Promise.resolve();
219
267
  }
220
268
  const actionSources = sources ?? this.sources;
221
269
  const actionCallsigns = callsignsLookup ?? this.callsignsLookup;
222
- if (this.callsignsLookup.lookupType === CallsignsLookupType.List) {
270
+ if (!actionCallsigns.isQuery()) {
223
271
  // Compose the request message
224
272
  const req = TelemetryRequest.create({
225
273
  subscriberId: this.subscriberId,
226
274
  requestedActions: [
227
275
  {
228
276
  operation: actionType,
229
- callsigns: actionCallsigns.lookupValue,
277
+ callsigns: actionCallsigns.getCallsigns(),
230
278
  sources: actionSources,
231
279
  },
232
280
  ],
@@ -238,12 +286,12 @@ export class TelemetryStreamAbstract {
238
286
  // Send the message to back-end.
239
287
  return this.requestTelemetry(req);
240
288
  }
241
- else if (actionCallsigns.lookupType === CallsignsLookupType.Query) {
242
- const lookupValue = actionCallsigns.lookupValue;
289
+ const query = actionCallsigns.getQuery();
290
+ if (query) {
243
291
  // We don't support nested query at the moment
244
292
  const queryOrPredicatesList = [];
245
293
  // Loop through user provided predicates to construct a telemetry query request
246
- lookupValue.predicates.forEach((callsignsPredicate) => {
294
+ query.predicates.forEach((callsignsPredicate) => {
247
295
  const queryOrPredicate = QueryOrPredicate.create({
248
296
  content: {
249
297
  oneofKind: 'predicate',
@@ -262,7 +310,7 @@ export class TelemetryStreamAbstract {
262
310
  sources: actionSources,
263
311
  operation: actionType,
264
312
  callsignQuery: {
265
- operation: lookupValue.operation.valueOf(),
313
+ operation: query.operation.valueOf(),
266
314
  queryOrPredicates: queryOrPredicatesList,
267
315
  },
268
316
  });
@@ -379,16 +427,11 @@ export class TelemetryStreamAbstract {
379
427
  this.lastRobotMessageReceived = undefined;
380
428
  this.registerReceiver();
381
429
  }
382
- isFoundCallsign(callsign) {
383
- return this.callsignsLookup.lookupType === CallsignsLookupType.List
384
- ? this.callsignsLookup.lookupValue.indexOf(callsign) !== -1
385
- : true; // If callsigns lookup option is query, we always return true
386
- }
387
430
  isFoundSource(source) {
388
431
  return this.sources.indexOf(source) !== -1;
389
432
  }
390
433
  isRegisteredMessage(callsign, source) {
391
- return this.isFoundCallsign(callsign) && this.isFoundSource(source);
434
+ return this.callsignsLookup.includesCallsign(callsign) && this.isFoundSource(source);
392
435
  }
393
436
  updateReceivedDataStatsWithMessage(msg) {
394
437
  const { source } = msg;
@@ -1,7 +1,7 @@
1
1
  import { AddIceCandidateRequest, GetDetailsRequest, OfferRequest, OperatorConnectRequest, } from '../../../grpc/pigeon_pb';
2
2
  import { SubscriberStatusEnum, } from '../../../models';
3
3
  import { BehaviorSubject } from 'rxjs';
4
- import { IDENTIFIER_NAME_COMMAND } from '../../../constants/identifier';
4
+ import { IDENTIFIER_NAME_WEBRTC_SIGNALLING } from '../../../constants/identifier';
5
5
  import { RocosStore } from '../../../store/RocosStore';
6
6
  import { StreamRegister } from '../../StreamRegister';
7
7
  import { filter } from 'rxjs/operators';
@@ -9,7 +9,7 @@ export class WebRTCSignallingStreamAbstract {
9
9
  constructor(config) {
10
10
  this.subscriberStatus = SubscriberStatusEnum.STOPPED;
11
11
  this.scope = config.scope;
12
- this.identifier = StreamRegister.getIdentifier(IDENTIFIER_NAME_COMMAND, this.scope);
12
+ this.identifier = StreamRegister.getIdentifier(IDENTIFIER_NAME_WEBRTC_SIGNALLING, this.scope);
13
13
  this.token = config.token;
14
14
  this.url = config.url;
15
15
  this.statusStream$ = new BehaviorSubject(SubscriberStatusEnum.STOPPED);
@@ -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();
@@ -31,6 +31,18 @@ export declare enum GamepadButtonType {
31
31
  }
32
32
  /** Per-axis tuning keyed by GamepadButtonType. Absent entirely on configs saved before axis tuning existed. */
33
33
  export type GamepadAxisValueMap = Partial<Record<GamepadButtonType, number>>;
34
+ /** Same shape as GamepadAxisValueMap, keyed by any input — a modifier is usually a bumper or trigger, not an axis. */
35
+ export type GamepadInputValueMap = Partial<Record<GamepadButtonType, number>>;
36
+ /**
37
+ * Engage point for an input with no GamepadConfig.modifierThresholds entry. Exported so editor and
38
+ * interpreter agree: a modifier held at 0.5 in one and 0.75 in the other works on screen, not on the robot.
39
+ */
40
+ export declare const DEFAULT_MODIFIER_THRESHOLD = 0.5;
41
+ /**
42
+ * Release point as a fraction of the engage threshold. Releasing at the engage value flaps the layer at
43
+ * frame rate while a trigger rests on the threshold. Exported for the same reason as the threshold.
44
+ */
45
+ export declare const MODIFIER_RELEASE_FRACTION = 0.7;
34
46
  export declare enum GamepadTriggerType {
35
47
  ONCE = "once",
36
48
  CONTINUOUSLY = "continuously"
@@ -54,6 +66,15 @@ export interface GamepadAction {
54
66
  triggerType: GamepadTriggerType;
55
67
  /** Milliseconds between repeats. Only meaningful for `continuously`. */
56
68
  frequency: number;
69
+ /**
70
+ * Inputs that must be held for this action to fire. A set: unordered, and absent or empty is the base
71
+ * layer, eligible whatever is held — which is what leaves pre-modifier configs unaffected.
72
+ *
73
+ * Matched on `type` alone, since `text` is a display label and the same input reaches us as "LB" from
74
+ * one author and "L1" from another. Must not contain the action's own `source`, which the triggering
75
+ * press would satisfy on its own.
76
+ */
77
+ modifiers?: GamepadButtonRef[];
57
78
  }
58
79
  export interface GamepadVariable {
59
80
  name: string;
@@ -80,6 +101,11 @@ export interface GamepadConfig {
80
101
  variables: GamepadVariable[];
81
102
  axisDeadzones?: GamepadAxisValueMap;
82
103
  axisExpos?: GamepadAxisValueMap;
104
+ /**
105
+ * Engage point per modifier input. Profile-level rather than per-action so one trigger cannot engage at
106
+ * two values. Absent entries use DEFAULT_MODIFIER_THRESHOLD.
107
+ */
108
+ modifierThresholds?: GamepadInputValueMap;
83
109
  }
84
110
  /** One stored gamepad config, scoped to a specific physical controller model (GamepadConfig.gamepad). */
85
111
  export type GamepadConfigItem = ConfigGroupItem<GamepadConfig>;
@@ -30,6 +30,16 @@ export var GamepadButtonType;
30
30
  GamepadButtonType[GamepadButtonType["SQUARE"] = 26] = "SQUARE";
31
31
  GamepadButtonType[GamepadButtonType["TRIANGLE"] = 27] = "TRIANGLE";
32
32
  })(GamepadButtonType || (GamepadButtonType = {}));
33
+ /**
34
+ * Engage point for an input with no GamepadConfig.modifierThresholds entry. Exported so editor and
35
+ * interpreter agree: a modifier held at 0.5 in one and 0.75 in the other works on screen, not on the robot.
36
+ */
37
+ export const DEFAULT_MODIFIER_THRESHOLD = 0.5;
38
+ /**
39
+ * Release point as a fraction of the engage threshold. Releasing at the engage value flaps the layer at
40
+ * frame rate while a trigger rests on the threshold. Exported for the same reason as the threshold.
41
+ */
42
+ export const MODIFIER_RELEASE_FRACTION = 0.7;
33
43
  export var GamepadTriggerType;
34
44
  (function (GamepadTriggerType) {
35
45
  GamepadTriggerType["ONCE"] = "once";
@@ -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