@camstack/addon-provider-bosch 0.1.36 → 0.1.38
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/{account-integration-offline-Bnm17sAE.js → account-integration-offline-CeMaWElV.js} +1 -1
- package/dist/{account-integration-offline-9ooJuNrN.mjs → account-integration-offline-DhHNdL3k.mjs} +1 -1
- package/dist/{bosch-instances-BIvOtuzj.mjs → bosch-instances-CIuDwXQ8.mjs} +252 -81
- package/dist/{bosch-instances-CXmuRkef.js → bosch-instances-Y9sMCElY.js} +252 -81
- package/dist/home-connect/addon.js +10 -4
- package/dist/home-connect/addon.mjs +10 -4
- package/dist/homecom/addon.js +10 -4
- package/dist/homecom/addon.mjs +10 -4
- package/dist/shc/addon.js +2 -1
- package/dist/shc/addon.mjs +2 -1
- package/package.json +2 -2
|
@@ -5981,7 +5981,7 @@ var ZodIssueCode = {
|
|
|
5981
5981
|
var ZodFirstPartyTypeKind;
|
|
5982
5982
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5983
5983
|
//#endregion
|
|
5984
|
-
//#region ../types/dist/sleep-
|
|
5984
|
+
//#region ../types/dist/sleep-DEZaLbiu.mjs
|
|
5985
5985
|
/**
|
|
5986
5986
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5987
5987
|
* window to float samples (D455).
|
|
@@ -6790,6 +6790,12 @@ function projectStore(store, perNodeKeys, nodeId) {
|
|
|
6790
6790
|
}
|
|
6791
6791
|
return out;
|
|
6792
6792
|
}
|
|
6793
|
+
/** Every scope, in the order the guard and the docs list them. */
|
|
6794
|
+
var SCOPED_SETTINGS_SCOPES = [
|
|
6795
|
+
"cluster",
|
|
6796
|
+
"device-defaults",
|
|
6797
|
+
"node"
|
|
6798
|
+
];
|
|
6793
6799
|
/**
|
|
6794
6800
|
* The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
|
|
6795
6801
|
*
|
|
@@ -7681,6 +7687,13 @@ var BaseAddon = class {
|
|
|
7681
7687
|
*/
|
|
7682
7688
|
secretStatePaths = [];
|
|
7683
7689
|
_addonSecretTree = null;
|
|
7690
|
+
/**
|
|
7691
|
+
* D986 M4: the scoped schemas this addon declared, resolved once per capability
|
|
7692
|
+
* (`loadScopedSchemas`) so the sync readers (`perNodeKeys`, the secret tree, the restart check)
|
|
7693
|
+
* see them. Empty until loaded: the readers then fall back to {@link globalSettingsSchema}.
|
|
7694
|
+
*/
|
|
7695
|
+
_scopedByCap = /* @__PURE__ */ new Map();
|
|
7696
|
+
_nodeKeysByCap = /* @__PURE__ */ new Map();
|
|
7684
7697
|
_deviceSecretTree = null;
|
|
7685
7698
|
/** Refs the last resolved config pointed at — a change to one re-resolves. */
|
|
7686
7699
|
_configSecretRefs = /* @__PURE__ */ new Set();
|
|
@@ -7750,6 +7763,7 @@ var BaseAddon = class {
|
|
|
7750
7763
|
this._ctx = context;
|
|
7751
7764
|
this._secretSettings = null;
|
|
7752
7765
|
}
|
|
7766
|
+
await this.loadScopedSchemas();
|
|
7753
7767
|
await this.resolveConfig();
|
|
7754
7768
|
if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
|
|
7755
7769
|
if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
|
|
@@ -7759,6 +7773,7 @@ var BaseAddon = class {
|
|
|
7759
7773
|
const normalized = normalizeAddonInitResult(result);
|
|
7760
7774
|
const providers = normalized && "providers" in normalized && normalized.providers ? normalized.providers : [];
|
|
7761
7775
|
this._registeredCapNames = providers.map((p) => p.capability.name);
|
|
7776
|
+
await this.loadScopedSchemas();
|
|
7762
7777
|
return normalized;
|
|
7763
7778
|
}
|
|
7764
7779
|
/**
|
|
@@ -7847,6 +7862,16 @@ var BaseAddon = class {
|
|
|
7847
7862
|
schema(schema) {
|
|
7848
7863
|
return schema;
|
|
7849
7864
|
}
|
|
7865
|
+
/**
|
|
7866
|
+
* The capability names whose scoped settings schemas ({@link clusterSettingsSchema},
|
|
7867
|
+
* {@link nodeSettingsSchema}, {@link deviceDefaultsSchema}) this addon declares. Read BEFORE
|
|
7868
|
+
* `onInitialize`, when the providers it registers are not known yet: the first config resolve
|
|
7869
|
+
* must already know which keys are per node. Names the providers register at init are added
|
|
7870
|
+
* once known.
|
|
7871
|
+
*/
|
|
7872
|
+
scopedSettingsCapabilities() {
|
|
7873
|
+
return [];
|
|
7874
|
+
}
|
|
7850
7875
|
/** Override to provide global-level settings UI schema. */
|
|
7851
7876
|
globalSettingsSchema(_cap) {
|
|
7852
7877
|
return null;
|
|
@@ -7890,7 +7915,8 @@ var BaseAddon = class {
|
|
|
7890
7915
|
return [];
|
|
7891
7916
|
}
|
|
7892
7917
|
async getGlobalSettings(overlay, cap, nodeId) {
|
|
7893
|
-
|
|
7918
|
+
await this.loadScopedSchemas(cap);
|
|
7919
|
+
const schema = this.globalSettingsSchema(cap) ?? this.scopedFormSchema(cap);
|
|
7894
7920
|
if (!schema) return { sections: [] };
|
|
7895
7921
|
const projected = await this.readRawProjectedStore(nodeId, cap);
|
|
7896
7922
|
return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
|
|
@@ -7925,10 +7951,9 @@ var BaseAddon = class {
|
|
|
7925
7951
|
*
|
|
7926
7952
|
* Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
|
|
7927
7953
|
* needs its connection fields, the default integration the addon's own sections. It is the
|
|
7928
|
-
* SHAPE of the form only — the values live
|
|
7929
|
-
* unchanged (`integrations.setSettings` for an account integration).
|
|
7954
|
+
* SHAPE of the form only — the values live on the instance record.
|
|
7930
7955
|
*/
|
|
7931
|
-
async
|
|
7956
|
+
async integrationFormSchema(_instance) {
|
|
7932
7957
|
return null;
|
|
7933
7958
|
}
|
|
7934
7959
|
/**
|
|
@@ -7974,7 +7999,7 @@ var BaseAddon = class {
|
|
|
7974
7999
|
* per-instance schema when it declares one, else its global schema (what they always showed).
|
|
7975
8000
|
*/
|
|
7976
8001
|
async getIntegrationFormSchema(instance, nodeId) {
|
|
7977
|
-
const scoped = await this.
|
|
8002
|
+
const scoped = await this.integrationFormSchema(instance);
|
|
7978
8003
|
if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
|
|
7979
8004
|
return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
|
|
7980
8005
|
}
|
|
@@ -8022,6 +8047,7 @@ var BaseAddon = class {
|
|
|
8022
8047
|
* resolved blob back is a no-op for every unchanged secret.
|
|
8023
8048
|
*/
|
|
8024
8049
|
async resolveGlobalStore(nodeId, cap) {
|
|
8050
|
+
await this.loadScopedSchemas(cap);
|
|
8025
8051
|
const raw = await this._ctx?.settings?.readAddonStore() ?? {};
|
|
8026
8052
|
return this.projectForNode(raw, nodeId, cap);
|
|
8027
8053
|
}
|
|
@@ -8032,6 +8058,7 @@ var BaseAddon = class {
|
|
|
8032
8058
|
}
|
|
8033
8059
|
/** The PROJECTED store with its markers — the form hydrate only (D798). */
|
|
8034
8060
|
async readRawProjectedStore(nodeId, cap) {
|
|
8061
|
+
await this.loadScopedSchemas(cap);
|
|
8035
8062
|
const raw = await this.rawSettings()?.readAddonStore() ?? {};
|
|
8036
8063
|
return this.projectForNode(raw, nodeId, cap);
|
|
8037
8064
|
}
|
|
@@ -8134,6 +8161,7 @@ var BaseAddon = class {
|
|
|
8134
8161
|
await this.applyGlobalPatch(patch, nodeId);
|
|
8135
8162
|
}
|
|
8136
8163
|
async applyGlobalPatch(barePatch, nodeId) {
|
|
8164
|
+
await this.loadScopedSchemas();
|
|
8137
8165
|
const keys = this.perNodeKeys();
|
|
8138
8166
|
const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
|
|
8139
8167
|
const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
|
|
@@ -8147,7 +8175,8 @@ var BaseAddon = class {
|
|
|
8147
8175
|
await this.resolveConfig();
|
|
8148
8176
|
await this.onConfigChanged();
|
|
8149
8177
|
this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
|
|
8150
|
-
|
|
8178
|
+
const legacy = this.globalSettingsSchema();
|
|
8179
|
+
this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
|
|
8151
8180
|
}
|
|
8152
8181
|
/**
|
|
8153
8182
|
* The set of field keys the global settings schema declares `perNode: true`
|
|
@@ -8155,14 +8184,67 @@ var BaseAddon = class {
|
|
|
8155
8184
|
* declarations). Empty set ⇒ every per-node code path is bypassed and the
|
|
8156
8185
|
* settings API behaves exactly like the legacy node-agnostic one.
|
|
8157
8186
|
*/
|
|
8158
|
-
|
|
8187
|
+
/**
|
|
8188
|
+
* Load the scoped schemas of every capability this addon declares (the override and the
|
|
8189
|
+
* providers registered at init, plus `extraCap`) that is not loaded yet, then drop what was
|
|
8190
|
+
* derived from the previous set. Idempotent.
|
|
8191
|
+
*/
|
|
8192
|
+
async loadScopedSchemas(extraCap) {
|
|
8193
|
+
const caps = new Set([
|
|
8194
|
+
...this.scopedSettingsCapabilities(),
|
|
8195
|
+
...this._registeredCapNames,
|
|
8196
|
+
...extraCap === void 0 ? [] : [extraCap]
|
|
8197
|
+
]);
|
|
8198
|
+
let added = false;
|
|
8199
|
+
for (const cap of caps) {
|
|
8200
|
+
if (this._scopedByCap.has(cap)) continue;
|
|
8201
|
+
const loaded = [];
|
|
8202
|
+
for (const scope of SCOPED_SETTINGS_SCOPES) {
|
|
8203
|
+
const schema = await this.getScopedSettingsSchema(scope, cap);
|
|
8204
|
+
if (schema !== null) loaded.push({
|
|
8205
|
+
scope,
|
|
8206
|
+
schema
|
|
8207
|
+
});
|
|
8208
|
+
}
|
|
8209
|
+
this._scopedByCap.set(cap, loaded);
|
|
8210
|
+
added = true;
|
|
8211
|
+
}
|
|
8212
|
+
if (!added) return;
|
|
8213
|
+
this._nodeKeysByCap = /* @__PURE__ */ new Map();
|
|
8214
|
+
this._addonSecretTree = null;
|
|
8215
|
+
}
|
|
8216
|
+
/**
|
|
8217
|
+
* The form the settings API serves while an addon declares no {@link globalSettingsSchema}: the
|
|
8218
|
+
* sections of its scoped schemas, so dropping the old hook does not blank the form.
|
|
8219
|
+
*/
|
|
8220
|
+
scopedFormSchema(cap) {
|
|
8221
|
+
const byId = /* @__PURE__ */ new Map();
|
|
8222
|
+
for (const entry of this.loadedScopedSchemas(cap)) for (const section of entry.schema.sections) if (!byId.has(section.id)) byId.set(section.id, section);
|
|
8223
|
+
return byId.size === 0 ? null : { sections: [...byId.values()] };
|
|
8224
|
+
}
|
|
8225
|
+
/** Every loaded scoped schema of `cap`, or of every loaded capability. */
|
|
8226
|
+
loadedScopedSchemas(cap) {
|
|
8227
|
+
if (cap !== void 0) return this._scopedByCap.get(cap) ?? [];
|
|
8228
|
+
return [...this._scopedByCap.values()].flat();
|
|
8229
|
+
}
|
|
8230
|
+
/**
|
|
8231
|
+
* The field keys that are stored per node: every field of a {@link nodeSettingsSchema}, plus
|
|
8232
|
+
* the fields the legacy {@link globalSettingsSchema} still marks `perNode: true`. Derived once
|
|
8233
|
+
* per `cap` argument and memoized (schemas are static declarations). Empty set ⇒ every
|
|
8234
|
+
* per-node code path is bypassed and the settings API behaves like the node-agnostic one.
|
|
8235
|
+
* Call {@link loadScopedSchemas} first (the async seams do).
|
|
8236
|
+
*/
|
|
8159
8237
|
perNodeKeys(cap) {
|
|
8160
8238
|
const cacheKey = cap ?? "";
|
|
8161
|
-
const cached = this.
|
|
8239
|
+
const cached = this._nodeKeysByCap.get(cacheKey);
|
|
8162
8240
|
if (cached) return cached;
|
|
8163
|
-
const
|
|
8164
|
-
const keys = new Set(
|
|
8165
|
-
this.
|
|
8241
|
+
const legacy = this.globalSettingsSchema(cap);
|
|
8242
|
+
const keys = new Set(legacy ? legacy.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
|
|
8243
|
+
for (const { scope, schema } of this.loadedScopedSchemas(cap)) {
|
|
8244
|
+
if (scope !== "node") continue;
|
|
8245
|
+
for (const section of schema.sections) for (const key of collectValueFieldKeys(section.fields)) keys.add(key);
|
|
8246
|
+
}
|
|
8247
|
+
this._nodeKeysByCap.set(cacheKey, keys);
|
|
8166
8248
|
return keys;
|
|
8167
8249
|
}
|
|
8168
8250
|
/**
|
|
@@ -8172,10 +8254,9 @@ var BaseAddon = class {
|
|
|
8172
8254
|
* time to return its response before the addon is torn down and
|
|
8173
8255
|
* re-initialised by `AddonRegistryService.restartAddon`.
|
|
8174
8256
|
*/
|
|
8175
|
-
maybeAutoRestart(patchKeys,
|
|
8176
|
-
if (!schema) return;
|
|
8257
|
+
maybeAutoRestart(patchKeys, schemas) {
|
|
8177
8258
|
const restartKeys = /* @__PURE__ */ new Set();
|
|
8178
|
-
for (const section of schema.sections) for (const field of section.fields) {
|
|
8259
|
+
for (const schema of schemas) for (const section of schema.sections) for (const field of section.fields) {
|
|
8179
8260
|
if (field.type === "separator" || field.type === "info") continue;
|
|
8180
8261
|
if (field.requiresRestart) restartKeys.add(field.key);
|
|
8181
8262
|
}
|
|
@@ -8371,12 +8452,14 @@ var BaseAddon = class {
|
|
|
8371
8452
|
* here, in the addon's own process, so the hub keeps no copy of it.
|
|
8372
8453
|
*/
|
|
8373
8454
|
async getDeclaredSettingsKeys() {
|
|
8455
|
+
await this.loadScopedSchemas();
|
|
8374
8456
|
return { keys: [...this.declaredKeySet()].toSorted() };
|
|
8375
8457
|
}
|
|
8376
8458
|
declaredKeySet() {
|
|
8377
8459
|
const keys = new Set(Object.keys(this.defaults));
|
|
8378
8460
|
const schema = this.globalSettingsSchema();
|
|
8379
8461
|
if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
|
|
8462
|
+
for (const { schema: scoped } of this.loadedScopedSchemas()) for (const section of scoped.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
|
|
8380
8463
|
for (const key of this._declaredStateKeys) keys.add(key);
|
|
8381
8464
|
for (const path of this.secretStatePaths) {
|
|
8382
8465
|
const root = path.split(/[.[]/, 1)[0];
|
|
@@ -8472,7 +8555,11 @@ var BaseAddon = class {
|
|
|
8472
8555
|
}
|
|
8473
8556
|
/** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
|
|
8474
8557
|
addonSecretTree() {
|
|
8475
|
-
this._addonSecretTree
|
|
8558
|
+
if (this._addonSecretTree === null) {
|
|
8559
|
+
const tree = new Map(secretTreeFromSchema(this.globalSettingsSchema()));
|
|
8560
|
+
for (const { schema } of this.loadedScopedSchemas()) for (const [key, node] of secretTreeFromSchema(schema)) tree.set(key, node);
|
|
8561
|
+
this._addonSecretTree = mergeSecretStatePaths(tree, this.secretStatePaths);
|
|
8562
|
+
}
|
|
8476
8563
|
return this._addonSecretTree;
|
|
8477
8564
|
}
|
|
8478
8565
|
deviceSecretTree() {
|
|
@@ -8722,6 +8809,23 @@ function dropPerNodeFields(fields) {
|
|
|
8722
8809
|
}
|
|
8723
8810
|
return kept;
|
|
8724
8811
|
}
|
|
8812
|
+
/** The keys of the value-holding fields, recursing into layout containers. */
|
|
8813
|
+
function collectValueFieldKeys(fields) {
|
|
8814
|
+
const collected = [];
|
|
8815
|
+
for (const field of fields) {
|
|
8816
|
+
if (field.type === "group") {
|
|
8817
|
+
collected.push(...collectValueFieldKeys(field.fields));
|
|
8818
|
+
continue;
|
|
8819
|
+
}
|
|
8820
|
+
if (field.type === "sub-tabs") {
|
|
8821
|
+
for (const tab of field.tabs) collected.push(...collectValueFieldKeys(tab.fields));
|
|
8822
|
+
continue;
|
|
8823
|
+
}
|
|
8824
|
+
if (field.type === "separator" || field.type === "info") continue;
|
|
8825
|
+
if ("key" in field && typeof field.key === "string") collected.push(field.key);
|
|
8826
|
+
}
|
|
8827
|
+
return collected;
|
|
8828
|
+
}
|
|
8725
8829
|
function collectFieldKeys(fields) {
|
|
8726
8830
|
const collected = [];
|
|
8727
8831
|
for (const field of fields) {
|
|
@@ -11956,7 +12060,7 @@ object({
|
|
|
11956
12060
|
params: record(string(), union([string(), number()])).optional()
|
|
11957
12061
|
});
|
|
11958
12062
|
//#endregion
|
|
11959
|
-
//#region ../types/dist/composition-
|
|
12063
|
+
//#region ../types/dist/composition-pOHbTzRB.mjs
|
|
11960
12064
|
/**
|
|
11961
12065
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11962
12066
|
*
|
|
@@ -13428,12 +13532,22 @@ var RecordIntegrationInstances = class {
|
|
|
13428
13532
|
...this.hooks.description,
|
|
13429
13533
|
mintsId: true
|
|
13430
13534
|
};
|
|
13431
|
-
|
|
13535
|
+
/**
|
|
13536
|
+
* The record an id names. A caller holding the `integrations` registry id (the Integrations
|
|
13537
|
+
* page) reaches a kind whose body links to it through `integrationId`; an id that is itself a
|
|
13538
|
+
* record id answers as it is. An id that names nothing comes back unchanged.
|
|
13539
|
+
*/
|
|
13540
|
+
async resolveRecordId(id) {
|
|
13541
|
+
if (await this.handle.get(id) !== null) return id;
|
|
13542
|
+
const linked = (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.find((record) => record.data["integrationId"] === id);
|
|
13543
|
+
return linked === void 0 ? id : linked.id;
|
|
13544
|
+
}
|
|
13545
|
+
instanceFormSchema = async ({ id }) => this.hooks.formSchema(id === null ? null : await this.resolveRecordId(id));
|
|
13432
13546
|
listInstances = async () => {
|
|
13433
13547
|
return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
|
|
13434
13548
|
};
|
|
13435
13549
|
getInstance = async ({ id }) => {
|
|
13436
|
-
const record = await this.handle.get(id);
|
|
13550
|
+
const record = await this.handle.get(await this.resolveRecordId(id));
|
|
13437
13551
|
return record === null ? null : this.viewOf(record);
|
|
13438
13552
|
};
|
|
13439
13553
|
/** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
|
|
@@ -13450,7 +13564,8 @@ var RecordIntegrationInstances = class {
|
|
|
13450
13564
|
await this.hooks.onChanged?.(minted, "created");
|
|
13451
13565
|
return { id: minted };
|
|
13452
13566
|
};
|
|
13453
|
-
updateInstance = async ({ id, name, values }) => {
|
|
13567
|
+
updateInstance = async ({ id: askedId, name, values }) => {
|
|
13568
|
+
const id = await this.resolveRecordId(askedId);
|
|
13454
13569
|
const patch = {};
|
|
13455
13570
|
for (const [key, value] of Object.entries(values)) {
|
|
13456
13571
|
if (value === void 0) continue;
|
|
@@ -13485,7 +13600,10 @@ var RecordIntegrationInstances = class {
|
|
|
13485
13600
|
status: "unsupported",
|
|
13486
13601
|
error: "This integration declares no connection test"
|
|
13487
13602
|
};
|
|
13488
|
-
return this.hooks.test(input
|
|
13603
|
+
return this.hooks.test(input.id === null ? input : {
|
|
13604
|
+
...input,
|
|
13605
|
+
id: await this.resolveRecordId(input.id)
|
|
13606
|
+
});
|
|
13489
13607
|
};
|
|
13490
13608
|
/** The flat provider object to hand to `registerProvider`. */
|
|
13491
13609
|
toProvider() {
|
|
@@ -14664,20 +14782,28 @@ async function moveIntegrationRowsToRecords(ports, spec) {
|
|
|
14664
14782
|
};
|
|
14665
14783
|
}
|
|
14666
14784
|
/**
|
|
14667
|
-
* The addon-side ports of `moveIntegrationRowsToRecords` (planning#416)
|
|
14668
|
-
* from its own process, so the
|
|
14669
|
-
* `
|
|
14670
|
-
*
|
|
14671
|
-
*
|
|
14672
|
-
|
|
14785
|
+
* The addon-side ports of `moveIntegrationRowsToRecords` (planning#416). An addon runs the move
|
|
14786
|
+
* from its own process, so the leftover rows are reached through the migration-only door
|
|
14787
|
+
* (`migrations.readLegacyIntegrationRows` / `clearLegacyIntegrationRows`) on the hub's connection.
|
|
14788
|
+
* The door returns markers. This process is the owner, so it resolves and releases them with
|
|
14789
|
+
* `resolveSecretMarkers` — the same call the old settings door used — and only then asks the
|
|
14790
|
+
* door to delete the rows. The addon's own record kind is its records handle.
|
|
14791
|
+
*/
|
|
14792
|
+
async function resolvedRows(stored, secrets) {
|
|
14793
|
+
if (secrets === null) return stored;
|
|
14794
|
+
const { value, unresolved } = await resolveSecretMarkers(stored, secrets);
|
|
14795
|
+
if (unresolved.length > 0) throw new Error(`${unresolved.length} secret(s) cannot be read from the keychain`);
|
|
14796
|
+
return value;
|
|
14797
|
+
}
|
|
14673
14798
|
function integrationRowsPortsOverApi(deps) {
|
|
14799
|
+
const secrets = deps.secrets ?? null;
|
|
14674
14800
|
return {
|
|
14675
14801
|
warn: (message) => deps.log.warn(message),
|
|
14676
14802
|
listIntegrations: async (addonId) => (await deps.api.list()).filter((i) => i.addonId === addonId).map((i) => ({
|
|
14677
14803
|
id: i.id,
|
|
14678
14804
|
name: i.name
|
|
14679
14805
|
})),
|
|
14680
|
-
readRows: (integrationId) => deps.api.
|
|
14806
|
+
readRows: async (integrationId) => resolvedRows(await deps.api.readLegacyIntegrationRows({ integrationId }), secrets),
|
|
14681
14807
|
putRecord: async (id, body) => {
|
|
14682
14808
|
if (await deps.records.get(id) !== null) return "exists";
|
|
14683
14809
|
try {
|
|
@@ -14696,15 +14822,17 @@ function integrationRowsPortsOverApi(deps) {
|
|
|
14696
14822
|
await deps.records.delete(id);
|
|
14697
14823
|
},
|
|
14698
14824
|
clearRows: async (integrationId) => {
|
|
14699
|
-
await deps.api.
|
|
14825
|
+
const refs = collectSecretMarkerRefs(await deps.api.readLegacyIntegrationRows({ integrationId }));
|
|
14826
|
+
if (refs.length > 0 && secrets !== null) await secrets.delete(refs);
|
|
14827
|
+
await deps.api.clearLegacyIntegrationRows({ integrationId });
|
|
14700
14828
|
}
|
|
14701
14829
|
};
|
|
14702
14830
|
}
|
|
14703
14831
|
function defaultIntegrationApiOver(api) {
|
|
14704
14832
|
return {
|
|
14705
14833
|
list: () => api.integrations.list.query(),
|
|
14706
|
-
|
|
14707
|
-
|
|
14834
|
+
readLegacyIntegrationRows: (input) => api.migrations.readLegacyIntegrationRows.query(input),
|
|
14835
|
+
clearLegacyIntegrationRows: (input) => api.migrations.clearLegacyIntegrationRows.mutate(input),
|
|
14708
14836
|
listDevices: (addonId) => api.deviceManager.listAll.query({
|
|
14709
14837
|
addonId,
|
|
14710
14838
|
projection: "slim"
|
|
@@ -50390,6 +50518,9 @@ var RunningProposalEvidenceSchema = object({
|
|
|
50390
50518
|
runsFound: number().int().nonnegative(),
|
|
50391
50519
|
pausesFound: number().int().nonnegative(),
|
|
50392
50520
|
longestPauseMs: number().nonnegative(),
|
|
50521
|
+
/** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
|
|
50522
|
+
pulsesFound: number().int().nonnegative(),
|
|
50523
|
+
longestPulseMs: number().nonnegative(),
|
|
50393
50524
|
idleFloorW: number(),
|
|
50394
50525
|
noiseCeilingW: number(),
|
|
50395
50526
|
plateauW: number(),
|
|
@@ -51983,7 +52114,14 @@ var DiscoveryCandidateSchema = object({
|
|
|
51983
52114
|
/** `stableId` of the gateway candidate this device was found BEHIND. */
|
|
51984
52115
|
parentStableId: string().optional(),
|
|
51985
52116
|
/** `false` = shown for context but not pickable. Default true. */
|
|
51986
|
-
selectable: boolean().optional()
|
|
52117
|
+
selectable: boolean().optional(),
|
|
52118
|
+
/**
|
|
52119
|
+
* Keys of the provider's creation form (`getChildCreationSchema`) this candidate
|
|
52120
|
+
* cannot be adopted without — a key for an encrypted board, a pairing code. The
|
|
52121
|
+
* admin asks for exactly these, on the creation form prefilled with
|
|
52122
|
+
* `prefilledConfig`, before it adopts. Absent or empty = adopt as it is.
|
|
52123
|
+
*/
|
|
52124
|
+
needsParams: array(string()).optional()
|
|
51987
52125
|
});
|
|
51988
52126
|
/**
|
|
51989
52127
|
* Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
|
|
@@ -52111,7 +52249,11 @@ var deviceProviderCapability = {
|
|
|
52111
52249
|
* pick the right type instead of assuming Camera.
|
|
52112
52250
|
*/
|
|
52113
52251
|
getManualCreationType: method(object({}), object({ deviceType: _enum(DeviceType).nullable() })),
|
|
52114
|
-
adoptDiscoveredDevice: method(object({
|
|
52252
|
+
adoptDiscoveredDevice: method(object({
|
|
52253
|
+
candidate: DiscoveryCandidateSchema,
|
|
52254
|
+
/** The integration the operator adopts into; a provider with several instances needs it. */
|
|
52255
|
+
integrationId: string().optional()
|
|
52256
|
+
}), DeviceSummarySchema, {
|
|
52115
52257
|
kind: "mutation",
|
|
52116
52258
|
auth: "admin"
|
|
52117
52259
|
}),
|
|
@@ -52125,7 +52267,9 @@ var deviceProviderCapability = {
|
|
|
52125
52267
|
getChildCreationSchema: method(object({ type: _enum(DeviceType) }), CreationSchemaOutputSchema),
|
|
52126
52268
|
createDevice: method(object({
|
|
52127
52269
|
type: _enum(DeviceType),
|
|
52128
|
-
config: record(string(), unknown())
|
|
52270
|
+
config: record(string(), unknown()),
|
|
52271
|
+
/** The integration the operator adds the device to; a provider with several instances needs it. */
|
|
52272
|
+
integrationId: string().optional()
|
|
52129
52273
|
}), DeviceSummarySchema, {
|
|
52130
52274
|
kind: "mutation",
|
|
52131
52275
|
auth: "admin"
|
|
@@ -56235,11 +56379,7 @@ var CreateIntegrationInputSchema = object({
|
|
|
56235
56379
|
info: record(string(), unknown()).optional(),
|
|
56236
56380
|
/** `stableId` of the discovery candidate the form was seeded from (D984). */
|
|
56237
56381
|
originStableId: string().optional(),
|
|
56238
|
-
settings: record(string(), unknown()).optional()
|
|
56239
|
-
/** Keys of `settings` the form's schema declares secret (by type) — kept in
|
|
56240
|
-
* the keychain, not the table (D787). */
|
|
56241
|
-
secretKeys: array(string()).optional(),
|
|
56242
|
-
skipRestart: boolean().optional()
|
|
56382
|
+
settings: record(string(), unknown()).optional()
|
|
56243
56383
|
});
|
|
56244
56384
|
var UpdateIntegrationInputSchema = object({
|
|
56245
56385
|
id: string(),
|
|
@@ -56260,22 +56400,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
56260
56400
|
}), method(object({ id: string() }), DeleteIntegrationResultSchema, {
|
|
56261
56401
|
kind: "mutation",
|
|
56262
56402
|
auth: "admin"
|
|
56263
|
-
}), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
|
|
56264
|
-
id: string(),
|
|
56265
|
-
settings: record(string(), unknown()),
|
|
56266
|
-
/** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
|
|
56267
|
-
secretKeys: array(string()).optional(),
|
|
56268
|
-
/** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
|
|
56269
|
-
skipRestart: boolean().optional()
|
|
56270
|
-
}), object({ success: boolean() }), {
|
|
56271
|
-
kind: "mutation",
|
|
56272
|
-
auth: "admin"
|
|
56273
|
-
}), method(object({ id: string() }), object({
|
|
56274
|
-
rows: number().int().nonnegative(),
|
|
56275
|
-
secrets: number().int().nonnegative()
|
|
56276
|
-
}), {
|
|
56277
|
-
kind: "mutation",
|
|
56278
|
-
auth: "admin"
|
|
56279
56403
|
}), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
|
|
56280
56404
|
addonId: string(),
|
|
56281
56405
|
settings: record(string(), unknown())
|
|
@@ -59252,6 +59376,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
|
|
|
59252
59376
|
}), {
|
|
59253
59377
|
kind: "mutation",
|
|
59254
59378
|
auth: "admin"
|
|
59379
|
+
}), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
|
|
59380
|
+
rows: number().int().nonnegative(),
|
|
59381
|
+
secrets: number().int().nonnegative()
|
|
59382
|
+
}), {
|
|
59383
|
+
kind: "mutation",
|
|
59384
|
+
auth: "admin"
|
|
59385
|
+
}), method(object({
|
|
59386
|
+
integrationId: string().min(1),
|
|
59387
|
+
patch: record(string(), unknown())
|
|
59388
|
+
}), object({ written: boolean() }), {
|
|
59389
|
+
kind: "mutation",
|
|
59390
|
+
auth: "admin"
|
|
59391
|
+
}), method(object({
|
|
59392
|
+
integrationId: string().min(1),
|
|
59393
|
+
keys: array(string().min(1))
|
|
59394
|
+
}), object({
|
|
59395
|
+
present: boolean(),
|
|
59396
|
+
removed: record(string(), unknown())
|
|
59397
|
+
}), {
|
|
59398
|
+
kind: "mutation",
|
|
59399
|
+
auth: "admin"
|
|
59255
59400
|
});
|
|
59256
59401
|
method(object({
|
|
59257
59402
|
sourceUrl: string(),
|
|
@@ -80617,11 +80762,15 @@ function runningFromDishwasherOperation(operation, program, previous, fetchedAt,
|
|
|
80617
80762
|
* 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
|
|
80618
80763
|
* near F. A device that is not near one level most of the time has no floor.
|
|
80619
80764
|
* 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
|
|
80620
|
-
* 3.
|
|
80621
|
-
*
|
|
80765
|
+
* 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
|
|
80766
|
+
* display waking): it is neither a run nor a pause. Among the remaining
|
|
80767
|
+
* episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
|
|
80768
|
+
* two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
|
|
80769
|
+
* a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
|
|
80770
|
+
* is no run.
|
|
80622
80771
|
* 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
|
|
80623
|
-
* grid a person would type; stop dwell covers the longest pause
|
|
80624
|
-
* covers the longest standby blip above T.
|
|
80772
|
+
* grid a person would type; stop dwell covers the longest pause and the
|
|
80773
|
+
* kind's floor; start dwell covers the longest pulse and standby blip above T.
|
|
80625
80774
|
* 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
|
|
80626
80775
|
* run must hold exactly one true interval and nothing may flap outside.
|
|
80627
80776
|
*/
|
|
@@ -80632,6 +80781,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
|
|
|
80632
80781
|
30 * SECOND_MS;
|
|
80633
80782
|
60 * SECOND_MS;
|
|
80634
80783
|
20 * MINUTE_MS$1;
|
|
80784
|
+
90 * SECOND_MS;
|
|
80785
|
+
30 * MINUTE_MS$1;
|
|
80786
|
+
10 * MINUTE_MS$1;
|
|
80787
|
+
DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
|
|
80635
80788
|
10 * SECOND_MS;
|
|
80636
80789
|
10 * SECOND_MS;
|
|
80637
80790
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
@@ -81805,7 +81958,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
81805
81958
|
* `deviceClasses` instead.
|
|
81806
81959
|
*/
|
|
81807
81960
|
async createDevice(input) {
|
|
81808
|
-
const spec = await this.onCreateDevice(input.type, input.config);
|
|
81961
|
+
const spec = await this.onCreateDevice(input.type, input.config, { ...input.integrationId !== void 0 ? { integrationId: input.integrationId } : {} });
|
|
81809
81962
|
const Class = this.deviceClasses[spec.meta.type];
|
|
81810
81963
|
if (!Class) throw new Error(`${this.providerName} provider: no device class registered for type "${spec.meta.type}" — add it to the deviceClasses map`);
|
|
81811
81964
|
const stableId = this.generateStableId(spec.meta.type, spec.config);
|
|
@@ -85916,13 +86069,6 @@ Object.freeze({
|
|
|
85916
86069
|
access: "create",
|
|
85917
86070
|
role: "admin"
|
|
85918
86071
|
},
|
|
85919
|
-
"integrations.clearSettings": {
|
|
85920
|
-
capName: "integrations",
|
|
85921
|
-
capScope: "system",
|
|
85922
|
-
addonId: null,
|
|
85923
|
-
access: "delete",
|
|
85924
|
-
role: "admin"
|
|
85925
|
-
},
|
|
85926
86072
|
"integrations.create": {
|
|
85927
86073
|
capName: "integrations",
|
|
85928
86074
|
capScope: "system",
|
|
@@ -85958,13 +86104,6 @@ Object.freeze({
|
|
|
85958
86104
|
access: "view",
|
|
85959
86105
|
role: "admin"
|
|
85960
86106
|
},
|
|
85961
|
-
"integrations.getSettings": {
|
|
85962
|
-
capName: "integrations",
|
|
85963
|
-
capScope: "system",
|
|
85964
|
-
addonId: null,
|
|
85965
|
-
access: "view",
|
|
85966
|
-
role: "admin"
|
|
85967
|
-
},
|
|
85968
86107
|
"integrations.list": {
|
|
85969
86108
|
capName: "integrations",
|
|
85970
86109
|
capScope: "system",
|
|
@@ -85972,13 +86111,6 @@ Object.freeze({
|
|
|
85972
86111
|
access: "view",
|
|
85973
86112
|
role: "admin"
|
|
85974
86113
|
},
|
|
85975
|
-
"integrations.setSettings": {
|
|
85976
|
-
capName: "integrations",
|
|
85977
|
-
capScope: "system",
|
|
85978
|
-
addonId: null,
|
|
85979
|
-
access: "create",
|
|
85980
|
-
role: "admin"
|
|
85981
|
-
},
|
|
85982
86114
|
"integrations.testConnection": {
|
|
85983
86115
|
capName: "integrations",
|
|
85984
86116
|
capScope: "system",
|
|
@@ -86973,6 +87105,13 @@ Object.freeze({
|
|
|
86973
87105
|
access: "view",
|
|
86974
87106
|
role: "admin"
|
|
86975
87107
|
},
|
|
87108
|
+
"migrations.clearLegacyIntegrationRows": {
|
|
87109
|
+
capName: "migrations",
|
|
87110
|
+
capScope: "system",
|
|
87111
|
+
addonId: null,
|
|
87112
|
+
access: "delete",
|
|
87113
|
+
role: "admin"
|
|
87114
|
+
},
|
|
86976
87115
|
"migrations.declareOwner": {
|
|
86977
87116
|
capName: "migrations",
|
|
86978
87117
|
capScope: "system",
|
|
@@ -86980,6 +87119,13 @@ Object.freeze({
|
|
|
86980
87119
|
access: "create",
|
|
86981
87120
|
role: "admin"
|
|
86982
87121
|
},
|
|
87122
|
+
"migrations.deleteLegacyIntegrationRowKeys": {
|
|
87123
|
+
capName: "migrations",
|
|
87124
|
+
capScope: "system",
|
|
87125
|
+
addonId: null,
|
|
87126
|
+
access: "delete",
|
|
87127
|
+
role: "admin"
|
|
87128
|
+
},
|
|
86983
87129
|
"migrations.dryRun": {
|
|
86984
87130
|
capName: "migrations",
|
|
86985
87131
|
capScope: "system",
|
|
@@ -87015,6 +87161,20 @@ Object.freeze({
|
|
|
87015
87161
|
access: "create",
|
|
87016
87162
|
role: "admin"
|
|
87017
87163
|
},
|
|
87164
|
+
"migrations.patchLegacyIntegrationRows": {
|
|
87165
|
+
capName: "migrations",
|
|
87166
|
+
capScope: "system",
|
|
87167
|
+
addonId: null,
|
|
87168
|
+
access: "create",
|
|
87169
|
+
role: "admin"
|
|
87170
|
+
},
|
|
87171
|
+
"migrations.readLegacyIntegrationRows": {
|
|
87172
|
+
capName: "migrations",
|
|
87173
|
+
capScope: "system",
|
|
87174
|
+
addonId: null,
|
|
87175
|
+
access: "view",
|
|
87176
|
+
role: "admin"
|
|
87177
|
+
},
|
|
87018
87178
|
"migrations.restorePreMigration": {
|
|
87019
87179
|
capName: "migrations",
|
|
87020
87180
|
capScope: "system",
|
|
@@ -97408,6 +97568,15 @@ function rewriteLocation(location, currentUpstream, route) {
|
|
|
97408
97568
|
/** Characters that may precede a URL that STARTS a value (never `=`, `?`, `&`, `%3D`). */
|
|
97409
97569
|
var VALUE_START = String.raw`(?<=^|["'(\s>,\[{])`;
|
|
97410
97570
|
/**
|
|
97571
|
+
* Subresource Integrity pins the bytes of a script or stylesheet. The proxy rewrites vendor
|
|
97572
|
+
* URLs inside those bytes (and re-serves them from another origin), so the hash can no longer
|
|
97573
|
+
* match and the browser refuses the file: the page renders with no script at all. The hub
|
|
97574
|
+
* already drops the CSP that would have enforced the same pin; the attribute goes with it.
|
|
97575
|
+
*/
|
|
97576
|
+
function stripIntegrity(html) {
|
|
97577
|
+
return html.replace(/\s+integrity\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, "");
|
|
97578
|
+
}
|
|
97579
|
+
/**
|
|
97411
97580
|
* Rewrite vendor URLs in a text body: absolute (`https://host/…`), protocol-relative
|
|
97412
97581
|
* (`//host/…`) and JSON-escaped (`https:\/\/host\/…`) when they start a value; and, in
|
|
97413
97582
|
* HTML/CSS, root-relative references (`href="/x"`, `url(/x)`) which would otherwise
|
|
@@ -97423,6 +97592,7 @@ function rewriteBody(body, kind, currentUpstream, route) {
|
|
|
97423
97592
|
const escaped = new RegExp(`${VALUE_START}(?:https?:)?\\\\/\\\\/${host}(?=\\\\/|["'?#\\s)\\]},<]|$)`, "g");
|
|
97424
97593
|
out = out.replace(escaped, escapedBase);
|
|
97425
97594
|
}
|
|
97595
|
+
if (kind === "html") out = stripIntegrity(out);
|
|
97426
97596
|
const current = `${route.basePath}/${currentUpstream.host}/`;
|
|
97427
97597
|
const notMounted = `(?!/)(?!${escapeRegex(route.basePath.slice(1))}/)`;
|
|
97428
97598
|
if (kind === "html") {
|
|
@@ -98082,6 +98252,7 @@ function boschRowsMigration(spec) {
|
|
|
98082
98252
|
function boschRowsPorts(deps) {
|
|
98083
98253
|
return integrationRowsPortsOverApi({
|
|
98084
98254
|
api: deps.api,
|
|
98255
|
+
...deps.secrets !== void 0 ? { secrets: deps.secrets } : {},
|
|
98085
98256
|
records: deps.records.kind({
|
|
98086
98257
|
name: deps.kind.name,
|
|
98087
98258
|
declaration: deps.kind.declaration,
|