@camstack/addon-terminal 0.1.69 → 0.1.71
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/dist/addon.js +54 -5
- package/dist/addon.mjs +54 -5
- package/package.json +1 -1
package/dist/addon.js
CHANGED
|
@@ -7644,7 +7644,8 @@ var CameraSwitchAuthoritySchema = discriminatedUnion("kind", [
|
|
|
7644
7644
|
var CameraSwitchUnavailableReasonSchema = _enum([
|
|
7645
7645
|
"no-provider",
|
|
7646
7646
|
"source-unreachable",
|
|
7647
|
-
"not-configured"
|
|
7647
|
+
"not-configured",
|
|
7648
|
+
"device-disabled"
|
|
7648
7649
|
]);
|
|
7649
7650
|
/**
|
|
7650
7651
|
* One switch, resolved for one camera.
|
|
@@ -13061,7 +13062,12 @@ var ConfigEntrySchema = object({
|
|
|
13061
13062
|
value: unknown(),
|
|
13062
13063
|
description: string().optional()
|
|
13063
13064
|
});
|
|
13064
|
-
|
|
13065
|
+
/**
|
|
13066
|
+
* Only `manual` since D356: the operator picks, nothing links itself. The
|
|
13067
|
+
* field survives on the wire because a viewer already OTA'd parses it —
|
|
13068
|
+
* drop it once no shipped client reads `mode`.
|
|
13069
|
+
*/
|
|
13070
|
+
var LinkedDevicesModeSchema = _enum(["manual"]);
|
|
13065
13071
|
/** One resolved linked device — the compact projection consumers need. */
|
|
13066
13072
|
var LinkedDeviceSchema = object({
|
|
13067
13073
|
deviceId: number(),
|
|
@@ -23737,12 +23743,20 @@ var BatteryStatusSchema = object({
|
|
|
23737
23743
|
* Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
|
|
23738
23744
|
* Reolink-specific for the Solar Panel 2 accessory (will become
|
|
23739
23745
|
* common on other battery cams). `'none'` means running on battery
|
|
23740
|
-
* alone.
|
|
23746
|
+
* alone — a NEGATIVE the firmware actually reported.
|
|
23747
|
+
*
|
|
23748
|
+
* `'unknown'` is the absence of an answer: the provider has not read the
|
|
23749
|
+
* power fields yet, or the firmware reported neither. Before it existed,
|
|
23750
|
+
* two unreadable fields produced `'none'`, and a camera nobody had reached
|
|
23751
|
+
* was drawn "on battery alone" — the same D315 defect `percentage` closed
|
|
23752
|
+
* by going nullable. Consumers SKIP it (no charger state published, no
|
|
23753
|
+
* glyph, no HomeKit ChargingState) rather than coerce it either way.
|
|
23741
23754
|
*/
|
|
23742
23755
|
charging: _enum([
|
|
23743
23756
|
"dc",
|
|
23744
23757
|
"solar",
|
|
23745
|
-
"none"
|
|
23758
|
+
"none",
|
|
23759
|
+
"unknown"
|
|
23746
23760
|
]),
|
|
23747
23761
|
/**
|
|
23748
23762
|
* True when the camera firmware has gone into low-power mode. Battery
|
|
@@ -23832,7 +23846,7 @@ onStatusChanged: { data: object({
|
|
|
23832
23846
|
kind: "push",
|
|
23833
23847
|
empty: {
|
|
23834
23848
|
percentage: 0,
|
|
23835
|
-
charging: "
|
|
23849
|
+
charging: "unknown",
|
|
23836
23850
|
sleeping: false,
|
|
23837
23851
|
lastUpdated: 0
|
|
23838
23852
|
}
|
|
@@ -47511,6 +47525,29 @@ var TerminalInstanceReconcileCoordinator = class {
|
|
|
47511
47525
|
return this.running;
|
|
47512
47526
|
}
|
|
47513
47527
|
};
|
|
47528
|
+
/**
|
|
47529
|
+
* Instances bound to a node the cluster no longer lists at all.
|
|
47530
|
+
*
|
|
47531
|
+
* An OFFLINE node is still in the topology; a FORGOTTEN one
|
|
47532
|
+
* (`clusterNodes.forgetNode`) is not. An instance bound to a forgotten node
|
|
47533
|
+
* warned every minute ("terminal profiles unavailable … node hub-test is
|
|
47534
|
+
* offline", 2026-09-05) and could never come back, so it is removed — but
|
|
47535
|
+
* only when two consecutive reads agree (D49): a topology answer that lost a
|
|
47536
|
+
* node once must not destroy an operator's configuration on its own.
|
|
47537
|
+
*/
|
|
47538
|
+
function instancesOnForgottenNodes(instances, knownNodeIds, absentBefore) {
|
|
47539
|
+
const absentNow = /* @__PURE__ */ new Set();
|
|
47540
|
+
const remove = [];
|
|
47541
|
+
for (const instance of instances) {
|
|
47542
|
+
if (knownNodeIds.has(instance.nodeId)) continue;
|
|
47543
|
+
absentNow.add(instance.id);
|
|
47544
|
+
if (absentBefore.has(instance.id)) remove.push(instance);
|
|
47545
|
+
}
|
|
47546
|
+
return {
|
|
47547
|
+
remove,
|
|
47548
|
+
absentNow
|
|
47549
|
+
};
|
|
47550
|
+
}
|
|
47514
47551
|
//#endregion
|
|
47515
47552
|
//#region src/profiles.ts
|
|
47516
47553
|
/**
|
|
@@ -48039,6 +48076,8 @@ var TerminalAddon = class extends BaseAddon {
|
|
|
48039
48076
|
/** Prevent repeat writes when a legacy device stays live after migration. */
|
|
48040
48077
|
migratedTerminalCameraConfigIds = /* @__PURE__ */ new Set();
|
|
48041
48078
|
terminalCameraTombstones = /* @__PURE__ */ new Set();
|
|
48079
|
+
/** Instance ids whose node was absent from the last topology read (D49 streak). */
|
|
48080
|
+
instancesOnAbsentNodes = /* @__PURE__ */ new Set();
|
|
48042
48081
|
instanceMutationQueue = new TerminalInstanceMutationQueue();
|
|
48043
48082
|
terminalReconcileCoordinator = new TerminalInstanceReconcileCoordinator(this.instanceMutationQueue);
|
|
48044
48083
|
glancesPythonPath = "";
|
|
@@ -48219,6 +48258,16 @@ var TerminalAddon = class extends BaseAddon {
|
|
|
48219
48258
|
const nodes = topology.filter((node) => typeof node.id === "string" && node.id.length > 0);
|
|
48220
48259
|
const nodeIds = new Set(nodes.map((node) => node.id));
|
|
48221
48260
|
for (const cachedNodeId of this.cameraProfilesByNode.keys()) if (!nodeIds.has(cachedNodeId)) this.cameraProfilesByNode.delete(cachedNodeId);
|
|
48261
|
+
const forgotten = instancesOnForgottenNodes(this.terminalInstances(), nodeIds, this.instancesOnAbsentNodes);
|
|
48262
|
+
this.instancesOnAbsentNodes = forgotten.absentNow;
|
|
48263
|
+
for (const instance of forgotten.remove) {
|
|
48264
|
+
this.ctx.logger.info("terminal instance removed — its node is no longer in the cluster", { meta: {
|
|
48265
|
+
instanceId: instance.id,
|
|
48266
|
+
nodeId: instance.nodeId,
|
|
48267
|
+
name: instance.name
|
|
48268
|
+
} });
|
|
48269
|
+
await this.instanceMutationQueue.run(() => this.deleteTerminalInstanceUnlocked(instance.id));
|
|
48270
|
+
}
|
|
48222
48271
|
const unavailableNodeIds = /* @__PURE__ */ new Set();
|
|
48223
48272
|
await Promise.all(nodes.map(async (node) => {
|
|
48224
48273
|
try {
|
package/dist/addon.mjs
CHANGED
|
@@ -7621,7 +7621,8 @@ var CameraSwitchAuthoritySchema = discriminatedUnion("kind", [
|
|
|
7621
7621
|
var CameraSwitchUnavailableReasonSchema = _enum([
|
|
7622
7622
|
"no-provider",
|
|
7623
7623
|
"source-unreachable",
|
|
7624
|
-
"not-configured"
|
|
7624
|
+
"not-configured",
|
|
7625
|
+
"device-disabled"
|
|
7625
7626
|
]);
|
|
7626
7627
|
/**
|
|
7627
7628
|
* One switch, resolved for one camera.
|
|
@@ -13038,7 +13039,12 @@ var ConfigEntrySchema = object({
|
|
|
13038
13039
|
value: unknown(),
|
|
13039
13040
|
description: string().optional()
|
|
13040
13041
|
});
|
|
13041
|
-
|
|
13042
|
+
/**
|
|
13043
|
+
* Only `manual` since D356: the operator picks, nothing links itself. The
|
|
13044
|
+
* field survives on the wire because a viewer already OTA'd parses it —
|
|
13045
|
+
* drop it once no shipped client reads `mode`.
|
|
13046
|
+
*/
|
|
13047
|
+
var LinkedDevicesModeSchema = _enum(["manual"]);
|
|
13042
13048
|
/** One resolved linked device — the compact projection consumers need. */
|
|
13043
13049
|
var LinkedDeviceSchema = object({
|
|
13044
13050
|
deviceId: number(),
|
|
@@ -23714,12 +23720,20 @@ var BatteryStatusSchema = object({
|
|
|
23714
23720
|
* Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
|
|
23715
23721
|
* Reolink-specific for the Solar Panel 2 accessory (will become
|
|
23716
23722
|
* common on other battery cams). `'none'` means running on battery
|
|
23717
|
-
* alone.
|
|
23723
|
+
* alone — a NEGATIVE the firmware actually reported.
|
|
23724
|
+
*
|
|
23725
|
+
* `'unknown'` is the absence of an answer: the provider has not read the
|
|
23726
|
+
* power fields yet, or the firmware reported neither. Before it existed,
|
|
23727
|
+
* two unreadable fields produced `'none'`, and a camera nobody had reached
|
|
23728
|
+
* was drawn "on battery alone" — the same D315 defect `percentage` closed
|
|
23729
|
+
* by going nullable. Consumers SKIP it (no charger state published, no
|
|
23730
|
+
* glyph, no HomeKit ChargingState) rather than coerce it either way.
|
|
23718
23731
|
*/
|
|
23719
23732
|
charging: _enum([
|
|
23720
23733
|
"dc",
|
|
23721
23734
|
"solar",
|
|
23722
|
-
"none"
|
|
23735
|
+
"none",
|
|
23736
|
+
"unknown"
|
|
23723
23737
|
]),
|
|
23724
23738
|
/**
|
|
23725
23739
|
* True when the camera firmware has gone into low-power mode. Battery
|
|
@@ -23809,7 +23823,7 @@ onStatusChanged: { data: object({
|
|
|
23809
23823
|
kind: "push",
|
|
23810
23824
|
empty: {
|
|
23811
23825
|
percentage: 0,
|
|
23812
|
-
charging: "
|
|
23826
|
+
charging: "unknown",
|
|
23813
23827
|
sleeping: false,
|
|
23814
23828
|
lastUpdated: 0
|
|
23815
23829
|
}
|
|
@@ -47488,6 +47502,29 @@ var TerminalInstanceReconcileCoordinator = class {
|
|
|
47488
47502
|
return this.running;
|
|
47489
47503
|
}
|
|
47490
47504
|
};
|
|
47505
|
+
/**
|
|
47506
|
+
* Instances bound to a node the cluster no longer lists at all.
|
|
47507
|
+
*
|
|
47508
|
+
* An OFFLINE node is still in the topology; a FORGOTTEN one
|
|
47509
|
+
* (`clusterNodes.forgetNode`) is not. An instance bound to a forgotten node
|
|
47510
|
+
* warned every minute ("terminal profiles unavailable … node hub-test is
|
|
47511
|
+
* offline", 2026-09-05) and could never come back, so it is removed — but
|
|
47512
|
+
* only when two consecutive reads agree (D49): a topology answer that lost a
|
|
47513
|
+
* node once must not destroy an operator's configuration on its own.
|
|
47514
|
+
*/
|
|
47515
|
+
function instancesOnForgottenNodes(instances, knownNodeIds, absentBefore) {
|
|
47516
|
+
const absentNow = /* @__PURE__ */ new Set();
|
|
47517
|
+
const remove = [];
|
|
47518
|
+
for (const instance of instances) {
|
|
47519
|
+
if (knownNodeIds.has(instance.nodeId)) continue;
|
|
47520
|
+
absentNow.add(instance.id);
|
|
47521
|
+
if (absentBefore.has(instance.id)) remove.push(instance);
|
|
47522
|
+
}
|
|
47523
|
+
return {
|
|
47524
|
+
remove,
|
|
47525
|
+
absentNow
|
|
47526
|
+
};
|
|
47527
|
+
}
|
|
47491
47528
|
//#endregion
|
|
47492
47529
|
//#region src/profiles.ts
|
|
47493
47530
|
/**
|
|
@@ -48016,6 +48053,8 @@ var TerminalAddon = class extends BaseAddon {
|
|
|
48016
48053
|
/** Prevent repeat writes when a legacy device stays live after migration. */
|
|
48017
48054
|
migratedTerminalCameraConfigIds = /* @__PURE__ */ new Set();
|
|
48018
48055
|
terminalCameraTombstones = /* @__PURE__ */ new Set();
|
|
48056
|
+
/** Instance ids whose node was absent from the last topology read (D49 streak). */
|
|
48057
|
+
instancesOnAbsentNodes = /* @__PURE__ */ new Set();
|
|
48019
48058
|
instanceMutationQueue = new TerminalInstanceMutationQueue();
|
|
48020
48059
|
terminalReconcileCoordinator = new TerminalInstanceReconcileCoordinator(this.instanceMutationQueue);
|
|
48021
48060
|
glancesPythonPath = "";
|
|
@@ -48196,6 +48235,16 @@ var TerminalAddon = class extends BaseAddon {
|
|
|
48196
48235
|
const nodes = topology.filter((node) => typeof node.id === "string" && node.id.length > 0);
|
|
48197
48236
|
const nodeIds = new Set(nodes.map((node) => node.id));
|
|
48198
48237
|
for (const cachedNodeId of this.cameraProfilesByNode.keys()) if (!nodeIds.has(cachedNodeId)) this.cameraProfilesByNode.delete(cachedNodeId);
|
|
48238
|
+
const forgotten = instancesOnForgottenNodes(this.terminalInstances(), nodeIds, this.instancesOnAbsentNodes);
|
|
48239
|
+
this.instancesOnAbsentNodes = forgotten.absentNow;
|
|
48240
|
+
for (const instance of forgotten.remove) {
|
|
48241
|
+
this.ctx.logger.info("terminal instance removed — its node is no longer in the cluster", { meta: {
|
|
48242
|
+
instanceId: instance.id,
|
|
48243
|
+
nodeId: instance.nodeId,
|
|
48244
|
+
name: instance.name
|
|
48245
|
+
} });
|
|
48246
|
+
await this.instanceMutationQueue.run(() => this.deleteTerminalInstanceUnlocked(instance.id));
|
|
48247
|
+
}
|
|
48199
48248
|
const unavailableNodeIds = /* @__PURE__ */ new Set();
|
|
48200
48249
|
await Promise.all(nodes.map(async (node) => {
|
|
48201
48250
|
try {
|