@camstack/addon-terminal 0.1.156 → 0.1.157
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/CHANGELOG.md +6 -0
- package/dist/addon.js +39 -6
- package/dist/addon.mjs +39 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.1.157] - 2026-10-31
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Deleting a terminal whose node left the cluster now completes instead of hanging
|
|
15
|
+
|
|
10
16
|
## [0.1.156] - 2026-10-30
|
|
11
17
|
|
|
12
18
|
### Changed
|
package/dist/addon.js
CHANGED
|
@@ -97868,6 +97868,8 @@ var SNAPSHOT_SETTLE_MAX_PULLS = 20;
|
|
|
97868
97868
|
var CLOSE_RETRY_BASE_MS = 50;
|
|
97869
97869
|
var CLOSE_RETRY_MAX_MS = 1e3;
|
|
97870
97870
|
var CLOSE_ATTEMPTS_PER_PASS = 3;
|
|
97871
|
+
/** How long a delete/edit waits for a node to acknowledge a camera close. */
|
|
97872
|
+
var CLOSE_INSTANCE_MAX_WAIT_MS = 5e3;
|
|
97871
97873
|
function relayKey(instanceId) {
|
|
97872
97874
|
return instanceId;
|
|
97873
97875
|
}
|
|
@@ -98222,18 +98224,43 @@ var TerminalCameraRelay = class {
|
|
|
98222
98224
|
if (state.clients === 0 && state.leases === 0) await this.closeState(state);
|
|
98223
98225
|
}
|
|
98224
98226
|
}
|
|
98225
|
-
/**
|
|
98226
|
-
|
|
98227
|
+
/**
|
|
98228
|
+
* Stop a withdrawn/offline camera's relay, including active HTTP readers.
|
|
98229
|
+
*
|
|
98230
|
+
* The remote close/open is addressed to the instance's node. A node that
|
|
98231
|
+
* left the cluster never answers, so the wait is bounded: the retrying
|
|
98232
|
+
* cleanup keeps running in the background, but a caller (a delete, an edit)
|
|
98233
|
+
* is never held hostage by a node that cannot reply.
|
|
98234
|
+
*/
|
|
98235
|
+
async closeInstance(instanceId, maxWaitMs = CLOSE_INSTANCE_MAX_WAIT_MS) {
|
|
98227
98236
|
const state = this.states.get(relayKey(instanceId));
|
|
98228
98237
|
if (!state) return;
|
|
98229
98238
|
for (const response of state.responses) response.destroy();
|
|
98230
98239
|
state.responses.clear();
|
|
98231
98240
|
state.clients = 0;
|
|
98232
98241
|
state.leases = 0;
|
|
98233
|
-
|
|
98234
|
-
if (state.openPromise) {
|
|
98235
|
-
await state.openPromise.catch(() => {});
|
|
98242
|
+
const closing = (async () => {
|
|
98236
98243
|
await this.closeState(state);
|
|
98244
|
+
if (state.openPromise) {
|
|
98245
|
+
await state.openPromise.catch(() => {});
|
|
98246
|
+
await this.closeState(state);
|
|
98247
|
+
}
|
|
98248
|
+
})();
|
|
98249
|
+
let timer;
|
|
98250
|
+
const bound = new Promise((resolve) => {
|
|
98251
|
+
timer = setTimeout(() => resolve("timeout"), maxWaitMs);
|
|
98252
|
+
});
|
|
98253
|
+
const outcome = await Promise.race([closing.then(() => "closed"), bound]);
|
|
98254
|
+
if (timer) clearTimeout(timer);
|
|
98255
|
+
if (outcome === "timeout") {
|
|
98256
|
+
closing.catch(() => {});
|
|
98257
|
+
this.logger.warn("terminal camera close still pending — node did not answer; continuing", {
|
|
98258
|
+
...this.tagsFor(instanceId),
|
|
98259
|
+
meta: {
|
|
98260
|
+
nodeId: state.nodeId,
|
|
98261
|
+
waitedMs: maxWaitMs
|
|
98262
|
+
}
|
|
98263
|
+
});
|
|
98237
98264
|
}
|
|
98238
98265
|
}
|
|
98239
98266
|
async dispose() {
|
|
@@ -99547,11 +99574,17 @@ var TerminalAddon = class extends BaseAddon {
|
|
|
99547
99574
|
} });
|
|
99548
99575
|
});
|
|
99549
99576
|
this.terminalCameraTombstones.add(instance.cameraStableId);
|
|
99550
|
-
await this.cameraRelay?.closeInstance(instance.id);
|
|
99551
99577
|
await this.updateGlobalSettings({
|
|
99552
99578
|
terminalInstances: this.config.terminalInstances.filter((candidate) => candidate.id !== instanceId),
|
|
99553
99579
|
terminalCameraTombstones: [...new Set([...this.config.terminalCameraTombstones, instance.cameraStableId])]
|
|
99554
99580
|
});
|
|
99581
|
+
await this.cameraRelay?.closeInstance(instance.id).catch((error) => {
|
|
99582
|
+
this.ctx.logger.warn("could not close the deleted Terminal instance session", { meta: {
|
|
99583
|
+
instanceId: instance.id,
|
|
99584
|
+
nodeId: instance.nodeId,
|
|
99585
|
+
error: error instanceof Error ? error.message : String(error)
|
|
99586
|
+
} });
|
|
99587
|
+
});
|
|
99555
99588
|
}
|
|
99556
99589
|
async setTerminalInstanceEnabled(instanceId, enabled) {
|
|
99557
99590
|
const instance = await this.instanceMutationQueue.run(() => this.setTerminalInstanceEnabledUnlocked(instanceId, enabled));
|
package/dist/addon.mjs
CHANGED
|
@@ -97845,6 +97845,8 @@ var SNAPSHOT_SETTLE_MAX_PULLS = 20;
|
|
|
97845
97845
|
var CLOSE_RETRY_BASE_MS = 50;
|
|
97846
97846
|
var CLOSE_RETRY_MAX_MS = 1e3;
|
|
97847
97847
|
var CLOSE_ATTEMPTS_PER_PASS = 3;
|
|
97848
|
+
/** How long a delete/edit waits for a node to acknowledge a camera close. */
|
|
97849
|
+
var CLOSE_INSTANCE_MAX_WAIT_MS = 5e3;
|
|
97848
97850
|
function relayKey(instanceId) {
|
|
97849
97851
|
return instanceId;
|
|
97850
97852
|
}
|
|
@@ -98199,18 +98201,43 @@ var TerminalCameraRelay = class {
|
|
|
98199
98201
|
if (state.clients === 0 && state.leases === 0) await this.closeState(state);
|
|
98200
98202
|
}
|
|
98201
98203
|
}
|
|
98202
|
-
/**
|
|
98203
|
-
|
|
98204
|
+
/**
|
|
98205
|
+
* Stop a withdrawn/offline camera's relay, including active HTTP readers.
|
|
98206
|
+
*
|
|
98207
|
+
* The remote close/open is addressed to the instance's node. A node that
|
|
98208
|
+
* left the cluster never answers, so the wait is bounded: the retrying
|
|
98209
|
+
* cleanup keeps running in the background, but a caller (a delete, an edit)
|
|
98210
|
+
* is never held hostage by a node that cannot reply.
|
|
98211
|
+
*/
|
|
98212
|
+
async closeInstance(instanceId, maxWaitMs = CLOSE_INSTANCE_MAX_WAIT_MS) {
|
|
98204
98213
|
const state = this.states.get(relayKey(instanceId));
|
|
98205
98214
|
if (!state) return;
|
|
98206
98215
|
for (const response of state.responses) response.destroy();
|
|
98207
98216
|
state.responses.clear();
|
|
98208
98217
|
state.clients = 0;
|
|
98209
98218
|
state.leases = 0;
|
|
98210
|
-
|
|
98211
|
-
if (state.openPromise) {
|
|
98212
|
-
await state.openPromise.catch(() => {});
|
|
98219
|
+
const closing = (async () => {
|
|
98213
98220
|
await this.closeState(state);
|
|
98221
|
+
if (state.openPromise) {
|
|
98222
|
+
await state.openPromise.catch(() => {});
|
|
98223
|
+
await this.closeState(state);
|
|
98224
|
+
}
|
|
98225
|
+
})();
|
|
98226
|
+
let timer;
|
|
98227
|
+
const bound = new Promise((resolve) => {
|
|
98228
|
+
timer = setTimeout(() => resolve("timeout"), maxWaitMs);
|
|
98229
|
+
});
|
|
98230
|
+
const outcome = await Promise.race([closing.then(() => "closed"), bound]);
|
|
98231
|
+
if (timer) clearTimeout(timer);
|
|
98232
|
+
if (outcome === "timeout") {
|
|
98233
|
+
closing.catch(() => {});
|
|
98234
|
+
this.logger.warn("terminal camera close still pending — node did not answer; continuing", {
|
|
98235
|
+
...this.tagsFor(instanceId),
|
|
98236
|
+
meta: {
|
|
98237
|
+
nodeId: state.nodeId,
|
|
98238
|
+
waitedMs: maxWaitMs
|
|
98239
|
+
}
|
|
98240
|
+
});
|
|
98214
98241
|
}
|
|
98215
98242
|
}
|
|
98216
98243
|
async dispose() {
|
|
@@ -99524,11 +99551,17 @@ var TerminalAddon = class extends BaseAddon {
|
|
|
99524
99551
|
} });
|
|
99525
99552
|
});
|
|
99526
99553
|
this.terminalCameraTombstones.add(instance.cameraStableId);
|
|
99527
|
-
await this.cameraRelay?.closeInstance(instance.id);
|
|
99528
99554
|
await this.updateGlobalSettings({
|
|
99529
99555
|
terminalInstances: this.config.terminalInstances.filter((candidate) => candidate.id !== instanceId),
|
|
99530
99556
|
terminalCameraTombstones: [...new Set([...this.config.terminalCameraTombstones, instance.cameraStableId])]
|
|
99531
99557
|
});
|
|
99558
|
+
await this.cameraRelay?.closeInstance(instance.id).catch((error) => {
|
|
99559
|
+
this.ctx.logger.warn("could not close the deleted Terminal instance session", { meta: {
|
|
99560
|
+
instanceId: instance.id,
|
|
99561
|
+
nodeId: instance.nodeId,
|
|
99562
|
+
error: error instanceof Error ? error.message : String(error)
|
|
99563
|
+
} });
|
|
99564
|
+
});
|
|
99532
99565
|
}
|
|
99533
99566
|
async setTerminalInstanceEnabled(instanceId, enabled) {
|
|
99534
99567
|
const instance = await this.instanceMutationQueue.run(() => this.setTerminalInstanceEnabledUnlocked(instanceId, enabled));
|