@camstack/addon-provider-ecowitt 0.2.155 → 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 +12 -0
- package/dist/addon.js +225 -77
- package/dist/addon.mjs +225 -77
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ 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
|
+
|
|
16
|
+
## [0.2.156] - 2026-11-05
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Copying an integration into its record reads the leftover rows through the migration-only door and resolves their secrets in this addon
|
|
21
|
+
|
|
10
22
|
## [0.2.155] - 2026-11-04
|
|
11
23
|
|
|
12
24
|
### 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 });
|
|
@@ -7928,10 +7954,9 @@ var BaseAddon = class {
|
|
|
7928
7954
|
*
|
|
7929
7955
|
* Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
|
|
7930
7956
|
* needs its connection fields, the default integration the addon's own sections. It is the
|
|
7931
|
-
* SHAPE of the form only — the values live
|
|
7932
|
-
* unchanged (`integrations.setSettings` for an account integration).
|
|
7957
|
+
* SHAPE of the form only — the values live on the instance record.
|
|
7933
7958
|
*/
|
|
7934
|
-
async
|
|
7959
|
+
async integrationFormSchema(_instance) {
|
|
7935
7960
|
return null;
|
|
7936
7961
|
}
|
|
7937
7962
|
/**
|
|
@@ -7977,7 +8002,7 @@ var BaseAddon = class {
|
|
|
7977
8002
|
* per-instance schema when it declares one, else its global schema (what they always showed).
|
|
7978
8003
|
*/
|
|
7979
8004
|
async getIntegrationFormSchema(instance, nodeId) {
|
|
7980
|
-
const scoped = await this.
|
|
8005
|
+
const scoped = await this.integrationFormSchema(instance);
|
|
7981
8006
|
if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
|
|
7982
8007
|
return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
|
|
7983
8008
|
}
|
|
@@ -8025,6 +8050,7 @@ var BaseAddon = class {
|
|
|
8025
8050
|
* resolved blob back is a no-op for every unchanged secret.
|
|
8026
8051
|
*/
|
|
8027
8052
|
async resolveGlobalStore(nodeId, cap) {
|
|
8053
|
+
await this.loadScopedSchemas(cap);
|
|
8028
8054
|
const raw = await this._ctx?.settings?.readAddonStore() ?? {};
|
|
8029
8055
|
return this.projectForNode(raw, nodeId, cap);
|
|
8030
8056
|
}
|
|
@@ -8035,6 +8061,7 @@ var BaseAddon = class {
|
|
|
8035
8061
|
}
|
|
8036
8062
|
/** The PROJECTED store with its markers — the form hydrate only (D798). */
|
|
8037
8063
|
async readRawProjectedStore(nodeId, cap) {
|
|
8064
|
+
await this.loadScopedSchemas(cap);
|
|
8038
8065
|
const raw = await this.rawSettings()?.readAddonStore() ?? {};
|
|
8039
8066
|
return this.projectForNode(raw, nodeId, cap);
|
|
8040
8067
|
}
|
|
@@ -8137,6 +8164,7 @@ var BaseAddon = class {
|
|
|
8137
8164
|
await this.applyGlobalPatch(patch, nodeId);
|
|
8138
8165
|
}
|
|
8139
8166
|
async applyGlobalPatch(barePatch, nodeId) {
|
|
8167
|
+
await this.loadScopedSchemas();
|
|
8140
8168
|
const keys = this.perNodeKeys();
|
|
8141
8169
|
const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
|
|
8142
8170
|
const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
|
|
@@ -8150,7 +8178,8 @@ var BaseAddon = class {
|
|
|
8150
8178
|
await this.resolveConfig();
|
|
8151
8179
|
await this.onConfigChanged();
|
|
8152
8180
|
this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
|
|
8153
|
-
|
|
8181
|
+
const legacy = this.globalSettingsSchema();
|
|
8182
|
+
this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
|
|
8154
8183
|
}
|
|
8155
8184
|
/**
|
|
8156
8185
|
* The set of field keys the global settings schema declares `perNode: true`
|
|
@@ -8158,14 +8187,67 @@ var BaseAddon = class {
|
|
|
8158
8187
|
* declarations). Empty set ⇒ every per-node code path is bypassed and the
|
|
8159
8188
|
* settings API behaves exactly like the legacy node-agnostic one.
|
|
8160
8189
|
*/
|
|
8161
|
-
|
|
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
|
+
*/
|
|
8162
8240
|
perNodeKeys(cap) {
|
|
8163
8241
|
const cacheKey = cap ?? "";
|
|
8164
|
-
const cached = this.
|
|
8242
|
+
const cached = this._nodeKeysByCap.get(cacheKey);
|
|
8165
8243
|
if (cached) return cached;
|
|
8166
|
-
const
|
|
8167
|
-
const keys = new Set(
|
|
8168
|
-
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);
|
|
8169
8251
|
return keys;
|
|
8170
8252
|
}
|
|
8171
8253
|
/**
|
|
@@ -8175,10 +8257,9 @@ var BaseAddon = class {
|
|
|
8175
8257
|
* time to return its response before the addon is torn down and
|
|
8176
8258
|
* re-initialised by `AddonRegistryService.restartAddon`.
|
|
8177
8259
|
*/
|
|
8178
|
-
maybeAutoRestart(patchKeys,
|
|
8179
|
-
if (!schema) return;
|
|
8260
|
+
maybeAutoRestart(patchKeys, schemas) {
|
|
8180
8261
|
const restartKeys = /* @__PURE__ */ new Set();
|
|
8181
|
-
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) {
|
|
8182
8263
|
if (field.type === "separator" || field.type === "info") continue;
|
|
8183
8264
|
if (field.requiresRestart) restartKeys.add(field.key);
|
|
8184
8265
|
}
|
|
@@ -8374,12 +8455,14 @@ var BaseAddon = class {
|
|
|
8374
8455
|
* here, in the addon's own process, so the hub keeps no copy of it.
|
|
8375
8456
|
*/
|
|
8376
8457
|
async getDeclaredSettingsKeys() {
|
|
8458
|
+
await this.loadScopedSchemas();
|
|
8377
8459
|
return { keys: [...this.declaredKeySet()].toSorted() };
|
|
8378
8460
|
}
|
|
8379
8461
|
declaredKeySet() {
|
|
8380
8462
|
const keys = new Set(Object.keys(this.defaults));
|
|
8381
8463
|
const schema = this.globalSettingsSchema();
|
|
8382
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);
|
|
8383
8466
|
for (const key of this._declaredStateKeys) keys.add(key);
|
|
8384
8467
|
for (const path of this.secretStatePaths) {
|
|
8385
8468
|
const root = path.split(/[.[]/, 1)[0];
|
|
@@ -8475,7 +8558,11 @@ var BaseAddon = class {
|
|
|
8475
8558
|
}
|
|
8476
8559
|
/** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
|
|
8477
8560
|
addonSecretTree() {
|
|
8478
|
-
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
|
+
}
|
|
8479
8566
|
return this._addonSecretTree;
|
|
8480
8567
|
}
|
|
8481
8568
|
deviceSecretTree() {
|
|
@@ -8725,6 +8812,23 @@ function dropPerNodeFields(fields) {
|
|
|
8725
8812
|
}
|
|
8726
8813
|
return kept;
|
|
8727
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
|
+
}
|
|
8728
8832
|
function collectFieldKeys(fields) {
|
|
8729
8833
|
const collected = [];
|
|
8730
8834
|
for (const field of fields) {
|
|
@@ -11951,7 +12055,7 @@ function userError(code, params, englishFallback) {
|
|
|
11951
12055
|
return new CamstackError(code, params, englishFallback);
|
|
11952
12056
|
}
|
|
11953
12057
|
//#endregion
|
|
11954
|
-
//#region ../types/dist/composition-
|
|
12058
|
+
//#region ../types/dist/composition-pOHbTzRB.mjs
|
|
11955
12059
|
/**
|
|
11956
12060
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11957
12061
|
*
|
|
@@ -13423,12 +13527,22 @@ var RecordIntegrationInstances = class {
|
|
|
13423
13527
|
...this.hooks.description,
|
|
13424
13528
|
mintsId: true
|
|
13425
13529
|
};
|
|
13426
|
-
|
|
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));
|
|
13427
13541
|
listInstances = async () => {
|
|
13428
13542
|
return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
|
|
13429
13543
|
};
|
|
13430
13544
|
getInstance = async ({ id }) => {
|
|
13431
|
-
const record = await this.handle.get(id);
|
|
13545
|
+
const record = await this.handle.get(await this.resolveRecordId(id));
|
|
13432
13546
|
return record === null ? null : this.viewOf(record);
|
|
13433
13547
|
};
|
|
13434
13548
|
/** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
|
|
@@ -13445,7 +13559,8 @@ var RecordIntegrationInstances = class {
|
|
|
13445
13559
|
await this.hooks.onChanged?.(minted, "created");
|
|
13446
13560
|
return { id: minted };
|
|
13447
13561
|
};
|
|
13448
|
-
updateInstance = async ({ id, name, values }) => {
|
|
13562
|
+
updateInstance = async ({ id: askedId, name, values }) => {
|
|
13563
|
+
const id = await this.resolveRecordId(askedId);
|
|
13449
13564
|
const patch = {};
|
|
13450
13565
|
for (const [key, value] of Object.entries(values)) {
|
|
13451
13566
|
if (value === void 0) continue;
|
|
@@ -13480,7 +13595,10 @@ var RecordIntegrationInstances = class {
|
|
|
13480
13595
|
status: "unsupported",
|
|
13481
13596
|
error: "This integration declares no connection test"
|
|
13482
13597
|
};
|
|
13483
|
-
return this.hooks.test(input
|
|
13598
|
+
return this.hooks.test(input.id === null ? input : {
|
|
13599
|
+
...input,
|
|
13600
|
+
id: await this.resolveRecordId(input.id)
|
|
13601
|
+
});
|
|
13484
13602
|
};
|
|
13485
13603
|
/** The flat provider object to hand to `registerProvider`. */
|
|
13486
13604
|
toProvider() {
|
|
@@ -14659,20 +14777,28 @@ async function moveIntegrationRowsToRecords(ports, spec) {
|
|
|
14659
14777
|
};
|
|
14660
14778
|
}
|
|
14661
14779
|
/**
|
|
14662
|
-
* The addon-side ports of `moveIntegrationRowsToRecords` (planning#416)
|
|
14663
|
-
* from its own process, so the
|
|
14664
|
-
* `
|
|
14665
|
-
*
|
|
14666
|
-
*
|
|
14780
|
+
* The addon-side ports of `moveIntegrationRowsToRecords` (planning#416). An addon runs the move
|
|
14781
|
+
* from its own process, so the leftover rows are reached through the migration-only door
|
|
14782
|
+
* (`migrations.readLegacyIntegrationRows` / `clearLegacyIntegrationRows`) on the hub's connection.
|
|
14783
|
+
* The door returns markers. This process is the owner, so it resolves and releases them with
|
|
14784
|
+
* `resolveSecretMarkers` — the same call the old settings door used — and only then asks the
|
|
14785
|
+
* door to delete the rows. The addon's own record kind is its records handle.
|
|
14667
14786
|
*/
|
|
14787
|
+
async function resolvedRows(stored, secrets) {
|
|
14788
|
+
if (secrets === null) return stored;
|
|
14789
|
+
const { value, unresolved } = await resolveSecretMarkers(stored, secrets);
|
|
14790
|
+
if (unresolved.length > 0) throw new Error(`${unresolved.length} secret(s) cannot be read from the keychain`);
|
|
14791
|
+
return value;
|
|
14792
|
+
}
|
|
14668
14793
|
function integrationRowsPortsOverApi(deps) {
|
|
14794
|
+
const secrets = deps.secrets ?? null;
|
|
14669
14795
|
return {
|
|
14670
14796
|
warn: (message) => deps.log.warn(message),
|
|
14671
14797
|
listIntegrations: async (addonId) => (await deps.api.list()).filter((i) => i.addonId === addonId).map((i) => ({
|
|
14672
14798
|
id: i.id,
|
|
14673
14799
|
name: i.name
|
|
14674
14800
|
})),
|
|
14675
|
-
readRows: (integrationId) => deps.api.
|
|
14801
|
+
readRows: async (integrationId) => resolvedRows(await deps.api.readLegacyIntegrationRows({ integrationId }), secrets),
|
|
14676
14802
|
putRecord: async (id, body) => {
|
|
14677
14803
|
if (await deps.records.get(id) !== null) return "exists";
|
|
14678
14804
|
try {
|
|
@@ -14691,15 +14817,17 @@ function integrationRowsPortsOverApi(deps) {
|
|
|
14691
14817
|
await deps.records.delete(id);
|
|
14692
14818
|
},
|
|
14693
14819
|
clearRows: async (integrationId) => {
|
|
14694
|
-
await deps.api.
|
|
14820
|
+
const refs = collectSecretMarkerRefs(await deps.api.readLegacyIntegrationRows({ integrationId }));
|
|
14821
|
+
if (refs.length > 0 && secrets !== null) await secrets.delete(refs);
|
|
14822
|
+
await deps.api.clearLegacyIntegrationRows({ integrationId });
|
|
14695
14823
|
}
|
|
14696
14824
|
};
|
|
14697
14825
|
}
|
|
14698
14826
|
function defaultIntegrationApiOver(api) {
|
|
14699
14827
|
return {
|
|
14700
14828
|
list: () => api.integrations.list.query(),
|
|
14701
|
-
|
|
14702
|
-
|
|
14829
|
+
readLegacyIntegrationRows: (input) => api.migrations.readLegacyIntegrationRows.query(input),
|
|
14830
|
+
clearLegacyIntegrationRows: (input) => api.migrations.clearLegacyIntegrationRows.mutate(input),
|
|
14703
14831
|
listDevices: (addonId) => api.deviceManager.listAll.query({
|
|
14704
14832
|
addonId,
|
|
14705
14833
|
projection: "slim"
|
|
@@ -50377,6 +50505,9 @@ var RunningProposalEvidenceSchema = object({
|
|
|
50377
50505
|
runsFound: number().int().nonnegative(),
|
|
50378
50506
|
pausesFound: number().int().nonnegative(),
|
|
50379
50507
|
longestPauseMs: number().nonnegative(),
|
|
50508
|
+
/** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
|
|
50509
|
+
pulsesFound: number().int().nonnegative(),
|
|
50510
|
+
longestPulseMs: number().nonnegative(),
|
|
50380
50511
|
idleFloorW: number(),
|
|
50381
50512
|
noiseCeilingW: number(),
|
|
50382
50513
|
plateauW: number(),
|
|
@@ -56243,11 +56374,7 @@ var CreateIntegrationInputSchema = object({
|
|
|
56243
56374
|
info: record(string(), unknown()).optional(),
|
|
56244
56375
|
/** `stableId` of the discovery candidate the form was seeded from (D984). */
|
|
56245
56376
|
originStableId: string().optional(),
|
|
56246
|
-
settings: record(string(), unknown()).optional()
|
|
56247
|
-
/** Keys of `settings` the form's schema declares secret (by type) — kept in
|
|
56248
|
-
* the keychain, not the table (D787). */
|
|
56249
|
-
secretKeys: array(string()).optional(),
|
|
56250
|
-
skipRestart: boolean().optional()
|
|
56377
|
+
settings: record(string(), unknown()).optional()
|
|
56251
56378
|
});
|
|
56252
56379
|
var UpdateIntegrationInputSchema = object({
|
|
56253
56380
|
id: string(),
|
|
@@ -56268,22 +56395,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
56268
56395
|
}), method(object({ id: string() }), DeleteIntegrationResultSchema, {
|
|
56269
56396
|
kind: "mutation",
|
|
56270
56397
|
auth: "admin"
|
|
56271
|
-
}), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
|
|
56272
|
-
id: string(),
|
|
56273
|
-
settings: record(string(), unknown()),
|
|
56274
|
-
/** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
|
|
56275
|
-
secretKeys: array(string()).optional(),
|
|
56276
|
-
/** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
|
|
56277
|
-
skipRestart: boolean().optional()
|
|
56278
|
-
}), object({ success: boolean() }), {
|
|
56279
|
-
kind: "mutation",
|
|
56280
|
-
auth: "admin"
|
|
56281
|
-
}), method(object({ id: string() }), object({
|
|
56282
|
-
rows: number().int().nonnegative(),
|
|
56283
|
-
secrets: number().int().nonnegative()
|
|
56284
|
-
}), {
|
|
56285
|
-
kind: "mutation",
|
|
56286
|
-
auth: "admin"
|
|
56287
56398
|
}), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
|
|
56288
56399
|
addonId: string(),
|
|
56289
56400
|
settings: record(string(), unknown())
|
|
@@ -59260,6 +59371,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
|
|
|
59260
59371
|
}), {
|
|
59261
59372
|
kind: "mutation",
|
|
59262
59373
|
auth: "admin"
|
|
59374
|
+
}), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
|
|
59375
|
+
rows: number().int().nonnegative(),
|
|
59376
|
+
secrets: number().int().nonnegative()
|
|
59377
|
+
}), {
|
|
59378
|
+
kind: "mutation",
|
|
59379
|
+
auth: "admin"
|
|
59380
|
+
}), method(object({
|
|
59381
|
+
integrationId: string().min(1),
|
|
59382
|
+
patch: record(string(), unknown())
|
|
59383
|
+
}), object({ written: boolean() }), {
|
|
59384
|
+
kind: "mutation",
|
|
59385
|
+
auth: "admin"
|
|
59386
|
+
}), method(object({
|
|
59387
|
+
integrationId: string().min(1),
|
|
59388
|
+
keys: array(string().min(1))
|
|
59389
|
+
}), object({
|
|
59390
|
+
present: boolean(),
|
|
59391
|
+
removed: record(string(), unknown())
|
|
59392
|
+
}), {
|
|
59393
|
+
kind: "mutation",
|
|
59394
|
+
auth: "admin"
|
|
59263
59395
|
});
|
|
59264
59396
|
method(object({
|
|
59265
59397
|
sourceUrl: string(),
|
|
@@ -80678,11 +80810,15 @@ discriminatedUnion("kind", [object({
|
|
|
80678
80810
|
* 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
|
|
80679
80811
|
* near F. A device that is not near one level most of the time has no floor.
|
|
80680
80812
|
* 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
|
|
80681
|
-
* 3.
|
|
80682
|
-
*
|
|
80813
|
+
* 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
|
|
80814
|
+
* display waking): it is neither a run nor a pause. Among the remaining
|
|
80815
|
+
* episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
|
|
80816
|
+
* two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
|
|
80817
|
+
* a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
|
|
80818
|
+
* is no run.
|
|
80683
80819
|
* 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
|
|
80684
|
-
* grid a person would type; stop dwell covers the longest pause
|
|
80685
|
-
* covers the longest standby blip above T.
|
|
80820
|
+
* grid a person would type; stop dwell covers the longest pause and the
|
|
80821
|
+
* kind's floor; start dwell covers the longest pulse and standby blip above T.
|
|
80686
80822
|
* 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
|
|
80687
80823
|
* run must hold exactly one true interval and nothing may flap outside.
|
|
80688
80824
|
*/
|
|
@@ -80693,6 +80829,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
|
|
|
80693
80829
|
30 * SECOND_MS;
|
|
80694
80830
|
60 * SECOND_MS;
|
|
80695
80831
|
20 * MINUTE_MS$1;
|
|
80832
|
+
90 * SECOND_MS;
|
|
80833
|
+
30 * MINUTE_MS$1;
|
|
80834
|
+
10 * MINUTE_MS$1;
|
|
80835
|
+
DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
|
|
80696
80836
|
10 * SECOND_MS;
|
|
80697
80837
|
10 * SECOND_MS;
|
|
80698
80838
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
@@ -85977,13 +86117,6 @@ Object.freeze({
|
|
|
85977
86117
|
access: "create",
|
|
85978
86118
|
role: "admin"
|
|
85979
86119
|
},
|
|
85980
|
-
"integrations.clearSettings": {
|
|
85981
|
-
capName: "integrations",
|
|
85982
|
-
capScope: "system",
|
|
85983
|
-
addonId: null,
|
|
85984
|
-
access: "delete",
|
|
85985
|
-
role: "admin"
|
|
85986
|
-
},
|
|
85987
86120
|
"integrations.create": {
|
|
85988
86121
|
capName: "integrations",
|
|
85989
86122
|
capScope: "system",
|
|
@@ -86019,13 +86152,6 @@ Object.freeze({
|
|
|
86019
86152
|
access: "view",
|
|
86020
86153
|
role: "admin"
|
|
86021
86154
|
},
|
|
86022
|
-
"integrations.getSettings": {
|
|
86023
|
-
capName: "integrations",
|
|
86024
|
-
capScope: "system",
|
|
86025
|
-
addonId: null,
|
|
86026
|
-
access: "view",
|
|
86027
|
-
role: "admin"
|
|
86028
|
-
},
|
|
86029
86155
|
"integrations.list": {
|
|
86030
86156
|
capName: "integrations",
|
|
86031
86157
|
capScope: "system",
|
|
@@ -86033,13 +86159,6 @@ Object.freeze({
|
|
|
86033
86159
|
access: "view",
|
|
86034
86160
|
role: "admin"
|
|
86035
86161
|
},
|
|
86036
|
-
"integrations.setSettings": {
|
|
86037
|
-
capName: "integrations",
|
|
86038
|
-
capScope: "system",
|
|
86039
|
-
addonId: null,
|
|
86040
|
-
access: "create",
|
|
86041
|
-
role: "admin"
|
|
86042
|
-
},
|
|
86043
86162
|
"integrations.testConnection": {
|
|
86044
86163
|
capName: "integrations",
|
|
86045
86164
|
capScope: "system",
|
|
@@ -87034,6 +87153,13 @@ Object.freeze({
|
|
|
87034
87153
|
access: "view",
|
|
87035
87154
|
role: "admin"
|
|
87036
87155
|
},
|
|
87156
|
+
"migrations.clearLegacyIntegrationRows": {
|
|
87157
|
+
capName: "migrations",
|
|
87158
|
+
capScope: "system",
|
|
87159
|
+
addonId: null,
|
|
87160
|
+
access: "delete",
|
|
87161
|
+
role: "admin"
|
|
87162
|
+
},
|
|
87037
87163
|
"migrations.declareOwner": {
|
|
87038
87164
|
capName: "migrations",
|
|
87039
87165
|
capScope: "system",
|
|
@@ -87041,6 +87167,13 @@ Object.freeze({
|
|
|
87041
87167
|
access: "create",
|
|
87042
87168
|
role: "admin"
|
|
87043
87169
|
},
|
|
87170
|
+
"migrations.deleteLegacyIntegrationRowKeys": {
|
|
87171
|
+
capName: "migrations",
|
|
87172
|
+
capScope: "system",
|
|
87173
|
+
addonId: null,
|
|
87174
|
+
access: "delete",
|
|
87175
|
+
role: "admin"
|
|
87176
|
+
},
|
|
87044
87177
|
"migrations.dryRun": {
|
|
87045
87178
|
capName: "migrations",
|
|
87046
87179
|
capScope: "system",
|
|
@@ -87076,6 +87209,20 @@ Object.freeze({
|
|
|
87076
87209
|
access: "create",
|
|
87077
87210
|
role: "admin"
|
|
87078
87211
|
},
|
|
87212
|
+
"migrations.patchLegacyIntegrationRows": {
|
|
87213
|
+
capName: "migrations",
|
|
87214
|
+
capScope: "system",
|
|
87215
|
+
addonId: null,
|
|
87216
|
+
access: "create",
|
|
87217
|
+
role: "admin"
|
|
87218
|
+
},
|
|
87219
|
+
"migrations.readLegacyIntegrationRows": {
|
|
87220
|
+
capName: "migrations",
|
|
87221
|
+
capScope: "system",
|
|
87222
|
+
addonId: null,
|
|
87223
|
+
access: "view",
|
|
87224
|
+
role: "admin"
|
|
87225
|
+
},
|
|
87079
87226
|
"migrations.restorePreMigration": {
|
|
87080
87227
|
capName: "migrations",
|
|
87081
87228
|
capScope: "system",
|
|
@@ -97450,7 +97597,7 @@ function gatewayKeyOf(integration) {
|
|
|
97450
97597
|
return origin.length > 0 ? origin : `ecowitt:${slugifyHost(integration.id)}`;
|
|
97451
97598
|
}
|
|
97452
97599
|
/**
|
|
97453
|
-
* The integration
|
|
97600
|
+
* The integration instance form (the `formSchema` of its instances): the connection lives on the
|
|
97454
97601
|
* INTEGRATION, which carries the name. The `password` field is what puts the value in the keychain.
|
|
97455
97602
|
*/
|
|
97456
97603
|
function buildConnectionFormSchema() {
|
|
@@ -105324,6 +105471,7 @@ var EcowittProviderAddon = class extends BaseDeviceProvider {
|
|
|
105324
105471
|
if (records === void 0) throw new Error("provider-ecowitt: ctx.records is required (hub-only addon)");
|
|
105325
105472
|
const rowsPorts = integrationRowsPortsOverApi({
|
|
105326
105473
|
api: defaultIntegrationApiOver(this.ctx.api),
|
|
105474
|
+
secrets: this.ctx.secrets ?? null,
|
|
105327
105475
|
records: records.kind({
|
|
105328
105476
|
name: ecowittConnectionsEntryKind.name,
|
|
105329
105477
|
declaration: ecowittConnectionsEntryKind.declaration,
|
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 });
|
|
@@ -7927,10 +7953,9 @@ var BaseAddon = class {
|
|
|
7927
7953
|
*
|
|
7928
7954
|
* Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
|
|
7929
7955
|
* needs its connection fields, the default integration the addon's own sections. It is the
|
|
7930
|
-
* SHAPE of the form only — the values live
|
|
7931
|
-
* unchanged (`integrations.setSettings` for an account integration).
|
|
7956
|
+
* SHAPE of the form only — the values live on the instance record.
|
|
7932
7957
|
*/
|
|
7933
|
-
async
|
|
7958
|
+
async integrationFormSchema(_instance) {
|
|
7934
7959
|
return null;
|
|
7935
7960
|
}
|
|
7936
7961
|
/**
|
|
@@ -7976,7 +8001,7 @@ var BaseAddon = class {
|
|
|
7976
8001
|
* per-instance schema when it declares one, else its global schema (what they always showed).
|
|
7977
8002
|
*/
|
|
7978
8003
|
async getIntegrationFormSchema(instance, nodeId) {
|
|
7979
|
-
const scoped = await this.
|
|
8004
|
+
const scoped = await this.integrationFormSchema(instance);
|
|
7980
8005
|
if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
|
|
7981
8006
|
return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
|
|
7982
8007
|
}
|
|
@@ -8024,6 +8049,7 @@ var BaseAddon = class {
|
|
|
8024
8049
|
* resolved blob back is a no-op for every unchanged secret.
|
|
8025
8050
|
*/
|
|
8026
8051
|
async resolveGlobalStore(nodeId, cap) {
|
|
8052
|
+
await this.loadScopedSchemas(cap);
|
|
8027
8053
|
const raw = await this._ctx?.settings?.readAddonStore() ?? {};
|
|
8028
8054
|
return this.projectForNode(raw, nodeId, cap);
|
|
8029
8055
|
}
|
|
@@ -8034,6 +8060,7 @@ var BaseAddon = class {
|
|
|
8034
8060
|
}
|
|
8035
8061
|
/** The PROJECTED store with its markers — the form hydrate only (D798). */
|
|
8036
8062
|
async readRawProjectedStore(nodeId, cap) {
|
|
8063
|
+
await this.loadScopedSchemas(cap);
|
|
8037
8064
|
const raw = await this.rawSettings()?.readAddonStore() ?? {};
|
|
8038
8065
|
return this.projectForNode(raw, nodeId, cap);
|
|
8039
8066
|
}
|
|
@@ -8136,6 +8163,7 @@ var BaseAddon = class {
|
|
|
8136
8163
|
await this.applyGlobalPatch(patch, nodeId);
|
|
8137
8164
|
}
|
|
8138
8165
|
async applyGlobalPatch(barePatch, nodeId) {
|
|
8166
|
+
await this.loadScopedSchemas();
|
|
8139
8167
|
const keys = this.perNodeKeys();
|
|
8140
8168
|
const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
|
|
8141
8169
|
const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
|
|
@@ -8149,7 +8177,8 @@ var BaseAddon = class {
|
|
|
8149
8177
|
await this.resolveConfig();
|
|
8150
8178
|
await this.onConfigChanged();
|
|
8151
8179
|
this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
|
|
8152
|
-
|
|
8180
|
+
const legacy = this.globalSettingsSchema();
|
|
8181
|
+
this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
|
|
8153
8182
|
}
|
|
8154
8183
|
/**
|
|
8155
8184
|
* The set of field keys the global settings schema declares `perNode: true`
|
|
@@ -8157,14 +8186,67 @@ var BaseAddon = class {
|
|
|
8157
8186
|
* declarations). Empty set ⇒ every per-node code path is bypassed and the
|
|
8158
8187
|
* settings API behaves exactly like the legacy node-agnostic one.
|
|
8159
8188
|
*/
|
|
8160
|
-
|
|
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
|
+
*/
|
|
8161
8239
|
perNodeKeys(cap) {
|
|
8162
8240
|
const cacheKey = cap ?? "";
|
|
8163
|
-
const cached = this.
|
|
8241
|
+
const cached = this._nodeKeysByCap.get(cacheKey);
|
|
8164
8242
|
if (cached) return cached;
|
|
8165
|
-
const
|
|
8166
|
-
const keys = new Set(
|
|
8167
|
-
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);
|
|
8168
8250
|
return keys;
|
|
8169
8251
|
}
|
|
8170
8252
|
/**
|
|
@@ -8174,10 +8256,9 @@ var BaseAddon = class {
|
|
|
8174
8256
|
* time to return its response before the addon is torn down and
|
|
8175
8257
|
* re-initialised by `AddonRegistryService.restartAddon`.
|
|
8176
8258
|
*/
|
|
8177
|
-
maybeAutoRestart(patchKeys,
|
|
8178
|
-
if (!schema) return;
|
|
8259
|
+
maybeAutoRestart(patchKeys, schemas) {
|
|
8179
8260
|
const restartKeys = /* @__PURE__ */ new Set();
|
|
8180
|
-
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) {
|
|
8181
8262
|
if (field.type === "separator" || field.type === "info") continue;
|
|
8182
8263
|
if (field.requiresRestart) restartKeys.add(field.key);
|
|
8183
8264
|
}
|
|
@@ -8373,12 +8454,14 @@ var BaseAddon = class {
|
|
|
8373
8454
|
* here, in the addon's own process, so the hub keeps no copy of it.
|
|
8374
8455
|
*/
|
|
8375
8456
|
async getDeclaredSettingsKeys() {
|
|
8457
|
+
await this.loadScopedSchemas();
|
|
8376
8458
|
return { keys: [...this.declaredKeySet()].toSorted() };
|
|
8377
8459
|
}
|
|
8378
8460
|
declaredKeySet() {
|
|
8379
8461
|
const keys = new Set(Object.keys(this.defaults));
|
|
8380
8462
|
const schema = this.globalSettingsSchema();
|
|
8381
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);
|
|
8382
8465
|
for (const key of this._declaredStateKeys) keys.add(key);
|
|
8383
8466
|
for (const path of this.secretStatePaths) {
|
|
8384
8467
|
const root = path.split(/[.[]/, 1)[0];
|
|
@@ -8474,7 +8557,11 @@ var BaseAddon = class {
|
|
|
8474
8557
|
}
|
|
8475
8558
|
/** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
|
|
8476
8559
|
addonSecretTree() {
|
|
8477
|
-
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
|
+
}
|
|
8478
8565
|
return this._addonSecretTree;
|
|
8479
8566
|
}
|
|
8480
8567
|
deviceSecretTree() {
|
|
@@ -8724,6 +8811,23 @@ function dropPerNodeFields(fields) {
|
|
|
8724
8811
|
}
|
|
8725
8812
|
return kept;
|
|
8726
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
|
+
}
|
|
8727
8831
|
function collectFieldKeys(fields) {
|
|
8728
8832
|
const collected = [];
|
|
8729
8833
|
for (const field of fields) {
|
|
@@ -11950,7 +12054,7 @@ function userError(code, params, englishFallback) {
|
|
|
11950
12054
|
return new CamstackError(code, params, englishFallback);
|
|
11951
12055
|
}
|
|
11952
12056
|
//#endregion
|
|
11953
|
-
//#region ../types/dist/composition-
|
|
12057
|
+
//#region ../types/dist/composition-pOHbTzRB.mjs
|
|
11954
12058
|
/**
|
|
11955
12059
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11956
12060
|
*
|
|
@@ -13422,12 +13526,22 @@ var RecordIntegrationInstances = class {
|
|
|
13422
13526
|
...this.hooks.description,
|
|
13423
13527
|
mintsId: true
|
|
13424
13528
|
};
|
|
13425
|
-
|
|
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));
|
|
13426
13540
|
listInstances = async () => {
|
|
13427
13541
|
return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
|
|
13428
13542
|
};
|
|
13429
13543
|
getInstance = async ({ id }) => {
|
|
13430
|
-
const record = await this.handle.get(id);
|
|
13544
|
+
const record = await this.handle.get(await this.resolveRecordId(id));
|
|
13431
13545
|
return record === null ? null : this.viewOf(record);
|
|
13432
13546
|
};
|
|
13433
13547
|
/** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
|
|
@@ -13444,7 +13558,8 @@ var RecordIntegrationInstances = class {
|
|
|
13444
13558
|
await this.hooks.onChanged?.(minted, "created");
|
|
13445
13559
|
return { id: minted };
|
|
13446
13560
|
};
|
|
13447
|
-
updateInstance = async ({ id, name, values }) => {
|
|
13561
|
+
updateInstance = async ({ id: askedId, name, values }) => {
|
|
13562
|
+
const id = await this.resolveRecordId(askedId);
|
|
13448
13563
|
const patch = {};
|
|
13449
13564
|
for (const [key, value] of Object.entries(values)) {
|
|
13450
13565
|
if (value === void 0) continue;
|
|
@@ -13479,7 +13594,10 @@ var RecordIntegrationInstances = class {
|
|
|
13479
13594
|
status: "unsupported",
|
|
13480
13595
|
error: "This integration declares no connection test"
|
|
13481
13596
|
};
|
|
13482
|
-
return this.hooks.test(input
|
|
13597
|
+
return this.hooks.test(input.id === null ? input : {
|
|
13598
|
+
...input,
|
|
13599
|
+
id: await this.resolveRecordId(input.id)
|
|
13600
|
+
});
|
|
13483
13601
|
};
|
|
13484
13602
|
/** The flat provider object to hand to `registerProvider`. */
|
|
13485
13603
|
toProvider() {
|
|
@@ -14658,20 +14776,28 @@ async function moveIntegrationRowsToRecords(ports, spec) {
|
|
|
14658
14776
|
};
|
|
14659
14777
|
}
|
|
14660
14778
|
/**
|
|
14661
|
-
* The addon-side ports of `moveIntegrationRowsToRecords` (planning#416)
|
|
14662
|
-
* from its own process, so the
|
|
14663
|
-
* `
|
|
14664
|
-
*
|
|
14665
|
-
*
|
|
14779
|
+
* The addon-side ports of `moveIntegrationRowsToRecords` (planning#416). An addon runs the move
|
|
14780
|
+
* from its own process, so the leftover rows are reached through the migration-only door
|
|
14781
|
+
* (`migrations.readLegacyIntegrationRows` / `clearLegacyIntegrationRows`) on the hub's connection.
|
|
14782
|
+
* The door returns markers. This process is the owner, so it resolves and releases them with
|
|
14783
|
+
* `resolveSecretMarkers` — the same call the old settings door used — and only then asks the
|
|
14784
|
+
* door to delete the rows. The addon's own record kind is its records handle.
|
|
14666
14785
|
*/
|
|
14786
|
+
async function resolvedRows(stored, secrets) {
|
|
14787
|
+
if (secrets === null) return stored;
|
|
14788
|
+
const { value, unresolved } = await resolveSecretMarkers(stored, secrets);
|
|
14789
|
+
if (unresolved.length > 0) throw new Error(`${unresolved.length} secret(s) cannot be read from the keychain`);
|
|
14790
|
+
return value;
|
|
14791
|
+
}
|
|
14667
14792
|
function integrationRowsPortsOverApi(deps) {
|
|
14793
|
+
const secrets = deps.secrets ?? null;
|
|
14668
14794
|
return {
|
|
14669
14795
|
warn: (message) => deps.log.warn(message),
|
|
14670
14796
|
listIntegrations: async (addonId) => (await deps.api.list()).filter((i) => i.addonId === addonId).map((i) => ({
|
|
14671
14797
|
id: i.id,
|
|
14672
14798
|
name: i.name
|
|
14673
14799
|
})),
|
|
14674
|
-
readRows: (integrationId) => deps.api.
|
|
14800
|
+
readRows: async (integrationId) => resolvedRows(await deps.api.readLegacyIntegrationRows({ integrationId }), secrets),
|
|
14675
14801
|
putRecord: async (id, body) => {
|
|
14676
14802
|
if (await deps.records.get(id) !== null) return "exists";
|
|
14677
14803
|
try {
|
|
@@ -14690,15 +14816,17 @@ function integrationRowsPortsOverApi(deps) {
|
|
|
14690
14816
|
await deps.records.delete(id);
|
|
14691
14817
|
},
|
|
14692
14818
|
clearRows: async (integrationId) => {
|
|
14693
|
-
await deps.api.
|
|
14819
|
+
const refs = collectSecretMarkerRefs(await deps.api.readLegacyIntegrationRows({ integrationId }));
|
|
14820
|
+
if (refs.length > 0 && secrets !== null) await secrets.delete(refs);
|
|
14821
|
+
await deps.api.clearLegacyIntegrationRows({ integrationId });
|
|
14694
14822
|
}
|
|
14695
14823
|
};
|
|
14696
14824
|
}
|
|
14697
14825
|
function defaultIntegrationApiOver(api) {
|
|
14698
14826
|
return {
|
|
14699
14827
|
list: () => api.integrations.list.query(),
|
|
14700
|
-
|
|
14701
|
-
|
|
14828
|
+
readLegacyIntegrationRows: (input) => api.migrations.readLegacyIntegrationRows.query(input),
|
|
14829
|
+
clearLegacyIntegrationRows: (input) => api.migrations.clearLegacyIntegrationRows.mutate(input),
|
|
14702
14830
|
listDevices: (addonId) => api.deviceManager.listAll.query({
|
|
14703
14831
|
addonId,
|
|
14704
14832
|
projection: "slim"
|
|
@@ -50376,6 +50504,9 @@ var RunningProposalEvidenceSchema = object({
|
|
|
50376
50504
|
runsFound: number().int().nonnegative(),
|
|
50377
50505
|
pausesFound: number().int().nonnegative(),
|
|
50378
50506
|
longestPauseMs: number().nonnegative(),
|
|
50507
|
+
/** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
|
|
50508
|
+
pulsesFound: number().int().nonnegative(),
|
|
50509
|
+
longestPulseMs: number().nonnegative(),
|
|
50379
50510
|
idleFloorW: number(),
|
|
50380
50511
|
noiseCeilingW: number(),
|
|
50381
50512
|
plateauW: number(),
|
|
@@ -56242,11 +56373,7 @@ var CreateIntegrationInputSchema = object({
|
|
|
56242
56373
|
info: record(string(), unknown()).optional(),
|
|
56243
56374
|
/** `stableId` of the discovery candidate the form was seeded from (D984). */
|
|
56244
56375
|
originStableId: string().optional(),
|
|
56245
|
-
settings: record(string(), unknown()).optional()
|
|
56246
|
-
/** Keys of `settings` the form's schema declares secret (by type) — kept in
|
|
56247
|
-
* the keychain, not the table (D787). */
|
|
56248
|
-
secretKeys: array(string()).optional(),
|
|
56249
|
-
skipRestart: boolean().optional()
|
|
56376
|
+
settings: record(string(), unknown()).optional()
|
|
56250
56377
|
});
|
|
56251
56378
|
var UpdateIntegrationInputSchema = object({
|
|
56252
56379
|
id: string(),
|
|
@@ -56267,22 +56394,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
56267
56394
|
}), method(object({ id: string() }), DeleteIntegrationResultSchema, {
|
|
56268
56395
|
kind: "mutation",
|
|
56269
56396
|
auth: "admin"
|
|
56270
|
-
}), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
|
|
56271
|
-
id: string(),
|
|
56272
|
-
settings: record(string(), unknown()),
|
|
56273
|
-
/** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
|
|
56274
|
-
secretKeys: array(string()).optional(),
|
|
56275
|
-
/** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
|
|
56276
|
-
skipRestart: boolean().optional()
|
|
56277
|
-
}), object({ success: boolean() }), {
|
|
56278
|
-
kind: "mutation",
|
|
56279
|
-
auth: "admin"
|
|
56280
|
-
}), method(object({ id: string() }), object({
|
|
56281
|
-
rows: number().int().nonnegative(),
|
|
56282
|
-
secrets: number().int().nonnegative()
|
|
56283
|
-
}), {
|
|
56284
|
-
kind: "mutation",
|
|
56285
|
-
auth: "admin"
|
|
56286
56397
|
}), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
|
|
56287
56398
|
addonId: string(),
|
|
56288
56399
|
settings: record(string(), unknown())
|
|
@@ -59259,6 +59370,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
|
|
|
59259
59370
|
}), {
|
|
59260
59371
|
kind: "mutation",
|
|
59261
59372
|
auth: "admin"
|
|
59373
|
+
}), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
|
|
59374
|
+
rows: number().int().nonnegative(),
|
|
59375
|
+
secrets: number().int().nonnegative()
|
|
59376
|
+
}), {
|
|
59377
|
+
kind: "mutation",
|
|
59378
|
+
auth: "admin"
|
|
59379
|
+
}), method(object({
|
|
59380
|
+
integrationId: string().min(1),
|
|
59381
|
+
patch: record(string(), unknown())
|
|
59382
|
+
}), object({ written: boolean() }), {
|
|
59383
|
+
kind: "mutation",
|
|
59384
|
+
auth: "admin"
|
|
59385
|
+
}), method(object({
|
|
59386
|
+
integrationId: string().min(1),
|
|
59387
|
+
keys: array(string().min(1))
|
|
59388
|
+
}), object({
|
|
59389
|
+
present: boolean(),
|
|
59390
|
+
removed: record(string(), unknown())
|
|
59391
|
+
}), {
|
|
59392
|
+
kind: "mutation",
|
|
59393
|
+
auth: "admin"
|
|
59262
59394
|
});
|
|
59263
59395
|
method(object({
|
|
59264
59396
|
sourceUrl: string(),
|
|
@@ -80677,11 +80809,15 @@ discriminatedUnion("kind", [object({
|
|
|
80677
80809
|
* 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
|
|
80678
80810
|
* near F. A device that is not near one level most of the time has no floor.
|
|
80679
80811
|
* 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
|
|
80680
|
-
* 3.
|
|
80681
|
-
*
|
|
80812
|
+
* 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
|
|
80813
|
+
* display waking): it is neither a run nor a pause. Among the remaining
|
|
80814
|
+
* episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
|
|
80815
|
+
* two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
|
|
80816
|
+
* a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
|
|
80817
|
+
* is no run.
|
|
80682
80818
|
* 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
|
|
80683
|
-
* grid a person would type; stop dwell covers the longest pause
|
|
80684
|
-
* covers the longest standby blip above T.
|
|
80819
|
+
* grid a person would type; stop dwell covers the longest pause and the
|
|
80820
|
+
* kind's floor; start dwell covers the longest pulse and standby blip above T.
|
|
80685
80821
|
* 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
|
|
80686
80822
|
* run must hold exactly one true interval and nothing may flap outside.
|
|
80687
80823
|
*/
|
|
@@ -80692,6 +80828,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
|
|
|
80692
80828
|
30 * SECOND_MS;
|
|
80693
80829
|
60 * SECOND_MS;
|
|
80694
80830
|
20 * MINUTE_MS$1;
|
|
80831
|
+
90 * SECOND_MS;
|
|
80832
|
+
30 * MINUTE_MS$1;
|
|
80833
|
+
10 * MINUTE_MS$1;
|
|
80834
|
+
DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
|
|
80695
80835
|
10 * SECOND_MS;
|
|
80696
80836
|
10 * SECOND_MS;
|
|
80697
80837
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
@@ -85976,13 +86116,6 @@ Object.freeze({
|
|
|
85976
86116
|
access: "create",
|
|
85977
86117
|
role: "admin"
|
|
85978
86118
|
},
|
|
85979
|
-
"integrations.clearSettings": {
|
|
85980
|
-
capName: "integrations",
|
|
85981
|
-
capScope: "system",
|
|
85982
|
-
addonId: null,
|
|
85983
|
-
access: "delete",
|
|
85984
|
-
role: "admin"
|
|
85985
|
-
},
|
|
85986
86119
|
"integrations.create": {
|
|
85987
86120
|
capName: "integrations",
|
|
85988
86121
|
capScope: "system",
|
|
@@ -86018,13 +86151,6 @@ Object.freeze({
|
|
|
86018
86151
|
access: "view",
|
|
86019
86152
|
role: "admin"
|
|
86020
86153
|
},
|
|
86021
|
-
"integrations.getSettings": {
|
|
86022
|
-
capName: "integrations",
|
|
86023
|
-
capScope: "system",
|
|
86024
|
-
addonId: null,
|
|
86025
|
-
access: "view",
|
|
86026
|
-
role: "admin"
|
|
86027
|
-
},
|
|
86028
86154
|
"integrations.list": {
|
|
86029
86155
|
capName: "integrations",
|
|
86030
86156
|
capScope: "system",
|
|
@@ -86032,13 +86158,6 @@ Object.freeze({
|
|
|
86032
86158
|
access: "view",
|
|
86033
86159
|
role: "admin"
|
|
86034
86160
|
},
|
|
86035
|
-
"integrations.setSettings": {
|
|
86036
|
-
capName: "integrations",
|
|
86037
|
-
capScope: "system",
|
|
86038
|
-
addonId: null,
|
|
86039
|
-
access: "create",
|
|
86040
|
-
role: "admin"
|
|
86041
|
-
},
|
|
86042
86161
|
"integrations.testConnection": {
|
|
86043
86162
|
capName: "integrations",
|
|
86044
86163
|
capScope: "system",
|
|
@@ -87033,6 +87152,13 @@ Object.freeze({
|
|
|
87033
87152
|
access: "view",
|
|
87034
87153
|
role: "admin"
|
|
87035
87154
|
},
|
|
87155
|
+
"migrations.clearLegacyIntegrationRows": {
|
|
87156
|
+
capName: "migrations",
|
|
87157
|
+
capScope: "system",
|
|
87158
|
+
addonId: null,
|
|
87159
|
+
access: "delete",
|
|
87160
|
+
role: "admin"
|
|
87161
|
+
},
|
|
87036
87162
|
"migrations.declareOwner": {
|
|
87037
87163
|
capName: "migrations",
|
|
87038
87164
|
capScope: "system",
|
|
@@ -87040,6 +87166,13 @@ Object.freeze({
|
|
|
87040
87166
|
access: "create",
|
|
87041
87167
|
role: "admin"
|
|
87042
87168
|
},
|
|
87169
|
+
"migrations.deleteLegacyIntegrationRowKeys": {
|
|
87170
|
+
capName: "migrations",
|
|
87171
|
+
capScope: "system",
|
|
87172
|
+
addonId: null,
|
|
87173
|
+
access: "delete",
|
|
87174
|
+
role: "admin"
|
|
87175
|
+
},
|
|
87043
87176
|
"migrations.dryRun": {
|
|
87044
87177
|
capName: "migrations",
|
|
87045
87178
|
capScope: "system",
|
|
@@ -87075,6 +87208,20 @@ Object.freeze({
|
|
|
87075
87208
|
access: "create",
|
|
87076
87209
|
role: "admin"
|
|
87077
87210
|
},
|
|
87211
|
+
"migrations.patchLegacyIntegrationRows": {
|
|
87212
|
+
capName: "migrations",
|
|
87213
|
+
capScope: "system",
|
|
87214
|
+
addonId: null,
|
|
87215
|
+
access: "create",
|
|
87216
|
+
role: "admin"
|
|
87217
|
+
},
|
|
87218
|
+
"migrations.readLegacyIntegrationRows": {
|
|
87219
|
+
capName: "migrations",
|
|
87220
|
+
capScope: "system",
|
|
87221
|
+
addonId: null,
|
|
87222
|
+
access: "view",
|
|
87223
|
+
role: "admin"
|
|
87224
|
+
},
|
|
87078
87225
|
"migrations.restorePreMigration": {
|
|
87079
87226
|
capName: "migrations",
|
|
87080
87227
|
capScope: "system",
|
|
@@ -97449,7 +97596,7 @@ function gatewayKeyOf(integration) {
|
|
|
97449
97596
|
return origin.length > 0 ? origin : `ecowitt:${slugifyHost(integration.id)}`;
|
|
97450
97597
|
}
|
|
97451
97598
|
/**
|
|
97452
|
-
* The integration
|
|
97599
|
+
* The integration instance form (the `formSchema` of its instances): the connection lives on the
|
|
97453
97600
|
* INTEGRATION, which carries the name. The `password` field is what puts the value in the keychain.
|
|
97454
97601
|
*/
|
|
97455
97602
|
function buildConnectionFormSchema() {
|
|
@@ -105323,6 +105470,7 @@ var EcowittProviderAddon = class extends BaseDeviceProvider {
|
|
|
105323
105470
|
if (records === void 0) throw new Error("provider-ecowitt: ctx.records is required (hub-only addon)");
|
|
105324
105471
|
const rowsPorts = integrationRowsPortsOverApi({
|
|
105325
105472
|
api: defaultIntegrationApiOver(this.ctx.api),
|
|
105473
|
+
secrets: this.ctx.secrets ?? null,
|
|
105326
105474
|
records: records.kind({
|
|
105327
105475
|
name: ecowittConnectionsEntryKind.name,
|
|
105328
105476
|
declaration: ecowittConnectionsEntryKind.declaration,
|
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": [
|