@camstack/system 1.2.241 → 1.2.243
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/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
- package/dist/builtins/device-manager/adoption-job-engine.d.ts +28 -7
- package/dist/builtins/device-manager/device-manager.addon.d.ts +11 -0
- package/dist/builtins/device-manager/device-manager.addon.js +147 -15
- package/dist/builtins/device-manager/device-manager.addon.mjs +147 -15
- package/dist/builtins/device-manager/device-meta-actions.d.ts +32 -0
- package/dist/builtins/device-manager/device-meta-types.d.ts +17 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-BjSYm1hH.mjs → dist-B4htkDVv.mjs} +104 -3
- package/dist/{dist-C6JIhqS6.js → dist-D6kqBW1G.js} +115 -2
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{retired-settings-keys-C53Y2Q1C.js → retired-settings-keys-BPIyHR4_.js} +1 -1
- package/dist/{retired-settings-keys-MpSfaPEY.mjs → retired-settings-keys-C9w4b4TV.mjs} +1 -1
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-D6kqBW1G.js");
|
|
7
7
|
const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { A as addonPagesCapability, Sn as EventCategory, Xt as BaseAddon, Yt as errMsg } from "../../dist-B4htkDVv.mjs";
|
|
2
2
|
import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-D6kqBW1G.js");
|
|
7
7
|
const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { M as addonWidgetsCapability, Sn as EventCategory, Xt as BaseAddon, Yt as errMsg } from "../../dist-B4htkDVv.mjs";
|
|
2
2
|
import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-D6kqBW1G.js");
|
|
7
7
|
//#region src/builtins/alerts/prune-policy.ts
|
|
8
8
|
/**
|
|
9
9
|
* Ids older than `cutoffMs`, oldest first.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { N as alertsCapability, Sn as EventCategory, Xt as BaseAddon, Yt as errMsg, ln as createEvent, n as AlertSchema } from "../../dist-B4htkDVv.mjs";
|
|
2
2
|
//#region src/builtins/alerts/prune-policy.ts
|
|
3
3
|
/**
|
|
4
4
|
* Ids older than `cutoffMs`, oldest first.
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-D6kqBW1G.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
let buffer = require("buffer");
|
|
9
9
|
let node_fs_promises = require("node:fs/promises");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { F as backupCapability, Ht as storageOccupancyCapability, Sn as EventCategory, Xt as BaseAddon, xt as mayWriteToLocation } from "../../dist-B4htkDVv.mjs";
|
|
2
2
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
3
3
|
import { Buffer as Buffer$1 } from "buffer";
|
|
4
4
|
import * as fsp from "node:fs/promises";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-D6kqBW1G.js");
|
|
7
7
|
const require_formatter = require("../../formatter-DqAKDlvN.js");
|
|
8
8
|
//#region src/builtins/console-logging/console-destination.ts
|
|
9
9
|
var LEVEL_RANK = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Xt as BaseAddon, _t as logDestinationCapability } from "../../dist-B4htkDVv.mjs";
|
|
2
2
|
import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
|
|
3
3
|
//#region src/builtins/console-logging/console-destination.ts
|
|
4
4
|
var LEVEL_RANK = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("../../dist-
|
|
3
|
+
const require_dist = require("../../dist-D6kqBW1G.js");
|
|
4
4
|
let node_crypto = require("node:crypto");
|
|
5
5
|
let node_fs_promises = require("node:fs/promises");
|
|
6
6
|
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as
|
|
1
|
+
import { B as coreBlockAddonId, H as coreBlocksCapability, V as coreBlockIdFromAddonId, Xt as BaseAddon, Yt as errMsg, c as CORE_BLOCK_ADDON_PREFIX, d as DeclaredDevices, l as CoreBlockSchema, s as CORE_BLOCKS_ADDON_ID } from "../../dist-B4htkDVv.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import * as fs from "node:fs/promises";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AdoptionJob, IScopedLogger } from '@camstack/types';
|
|
2
|
+
import { RemovalFence } from './removal-fence.js';
|
|
2
3
|
/**
|
|
3
4
|
* The slice of a `device-adoption` provider this engine drives. Structural on
|
|
4
5
|
* purpose: the real provider satisfies it, and a spec can exercise the whole
|
|
@@ -25,18 +26,38 @@ export interface AdoptionJobProvider {
|
|
|
25
26
|
name?: string;
|
|
26
27
|
hiddenChildIds?: string[];
|
|
27
28
|
}>;
|
|
28
|
-
}): Promise<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
}): Promise<AdoptResult>;
|
|
30
|
+
}
|
|
31
|
+
/** What one `adopt` call answers. Named so the per-candidate suspension in
|
|
32
|
+
* {@link AdoptionJobEngine} has a return type to state. */
|
|
33
|
+
export interface AdoptResult {
|
|
34
|
+
readonly adopted: readonly {
|
|
35
|
+
readonly childNativeId: string;
|
|
36
|
+
readonly parentDeviceId: number;
|
|
37
|
+
readonly accessoryDeviceIds: readonly number[];
|
|
38
|
+
}[];
|
|
35
39
|
}
|
|
36
40
|
export interface AdoptionJobEngineDeps {
|
|
37
41
|
/** Resolve the integration's adoption provider for an `addonId` pin. */
|
|
38
42
|
resolveProvider(addonId: string): Promise<AdoptionJobProvider>;
|
|
39
43
|
readonly logger: IScopedLogger;
|
|
44
|
+
/**
|
|
45
|
+
* The device-manager's removal fence, so an operator re-adopting hardware
|
|
46
|
+
* they just deleted is not refused for their OWN action.
|
|
47
|
+
*
|
|
48
|
+
* `removal-fence.ts` says a deliberate re-add lifts the fence, and
|
|
49
|
+
* `adoptDevice` does exactly that — but a job never went near it, so every
|
|
50
|
+
* integration driven through this engine refused a re-add for the whole
|
|
51
|
+
* 5-minute TTL. Reported on a Dreame vacuum, 2026-09-16.
|
|
52
|
+
*
|
|
53
|
+
* It has to be suspended HERE and not by the caller: `runUnfenced` is a
|
|
54
|
+
* counter held for the length of an awaited call, while `start()` hands back
|
|
55
|
+
* a jobId immediately and adopts on a detached promise. Wrapping the caller
|
|
56
|
+
* would lower the fence for the microseconds it takes to mint an id.
|
|
57
|
+
*
|
|
58
|
+
* Optional: absent means exactly the previous behaviour.
|
|
59
|
+
*/
|
|
60
|
+
readonly removalFence?: RemovalFence;
|
|
40
61
|
readonly now?: () => number;
|
|
41
62
|
readonly newId?: () => string;
|
|
42
63
|
}
|
|
@@ -50,6 +50,17 @@ export declare class DeviceManagerAddon extends BaseAddon {
|
|
|
50
50
|
private remoteNativeCaps;
|
|
51
51
|
/** Background adoption jobs — lazily constructed (needs `ctx.logger`). */
|
|
52
52
|
private adoptionJobEngine;
|
|
53
|
+
/**
|
|
54
|
+
* ONE removal fence for this process, shared by the provider context and the
|
|
55
|
+
* adoption-job engine.
|
|
56
|
+
*
|
|
57
|
+
* It used to be constructed inline into the context, so the job engine had
|
|
58
|
+
* no way to reach it and an operator re-adopting hardware they had just
|
|
59
|
+
* deleted was refused for the whole 5-minute TTL — their own action, denied.
|
|
60
|
+
* Two fences would be worse than one: the engine would suspend a fence that
|
|
61
|
+
* `allocateDeviceId` never consults.
|
|
62
|
+
*/
|
|
63
|
+
private readonly removalFence;
|
|
53
64
|
/** The runtime-state mirror, asserted present (constructed in onInitialize). */
|
|
54
65
|
private get stateMirror();
|
|
55
66
|
/** Build the `ProviderHost` the extracted provider-method modules
|
|
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
7
|
-
const require_retired_settings_keys = require("../../retired-settings-keys-
|
|
6
|
+
const require_dist = require("../../dist-D6kqBW1G.js");
|
|
7
|
+
const require_retired_settings_keys = require("../../retired-settings-keys-BPIyHR4_.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let _camstack_types_node = require("@camstack/types/node");
|
|
10
10
|
//#region src/builtins/device-manager/adoption-job-engine.ts
|
|
@@ -228,13 +228,15 @@ var AdoptionJobEngine = class {
|
|
|
228
228
|
const startedAt = this.now();
|
|
229
229
|
try {
|
|
230
230
|
const per = input.perCandidate?.[childNativeId];
|
|
231
|
-
const
|
|
231
|
+
const adoptCandidate = async () => await provider.adopt({
|
|
232
232
|
integrationId: input.integrationId,
|
|
233
233
|
childNativeIds: [childNativeId],
|
|
234
234
|
...input.filter !== void 0 ? { filter: input.filter } : {},
|
|
235
235
|
...input.importLocations !== void 0 ? { importLocations: input.importLocations } : {},
|
|
236
236
|
...per !== void 0 ? { perCandidate: { [childNativeId]: per } } : {}
|
|
237
|
-
})
|
|
237
|
+
});
|
|
238
|
+
const fence = this.deps.removalFence;
|
|
239
|
+
const entry = (fence === void 0 ? await adoptCandidate() : await fence.runUnfenced(adoptCandidate)).adopted[0];
|
|
238
240
|
if (!entry) {
|
|
239
241
|
job.results.push({
|
|
240
242
|
childNativeId,
|
|
@@ -3926,7 +3928,8 @@ async function runLocationBackfillAtBoot(pctx, bootRows) {
|
|
|
3926
3928
|
* setLocation, setType, setIntegrationId, setLinkDeviceId,
|
|
3927
3929
|
* setPrimaryChildEntityId, setChildLayout, setRole, setDisplay,
|
|
3928
3930
|
* applyInitialMeta, setMetadata, setDisabled), and the location registry
|
|
3929
|
-
* (listLocations, addLocation, removeLocation)
|
|
3931
|
+
* (listLocations, addLocation, removeLocation, renameLocation) with its icon
|
|
3932
|
+
* map (listLocationIcons, setLocationIcon).
|
|
3930
3933
|
*
|
|
3931
3934
|
* EXACT ordering and side effects preserved — notably registerDevice's
|
|
3932
3935
|
* name-precedence reconcile + field-preservation spreads, the
|
|
@@ -4978,6 +4981,83 @@ async function addLocation(pctx, input) {
|
|
|
4978
4981
|
});
|
|
4979
4982
|
}
|
|
4980
4983
|
/**
|
|
4984
|
+
* Every location that carries an icon, as `(locationKey, icon)` pairs.
|
|
4985
|
+
*
|
|
4986
|
+
* ADDITIVE beside `listLocations`, which still answers `string[]` — the
|
|
4987
|
+
* autocomplete, the HA adoption import and the recorder all read a list of
|
|
4988
|
+
* LABELS and a shape change there would break every one of them. A surface
|
|
4989
|
+
* that wants icons asks for them and joins on `locationIconKey`.
|
|
4990
|
+
*
|
|
4991
|
+
* Returns only what was actually stored. A location with no assignment is
|
|
4992
|
+
* ABSENT, never `map-pin`: the fallback belongs to the renderer
|
|
4993
|
+
* (`resolveLocationIcon`), and folding it in here would make "the operator
|
|
4994
|
+
* picked the pin" indistinguishable from "the operator picked nothing" —
|
|
4995
|
+
* which is the difference an icon picker has to draw.
|
|
4996
|
+
*/
|
|
4997
|
+
async function listLocationIcons(pctx) {
|
|
4998
|
+
const icons = (await pctx.metaStore.readStore()).locationIcons ?? {};
|
|
4999
|
+
return Object.entries(icons).map(([locationKey, icon]) => ({
|
|
5000
|
+
locationKey,
|
|
5001
|
+
icon
|
|
5002
|
+
})).toSorted((a, b) => a.locationKey.localeCompare(b.locationKey));
|
|
5003
|
+
}
|
|
5004
|
+
/**
|
|
5005
|
+
* Set (or clear) the icon of a location label.
|
|
5006
|
+
*
|
|
5007
|
+
* Keyed on `locationIconKey` — trimmed + lower-cased, the equality the
|
|
5008
|
+
* registry matches on everywhere — so a case-only rename keeps the icon and
|
|
5009
|
+
* two spellings of one room can never carry two different icons.
|
|
5010
|
+
*
|
|
5011
|
+
* The label need NOT be registered: `setLocation` accepts a free-form string
|
|
5012
|
+
* and `listLocations` folds in whatever devices carry, so requiring
|
|
5013
|
+
* registration here would leave exactly the ad-hoc rooms unable to have an
|
|
5014
|
+
* icon. An empty label throws — there is no room to name.
|
|
5015
|
+
*
|
|
5016
|
+
* Same read-modify-write and same lock as `addLocation`: the map is one key of
|
|
5017
|
+
* the addon store, and `writeAddonStore` re-inserts the whole key range, so an
|
|
5018
|
+
* unordered write carries a stale copy of every other key.
|
|
5019
|
+
*/
|
|
5020
|
+
async function setLocationIcon(pctx, input) {
|
|
5021
|
+
const trimmed = input.location.trim();
|
|
5022
|
+
if (trimmed.length === 0) throw new Error("[device-manager] setLocationIcon: location must be non-empty");
|
|
5023
|
+
const key = require_dist.locationIconKey(trimmed);
|
|
5024
|
+
await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
|
|
5025
|
+
const icons = (await pctx.metaStore.readStore()).locationIcons ?? {};
|
|
5026
|
+
if (input.icon === null) {
|
|
5027
|
+
if (!(key in icons)) return;
|
|
5028
|
+
const { [key]: _cleared, ...kept } = icons;
|
|
5029
|
+
await pctx.settings.writeAddonStore({ locationIcons: kept });
|
|
5030
|
+
return;
|
|
5031
|
+
}
|
|
5032
|
+
if (icons[key] === input.icon) return;
|
|
5033
|
+
await pctx.settings.writeAddonStore({ locationIcons: {
|
|
5034
|
+
...icons,
|
|
5035
|
+
[key]: input.icon
|
|
5036
|
+
} });
|
|
5037
|
+
});
|
|
5038
|
+
}
|
|
5039
|
+
/**
|
|
5040
|
+
* Move a location's icon from one key to another, for `renameLocation`.
|
|
5041
|
+
*
|
|
5042
|
+
* Returns `null` when NOTHING changes, so the caller can leave the key out of
|
|
5043
|
+
* its `writeAddonStore` patch instead of re-writing an identical map.
|
|
5044
|
+
*
|
|
5045
|
+
* A rename onto a room that already has an icon is a MERGE, and the surviving
|
|
5046
|
+
* room keeps its own: the operator renaming "Sala" into "Salone" is moving
|
|
5047
|
+
* devices into Salone, not restyling it. The source key is dropped either way.
|
|
5048
|
+
* Pure — builds a new record, mutates nothing.
|
|
5049
|
+
*/
|
|
5050
|
+
function renameLocationIcon(icons, fromKey, toKey) {
|
|
5051
|
+
const moved = icons[fromKey];
|
|
5052
|
+
if (moved === void 0) return null;
|
|
5053
|
+
if (fromKey === toKey) return null;
|
|
5054
|
+
const { [fromKey]: _dropped, ...rest } = icons;
|
|
5055
|
+
return toKey in rest ? rest : {
|
|
5056
|
+
...rest,
|
|
5057
|
+
[toKey]: moved
|
|
5058
|
+
};
|
|
5059
|
+
}
|
|
5060
|
+
/**
|
|
4981
5061
|
* Remove a label from the curated registry. Match is
|
|
4982
5062
|
* case-insensitive. Devices that still reference this label keep
|
|
4983
5063
|
* their `meta.location` value (the registry is a suggestion
|
|
@@ -5008,13 +5088,19 @@ async function renameLocation(pctx, input) {
|
|
|
5008
5088
|
const fromKey = from.toLowerCase();
|
|
5009
5089
|
const toKey = to.toLowerCase();
|
|
5010
5090
|
await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
|
|
5011
|
-
const
|
|
5012
|
-
|
|
5091
|
+
const store = await pctx.metaStore.readStore();
|
|
5092
|
+
const current = store.locations ?? [];
|
|
5093
|
+
const registered = current.some((l) => l.trim().toLowerCase() === fromKey);
|
|
5094
|
+
const movedIcons = renameLocationIcon(store.locationIcons ?? {}, fromKey, toKey);
|
|
5095
|
+
if (!registered && movedIcons === null) return;
|
|
5013
5096
|
const kept = current.filter((l) => {
|
|
5014
5097
|
const key = l.trim().toLowerCase();
|
|
5015
5098
|
return key !== fromKey && key !== toKey;
|
|
5016
5099
|
});
|
|
5017
|
-
await pctx.settings.writeAddonStore({
|
|
5100
|
+
await pctx.settings.writeAddonStore({
|
|
5101
|
+
...registered ? { locations: [...kept, to] } : {},
|
|
5102
|
+
...movedIcons !== null ? { locationIcons: movedIcons } : {}
|
|
5103
|
+
});
|
|
5018
5104
|
});
|
|
5019
5105
|
const moved = await pctx.metaStore.withMetaWriteLock(async () => {
|
|
5020
5106
|
const out = [];
|
|
@@ -5052,10 +5138,19 @@ async function removeLocation(pctx, input) {
|
|
|
5052
5138
|
const trimmed = input.name.trim();
|
|
5053
5139
|
if (trimmed.length === 0) return;
|
|
5054
5140
|
await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
|
|
5055
|
-
const
|
|
5056
|
-
const
|
|
5057
|
-
|
|
5058
|
-
|
|
5141
|
+
const store = await pctx.metaStore.readStore();
|
|
5142
|
+
const current = store.locations ?? [];
|
|
5143
|
+
const key = require_dist.locationIconKey(trimmed);
|
|
5144
|
+
const remaining = current.filter((l) => l.toLowerCase() !== key);
|
|
5145
|
+
const droppedLabel = remaining.length !== current.length;
|
|
5146
|
+
const icons = store.locationIcons ?? {};
|
|
5147
|
+
const droppedIcon = key in icons;
|
|
5148
|
+
if (!droppedLabel && !droppedIcon) return;
|
|
5149
|
+
const { [key]: _removedIcon, ...keptIcons } = icons;
|
|
5150
|
+
await pctx.settings.writeAddonStore({
|
|
5151
|
+
...droppedLabel ? { locations: remaining } : {},
|
|
5152
|
+
...droppedIcon ? { locationIcons: keptIcons } : {}
|
|
5153
|
+
});
|
|
5059
5154
|
});
|
|
5060
5155
|
if (input.cascade !== true) return;
|
|
5061
5156
|
const cleared = await pctx.metaStore.withMetaWriteLock(async () => {
|
|
@@ -5157,12 +5252,35 @@ function decodeAddonStore(raw) {
|
|
|
5157
5252
|
const nextDeviceId = raw["nextDeviceId"];
|
|
5158
5253
|
const locations = raw["locations"];
|
|
5159
5254
|
const roleDisplayDefaults = raw["roleDisplayDefaults"];
|
|
5255
|
+
const locationIcons = decodeLocationIcons(raw["locationIcons"]);
|
|
5160
5256
|
return {
|
|
5161
5257
|
...typeof nextDeviceId === "number" && Number.isFinite(nextDeviceId) ? { nextDeviceId } : {},
|
|
5162
5258
|
...Array.isArray(locations) ? { locations: locations.filter((l) => typeof l === "string") } : {},
|
|
5163
|
-
...isRoleDisplayDefaults(roleDisplayDefaults) ? { roleDisplayDefaults } : {}
|
|
5259
|
+
...isRoleDisplayDefaults(roleDisplayDefaults) ? { roleDisplayDefaults } : {},
|
|
5260
|
+
...locationIcons !== null ? { locationIcons } : {}
|
|
5164
5261
|
};
|
|
5165
5262
|
}
|
|
5263
|
+
/**
|
|
5264
|
+
* Decode the `locationIcons` map, dropping every entry whose value is not a
|
|
5265
|
+
* member of the closed vocabulary.
|
|
5266
|
+
*
|
|
5267
|
+
* Dropping, not passing through: the icon's whole point is that both surfaces
|
|
5268
|
+
* can draw it, and an id from a hand-edit or a newer hub is exactly the value
|
|
5269
|
+
* that draws nothing. Absent reads as "no icon chosen", which every renderer
|
|
5270
|
+
* already degrades to the fallback pin. Returns `null` when the key itself is
|
|
5271
|
+
* absent or of the wrong shape, so the field stays OMITTED rather than being
|
|
5272
|
+
* written back as an empty object.
|
|
5273
|
+
*/
|
|
5274
|
+
function decodeLocationIcons(raw) {
|
|
5275
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
5276
|
+
const out = {};
|
|
5277
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
5278
|
+
const parsed = require_dist.LocationIconIdSchema.safeParse(value);
|
|
5279
|
+
if (!parsed.success) continue;
|
|
5280
|
+
out[key] = parsed.data;
|
|
5281
|
+
}
|
|
5282
|
+
return out;
|
|
5283
|
+
}
|
|
5166
5284
|
function isRoleDisplayDefaults(value) {
|
|
5167
5285
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5168
5286
|
}
|
|
@@ -6749,6 +6867,17 @@ var DeviceManagerAddon = class extends require_dist.BaseAddon {
|
|
|
6749
6867
|
remoteNativeCaps = /* @__PURE__ */ new Map();
|
|
6750
6868
|
/** Background adoption jobs — lazily constructed (needs `ctx.logger`). */
|
|
6751
6869
|
adoptionJobEngine = null;
|
|
6870
|
+
/**
|
|
6871
|
+
* ONE removal fence for this process, shared by the provider context and the
|
|
6872
|
+
* adoption-job engine.
|
|
6873
|
+
*
|
|
6874
|
+
* It used to be constructed inline into the context, so the job engine had
|
|
6875
|
+
* no way to reach it and an operator re-adopting hardware they had just
|
|
6876
|
+
* deleted was refused for the whole 5-minute TTL — their own action, denied.
|
|
6877
|
+
* Two fences would be worse than one: the engine would suspend a fence that
|
|
6878
|
+
* `allocateDeviceId` never consults.
|
|
6879
|
+
*/
|
|
6880
|
+
removalFence = createRemovalFence();
|
|
6752
6881
|
/** The runtime-state mirror, asserted present (constructed in onInitialize). */
|
|
6753
6882
|
get stateMirror() {
|
|
6754
6883
|
if (!this.stateMirrorImpl) throw new Error("DeviceManagerAddon: state mirror accessed before onInitialize");
|
|
@@ -6786,7 +6915,8 @@ var DeviceManagerAddon = class extends require_dist.BaseAddon {
|
|
|
6786
6915
|
get adoptionJobsImpl() {
|
|
6787
6916
|
if (!this.adoptionJobEngine) this.adoptionJobEngine = new AdoptionJobEngine({
|
|
6788
6917
|
resolveProvider: async (addonId) => this.requireDeviceAdoptionProvider(addonId),
|
|
6789
|
-
logger: this.ctx.logger
|
|
6918
|
+
logger: this.ctx.logger,
|
|
6919
|
+
removalFence: this.removalFence
|
|
6790
6920
|
});
|
|
6791
6921
|
return this.adoptionJobEngine;
|
|
6792
6922
|
}
|
|
@@ -6937,7 +7067,7 @@ var DeviceManagerAddon = class extends require_dist.BaseAddon {
|
|
|
6937
7067
|
registry,
|
|
6938
7068
|
bindingsDeps: this.bindingsDeps,
|
|
6939
7069
|
migrationGuard: createMigrationGuard(),
|
|
6940
|
-
removalFence:
|
|
7070
|
+
removalFence: this.removalFence,
|
|
6941
7071
|
requireDeviceOps,
|
|
6942
7072
|
stampIntegrationId: stampIntegrationId$1,
|
|
6943
7073
|
get provider() {
|
|
@@ -6983,6 +7113,8 @@ var DeviceManagerAddon = class extends require_dist.BaseAddon {
|
|
|
6983
7113
|
addLocation: (input) => addLocation(pctx, input),
|
|
6984
7114
|
removeLocation: (input) => removeLocation(pctx, input),
|
|
6985
7115
|
renameLocation: (input) => renameLocation(pctx, input),
|
|
7116
|
+
listLocationIcons: () => listLocationIcons(pctx),
|
|
7117
|
+
setLocationIcon: (input) => setLocationIcon(pctx, input),
|
|
6986
7118
|
listPersistedByAddon: (input) => listPersistedByAddon(pctx, input),
|
|
6987
7119
|
listAll: (input) => listAll(pctx, input),
|
|
6988
7120
|
getDevice: (input) => getDevice(pctx, input),
|