@dronedeploy/rocos-js-sdk 4.4.4 → 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.
- package/LICENSE +3 -0
- package/cjs/api/StreamRegister.d.ts +18 -3
- package/cjs/api/StreamRegister.js +71 -38
- package/cjs/api/atoms/StreamHeartbeat.js +2 -2
- package/cjs/api/streams/telemetry/TelemetryStreamAbstract.d.ts +8 -1
- package/cjs/api/streams/telemetry/TelemetryStreamAbstract.js +83 -40
- package/cjs/api/streams/webRTCSignalling/WebRTCSignallingStreamAbstract.js +1 -1
- package/cjs/constants/api.d.ts +3 -2
- package/cjs/constants/api.js +5 -4
- package/cjs/constants/identifier.d.ts +1 -0
- package/cjs/constants/identifier.js +2 -1
- package/cjs/helpers/getUniqueId.js +2 -1
- package/cjs/models/callsigns/CallsignsLookup.d.ts +4 -0
- package/cjs/models/callsigns/CallsignsLookup.js +19 -0
- package/cjs/services/AssetStorageService.js +6 -1
- package/cjs/services/BaseServiceAbstract.js +1 -1
- package/cjs/services/BaseStreamService.d.ts +12 -1
- package/cjs/services/BaseStreamService.js +19 -30
- package/cjs/services/EnvironmentService.d.ts +4 -0
- package/cjs/services/EnvironmentService.js +5 -1
- package/cjs/services/MapService.d.ts +15 -3
- package/cjs/services/MapService.js +25 -13
- package/cjs/services/SearchService.js +1 -1
- package/cjs/services/TargetService.js +2 -2
- package/cjs/services/TelemetryService.d.ts +1 -1
- package/cjs/services/TelemetryService.js +29 -22
- package/cjs/services/WebRTCSignallingService.js +7 -9
- package/esm/api/StreamRegister.d.ts +18 -3
- package/esm/api/StreamRegister.js +71 -38
- package/esm/api/atoms/StreamHeartbeat.js +2 -2
- package/esm/api/streams/telemetry/TelemetryStreamAbstract.d.ts +8 -1
- package/esm/api/streams/telemetry/TelemetryStreamAbstract.js +84 -41
- package/esm/api/streams/webRTCSignalling/WebRTCSignallingStreamAbstract.js +2 -2
- package/esm/constants/api.d.ts +3 -2
- package/esm/constants/api.js +3 -2
- package/esm/constants/identifier.d.ts +1 -0
- package/esm/constants/identifier.js +1 -0
- package/esm/helpers/getUniqueId.js +2 -1
- package/esm/models/callsigns/CallsignsLookup.d.ts +4 -0
- package/esm/models/callsigns/CallsignsLookup.js +19 -0
- package/esm/services/AssetStorageService.js +6 -1
- package/esm/services/BaseServiceAbstract.js +1 -1
- package/esm/services/BaseStreamService.d.ts +12 -1
- package/esm/services/BaseStreamService.js +19 -30
- package/esm/services/EnvironmentService.d.ts +4 -0
- package/esm/services/EnvironmentService.js +5 -1
- package/esm/services/MapService.d.ts +15 -3
- package/esm/services/MapService.js +26 -14
- package/esm/services/SearchService.js +2 -2
- package/esm/services/TargetService.js +3 -3
- package/esm/services/TelemetryService.d.ts +1 -1
- package/esm/services/TelemetryService.js +30 -23
- package/esm/services/WebRTCSignallingService.js +8 -10
- package/package.json +1 -1
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { BehaviorSubject, Subject } from 'rxjs';
|
|
2
|
-
import { CallsignsLookup,
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|
115
|
+
const isQuery = this.isQueryLookup();
|
|
116
|
+
const before = isQuery ? {} : this.getSubscriptions();
|
|
101
117
|
this.subscriptions.delete(params.uniqueId);
|
|
102
|
-
const after = this.getSubscriptions();
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
this.
|
|
106
|
-
this.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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 (
|
|
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.
|
|
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
|
-
|
|
242
|
-
|
|
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
|
-
|
|
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:
|
|
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.
|
|
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 {
|
|
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(
|
|
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);
|
package/esm/constants/api.d.ts
CHANGED
|
@@ -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/{
|
|
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
|
|
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";
|
package/esm/constants/api.js
CHANGED
|
@@ -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/{
|
|
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
|
|
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
|
-
|
|
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();
|
|
@@ -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, {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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.
|
|
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
|
|
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
|
|
42
|
-
* @param options
|
|
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,
|
|
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
|
|
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
|
|
58
|
-
* @param options
|
|
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
|
-
|
|
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),
|
|
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,7 +1,7 @@
|
|
|
1
1
|
import { SubscriberStatusEnum, } from '../models';
|
|
2
2
|
import { Subject } from 'rxjs';
|
|
3
3
|
import { BaseStreamService } from './BaseStreamService';
|
|
4
|
-
import {
|
|
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(
|
|
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',
|