@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.
- 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/models/gamepad/GamepadConfig.d.ts +26 -0
- package/cjs/models/gamepad/GamepadConfig.js +11 -1
- 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/models/gamepad/GamepadConfig.d.ts +26 -0
- package/esm/models/gamepad/GamepadConfig.js +10 -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 +8 -1
|
@@ -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>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.GamepadCommandVersion = exports.GamepadTriggerType = exports.GamepadButtonType = void 0;
|
|
3
|
+
exports.GamepadCommandVersion = exports.GamepadTriggerType = exports.MODIFIER_RELEASE_FRACTION = exports.DEFAULT_MODIFIER_THRESHOLD = exports.GamepadButtonType = void 0;
|
|
4
4
|
// Ref: https://github.com/alaingilbert/GamepadJs
|
|
5
5
|
var GamepadButtonType;
|
|
6
6
|
(function (GamepadButtonType) {
|
|
@@ -33,6 +33,16 @@ var GamepadButtonType;
|
|
|
33
33
|
GamepadButtonType[GamepadButtonType["SQUARE"] = 26] = "SQUARE";
|
|
34
34
|
GamepadButtonType[GamepadButtonType["TRIANGLE"] = 27] = "TRIANGLE";
|
|
35
35
|
})(GamepadButtonType || (exports.GamepadButtonType = GamepadButtonType = {}));
|
|
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
|
+
exports.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
|
+
exports.MODIFIER_RELEASE_FRACTION = 0.7;
|
|
36
46
|
var GamepadTriggerType;
|
|
37
47
|
(function (GamepadTriggerType) {
|
|
38
48
|
GamepadTriggerType["ONCE"] = "once";
|
|
@@ -99,7 +99,12 @@ class AssetStorageService extends BaseServiceAbstract_1.BaseServiceAbstract {
|
|
|
99
99
|
* @param signedURL
|
|
100
100
|
*/
|
|
101
101
|
async listFlowAssets(projectId, flowId, flowInstance, parent = '/', signedURL = false) {
|
|
102
|
-
return this.callGet((0, formatServiceUrl_1.formatServiceUrl)(api_1.API_PROJECT_FLOW_ASSET_PATH_URL, {
|
|
102
|
+
return this.callGet((0, formatServiceUrl_1.formatServiceUrl)(api_1.API_PROJECT_FLOW_ASSET_PATH_URL, {
|
|
103
|
+
url: this.config.url,
|
|
104
|
+
projectId,
|
|
105
|
+
flowId: encodeURIComponent(flowId),
|
|
106
|
+
flowInstance: encodeURIComponent(flowInstance),
|
|
107
|
+
}, this.config.insecure), `Failed to get asset for ${projectId}, flow ${flowId}, flow instance ${flowInstance}.`, { parent, signedURL });
|
|
103
108
|
}
|
|
104
109
|
/**
|
|
105
110
|
* List assets for a mission
|
|
@@ -44,7 +44,7 @@ class BaseServiceAbstract {
|
|
|
44
44
|
requestConfig.body = JSON.stringify(payload);
|
|
45
45
|
requestConfig.headers = { ...requestConfig.headers, 'Content-Type': 'application/json' };
|
|
46
46
|
}
|
|
47
|
-
return this.fetchOrThrow(formattedUrl, requestConfig, config?.responseType);
|
|
47
|
+
return await this.fetchOrThrow(formattedUrl, requestConfig, config?.responseType);
|
|
48
48
|
}
|
|
49
49
|
catch (e) {
|
|
50
50
|
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,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.BaseStreamService = void 0;
|
|
4
|
+
const StreamRegister_1 = require("../api/StreamRegister");
|
|
4
5
|
const models_1 = require("../models");
|
|
5
6
|
const RocosLogger_1 = require("../logger/RocosLogger");
|
|
6
7
|
const RocosStore_1 = require("../store/RocosStore");
|
|
7
|
-
const StreamRegister_1 = require("../api/StreamRegister");
|
|
8
8
|
const rxjs_1 = require("rxjs");
|
|
9
9
|
class BaseStreamService {
|
|
10
10
|
constructor(name, config) {
|
|
@@ -32,40 +32,29 @@ class BaseStreamService {
|
|
|
32
32
|
}
|
|
33
33
|
authService.startTokenRefreshChecker();
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Get-or-create the shared stream for this identifier/scope and take a refcount on it,
|
|
37
|
+
* synchronously. Holders that stop consuming must call the handle's release(); the registry
|
|
38
|
+
* tears the stream down when the last holder releases.
|
|
39
|
+
*/
|
|
40
|
+
acquireStream(identifier, config) {
|
|
36
41
|
const identifierWithScope = StreamRegister_1.StreamRegister.getIdentifier(identifier, config.scope);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
let isNew = false;
|
|
40
|
-
if (!stream) {
|
|
41
|
-
isNew = true;
|
|
42
|
-
stream = this.getStream(config);
|
|
42
|
+
return StreamRegister_1.StreamRegister.getInstance().acquireStream(identifierWithScope, () => {
|
|
43
|
+
const stream = this.getStream(config);
|
|
43
44
|
stream.statusStream$.subscribe((msg) => {
|
|
44
45
|
this.status = msg === models_1.SubscriberStatusEnum.STOPPED || msg === models_1.SubscriberStatusEnum.ALIVE;
|
|
45
46
|
this.status$.next(msg);
|
|
46
47
|
});
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
catch (e) {
|
|
58
|
-
// Evict eagerly, before the error reaches the subscriber, so a synchronous resubscribe
|
|
59
|
-
// can't re-acquire this uninitialised instance.
|
|
60
|
-
streamRegister.removeStream(stream);
|
|
61
|
-
throw e;
|
|
62
|
-
}
|
|
63
|
-
// If a concurrent teardown deregistered this stream during init, init() has opened a receiver
|
|
64
|
-
// on an instance nothing can reach; close it.
|
|
65
|
-
if (streamRegister.getStream(identifierWithScope) !== stream) {
|
|
66
|
-
stream.stopStream();
|
|
67
|
-
}
|
|
68
|
-
}
|
|
48
|
+
return stream;
|
|
49
|
+
}, (stream) => this.initStream(stream));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Acquire-and-init without a paired release: callers of this path hold their refcount for the
|
|
53
|
+
* life of the process, so these streams are only torn down by removeAllStreams.
|
|
54
|
+
*/
|
|
55
|
+
async createStreamFromConfig(identifier, config) {
|
|
56
|
+
const { stream, isNew, ready } = this.acquireStream(identifier, config);
|
|
57
|
+
await ready;
|
|
69
58
|
return { stream, isNew };
|
|
70
59
|
}
|
|
71
60
|
}
|
|
@@ -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
|
/**
|
|
@@ -36,9 +36,13 @@ class EnvironmentService extends BaseServiceAbstract_1.BaseServiceAbstract {
|
|
|
36
36
|
* CAUTION: This will delete the existing environment and create
|
|
37
37
|
* a new one. Think of this as a "reset" for the environment.
|
|
38
38
|
* This will cause ALL edges to other objects (assets, panos, etc) to be removed.
|
|
39
|
+
*
|
|
40
|
+
* @deprecated Prefer {@link update}. The environment has no create endpoint -
|
|
41
|
+
* `PUT /environments` is the only full-replace write, so this method now
|
|
42
|
+
* issues the same request as `update`.
|
|
39
43
|
*/
|
|
40
44
|
async set(projectId, scene) {
|
|
41
|
-
return this.
|
|
45
|
+
return this.callPut((0, formatServiceUrl_1.formatServiceUrl)(api_1.API_PROJECT_ENVIRONMENT_URL, { url: this.config.url, projectId }, this.config.insecure), scene, `Failed to set environment for ${projectId}.`);
|
|
42
46
|
}
|
|
43
47
|
/**
|
|
44
48
|
* 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;
|
|
@@ -55,26 +55,38 @@ class MapService extends BaseServiceAbstract_1.BaseServiceAbstract {
|
|
|
55
55
|
return resp.maps;
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
|
-
* Creates a new map by
|
|
58
|
+
* Creates a new map by folding two existing maps together.
|
|
59
|
+
*
|
|
60
|
+
* `options.name` is the mapId of the new target map and must not already
|
|
61
|
+
* exist. `options.callsign` is accepted for signature compatibility and is
|
|
62
|
+
* not sent: the union target is a cloud map, and deploying it to a robot is
|
|
63
|
+
* {@link deploy}'s job.
|
|
64
|
+
*
|
|
65
|
+
* `options.description` needs a second request - the union endpoint takes no
|
|
66
|
+
* metadata. That request is best-effort: a failure is logged and swallowed,
|
|
67
|
+
* because the map itself already exists by then and retrying `merge` would
|
|
68
|
+
* conflict on `targetMapId`. Call {@link updateMetadata} directly to retry
|
|
69
|
+
* the description.
|
|
70
|
+
*
|
|
59
71
|
* @param projectId Project ID
|
|
60
|
-
* @param maps Array of two map IDs to
|
|
61
|
-
* @param options
|
|
72
|
+
* @param maps Array of two source map IDs to fold together
|
|
73
|
+
* @param options `name` for the new map, optional `description`
|
|
62
74
|
*/
|
|
63
75
|
async merge(projectId, maps, options) {
|
|
64
76
|
if (maps.length !== 2)
|
|
65
77
|
throw new Error('You must provide two maps to merge.');
|
|
66
|
-
|
|
67
|
-
projectID: projectId,
|
|
68
|
-
mapIDLeft: maps[0],
|
|
69
|
-
mapIDRight: maps[1],
|
|
70
|
-
name: options.name,
|
|
71
|
-
description: options.description,
|
|
72
|
-
callsign: options.callsign,
|
|
73
|
-
};
|
|
74
|
-
return this.callPost((0, formatServiceUrl_1.formatServiceUrl)(api_1.API_GRAPHS_MAPS_MERGE_URL, {
|
|
78
|
+
await this.callPost((0, formatServiceUrl_1.formatServiceUrl)(api_1.API_GRAPHS_MAPS_UNION_URL, {
|
|
75
79
|
url: this.config.url,
|
|
76
80
|
projectId,
|
|
77
|
-
}, this.config.insecure),
|
|
81
|
+
}, this.config.insecure), { targetMapId: options.name, sources: [maps[0], maps[1]] }, 'Failed to merge maps.');
|
|
82
|
+
if (options.description) {
|
|
83
|
+
try {
|
|
84
|
+
await this.updateMetadata(projectId, options.name, { description: options.description });
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
this.logger.error(`Map ${options.name} was created but its description could not be set.`, e);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
78
90
|
}
|
|
79
91
|
/** Returns a map in its canonical form
|
|
80
92
|
*
|
|
@@ -39,7 +39,7 @@ class SearchService extends BaseStreamService_1.BaseStreamService {
|
|
|
39
39
|
return stream.deleteProjectData(params);
|
|
40
40
|
}
|
|
41
41
|
async createStream() {
|
|
42
|
-
const newStream = await this.createStreamFromConfig(identifier_1.
|
|
42
|
+
const newStream = await this.createStreamFromConfig(identifier_1.IDENTIFIER_NAME_SEARCH, {
|
|
43
43
|
url: this.config.url,
|
|
44
44
|
token: this.config.token,
|
|
45
45
|
scope: 'default',
|
|
@@ -56,10 +56,10 @@ class TargetService extends BaseServiceAbstract_1.BaseServiceAbstract {
|
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
async addMedia(projectId, targetId, media) {
|
|
59
|
-
await this.
|
|
59
|
+
await this.callPut((0, formatServiceUrl_1.formatServiceUrl)(api_1.API_GRAPHS_TARGET_MEDIA_URL, {
|
|
60
60
|
url: this.config.url,
|
|
61
61
|
projectId,
|
|
62
|
-
targetId,
|
|
62
|
+
targetId: encodeURIComponent(targetId),
|
|
63
63
|
}, this.config.insecure), media, 'Failed to add media to a target');
|
|
64
64
|
}
|
|
65
65
|
}
|
|
@@ -46,6 +46,6 @@ export declare class TelemetryService extends BaseStreamService<ITelemetryStream
|
|
|
46
46
|
monitorTelemetryWithTimeout(projectId: string, callsign: string, source: string, heartbeatTimeoutMs?: number, intervalMs?: number): Observable<TelemetryMonitorStatus>;
|
|
47
47
|
protected initStream(stream: ITelemetryStream): Promise<void>;
|
|
48
48
|
private buildScope;
|
|
49
|
-
private
|
|
49
|
+
private buildStreamConfig;
|
|
50
50
|
protected getStream(config: ITelemetryStreamConfig): ITelemetryStream;
|
|
51
51
|
}
|
|
@@ -55,20 +55,33 @@ class TelemetryService extends BaseStreamService_1.BaseStreamService {
|
|
|
55
55
|
// scope is used as a key to generate unique streams
|
|
56
56
|
// ideally there should be just one concurrent stream opened
|
|
57
57
|
const scope = this.buildScope(params, callsignsLookup);
|
|
58
|
-
const callsigns = callsignsLookup.lookupValue;
|
|
59
58
|
const subscriptionParams = {
|
|
60
59
|
uniqueId: (0, getUniqueId_1.getUniqueId)(params.projectId, callsignsLookup, sources, scope),
|
|
61
|
-
callsigns: callsignsLookup
|
|
60
|
+
callsigns: callsignsLookup.getCallsigns(),
|
|
62
61
|
sources,
|
|
63
62
|
};
|
|
64
63
|
// `defer` acquires the stream only when subscribed; `finalize` releases it on unsubscribe, error
|
|
65
|
-
// and complete — so acquire and release stay balanced on every path.
|
|
64
|
+
// and complete — so acquire and release stay balanced on every path. The registry refcount taken
|
|
65
|
+
// here keeps the stream alive until the finalize, so everything in between is race-free.
|
|
66
66
|
return (0, rxjs_1.defer)(() => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
acquired = stream;
|
|
67
|
+
const { stream, isNew, ready, release } = this.acquireStream(identifier_1.IDENTIFIER_NAME_TELEMETRY, this.buildStreamConfig(params.projectId, callsignsLookup, sources, scope));
|
|
68
|
+
try {
|
|
70
69
|
stream.addSubscription(subscriptionParams);
|
|
71
|
-
|
|
70
|
+
if (!isNew) {
|
|
71
|
+
stream.statusStream$.subscribe((msg) => {
|
|
72
|
+
this.streamStatusSubject$.next({
|
|
73
|
+
scope,
|
|
74
|
+
status: msg,
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
// finalize below is not wired up yet, so a throw here must hand the refcount back itself.
|
|
81
|
+
release();
|
|
82
|
+
throw e;
|
|
83
|
+
}
|
|
84
|
+
const messages$ = ready.then(() => {
|
|
72
85
|
if (!this.statusSubscription) {
|
|
73
86
|
this.statusSubscription = stream.statusStream$.subscribe((msg) => {
|
|
74
87
|
this.status = msg === models_1.SubscriberStatusEnum.STOPPED || msg === models_1.SubscriberStatusEnum.ALIVE;
|
|
@@ -77,9 +90,12 @@ class TelemetryService extends BaseStreamService_1.BaseStreamService {
|
|
|
77
90
|
}
|
|
78
91
|
return (0, rxjs_1.from)(stream.messageStream$);
|
|
79
92
|
});
|
|
80
|
-
return (0, rxjs_1.from)(
|
|
81
|
-
return
|
|
82
|
-
}), (0, operators_1.finalize)(() =>
|
|
93
|
+
return (0, rxjs_1.from)(messages$).pipe((0, rxjs_1.mergeAll)(), (0, operators_1.filter)((message) => {
|
|
94
|
+
return callsignsLookup.includesCallsign(message.callsign) && sources.includes(message.source);
|
|
95
|
+
}), (0, operators_1.finalize)(() => {
|
|
96
|
+
void stream.removeSubscription(subscriptionParams, params.terminateReceiverGroupOnUnsubscribe);
|
|
97
|
+
release();
|
|
98
|
+
}));
|
|
83
99
|
});
|
|
84
100
|
}
|
|
85
101
|
/**
|
|
@@ -159,8 +175,8 @@ class TelemetryService extends BaseStreamService_1.BaseStreamService {
|
|
|
159
175
|
return `${params.projectId}-default-${(0, flattenCallsignsLookup_1.flattenCallsignsLookup)(callsignsLookup).join()}`;
|
|
160
176
|
return `${params.projectId}-default`;
|
|
161
177
|
}
|
|
162
|
-
|
|
163
|
-
|
|
178
|
+
buildStreamConfig(projectId, callsignsLookup, sources, scope) {
|
|
179
|
+
return {
|
|
164
180
|
url: this.config.url,
|
|
165
181
|
projectId,
|
|
166
182
|
callsignsLookup,
|
|
@@ -172,16 +188,7 @@ class TelemetryService extends BaseStreamService_1.BaseStreamService {
|
|
|
172
188
|
insecure: this.config.insecure,
|
|
173
189
|
transport: this.config.transport,
|
|
174
190
|
fetch: this.config.fetch,
|
|
175
|
-
}
|
|
176
|
-
if (!newStream.isNew) {
|
|
177
|
-
newStream.stream.statusStream$.subscribe((msg) => {
|
|
178
|
-
this.streamStatusSubject$.next({
|
|
179
|
-
scope,
|
|
180
|
-
status: msg,
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
return newStream.stream;
|
|
191
|
+
};
|
|
185
192
|
}
|
|
186
193
|
getStream(config) {
|
|
187
194
|
if (config.transport === 'grpc-web') {
|
|
@@ -37,11 +37,9 @@ class WebRTCSignallingService {
|
|
|
37
37
|
}
|
|
38
38
|
async createSignallingStream() {
|
|
39
39
|
const scope = 'default';
|
|
40
|
-
const identifier = StreamRegister_1.StreamRegister.getIdentifier(identifier_1.
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
if (!stream) {
|
|
44
|
-
stream = this.getStream({
|
|
40
|
+
const identifier = StreamRegister_1.StreamRegister.getIdentifier(identifier_1.IDENTIFIER_NAME_WEBRTC_SIGNALLING, scope);
|
|
41
|
+
const { stream, ready } = StreamRegister_1.StreamRegister.getInstance().acquireStream(identifier, () => {
|
|
42
|
+
const newStream = this.getStream({
|
|
45
43
|
url: this.config.url,
|
|
46
44
|
token: this.config.token,
|
|
47
45
|
scope,
|
|
@@ -49,13 +47,13 @@ class WebRTCSignallingService {
|
|
|
49
47
|
port: this.config.port,
|
|
50
48
|
insecure: this.config.insecure,
|
|
51
49
|
});
|
|
52
|
-
|
|
50
|
+
newStream.statusStream$.subscribe((msg) => {
|
|
53
51
|
this.status = msg === SubscriberStatusEnum_1.SubscriberStatusEnum.STOPPED || msg === SubscriberStatusEnum_1.SubscriberStatusEnum.ALIVE;
|
|
54
52
|
this.status$.next(msg);
|
|
55
53
|
});
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
return newStream;
|
|
55
|
+
}, (newStream) => this.initStream(newStream));
|
|
56
|
+
await ready;
|
|
59
57
|
return stream;
|
|
60
58
|
}
|
|
61
59
|
async initStream(stream) {
|
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
import { IBaseStream } from '../models';
|
|
2
|
+
export interface IAcquiredStream<T extends IBaseStream> {
|
|
3
|
+
stream: T;
|
|
4
|
+
isNew: boolean;
|
|
5
|
+
/** Settles when the stream's one-time init completes; rejects (and evicts) if init fails. */
|
|
6
|
+
ready: Promise<void>;
|
|
7
|
+
/** Releases this holder's refcount. One-shot: extra calls are ignored. */
|
|
8
|
+
release: () => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Sole owner of stream lifecycle. Streams are shared per identifier and refcounted: acquireStream
|
|
12
|
+
* gets-or-creates and increments in one synchronous step, and the returned handle's release()
|
|
13
|
+
* tears the stream down when the last holder leaves. Teardown never happens outside
|
|
14
|
+
* release/removeAllStreams, so a held stream can not be stopped or replaced underneath its holders.
|
|
15
|
+
*/
|
|
2
16
|
export declare class StreamRegister {
|
|
3
|
-
private
|
|
17
|
+
private entries;
|
|
4
18
|
private logger;
|
|
5
19
|
private static instance;
|
|
6
20
|
private constructor();
|
|
7
21
|
static getInstance(): StreamRegister;
|
|
8
22
|
static getIdentifier($identifier: string, scope?: string): string;
|
|
9
|
-
|
|
23
|
+
acquireStream<T extends IBaseStream>(identifier: string, create: () => T, init: (stream: T) => Promise<void>): IAcquiredStream<T>;
|
|
10
24
|
getStream(identifier: string): IBaseStream | undefined;
|
|
11
|
-
removeStream(stream: IBaseStream): void;
|
|
12
25
|
removeAllStreams(): boolean;
|
|
26
|
+
private release;
|
|
27
|
+
private evict;
|
|
13
28
|
}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { RocosError, errorCodes } from '../models';
|
|
2
2
|
import { RocosLogger } from '../logger/RocosLogger';
|
|
3
|
+
/**
|
|
4
|
+
* Sole owner of stream lifecycle. Streams are shared per identifier and refcounted: acquireStream
|
|
5
|
+
* gets-or-creates and increments in one synchronous step, and the returned handle's release()
|
|
6
|
+
* tears the stream down when the last holder leaves. Teardown never happens outside
|
|
7
|
+
* release/removeAllStreams, so a held stream can not be stopped or replaced underneath its holders.
|
|
8
|
+
*/
|
|
3
9
|
export class StreamRegister {
|
|
4
10
|
constructor() {
|
|
5
|
-
this.
|
|
11
|
+
this.entries = new Map();
|
|
6
12
|
this.logger = RocosLogger.getInstance('StreamRegister');
|
|
7
13
|
}
|
|
8
14
|
static getInstance() {
|
|
@@ -14,55 +20,83 @@ export class StreamRegister {
|
|
|
14
20
|
static getIdentifier($identifier, scope) {
|
|
15
21
|
return `${$identifier}-${scope ?? ''}`;
|
|
16
22
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
existing.stopStream();
|
|
23
|
-
}
|
|
24
|
-
this.teleStreams.set(stream.identifier, stream);
|
|
23
|
+
acquireStream(identifier, create, init) {
|
|
24
|
+
let entry = this.entries.get(identifier);
|
|
25
|
+
let isNew = false;
|
|
26
|
+
if (entry) {
|
|
27
|
+
entry.refCount++;
|
|
25
28
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
else {
|
|
30
|
+
isNew = true;
|
|
31
|
+
const stream = create();
|
|
32
|
+
const ready = init(stream).catch((error) => {
|
|
33
|
+
this.logger.error(`Stream init failed, evicting. identifier: ${identifier}`, error);
|
|
34
|
+
try {
|
|
35
|
+
this.evict(identifier, stream);
|
|
36
|
+
}
|
|
37
|
+
catch (evictionError) {
|
|
38
|
+
this.logger.error(`Failed to evict after init failure. identifier: ${identifier}`, evictionError);
|
|
39
|
+
}
|
|
40
|
+
throw error;
|
|
41
|
+
});
|
|
42
|
+
// `ready` is stored and shared; this discarded catch pins a rejection observer so an init
|
|
43
|
+
// failure can never surface as an unhandled rejection, while holders still see the rejection.
|
|
44
|
+
void ready.catch(() => undefined);
|
|
45
|
+
entry = { stream, refCount: 1, ready };
|
|
46
|
+
this.entries.set(identifier, entry);
|
|
34
47
|
}
|
|
48
|
+
const held = entry;
|
|
49
|
+
let released = false;
|
|
50
|
+
const release = () => {
|
|
51
|
+
// One-shot so a double release can never consume another holder's refcount.
|
|
52
|
+
if (released)
|
|
53
|
+
return;
|
|
54
|
+
released = true;
|
|
55
|
+
this.release(identifier, held);
|
|
56
|
+
};
|
|
57
|
+
return { stream: entry.stream, isNew, ready: entry.ready, release };
|
|
35
58
|
}
|
|
36
59
|
getStream(identifier) {
|
|
37
|
-
return this.
|
|
60
|
+
return this.entries.get(identifier)?.stream;
|
|
38
61
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// and evicting that would leave subscribers forking duplicate streams for the scope.
|
|
44
|
-
if (this.teleStreams.get(stream.identifier) === stream) {
|
|
45
|
-
this.teleStreams.delete(stream.identifier);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
catch (e) {
|
|
49
|
-
this.logger.error(`Failed to remove stream from list. identifier: ${stream.identifier}`, e);
|
|
50
|
-
if (e instanceof Error || typeof e === 'string') {
|
|
51
|
-
throw new RocosError(e, errorCodes.STREAM_LISTENER_ERROR);
|
|
62
|
+
removeAllStreams() {
|
|
63
|
+
for (const [, entry] of this.entries.entries()) {
|
|
64
|
+
try {
|
|
65
|
+
entry.stream.stopStream();
|
|
52
66
|
}
|
|
53
|
-
|
|
54
|
-
|
|
67
|
+
catch (e) {
|
|
68
|
+
this.logger.error(`error removing stream, ${e}`);
|
|
55
69
|
}
|
|
56
70
|
}
|
|
71
|
+
this.entries.clear();
|
|
72
|
+
return true;
|
|
57
73
|
}
|
|
58
|
-
|
|
74
|
+
release(identifier, entry) {
|
|
75
|
+
if (this.entries.get(identifier) !== entry)
|
|
76
|
+
return; // already evicted, or force-removed
|
|
77
|
+
entry.refCount--;
|
|
78
|
+
if (entry.refCount > 0)
|
|
79
|
+
return;
|
|
80
|
+
// Wait for init to settle before tearing down, so init can never open resources on an already
|
|
81
|
+
// stopped stream. A re-acquire in the meantime revives the entry and cancels the teardown.
|
|
82
|
+
void entry.ready
|
|
83
|
+
.catch(() => undefined)
|
|
84
|
+
.then(() => {
|
|
85
|
+
if (this.entries.get(identifier) === entry && entry.refCount <= 0) {
|
|
86
|
+
this.evict(identifier, entry.stream);
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
.catch((e) => this.logger.error(`Failed to evict stream. identifier: ${identifier}`, e));
|
|
90
|
+
}
|
|
91
|
+
evict(identifier, stream) {
|
|
59
92
|
try {
|
|
60
|
-
|
|
61
|
-
this.
|
|
93
|
+
if (this.entries.get(identifier)?.stream === stream) {
|
|
94
|
+
this.entries.delete(identifier);
|
|
62
95
|
}
|
|
96
|
+
stream.stopStream();
|
|
63
97
|
}
|
|
64
98
|
catch (e) {
|
|
65
|
-
this.logger.error(`
|
|
99
|
+
this.logger.error(`Failed to remove stream from list. identifier: ${identifier}`, e);
|
|
66
100
|
if (e instanceof Error || typeof e === 'string') {
|
|
67
101
|
throw new RocosError(e, errorCodes.STREAM_LISTENER_ERROR);
|
|
68
102
|
}
|
|
@@ -70,6 +104,5 @@ export class StreamRegister {
|
|
|
70
104
|
throw e;
|
|
71
105
|
}
|
|
72
106
|
}
|
|
73
|
-
return true;
|
|
74
107
|
}
|
|
75
108
|
}
|
|
@@ -35,7 +35,7 @@ export class StreamHeartbeat {
|
|
|
35
35
|
this.callback = callback;
|
|
36
36
|
this.timeout = timeout;
|
|
37
37
|
this.logger.info('Creating stream heartbeat');
|
|
38
|
-
this.interval = setInterval(this.callback, this.timeout);
|
|
38
|
+
this.interval = setInterval(() => this.callback?.(), this.timeout);
|
|
39
39
|
}
|
|
40
40
|
else {
|
|
41
41
|
this.logger.warn('Stream heartbeat already exists');
|
|
@@ -55,7 +55,7 @@ export class StreamHeartbeat {
|
|
|
55
55
|
// if the health check has not started yet, we initialise it
|
|
56
56
|
if (!this.healthInterval) {
|
|
57
57
|
this.logger.debug('Starting health check timer');
|
|
58
|
-
this.healthInterval = setInterval(this.checkHealth, this.healthTimeout);
|
|
58
|
+
this.healthInterval = setInterval(() => void this.checkHealth(), this.healthTimeout);
|
|
59
59
|
this.healthLastSeenDate = new Date();
|
|
60
60
|
this.healthMisses = 0;
|
|
61
61
|
}
|