@camstack/addon-provider-ecowitt 0.2.156 → 0.2.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 +136 -17
- package/dist/addon.mjs +136 -17
- package/package.json +2 -2
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.2.157] - 2026-11-05
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Internal: the connection form comment names the instance form; no behaviour change.
|
|
15
|
+
|
|
10
16
|
## [0.2.156] - 2026-11-05
|
|
11
17
|
|
|
12
18
|
### Changed
|
package/dist/addon.js
CHANGED
|
@@ -5984,7 +5984,7 @@ var ZodIssueCode$1 = {
|
|
|
5984
5984
|
var ZodFirstPartyTypeKind$1;
|
|
5985
5985
|
ZodFirstPartyTypeKind$1 || (ZodFirstPartyTypeKind$1 = {});
|
|
5986
5986
|
//#endregion
|
|
5987
|
-
//#region ../types/dist/sleep-
|
|
5987
|
+
//#region ../types/dist/sleep-DEZaLbiu.mjs
|
|
5988
5988
|
/**
|
|
5989
5989
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5990
5990
|
* window to float samples (D455).
|
|
@@ -6793,6 +6793,12 @@ function projectStore(store, perNodeKeys, nodeId) {
|
|
|
6793
6793
|
}
|
|
6794
6794
|
return out;
|
|
6795
6795
|
}
|
|
6796
|
+
/** Every scope, in the order the guard and the docs list them. */
|
|
6797
|
+
var SCOPED_SETTINGS_SCOPES = [
|
|
6798
|
+
"cluster",
|
|
6799
|
+
"device-defaults",
|
|
6800
|
+
"node"
|
|
6801
|
+
];
|
|
6796
6802
|
/**
|
|
6797
6803
|
* The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
|
|
6798
6804
|
*
|
|
@@ -7684,6 +7690,13 @@ var BaseAddon = class {
|
|
|
7684
7690
|
*/
|
|
7685
7691
|
secretStatePaths = [];
|
|
7686
7692
|
_addonSecretTree = null;
|
|
7693
|
+
/**
|
|
7694
|
+
* D986 M4: the scoped schemas this addon declared, resolved once per capability
|
|
7695
|
+
* (`loadScopedSchemas`) so the sync readers (`perNodeKeys`, the secret tree, the restart check)
|
|
7696
|
+
* see them. Empty until loaded: the readers then fall back to {@link globalSettingsSchema}.
|
|
7697
|
+
*/
|
|
7698
|
+
_scopedByCap = /* @__PURE__ */ new Map();
|
|
7699
|
+
_nodeKeysByCap = /* @__PURE__ */ new Map();
|
|
7687
7700
|
_deviceSecretTree = null;
|
|
7688
7701
|
/** Refs the last resolved config pointed at — a change to one re-resolves. */
|
|
7689
7702
|
_configSecretRefs = /* @__PURE__ */ new Set();
|
|
@@ -7753,6 +7766,7 @@ var BaseAddon = class {
|
|
|
7753
7766
|
this._ctx = context;
|
|
7754
7767
|
this._secretSettings = null;
|
|
7755
7768
|
}
|
|
7769
|
+
await this.loadScopedSchemas();
|
|
7756
7770
|
await this.resolveConfig();
|
|
7757
7771
|
if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
|
|
7758
7772
|
if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
|
|
@@ -7762,6 +7776,7 @@ var BaseAddon = class {
|
|
|
7762
7776
|
const normalized = normalizeAddonInitResult(result);
|
|
7763
7777
|
const providers = normalized && "providers" in normalized && normalized.providers ? normalized.providers : [];
|
|
7764
7778
|
this._registeredCapNames = providers.map((p) => p.capability.name);
|
|
7779
|
+
await this.loadScopedSchemas();
|
|
7765
7780
|
return normalized;
|
|
7766
7781
|
}
|
|
7767
7782
|
/**
|
|
@@ -7850,6 +7865,16 @@ var BaseAddon = class {
|
|
|
7850
7865
|
schema(schema) {
|
|
7851
7866
|
return schema;
|
|
7852
7867
|
}
|
|
7868
|
+
/**
|
|
7869
|
+
* The capability names whose scoped settings schemas ({@link clusterSettingsSchema},
|
|
7870
|
+
* {@link nodeSettingsSchema}, {@link deviceDefaultsSchema}) this addon declares. Read BEFORE
|
|
7871
|
+
* `onInitialize`, when the providers it registers are not known yet: the first config resolve
|
|
7872
|
+
* must already know which keys are per node. Names the providers register at init are added
|
|
7873
|
+
* once known.
|
|
7874
|
+
*/
|
|
7875
|
+
scopedSettingsCapabilities() {
|
|
7876
|
+
return [];
|
|
7877
|
+
}
|
|
7853
7878
|
/** Override to provide global-level settings UI schema. */
|
|
7854
7879
|
globalSettingsSchema(_cap) {
|
|
7855
7880
|
return null;
|
|
@@ -7893,7 +7918,8 @@ var BaseAddon = class {
|
|
|
7893
7918
|
return [];
|
|
7894
7919
|
}
|
|
7895
7920
|
async getGlobalSettings(overlay, cap, nodeId) {
|
|
7896
|
-
|
|
7921
|
+
await this.loadScopedSchemas(cap);
|
|
7922
|
+
const schema = this.globalSettingsSchema(cap) ?? this.scopedFormSchema(cap);
|
|
7897
7923
|
if (!schema) return { sections: [] };
|
|
7898
7924
|
const projected = await this.readRawProjectedStore(nodeId, cap);
|
|
7899
7925
|
return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
|
|
@@ -8024,6 +8050,7 @@ var BaseAddon = class {
|
|
|
8024
8050
|
* resolved blob back is a no-op for every unchanged secret.
|
|
8025
8051
|
*/
|
|
8026
8052
|
async resolveGlobalStore(nodeId, cap) {
|
|
8053
|
+
await this.loadScopedSchemas(cap);
|
|
8027
8054
|
const raw = await this._ctx?.settings?.readAddonStore() ?? {};
|
|
8028
8055
|
return this.projectForNode(raw, nodeId, cap);
|
|
8029
8056
|
}
|
|
@@ -8034,6 +8061,7 @@ var BaseAddon = class {
|
|
|
8034
8061
|
}
|
|
8035
8062
|
/** The PROJECTED store with its markers — the form hydrate only (D798). */
|
|
8036
8063
|
async readRawProjectedStore(nodeId, cap) {
|
|
8064
|
+
await this.loadScopedSchemas(cap);
|
|
8037
8065
|
const raw = await this.rawSettings()?.readAddonStore() ?? {};
|
|
8038
8066
|
return this.projectForNode(raw, nodeId, cap);
|
|
8039
8067
|
}
|
|
@@ -8136,6 +8164,7 @@ var BaseAddon = class {
|
|
|
8136
8164
|
await this.applyGlobalPatch(patch, nodeId);
|
|
8137
8165
|
}
|
|
8138
8166
|
async applyGlobalPatch(barePatch, nodeId) {
|
|
8167
|
+
await this.loadScopedSchemas();
|
|
8139
8168
|
const keys = this.perNodeKeys();
|
|
8140
8169
|
const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
|
|
8141
8170
|
const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
|
|
@@ -8149,7 +8178,8 @@ var BaseAddon = class {
|
|
|
8149
8178
|
await this.resolveConfig();
|
|
8150
8179
|
await this.onConfigChanged();
|
|
8151
8180
|
this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
|
|
8152
|
-
|
|
8181
|
+
const legacy = this.globalSettingsSchema();
|
|
8182
|
+
this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
|
|
8153
8183
|
}
|
|
8154
8184
|
/**
|
|
8155
8185
|
* The set of field keys the global settings schema declares `perNode: true`
|
|
@@ -8157,14 +8187,67 @@ var BaseAddon = class {
|
|
|
8157
8187
|
* declarations). Empty set ⇒ every per-node code path is bypassed and the
|
|
8158
8188
|
* settings API behaves exactly like the legacy node-agnostic one.
|
|
8159
8189
|
*/
|
|
8160
|
-
|
|
8190
|
+
/**
|
|
8191
|
+
* Load the scoped schemas of every capability this addon declares (the override and the
|
|
8192
|
+
* providers registered at init, plus `extraCap`) that is not loaded yet, then drop what was
|
|
8193
|
+
* derived from the previous set. Idempotent.
|
|
8194
|
+
*/
|
|
8195
|
+
async loadScopedSchemas(extraCap) {
|
|
8196
|
+
const caps = new Set([
|
|
8197
|
+
...this.scopedSettingsCapabilities(),
|
|
8198
|
+
...this._registeredCapNames,
|
|
8199
|
+
...extraCap === void 0 ? [] : [extraCap]
|
|
8200
|
+
]);
|
|
8201
|
+
let added = false;
|
|
8202
|
+
for (const cap of caps) {
|
|
8203
|
+
if (this._scopedByCap.has(cap)) continue;
|
|
8204
|
+
const loaded = [];
|
|
8205
|
+
for (const scope of SCOPED_SETTINGS_SCOPES) {
|
|
8206
|
+
const schema = await this.getScopedSettingsSchema(scope, cap);
|
|
8207
|
+
if (schema !== null) loaded.push({
|
|
8208
|
+
scope,
|
|
8209
|
+
schema
|
|
8210
|
+
});
|
|
8211
|
+
}
|
|
8212
|
+
this._scopedByCap.set(cap, loaded);
|
|
8213
|
+
added = true;
|
|
8214
|
+
}
|
|
8215
|
+
if (!added) return;
|
|
8216
|
+
this._nodeKeysByCap = /* @__PURE__ */ new Map();
|
|
8217
|
+
this._addonSecretTree = null;
|
|
8218
|
+
}
|
|
8219
|
+
/**
|
|
8220
|
+
* The form the settings API serves while an addon declares no {@link globalSettingsSchema}: the
|
|
8221
|
+
* sections of its scoped schemas, so dropping the old hook does not blank the form.
|
|
8222
|
+
*/
|
|
8223
|
+
scopedFormSchema(cap) {
|
|
8224
|
+
const byId = /* @__PURE__ */ new Map();
|
|
8225
|
+
for (const entry of this.loadedScopedSchemas(cap)) for (const section of entry.schema.sections) if (!byId.has(section.id)) byId.set(section.id, section);
|
|
8226
|
+
return byId.size === 0 ? null : { sections: [...byId.values()] };
|
|
8227
|
+
}
|
|
8228
|
+
/** Every loaded scoped schema of `cap`, or of every loaded capability. */
|
|
8229
|
+
loadedScopedSchemas(cap) {
|
|
8230
|
+
if (cap !== void 0) return this._scopedByCap.get(cap) ?? [];
|
|
8231
|
+
return [...this._scopedByCap.values()].flat();
|
|
8232
|
+
}
|
|
8233
|
+
/**
|
|
8234
|
+
* The field keys that are stored per node: every field of a {@link nodeSettingsSchema}, plus
|
|
8235
|
+
* the fields the legacy {@link globalSettingsSchema} still marks `perNode: true`. Derived once
|
|
8236
|
+
* per `cap` argument and memoized (schemas are static declarations). Empty set ⇒ every
|
|
8237
|
+
* per-node code path is bypassed and the settings API behaves like the node-agnostic one.
|
|
8238
|
+
* Call {@link loadScopedSchemas} first (the async seams do).
|
|
8239
|
+
*/
|
|
8161
8240
|
perNodeKeys(cap) {
|
|
8162
8241
|
const cacheKey = cap ?? "";
|
|
8163
|
-
const cached = this.
|
|
8242
|
+
const cached = this._nodeKeysByCap.get(cacheKey);
|
|
8164
8243
|
if (cached) return cached;
|
|
8165
|
-
const
|
|
8166
|
-
const keys = new Set(
|
|
8167
|
-
this.
|
|
8244
|
+
const legacy = this.globalSettingsSchema(cap);
|
|
8245
|
+
const keys = new Set(legacy ? legacy.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
|
|
8246
|
+
for (const { scope, schema } of this.loadedScopedSchemas(cap)) {
|
|
8247
|
+
if (scope !== "node") continue;
|
|
8248
|
+
for (const section of schema.sections) for (const key of collectValueFieldKeys(section.fields)) keys.add(key);
|
|
8249
|
+
}
|
|
8250
|
+
this._nodeKeysByCap.set(cacheKey, keys);
|
|
8168
8251
|
return keys;
|
|
8169
8252
|
}
|
|
8170
8253
|
/**
|
|
@@ -8174,10 +8257,9 @@ var BaseAddon = class {
|
|
|
8174
8257
|
* time to return its response before the addon is torn down and
|
|
8175
8258
|
* re-initialised by `AddonRegistryService.restartAddon`.
|
|
8176
8259
|
*/
|
|
8177
|
-
maybeAutoRestart(patchKeys,
|
|
8178
|
-
if (!schema) return;
|
|
8260
|
+
maybeAutoRestart(patchKeys, schemas) {
|
|
8179
8261
|
const restartKeys = /* @__PURE__ */ new Set();
|
|
8180
|
-
for (const section of schema.sections) for (const field of section.fields) {
|
|
8262
|
+
for (const schema of schemas) for (const section of schema.sections) for (const field of section.fields) {
|
|
8181
8263
|
if (field.type === "separator" || field.type === "info") continue;
|
|
8182
8264
|
if (field.requiresRestart) restartKeys.add(field.key);
|
|
8183
8265
|
}
|
|
@@ -8373,12 +8455,14 @@ var BaseAddon = class {
|
|
|
8373
8455
|
* here, in the addon's own process, so the hub keeps no copy of it.
|
|
8374
8456
|
*/
|
|
8375
8457
|
async getDeclaredSettingsKeys() {
|
|
8458
|
+
await this.loadScopedSchemas();
|
|
8376
8459
|
return { keys: [...this.declaredKeySet()].toSorted() };
|
|
8377
8460
|
}
|
|
8378
8461
|
declaredKeySet() {
|
|
8379
8462
|
const keys = new Set(Object.keys(this.defaults));
|
|
8380
8463
|
const schema = this.globalSettingsSchema();
|
|
8381
8464
|
if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
|
|
8465
|
+
for (const { schema: scoped } of this.loadedScopedSchemas()) for (const section of scoped.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
|
|
8382
8466
|
for (const key of this._declaredStateKeys) keys.add(key);
|
|
8383
8467
|
for (const path of this.secretStatePaths) {
|
|
8384
8468
|
const root = path.split(/[.[]/, 1)[0];
|
|
@@ -8474,7 +8558,11 @@ var BaseAddon = class {
|
|
|
8474
8558
|
}
|
|
8475
8559
|
/** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
|
|
8476
8560
|
addonSecretTree() {
|
|
8477
|
-
this._addonSecretTree
|
|
8561
|
+
if (this._addonSecretTree === null) {
|
|
8562
|
+
const tree = new Map(secretTreeFromSchema(this.globalSettingsSchema()));
|
|
8563
|
+
for (const { schema } of this.loadedScopedSchemas()) for (const [key, node] of secretTreeFromSchema(schema)) tree.set(key, node);
|
|
8564
|
+
this._addonSecretTree = mergeSecretStatePaths(tree, this.secretStatePaths);
|
|
8565
|
+
}
|
|
8478
8566
|
return this._addonSecretTree;
|
|
8479
8567
|
}
|
|
8480
8568
|
deviceSecretTree() {
|
|
@@ -8724,6 +8812,23 @@ function dropPerNodeFields(fields) {
|
|
|
8724
8812
|
}
|
|
8725
8813
|
return kept;
|
|
8726
8814
|
}
|
|
8815
|
+
/** The keys of the value-holding fields, recursing into layout containers. */
|
|
8816
|
+
function collectValueFieldKeys(fields) {
|
|
8817
|
+
const collected = [];
|
|
8818
|
+
for (const field of fields) {
|
|
8819
|
+
if (field.type === "group") {
|
|
8820
|
+
collected.push(...collectValueFieldKeys(field.fields));
|
|
8821
|
+
continue;
|
|
8822
|
+
}
|
|
8823
|
+
if (field.type === "sub-tabs") {
|
|
8824
|
+
for (const tab of field.tabs) collected.push(...collectValueFieldKeys(tab.fields));
|
|
8825
|
+
continue;
|
|
8826
|
+
}
|
|
8827
|
+
if (field.type === "separator" || field.type === "info") continue;
|
|
8828
|
+
if ("key" in field && typeof field.key === "string") collected.push(field.key);
|
|
8829
|
+
}
|
|
8830
|
+
return collected;
|
|
8831
|
+
}
|
|
8727
8832
|
function collectFieldKeys(fields) {
|
|
8728
8833
|
const collected = [];
|
|
8729
8834
|
for (const field of fields) {
|
|
@@ -13422,12 +13527,22 @@ var RecordIntegrationInstances = class {
|
|
|
13422
13527
|
...this.hooks.description,
|
|
13423
13528
|
mintsId: true
|
|
13424
13529
|
};
|
|
13425
|
-
|
|
13530
|
+
/**
|
|
13531
|
+
* The record an id names. A caller holding the `integrations` registry id (the Integrations
|
|
13532
|
+
* page) reaches a kind whose body links to it through `integrationId`; an id that is itself a
|
|
13533
|
+
* record id answers as it is. An id that names nothing comes back unchanged.
|
|
13534
|
+
*/
|
|
13535
|
+
async resolveRecordId(id) {
|
|
13536
|
+
if (await this.handle.get(id) !== null) return id;
|
|
13537
|
+
const linked = (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.find((record) => record.data["integrationId"] === id);
|
|
13538
|
+
return linked === void 0 ? id : linked.id;
|
|
13539
|
+
}
|
|
13540
|
+
instanceFormSchema = async ({ id }) => this.hooks.formSchema(id === null ? null : await this.resolveRecordId(id));
|
|
13426
13541
|
listInstances = async () => {
|
|
13427
13542
|
return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
|
|
13428
13543
|
};
|
|
13429
13544
|
getInstance = async ({ id }) => {
|
|
13430
|
-
const record = await this.handle.get(id);
|
|
13545
|
+
const record = await this.handle.get(await this.resolveRecordId(id));
|
|
13431
13546
|
return record === null ? null : this.viewOf(record);
|
|
13432
13547
|
};
|
|
13433
13548
|
/** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
|
|
@@ -13444,7 +13559,8 @@ var RecordIntegrationInstances = class {
|
|
|
13444
13559
|
await this.hooks.onChanged?.(minted, "created");
|
|
13445
13560
|
return { id: minted };
|
|
13446
13561
|
};
|
|
13447
|
-
updateInstance = async ({ id, name, values }) => {
|
|
13562
|
+
updateInstance = async ({ id: askedId, name, values }) => {
|
|
13563
|
+
const id = await this.resolveRecordId(askedId);
|
|
13448
13564
|
const patch = {};
|
|
13449
13565
|
for (const [key, value] of Object.entries(values)) {
|
|
13450
13566
|
if (value === void 0) continue;
|
|
@@ -13479,7 +13595,10 @@ var RecordIntegrationInstances = class {
|
|
|
13479
13595
|
status: "unsupported",
|
|
13480
13596
|
error: "This integration declares no connection test"
|
|
13481
13597
|
};
|
|
13482
|
-
return this.hooks.test(input
|
|
13598
|
+
return this.hooks.test(input.id === null ? input : {
|
|
13599
|
+
...input,
|
|
13600
|
+
id: await this.resolveRecordId(input.id)
|
|
13601
|
+
});
|
|
13483
13602
|
};
|
|
13484
13603
|
/** The flat provider object to hand to `registerProvider`. */
|
|
13485
13604
|
toProvider() {
|
|
@@ -97478,7 +97597,7 @@ function gatewayKeyOf(integration) {
|
|
|
97478
97597
|
return origin.length > 0 ? origin : `ecowitt:${slugifyHost(integration.id)}`;
|
|
97479
97598
|
}
|
|
97480
97599
|
/**
|
|
97481
|
-
* The integration
|
|
97600
|
+
* The integration instance form (the `formSchema` of its instances): the connection lives on the
|
|
97482
97601
|
* INTEGRATION, which carries the name. The `password` field is what puts the value in the keychain.
|
|
97483
97602
|
*/
|
|
97484
97603
|
function buildConnectionFormSchema() {
|
package/dist/addon.mjs
CHANGED
|
@@ -5983,7 +5983,7 @@ var ZodIssueCode$1 = {
|
|
|
5983
5983
|
var ZodFirstPartyTypeKind$1;
|
|
5984
5984
|
ZodFirstPartyTypeKind$1 || (ZodFirstPartyTypeKind$1 = {});
|
|
5985
5985
|
//#endregion
|
|
5986
|
-
//#region ../types/dist/sleep-
|
|
5986
|
+
//#region ../types/dist/sleep-DEZaLbiu.mjs
|
|
5987
5987
|
/**
|
|
5988
5988
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5989
5989
|
* window to float samples (D455).
|
|
@@ -6792,6 +6792,12 @@ function projectStore(store, perNodeKeys, nodeId) {
|
|
|
6792
6792
|
}
|
|
6793
6793
|
return out;
|
|
6794
6794
|
}
|
|
6795
|
+
/** Every scope, in the order the guard and the docs list them. */
|
|
6796
|
+
var SCOPED_SETTINGS_SCOPES = [
|
|
6797
|
+
"cluster",
|
|
6798
|
+
"device-defaults",
|
|
6799
|
+
"node"
|
|
6800
|
+
];
|
|
6795
6801
|
/**
|
|
6796
6802
|
* The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
|
|
6797
6803
|
*
|
|
@@ -7683,6 +7689,13 @@ var BaseAddon = class {
|
|
|
7683
7689
|
*/
|
|
7684
7690
|
secretStatePaths = [];
|
|
7685
7691
|
_addonSecretTree = null;
|
|
7692
|
+
/**
|
|
7693
|
+
* D986 M4: the scoped schemas this addon declared, resolved once per capability
|
|
7694
|
+
* (`loadScopedSchemas`) so the sync readers (`perNodeKeys`, the secret tree, the restart check)
|
|
7695
|
+
* see them. Empty until loaded: the readers then fall back to {@link globalSettingsSchema}.
|
|
7696
|
+
*/
|
|
7697
|
+
_scopedByCap = /* @__PURE__ */ new Map();
|
|
7698
|
+
_nodeKeysByCap = /* @__PURE__ */ new Map();
|
|
7686
7699
|
_deviceSecretTree = null;
|
|
7687
7700
|
/** Refs the last resolved config pointed at — a change to one re-resolves. */
|
|
7688
7701
|
_configSecretRefs = /* @__PURE__ */ new Set();
|
|
@@ -7752,6 +7765,7 @@ var BaseAddon = class {
|
|
|
7752
7765
|
this._ctx = context;
|
|
7753
7766
|
this._secretSettings = null;
|
|
7754
7767
|
}
|
|
7768
|
+
await this.loadScopedSchemas();
|
|
7755
7769
|
await this.resolveConfig();
|
|
7756
7770
|
if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
|
|
7757
7771
|
if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
|
|
@@ -7761,6 +7775,7 @@ var BaseAddon = class {
|
|
|
7761
7775
|
const normalized = normalizeAddonInitResult(result);
|
|
7762
7776
|
const providers = normalized && "providers" in normalized && normalized.providers ? normalized.providers : [];
|
|
7763
7777
|
this._registeredCapNames = providers.map((p) => p.capability.name);
|
|
7778
|
+
await this.loadScopedSchemas();
|
|
7764
7779
|
return normalized;
|
|
7765
7780
|
}
|
|
7766
7781
|
/**
|
|
@@ -7849,6 +7864,16 @@ var BaseAddon = class {
|
|
|
7849
7864
|
schema(schema) {
|
|
7850
7865
|
return schema;
|
|
7851
7866
|
}
|
|
7867
|
+
/**
|
|
7868
|
+
* The capability names whose scoped settings schemas ({@link clusterSettingsSchema},
|
|
7869
|
+
* {@link nodeSettingsSchema}, {@link deviceDefaultsSchema}) this addon declares. Read BEFORE
|
|
7870
|
+
* `onInitialize`, when the providers it registers are not known yet: the first config resolve
|
|
7871
|
+
* must already know which keys are per node. Names the providers register at init are added
|
|
7872
|
+
* once known.
|
|
7873
|
+
*/
|
|
7874
|
+
scopedSettingsCapabilities() {
|
|
7875
|
+
return [];
|
|
7876
|
+
}
|
|
7852
7877
|
/** Override to provide global-level settings UI schema. */
|
|
7853
7878
|
globalSettingsSchema(_cap) {
|
|
7854
7879
|
return null;
|
|
@@ -7892,7 +7917,8 @@ var BaseAddon = class {
|
|
|
7892
7917
|
return [];
|
|
7893
7918
|
}
|
|
7894
7919
|
async getGlobalSettings(overlay, cap, nodeId) {
|
|
7895
|
-
|
|
7920
|
+
await this.loadScopedSchemas(cap);
|
|
7921
|
+
const schema = this.globalSettingsSchema(cap) ?? this.scopedFormSchema(cap);
|
|
7896
7922
|
if (!schema) return { sections: [] };
|
|
7897
7923
|
const projected = await this.readRawProjectedStore(nodeId, cap);
|
|
7898
7924
|
return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
|
|
@@ -8023,6 +8049,7 @@ var BaseAddon = class {
|
|
|
8023
8049
|
* resolved blob back is a no-op for every unchanged secret.
|
|
8024
8050
|
*/
|
|
8025
8051
|
async resolveGlobalStore(nodeId, cap) {
|
|
8052
|
+
await this.loadScopedSchemas(cap);
|
|
8026
8053
|
const raw = await this._ctx?.settings?.readAddonStore() ?? {};
|
|
8027
8054
|
return this.projectForNode(raw, nodeId, cap);
|
|
8028
8055
|
}
|
|
@@ -8033,6 +8060,7 @@ var BaseAddon = class {
|
|
|
8033
8060
|
}
|
|
8034
8061
|
/** The PROJECTED store with its markers — the form hydrate only (D798). */
|
|
8035
8062
|
async readRawProjectedStore(nodeId, cap) {
|
|
8063
|
+
await this.loadScopedSchemas(cap);
|
|
8036
8064
|
const raw = await this.rawSettings()?.readAddonStore() ?? {};
|
|
8037
8065
|
return this.projectForNode(raw, nodeId, cap);
|
|
8038
8066
|
}
|
|
@@ -8135,6 +8163,7 @@ var BaseAddon = class {
|
|
|
8135
8163
|
await this.applyGlobalPatch(patch, nodeId);
|
|
8136
8164
|
}
|
|
8137
8165
|
async applyGlobalPatch(barePatch, nodeId) {
|
|
8166
|
+
await this.loadScopedSchemas();
|
|
8138
8167
|
const keys = this.perNodeKeys();
|
|
8139
8168
|
const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
|
|
8140
8169
|
const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
|
|
@@ -8148,7 +8177,8 @@ var BaseAddon = class {
|
|
|
8148
8177
|
await this.resolveConfig();
|
|
8149
8178
|
await this.onConfigChanged();
|
|
8150
8179
|
this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
|
|
8151
|
-
|
|
8180
|
+
const legacy = this.globalSettingsSchema();
|
|
8181
|
+
this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
|
|
8152
8182
|
}
|
|
8153
8183
|
/**
|
|
8154
8184
|
* The set of field keys the global settings schema declares `perNode: true`
|
|
@@ -8156,14 +8186,67 @@ var BaseAddon = class {
|
|
|
8156
8186
|
* declarations). Empty set ⇒ every per-node code path is bypassed and the
|
|
8157
8187
|
* settings API behaves exactly like the legacy node-agnostic one.
|
|
8158
8188
|
*/
|
|
8159
|
-
|
|
8189
|
+
/**
|
|
8190
|
+
* Load the scoped schemas of every capability this addon declares (the override and the
|
|
8191
|
+
* providers registered at init, plus `extraCap`) that is not loaded yet, then drop what was
|
|
8192
|
+
* derived from the previous set. Idempotent.
|
|
8193
|
+
*/
|
|
8194
|
+
async loadScopedSchemas(extraCap) {
|
|
8195
|
+
const caps = new Set([
|
|
8196
|
+
...this.scopedSettingsCapabilities(),
|
|
8197
|
+
...this._registeredCapNames,
|
|
8198
|
+
...extraCap === void 0 ? [] : [extraCap]
|
|
8199
|
+
]);
|
|
8200
|
+
let added = false;
|
|
8201
|
+
for (const cap of caps) {
|
|
8202
|
+
if (this._scopedByCap.has(cap)) continue;
|
|
8203
|
+
const loaded = [];
|
|
8204
|
+
for (const scope of SCOPED_SETTINGS_SCOPES) {
|
|
8205
|
+
const schema = await this.getScopedSettingsSchema(scope, cap);
|
|
8206
|
+
if (schema !== null) loaded.push({
|
|
8207
|
+
scope,
|
|
8208
|
+
schema
|
|
8209
|
+
});
|
|
8210
|
+
}
|
|
8211
|
+
this._scopedByCap.set(cap, loaded);
|
|
8212
|
+
added = true;
|
|
8213
|
+
}
|
|
8214
|
+
if (!added) return;
|
|
8215
|
+
this._nodeKeysByCap = /* @__PURE__ */ new Map();
|
|
8216
|
+
this._addonSecretTree = null;
|
|
8217
|
+
}
|
|
8218
|
+
/**
|
|
8219
|
+
* The form the settings API serves while an addon declares no {@link globalSettingsSchema}: the
|
|
8220
|
+
* sections of its scoped schemas, so dropping the old hook does not blank the form.
|
|
8221
|
+
*/
|
|
8222
|
+
scopedFormSchema(cap) {
|
|
8223
|
+
const byId = /* @__PURE__ */ new Map();
|
|
8224
|
+
for (const entry of this.loadedScopedSchemas(cap)) for (const section of entry.schema.sections) if (!byId.has(section.id)) byId.set(section.id, section);
|
|
8225
|
+
return byId.size === 0 ? null : { sections: [...byId.values()] };
|
|
8226
|
+
}
|
|
8227
|
+
/** Every loaded scoped schema of `cap`, or of every loaded capability. */
|
|
8228
|
+
loadedScopedSchemas(cap) {
|
|
8229
|
+
if (cap !== void 0) return this._scopedByCap.get(cap) ?? [];
|
|
8230
|
+
return [...this._scopedByCap.values()].flat();
|
|
8231
|
+
}
|
|
8232
|
+
/**
|
|
8233
|
+
* The field keys that are stored per node: every field of a {@link nodeSettingsSchema}, plus
|
|
8234
|
+
* the fields the legacy {@link globalSettingsSchema} still marks `perNode: true`. Derived once
|
|
8235
|
+
* per `cap` argument and memoized (schemas are static declarations). Empty set ⇒ every
|
|
8236
|
+
* per-node code path is bypassed and the settings API behaves like the node-agnostic one.
|
|
8237
|
+
* Call {@link loadScopedSchemas} first (the async seams do).
|
|
8238
|
+
*/
|
|
8160
8239
|
perNodeKeys(cap) {
|
|
8161
8240
|
const cacheKey = cap ?? "";
|
|
8162
|
-
const cached = this.
|
|
8241
|
+
const cached = this._nodeKeysByCap.get(cacheKey);
|
|
8163
8242
|
if (cached) return cached;
|
|
8164
|
-
const
|
|
8165
|
-
const keys = new Set(
|
|
8166
|
-
this.
|
|
8243
|
+
const legacy = this.globalSettingsSchema(cap);
|
|
8244
|
+
const keys = new Set(legacy ? legacy.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
|
|
8245
|
+
for (const { scope, schema } of this.loadedScopedSchemas(cap)) {
|
|
8246
|
+
if (scope !== "node") continue;
|
|
8247
|
+
for (const section of schema.sections) for (const key of collectValueFieldKeys(section.fields)) keys.add(key);
|
|
8248
|
+
}
|
|
8249
|
+
this._nodeKeysByCap.set(cacheKey, keys);
|
|
8167
8250
|
return keys;
|
|
8168
8251
|
}
|
|
8169
8252
|
/**
|
|
@@ -8173,10 +8256,9 @@ var BaseAddon = class {
|
|
|
8173
8256
|
* time to return its response before the addon is torn down and
|
|
8174
8257
|
* re-initialised by `AddonRegistryService.restartAddon`.
|
|
8175
8258
|
*/
|
|
8176
|
-
maybeAutoRestart(patchKeys,
|
|
8177
|
-
if (!schema) return;
|
|
8259
|
+
maybeAutoRestart(patchKeys, schemas) {
|
|
8178
8260
|
const restartKeys = /* @__PURE__ */ new Set();
|
|
8179
|
-
for (const section of schema.sections) for (const field of section.fields) {
|
|
8261
|
+
for (const schema of schemas) for (const section of schema.sections) for (const field of section.fields) {
|
|
8180
8262
|
if (field.type === "separator" || field.type === "info") continue;
|
|
8181
8263
|
if (field.requiresRestart) restartKeys.add(field.key);
|
|
8182
8264
|
}
|
|
@@ -8372,12 +8454,14 @@ var BaseAddon = class {
|
|
|
8372
8454
|
* here, in the addon's own process, so the hub keeps no copy of it.
|
|
8373
8455
|
*/
|
|
8374
8456
|
async getDeclaredSettingsKeys() {
|
|
8457
|
+
await this.loadScopedSchemas();
|
|
8375
8458
|
return { keys: [...this.declaredKeySet()].toSorted() };
|
|
8376
8459
|
}
|
|
8377
8460
|
declaredKeySet() {
|
|
8378
8461
|
const keys = new Set(Object.keys(this.defaults));
|
|
8379
8462
|
const schema = this.globalSettingsSchema();
|
|
8380
8463
|
if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
|
|
8464
|
+
for (const { schema: scoped } of this.loadedScopedSchemas()) for (const section of scoped.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
|
|
8381
8465
|
for (const key of this._declaredStateKeys) keys.add(key);
|
|
8382
8466
|
for (const path of this.secretStatePaths) {
|
|
8383
8467
|
const root = path.split(/[.[]/, 1)[0];
|
|
@@ -8473,7 +8557,11 @@ var BaseAddon = class {
|
|
|
8473
8557
|
}
|
|
8474
8558
|
/** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
|
|
8475
8559
|
addonSecretTree() {
|
|
8476
|
-
this._addonSecretTree
|
|
8560
|
+
if (this._addonSecretTree === null) {
|
|
8561
|
+
const tree = new Map(secretTreeFromSchema(this.globalSettingsSchema()));
|
|
8562
|
+
for (const { schema } of this.loadedScopedSchemas()) for (const [key, node] of secretTreeFromSchema(schema)) tree.set(key, node);
|
|
8563
|
+
this._addonSecretTree = mergeSecretStatePaths(tree, this.secretStatePaths);
|
|
8564
|
+
}
|
|
8477
8565
|
return this._addonSecretTree;
|
|
8478
8566
|
}
|
|
8479
8567
|
deviceSecretTree() {
|
|
@@ -8723,6 +8811,23 @@ function dropPerNodeFields(fields) {
|
|
|
8723
8811
|
}
|
|
8724
8812
|
return kept;
|
|
8725
8813
|
}
|
|
8814
|
+
/** The keys of the value-holding fields, recursing into layout containers. */
|
|
8815
|
+
function collectValueFieldKeys(fields) {
|
|
8816
|
+
const collected = [];
|
|
8817
|
+
for (const field of fields) {
|
|
8818
|
+
if (field.type === "group") {
|
|
8819
|
+
collected.push(...collectValueFieldKeys(field.fields));
|
|
8820
|
+
continue;
|
|
8821
|
+
}
|
|
8822
|
+
if (field.type === "sub-tabs") {
|
|
8823
|
+
for (const tab of field.tabs) collected.push(...collectValueFieldKeys(tab.fields));
|
|
8824
|
+
continue;
|
|
8825
|
+
}
|
|
8826
|
+
if (field.type === "separator" || field.type === "info") continue;
|
|
8827
|
+
if ("key" in field && typeof field.key === "string") collected.push(field.key);
|
|
8828
|
+
}
|
|
8829
|
+
return collected;
|
|
8830
|
+
}
|
|
8726
8831
|
function collectFieldKeys(fields) {
|
|
8727
8832
|
const collected = [];
|
|
8728
8833
|
for (const field of fields) {
|
|
@@ -13421,12 +13526,22 @@ var RecordIntegrationInstances = class {
|
|
|
13421
13526
|
...this.hooks.description,
|
|
13422
13527
|
mintsId: true
|
|
13423
13528
|
};
|
|
13424
|
-
|
|
13529
|
+
/**
|
|
13530
|
+
* The record an id names. A caller holding the `integrations` registry id (the Integrations
|
|
13531
|
+
* page) reaches a kind whose body links to it through `integrationId`; an id that is itself a
|
|
13532
|
+
* record id answers as it is. An id that names nothing comes back unchanged.
|
|
13533
|
+
*/
|
|
13534
|
+
async resolveRecordId(id) {
|
|
13535
|
+
if (await this.handle.get(id) !== null) return id;
|
|
13536
|
+
const linked = (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.find((record) => record.data["integrationId"] === id);
|
|
13537
|
+
return linked === void 0 ? id : linked.id;
|
|
13538
|
+
}
|
|
13539
|
+
instanceFormSchema = async ({ id }) => this.hooks.formSchema(id === null ? null : await this.resolveRecordId(id));
|
|
13425
13540
|
listInstances = async () => {
|
|
13426
13541
|
return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
|
|
13427
13542
|
};
|
|
13428
13543
|
getInstance = async ({ id }) => {
|
|
13429
|
-
const record = await this.handle.get(id);
|
|
13544
|
+
const record = await this.handle.get(await this.resolveRecordId(id));
|
|
13430
13545
|
return record === null ? null : this.viewOf(record);
|
|
13431
13546
|
};
|
|
13432
13547
|
/** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
|
|
@@ -13443,7 +13558,8 @@ var RecordIntegrationInstances = class {
|
|
|
13443
13558
|
await this.hooks.onChanged?.(minted, "created");
|
|
13444
13559
|
return { id: minted };
|
|
13445
13560
|
};
|
|
13446
|
-
updateInstance = async ({ id, name, values }) => {
|
|
13561
|
+
updateInstance = async ({ id: askedId, name, values }) => {
|
|
13562
|
+
const id = await this.resolveRecordId(askedId);
|
|
13447
13563
|
const patch = {};
|
|
13448
13564
|
for (const [key, value] of Object.entries(values)) {
|
|
13449
13565
|
if (value === void 0) continue;
|
|
@@ -13478,7 +13594,10 @@ var RecordIntegrationInstances = class {
|
|
|
13478
13594
|
status: "unsupported",
|
|
13479
13595
|
error: "This integration declares no connection test"
|
|
13480
13596
|
};
|
|
13481
|
-
return this.hooks.test(input
|
|
13597
|
+
return this.hooks.test(input.id === null ? input : {
|
|
13598
|
+
...input,
|
|
13599
|
+
id: await this.resolveRecordId(input.id)
|
|
13600
|
+
});
|
|
13482
13601
|
};
|
|
13483
13602
|
/** The flat provider object to hand to `registerProvider`. */
|
|
13484
13603
|
toProvider() {
|
|
@@ -97477,7 +97596,7 @@ function gatewayKeyOf(integration) {
|
|
|
97477
97596
|
return origin.length > 0 ? origin : `ecowitt:${slugifyHost(integration.id)}`;
|
|
97478
97597
|
}
|
|
97479
97598
|
/**
|
|
97480
|
-
* The integration
|
|
97599
|
+
* The integration instance form (the `formSchema` of its instances): the connection lives on the
|
|
97481
97600
|
* INTEGRATION, which carries the name. The `password` field is what puts the value in the keychain.
|
|
97482
97601
|
*/
|
|
97483
97602
|
function buildConnectionFormSchema() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-provider-ecowitt",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.157",
|
|
4
4
|
"description": "Ecowitt weather-station device-provider addon for CamStack — wraps the @apocaliss92/nodewitt local-poll / push client",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
}
|
|
64
64
|
],
|
|
65
65
|
"requires": {
|
|
66
|
-
"server": ">=1.2.
|
|
66
|
+
"server": ">=1.2.777"
|
|
67
67
|
}
|
|
68
68
|
},
|
|
69
69
|
"files": [
|