@camstack/addon-provider-homeassistant 1.2.660 → 1.2.662
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 +8 -3
- package/dist/addon.mjs +8 -3
- package/dist/ha-export/ha-export.addon.js +8 -2
- package/dist/ha-export/ha-export.addon.mjs +8 -2
- package/dist/{legacy-integration-rows-BWVmjfDO.js → legacy-integration-rows-BaTsNMtt.js} +216 -90
- package/dist/{legacy-integration-rows-BIbFoT26.mjs → legacy-integration-rows-uGrBo3a3.mjs} +216 -90
- 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
|
+
## [1.2.662] - 2026-11-05
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- The integration form is declared as the instance form; brokers and their stored values are unchanged.
|
|
15
|
+
|
|
16
|
+
## [1.2.661] - 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
|
## [1.2.660] - 2026-11-05
|
|
11
23
|
|
|
12
24
|
### Changed
|
package/dist/addon.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_legacy_integration_rows = require("./legacy-integration-rows-
|
|
2
|
+
const require_legacy_integration_rows = require("./legacy-integration-rows-BaTsNMtt.js");
|
|
3
3
|
let node_crypto = require("node:crypto");
|
|
4
4
|
let node_zlib = require("node:zlib");
|
|
5
5
|
//#region src/ha-admin-link.ts
|
|
@@ -9107,7 +9107,12 @@ var HaProviderAddon = class HaProviderAddon extends require_legacy_integration_r
|
|
|
9107
9107
|
if (this.ctx.secrets === void 0) throw new Error("provider-homeassistant: ctx.secrets is required");
|
|
9108
9108
|
return {
|
|
9109
9109
|
readStored: (key) => this.readStoredGlobalKey(key),
|
|
9110
|
-
...require_legacy_integration_rows.haInstanceSettingsPortsOver(
|
|
9110
|
+
...require_legacy_integration_rows.haInstanceSettingsPortsOver({
|
|
9111
|
+
list: this.ctx.api.integrations.list,
|
|
9112
|
+
readLegacyIntegrationRows: this.ctx.api.migrations.readLegacyIntegrationRows,
|
|
9113
|
+
patchLegacyIntegrationRows: this.ctx.api.migrations.patchLegacyIntegrationRows,
|
|
9114
|
+
deleteLegacyIntegrationRowKeys: this.ctx.api.migrations.deleteLegacyIntegrationRowKeys
|
|
9115
|
+
}, this.ctx.secrets ?? null),
|
|
9111
9116
|
...haInstanceRecordPortsOver(records.kind(haBrokersEntryKind)),
|
|
9112
9117
|
...require_legacy_integration_rows.flattenPortsOver(records.kind(haBrokersEntryKind))
|
|
9113
9118
|
};
|
|
@@ -9321,7 +9326,7 @@ var HaProviderAddon = class HaProviderAddon extends require_legacy_integration_r
|
|
|
9321
9326
|
eb.subscribe({ category: "integration.disabled" }, handler);
|
|
9322
9327
|
eb.subscribe({ category: "integration.deleted" }, handler);
|
|
9323
9328
|
}
|
|
9324
|
-
|
|
9329
|
+
async integrationFormSchema(_instance) {
|
|
9325
9330
|
return this.schema({ sections: [{
|
|
9326
9331
|
id: "ha-broker",
|
|
9327
9332
|
title: "Home Assistant broker",
|
package/dist/addon.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as fanControlCapability, $t as createEvent, A as bestLocationMatch, At as runningFromMediaPlayer, B as climateControlCapability, Bt as tryConvertDelta, C as accessoriesCapability, Ct as pressureSensorCapability, D as ambientLightSensorCapability, Dt as resolveAttachmentUrls, E as alarmPanelCapability, Et as quantityFromUnit, F as buildAddonRouteProvider, Ft as smokeCapability, G as contactCapability, Gt as vibrationCapability, H as connectivityCapability, Ht as updateCapability, I as buildDeviceAdminUrl, J as deviceAdminLinkCapability, K as controlCapability, Kt as waterHeaterCapability, L as buttonCapability, Lt as switchCapability, M as boardKeySourceCapability, Mt as runningStateCapability, N as brightnessCapability, Nt as scriptRunnerCapability, O as automationControlCapability, P as brokerCapability, Q as eventEmitterCapability, Qt as baseUnitOf, R as calendarSourceCapability, Rt as tamperCapability, S as TargetSchema, Sn as EventCategory, St as presenceCapability, T as airQualitySensorCapability, Tt as publicEndpointReaderFrom, Ut as vacuumControlCapability, V as colorCapability, Vt as tryConvertUnit, Wt as valveCapability, Xt as RecordSecretUnavailableError, Y as deviceAdoptionCapability, Z as enumSensorCapability, _n as url, at as humiditySensorCapability, b as RUNNING_STATE_COLD, bn as DeviceType, bt as preconditionOf, cn as boolean, ct as integrationInstancesCapability, dn as number, dt as mediaPlayerCapability, en as defineAddonMigrations, et as flattenNestedRecords, fn as object, ft as motionCapability, gn as unknown, gt as numericSensorCapability, hn as union, ht as notifierCapability, it as humidifierCapability, j as binaryCapability, jt as runningFromVacuumState, k as batteryCapability, kt as runningFromLawnMower, ln as discriminatedUnion, lt as lawnMowerControlCapability, m as EnumSensorDateTimeFormatSchema, mn as string, mt as notificationOutputCapability, n as HA_BROKER_KIND, nn as defineRecordKinds, nt as floodCapability, o as BaseDevice, on as _enum, ot as imageCapability, pn as record, pt as normalizeUnit, q as coverCapability, qt as weatherCapability, rn as entryBody, rt as gasCapability, s as BaseDeviceProvider, sn as array, st as ingestReading, t as haInstanceSettingsPortsOver, tn as defineEntryKind, tt as flattenPortsOver, un as literal, ut as lockControlCapability, vn as DeviceFeature, vt as parseAdminBase, w as addonRoutesCapability, x as RecordIntegrationInstances, xn as errMsg, xt as prepareNotification, yn as DeviceRole, yt as powerMeterCapability, z as carbonMonoxideCapability, zt as temperatureSensorCapability } from "./legacy-integration-rows-
|
|
1
|
+
import { $ as fanControlCapability, $t as createEvent, A as bestLocationMatch, At as runningFromMediaPlayer, B as climateControlCapability, Bt as tryConvertDelta, C as accessoriesCapability, Ct as pressureSensorCapability, D as ambientLightSensorCapability, Dt as resolveAttachmentUrls, E as alarmPanelCapability, Et as quantityFromUnit, F as buildAddonRouteProvider, Ft as smokeCapability, G as contactCapability, Gt as vibrationCapability, H as connectivityCapability, Ht as updateCapability, I as buildDeviceAdminUrl, J as deviceAdminLinkCapability, K as controlCapability, Kt as waterHeaterCapability, L as buttonCapability, Lt as switchCapability, M as boardKeySourceCapability, Mt as runningStateCapability, N as brightnessCapability, Nt as scriptRunnerCapability, O as automationControlCapability, P as brokerCapability, Q as eventEmitterCapability, Qt as baseUnitOf, R as calendarSourceCapability, Rt as tamperCapability, S as TargetSchema, Sn as EventCategory, St as presenceCapability, T as airQualitySensorCapability, Tt as publicEndpointReaderFrom, Ut as vacuumControlCapability, V as colorCapability, Vt as tryConvertUnit, Wt as valveCapability, Xt as RecordSecretUnavailableError, Y as deviceAdoptionCapability, Z as enumSensorCapability, _n as url, at as humiditySensorCapability, b as RUNNING_STATE_COLD, bn as DeviceType, bt as preconditionOf, cn as boolean, ct as integrationInstancesCapability, dn as number, dt as mediaPlayerCapability, en as defineAddonMigrations, et as flattenNestedRecords, fn as object, ft as motionCapability, gn as unknown, gt as numericSensorCapability, hn as union, ht as notifierCapability, it as humidifierCapability, j as binaryCapability, jt as runningFromVacuumState, k as batteryCapability, kt as runningFromLawnMower, ln as discriminatedUnion, lt as lawnMowerControlCapability, m as EnumSensorDateTimeFormatSchema, mn as string, mt as notificationOutputCapability, n as HA_BROKER_KIND, nn as defineRecordKinds, nt as floodCapability, o as BaseDevice, on as _enum, ot as imageCapability, pn as record, pt as normalizeUnit, q as coverCapability, qt as weatherCapability, rn as entryBody, rt as gasCapability, s as BaseDeviceProvider, sn as array, st as ingestReading, t as haInstanceSettingsPortsOver, tn as defineEntryKind, tt as flattenPortsOver, un as literal, ut as lockControlCapability, vn as DeviceFeature, vt as parseAdminBase, w as addonRoutesCapability, x as RecordIntegrationInstances, xn as errMsg, xt as prepareNotification, yn as DeviceRole, yt as powerMeterCapability, z as carbonMonoxideCapability, zt as temperatureSensorCapability } from "./legacy-integration-rows-uGrBo3a3.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { brotliCompressSync, deflateSync, gzipSync } from "node:zlib";
|
|
4
4
|
//#region src/ha-admin-link.ts
|
|
@@ -9086,7 +9086,12 @@ var HaProviderAddon = class HaProviderAddon extends BaseDeviceProvider {
|
|
|
9086
9086
|
if (this.ctx.secrets === void 0) throw new Error("provider-homeassistant: ctx.secrets is required");
|
|
9087
9087
|
return {
|
|
9088
9088
|
readStored: (key) => this.readStoredGlobalKey(key),
|
|
9089
|
-
...haInstanceSettingsPortsOver(
|
|
9089
|
+
...haInstanceSettingsPortsOver({
|
|
9090
|
+
list: this.ctx.api.integrations.list,
|
|
9091
|
+
readLegacyIntegrationRows: this.ctx.api.migrations.readLegacyIntegrationRows,
|
|
9092
|
+
patchLegacyIntegrationRows: this.ctx.api.migrations.patchLegacyIntegrationRows,
|
|
9093
|
+
deleteLegacyIntegrationRowKeys: this.ctx.api.migrations.deleteLegacyIntegrationRowKeys
|
|
9094
|
+
}, this.ctx.secrets ?? null),
|
|
9090
9095
|
...haInstanceRecordPortsOver(records.kind(haBrokersEntryKind)),
|
|
9091
9096
|
...flattenPortsOver(records.kind(haBrokersEntryKind))
|
|
9092
9097
|
};
|
|
@@ -9300,7 +9305,7 @@ var HaProviderAddon = class HaProviderAddon extends BaseDeviceProvider {
|
|
|
9300
9305
|
eb.subscribe({ category: "integration.disabled" }, handler);
|
|
9301
9306
|
eb.subscribe({ category: "integration.deleted" }, handler);
|
|
9302
9307
|
}
|
|
9303
|
-
|
|
9308
|
+
async integrationFormSchema(_instance) {
|
|
9304
9309
|
return this.schema({ sections: [{
|
|
9305
9310
|
id: "ha-broker",
|
|
9306
9311
|
title: "Home Assistant broker",
|
|
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
enumerable: true
|
|
25
25
|
}) : target, mod));
|
|
26
26
|
//#endregion
|
|
27
|
-
const require_legacy_integration_rows = require("../legacy-integration-rows-
|
|
27
|
+
const require_legacy_integration_rows = require("../legacy-integration-rows-BaTsNMtt.js");
|
|
28
28
|
let node_crypto = require("node:crypto");
|
|
29
29
|
require("node:zlib");
|
|
30
30
|
let node_fs = require("node:fs");
|
|
@@ -5781,7 +5781,12 @@ async function readAllLinks(handle) {
|
|
|
5781
5781
|
function haExportMigrationPorts(deps) {
|
|
5782
5782
|
return {
|
|
5783
5783
|
readStored: deps.readStored,
|
|
5784
|
-
...require_legacy_integration_rows.haInstanceSettingsPortsOver(
|
|
5784
|
+
...require_legacy_integration_rows.haInstanceSettingsPortsOver({
|
|
5785
|
+
list: deps.api.integrations.list,
|
|
5786
|
+
readLegacyIntegrationRows: deps.api.migrations.readLegacyIntegrationRows,
|
|
5787
|
+
patchLegacyIntegrationRows: deps.api.migrations.patchLegacyIntegrationRows,
|
|
5788
|
+
deleteLegacyIntegrationRowKeys: deps.api.migrations.deleteLegacyIntegrationRowKeys
|
|
5789
|
+
}, deps.secrets ?? null),
|
|
5785
5790
|
warn: deps.warn,
|
|
5786
5791
|
putLinks: async (brokerId, body) => {
|
|
5787
5792
|
if (await deps.links.get(brokerId) !== null) return "exists";
|
|
@@ -8108,6 +8113,7 @@ var HaExportAddon = class extends require_legacy_integration_rows.BaseAddon {
|
|
|
8108
8113
|
await this.runMigrations(require_legacy_integration_rows.defineAddonMigrations(ADDON_MIGRATIONS, haExportMigrationPorts({
|
|
8109
8114
|
readStored: async () => this.resolveGlobalStore(),
|
|
8110
8115
|
api: this.ctx.api,
|
|
8116
|
+
secrets: this.ctx.secrets ?? null,
|
|
8111
8117
|
links: legacyLinks,
|
|
8112
8118
|
targets: targetsHandle,
|
|
8113
8119
|
clearSettingKeys: async (keys) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { F as buildAddonRouteProvider, It as statusInstanceOf, Jt as BaseAddon, Ot as runExportedAction, Pt as sendToExportedTarget, Qt as baseUnitOf, Sn as EventCategory, U as consumableFormOf, W as consumableKindOf, X as deviceExportCapability, Zt as RevConflictError, _ as HvacModeSchema, _t as oauthIntegrationCapability, a as AlarmArmModeSchema, an as nodePin, bt as preconditionOf, c as CAMERA_SWITCH_ORDER, cn as boolean, ct as integrationInstancesCapability, d as ConsumableFormSchema, dn as number, en as defineAddonMigrations, f as ConsumableItemSchema, fn as object, g as FanDirectionSchema, gn as unknown, h as ExportInstanceUnknownError, i as listHomeAssistantBrokers, in as isReadingQuantity, l as COCO_TO_MACRO, mn as string, on as _enum, p as ConsumableStatusSchema, sn as array, t as haInstanceSettingsPortsOver, tn as defineEntryKind, u as CameraSwitchIdSchema, v as MediaPlayerRepeatSchema, w as addonRoutesCapability, wt as pruneDeletedEntities, x as RecordIntegrationInstances, y as PLUG_APPLIANCE_TYPES } from "../legacy-integration-rows-
|
|
1
|
+
import { F as buildAddonRouteProvider, It as statusInstanceOf, Jt as BaseAddon, Ot as runExportedAction, Pt as sendToExportedTarget, Qt as baseUnitOf, Sn as EventCategory, U as consumableFormOf, W as consumableKindOf, X as deviceExportCapability, Zt as RevConflictError, _ as HvacModeSchema, _t as oauthIntegrationCapability, a as AlarmArmModeSchema, an as nodePin, bt as preconditionOf, c as CAMERA_SWITCH_ORDER, cn as boolean, ct as integrationInstancesCapability, d as ConsumableFormSchema, dn as number, en as defineAddonMigrations, f as ConsumableItemSchema, fn as object, g as FanDirectionSchema, gn as unknown, h as ExportInstanceUnknownError, i as listHomeAssistantBrokers, in as isReadingQuantity, l as COCO_TO_MACRO, mn as string, on as _enum, p as ConsumableStatusSchema, sn as array, t as haInstanceSettingsPortsOver, tn as defineEntryKind, u as CameraSwitchIdSchema, v as MediaPlayerRepeatSchema, w as addonRoutesCapability, wt as pruneDeletedEntities, x as RecordIntegrationInstances, y as PLUG_APPLIANCE_TYPES } from "../legacy-integration-rows-uGrBo3a3.mjs";
|
|
2
2
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
3
3
|
import "node:zlib";
|
|
4
4
|
import "node:fs";
|
|
@@ -5743,7 +5743,12 @@ async function readAllLinks(handle) {
|
|
|
5743
5743
|
function haExportMigrationPorts(deps) {
|
|
5744
5744
|
return {
|
|
5745
5745
|
readStored: deps.readStored,
|
|
5746
|
-
...haInstanceSettingsPortsOver(
|
|
5746
|
+
...haInstanceSettingsPortsOver({
|
|
5747
|
+
list: deps.api.integrations.list,
|
|
5748
|
+
readLegacyIntegrationRows: deps.api.migrations.readLegacyIntegrationRows,
|
|
5749
|
+
patchLegacyIntegrationRows: deps.api.migrations.patchLegacyIntegrationRows,
|
|
5750
|
+
deleteLegacyIntegrationRowKeys: deps.api.migrations.deleteLegacyIntegrationRowKeys
|
|
5751
|
+
}, deps.secrets ?? null),
|
|
5747
5752
|
warn: deps.warn,
|
|
5748
5753
|
putLinks: async (brokerId, body) => {
|
|
5749
5754
|
if (await deps.links.get(brokerId) !== null) return "exists";
|
|
@@ -8070,6 +8075,7 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
8070
8075
|
await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, haExportMigrationPorts({
|
|
8071
8076
|
readStored: async () => this.resolveGlobalStore(),
|
|
8072
8077
|
api: this.ctx.api,
|
|
8078
|
+
secrets: this.ctx.secrets ?? null,
|
|
8073
8079
|
links: legacyLinks,
|
|
8074
8080
|
targets: targetsHandle,
|
|
8075
8081
|
clearSettingKeys: async (keys) => {
|
|
@@ -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).
|
|
@@ -6800,6 +6800,12 @@ function projectStore(store, perNodeKeys, nodeId) {
|
|
|
6800
6800
|
}
|
|
6801
6801
|
return out;
|
|
6802
6802
|
}
|
|
6803
|
+
/** Every scope, in the order the guard and the docs list them. */
|
|
6804
|
+
var SCOPED_SETTINGS_SCOPES = [
|
|
6805
|
+
"cluster",
|
|
6806
|
+
"device-defaults",
|
|
6807
|
+
"node"
|
|
6808
|
+
];
|
|
6803
6809
|
/**
|
|
6804
6810
|
* The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
|
|
6805
6811
|
*
|
|
@@ -7691,6 +7697,13 @@ var BaseAddon = class {
|
|
|
7691
7697
|
*/
|
|
7692
7698
|
secretStatePaths = [];
|
|
7693
7699
|
_addonSecretTree = null;
|
|
7700
|
+
/**
|
|
7701
|
+
* D986 M4: the scoped schemas this addon declared, resolved once per capability
|
|
7702
|
+
* (`loadScopedSchemas`) so the sync readers (`perNodeKeys`, the secret tree, the restart check)
|
|
7703
|
+
* see them. Empty until loaded: the readers then fall back to {@link globalSettingsSchema}.
|
|
7704
|
+
*/
|
|
7705
|
+
_scopedByCap = /* @__PURE__ */ new Map();
|
|
7706
|
+
_nodeKeysByCap = /* @__PURE__ */ new Map();
|
|
7694
7707
|
_deviceSecretTree = null;
|
|
7695
7708
|
/** Refs the last resolved config pointed at — a change to one re-resolves. */
|
|
7696
7709
|
_configSecretRefs = /* @__PURE__ */ new Set();
|
|
@@ -7760,6 +7773,7 @@ var BaseAddon = class {
|
|
|
7760
7773
|
this._ctx = context;
|
|
7761
7774
|
this._secretSettings = null;
|
|
7762
7775
|
}
|
|
7776
|
+
await this.loadScopedSchemas();
|
|
7763
7777
|
await this.resolveConfig();
|
|
7764
7778
|
if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
|
|
7765
7779
|
if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
|
|
@@ -7769,6 +7783,7 @@ var BaseAddon = class {
|
|
|
7769
7783
|
const normalized = normalizeAddonInitResult(result);
|
|
7770
7784
|
const providers = normalized && "providers" in normalized && normalized.providers ? normalized.providers : [];
|
|
7771
7785
|
this._registeredCapNames = providers.map((p) => p.capability.name);
|
|
7786
|
+
await this.loadScopedSchemas();
|
|
7772
7787
|
return normalized;
|
|
7773
7788
|
}
|
|
7774
7789
|
/**
|
|
@@ -7857,6 +7872,16 @@ var BaseAddon = class {
|
|
|
7857
7872
|
schema(schema) {
|
|
7858
7873
|
return schema;
|
|
7859
7874
|
}
|
|
7875
|
+
/**
|
|
7876
|
+
* The capability names whose scoped settings schemas ({@link clusterSettingsSchema},
|
|
7877
|
+
* {@link nodeSettingsSchema}, {@link deviceDefaultsSchema}) this addon declares. Read BEFORE
|
|
7878
|
+
* `onInitialize`, when the providers it registers are not known yet: the first config resolve
|
|
7879
|
+
* must already know which keys are per node. Names the providers register at init are added
|
|
7880
|
+
* once known.
|
|
7881
|
+
*/
|
|
7882
|
+
scopedSettingsCapabilities() {
|
|
7883
|
+
return [];
|
|
7884
|
+
}
|
|
7860
7885
|
/** Override to provide global-level settings UI schema. */
|
|
7861
7886
|
globalSettingsSchema(_cap) {
|
|
7862
7887
|
return null;
|
|
@@ -7900,7 +7925,8 @@ var BaseAddon = class {
|
|
|
7900
7925
|
return [];
|
|
7901
7926
|
}
|
|
7902
7927
|
async getGlobalSettings(overlay, cap, nodeId) {
|
|
7903
|
-
|
|
7928
|
+
await this.loadScopedSchemas(cap);
|
|
7929
|
+
const schema = this.globalSettingsSchema(cap) ?? this.scopedFormSchema(cap);
|
|
7904
7930
|
if (!schema) return { sections: [] };
|
|
7905
7931
|
const projected = await this.readRawProjectedStore(nodeId, cap);
|
|
7906
7932
|
return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
|
|
@@ -7935,10 +7961,9 @@ var BaseAddon = class {
|
|
|
7935
7961
|
*
|
|
7936
7962
|
* Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
|
|
7937
7963
|
* needs its connection fields, the default integration the addon's own sections. It is the
|
|
7938
|
-
* SHAPE of the form only — the values live
|
|
7939
|
-
* unchanged (`integrations.setSettings` for an account integration).
|
|
7964
|
+
* SHAPE of the form only — the values live on the instance record.
|
|
7940
7965
|
*/
|
|
7941
|
-
async
|
|
7966
|
+
async integrationFormSchema(_instance) {
|
|
7942
7967
|
return null;
|
|
7943
7968
|
}
|
|
7944
7969
|
/**
|
|
@@ -7984,7 +8009,7 @@ var BaseAddon = class {
|
|
|
7984
8009
|
* per-instance schema when it declares one, else its global schema (what they always showed).
|
|
7985
8010
|
*/
|
|
7986
8011
|
async getIntegrationFormSchema(instance, nodeId) {
|
|
7987
|
-
const scoped = await this.
|
|
8012
|
+
const scoped = await this.integrationFormSchema(instance);
|
|
7988
8013
|
if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
|
|
7989
8014
|
return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
|
|
7990
8015
|
}
|
|
@@ -8032,6 +8057,7 @@ var BaseAddon = class {
|
|
|
8032
8057
|
* resolved blob back is a no-op for every unchanged secret.
|
|
8033
8058
|
*/
|
|
8034
8059
|
async resolveGlobalStore(nodeId, cap) {
|
|
8060
|
+
await this.loadScopedSchemas(cap);
|
|
8035
8061
|
const raw = await this._ctx?.settings?.readAddonStore() ?? {};
|
|
8036
8062
|
return this.projectForNode(raw, nodeId, cap);
|
|
8037
8063
|
}
|
|
@@ -8042,6 +8068,7 @@ var BaseAddon = class {
|
|
|
8042
8068
|
}
|
|
8043
8069
|
/** The PROJECTED store with its markers — the form hydrate only (D798). */
|
|
8044
8070
|
async readRawProjectedStore(nodeId, cap) {
|
|
8071
|
+
await this.loadScopedSchemas(cap);
|
|
8045
8072
|
const raw = await this.rawSettings()?.readAddonStore() ?? {};
|
|
8046
8073
|
return this.projectForNode(raw, nodeId, cap);
|
|
8047
8074
|
}
|
|
@@ -8144,6 +8171,7 @@ var BaseAddon = class {
|
|
|
8144
8171
|
await this.applyGlobalPatch(patch, nodeId);
|
|
8145
8172
|
}
|
|
8146
8173
|
async applyGlobalPatch(barePatch, nodeId) {
|
|
8174
|
+
await this.loadScopedSchemas();
|
|
8147
8175
|
const keys = this.perNodeKeys();
|
|
8148
8176
|
const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
|
|
8149
8177
|
const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
|
|
@@ -8157,7 +8185,8 @@ var BaseAddon = class {
|
|
|
8157
8185
|
await this.resolveConfig();
|
|
8158
8186
|
await this.onConfigChanged();
|
|
8159
8187
|
this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
|
|
8160
|
-
|
|
8188
|
+
const legacy = this.globalSettingsSchema();
|
|
8189
|
+
this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
|
|
8161
8190
|
}
|
|
8162
8191
|
/**
|
|
8163
8192
|
* The set of field keys the global settings schema declares `perNode: true`
|
|
@@ -8165,14 +8194,67 @@ var BaseAddon = class {
|
|
|
8165
8194
|
* declarations). Empty set ⇒ every per-node code path is bypassed and the
|
|
8166
8195
|
* settings API behaves exactly like the legacy node-agnostic one.
|
|
8167
8196
|
*/
|
|
8168
|
-
|
|
8197
|
+
/**
|
|
8198
|
+
* Load the scoped schemas of every capability this addon declares (the override and the
|
|
8199
|
+
* providers registered at init, plus `extraCap`) that is not loaded yet, then drop what was
|
|
8200
|
+
* derived from the previous set. Idempotent.
|
|
8201
|
+
*/
|
|
8202
|
+
async loadScopedSchemas(extraCap) {
|
|
8203
|
+
const caps = new Set([
|
|
8204
|
+
...this.scopedSettingsCapabilities(),
|
|
8205
|
+
...this._registeredCapNames,
|
|
8206
|
+
...extraCap === void 0 ? [] : [extraCap]
|
|
8207
|
+
]);
|
|
8208
|
+
let added = false;
|
|
8209
|
+
for (const cap of caps) {
|
|
8210
|
+
if (this._scopedByCap.has(cap)) continue;
|
|
8211
|
+
const loaded = [];
|
|
8212
|
+
for (const scope of SCOPED_SETTINGS_SCOPES) {
|
|
8213
|
+
const schema = await this.getScopedSettingsSchema(scope, cap);
|
|
8214
|
+
if (schema !== null) loaded.push({
|
|
8215
|
+
scope,
|
|
8216
|
+
schema
|
|
8217
|
+
});
|
|
8218
|
+
}
|
|
8219
|
+
this._scopedByCap.set(cap, loaded);
|
|
8220
|
+
added = true;
|
|
8221
|
+
}
|
|
8222
|
+
if (!added) return;
|
|
8223
|
+
this._nodeKeysByCap = /* @__PURE__ */ new Map();
|
|
8224
|
+
this._addonSecretTree = null;
|
|
8225
|
+
}
|
|
8226
|
+
/**
|
|
8227
|
+
* The form the settings API serves while an addon declares no {@link globalSettingsSchema}: the
|
|
8228
|
+
* sections of its scoped schemas, so dropping the old hook does not blank the form.
|
|
8229
|
+
*/
|
|
8230
|
+
scopedFormSchema(cap) {
|
|
8231
|
+
const byId = /* @__PURE__ */ new Map();
|
|
8232
|
+
for (const entry of this.loadedScopedSchemas(cap)) for (const section of entry.schema.sections) if (!byId.has(section.id)) byId.set(section.id, section);
|
|
8233
|
+
return byId.size === 0 ? null : { sections: [...byId.values()] };
|
|
8234
|
+
}
|
|
8235
|
+
/** Every loaded scoped schema of `cap`, or of every loaded capability. */
|
|
8236
|
+
loadedScopedSchemas(cap) {
|
|
8237
|
+
if (cap !== void 0) return this._scopedByCap.get(cap) ?? [];
|
|
8238
|
+
return [...this._scopedByCap.values()].flat();
|
|
8239
|
+
}
|
|
8240
|
+
/**
|
|
8241
|
+
* The field keys that are stored per node: every field of a {@link nodeSettingsSchema}, plus
|
|
8242
|
+
* the fields the legacy {@link globalSettingsSchema} still marks `perNode: true`. Derived once
|
|
8243
|
+
* per `cap` argument and memoized (schemas are static declarations). Empty set ⇒ every
|
|
8244
|
+
* per-node code path is bypassed and the settings API behaves like the node-agnostic one.
|
|
8245
|
+
* Call {@link loadScopedSchemas} first (the async seams do).
|
|
8246
|
+
*/
|
|
8169
8247
|
perNodeKeys(cap) {
|
|
8170
8248
|
const cacheKey = cap ?? "";
|
|
8171
|
-
const cached = this.
|
|
8249
|
+
const cached = this._nodeKeysByCap.get(cacheKey);
|
|
8172
8250
|
if (cached) return cached;
|
|
8173
|
-
const
|
|
8174
|
-
const keys = new Set(
|
|
8175
|
-
this.
|
|
8251
|
+
const legacy = this.globalSettingsSchema(cap);
|
|
8252
|
+
const keys = new Set(legacy ? legacy.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
|
|
8253
|
+
for (const { scope, schema } of this.loadedScopedSchemas(cap)) {
|
|
8254
|
+
if (scope !== "node") continue;
|
|
8255
|
+
for (const section of schema.sections) for (const key of collectValueFieldKeys(section.fields)) keys.add(key);
|
|
8256
|
+
}
|
|
8257
|
+
this._nodeKeysByCap.set(cacheKey, keys);
|
|
8176
8258
|
return keys;
|
|
8177
8259
|
}
|
|
8178
8260
|
/**
|
|
@@ -8182,10 +8264,9 @@ var BaseAddon = class {
|
|
|
8182
8264
|
* time to return its response before the addon is torn down and
|
|
8183
8265
|
* re-initialised by `AddonRegistryService.restartAddon`.
|
|
8184
8266
|
*/
|
|
8185
|
-
maybeAutoRestart(patchKeys,
|
|
8186
|
-
if (!schema) return;
|
|
8267
|
+
maybeAutoRestart(patchKeys, schemas) {
|
|
8187
8268
|
const restartKeys = /* @__PURE__ */ new Set();
|
|
8188
|
-
for (const section of schema.sections) for (const field of section.fields) {
|
|
8269
|
+
for (const schema of schemas) for (const section of schema.sections) for (const field of section.fields) {
|
|
8189
8270
|
if (field.type === "separator" || field.type === "info") continue;
|
|
8190
8271
|
if (field.requiresRestart) restartKeys.add(field.key);
|
|
8191
8272
|
}
|
|
@@ -8381,12 +8462,14 @@ var BaseAddon = class {
|
|
|
8381
8462
|
* here, in the addon's own process, so the hub keeps no copy of it.
|
|
8382
8463
|
*/
|
|
8383
8464
|
async getDeclaredSettingsKeys() {
|
|
8465
|
+
await this.loadScopedSchemas();
|
|
8384
8466
|
return { keys: [...this.declaredKeySet()].toSorted() };
|
|
8385
8467
|
}
|
|
8386
8468
|
declaredKeySet() {
|
|
8387
8469
|
const keys = new Set(Object.keys(this.defaults));
|
|
8388
8470
|
const schema = this.globalSettingsSchema();
|
|
8389
8471
|
if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
|
|
8472
|
+
for (const { schema: scoped } of this.loadedScopedSchemas()) for (const section of scoped.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
|
|
8390
8473
|
for (const key of this._declaredStateKeys) keys.add(key);
|
|
8391
8474
|
for (const path of this.secretStatePaths) {
|
|
8392
8475
|
const root = path.split(/[.[]/, 1)[0];
|
|
@@ -8482,7 +8565,11 @@ var BaseAddon = class {
|
|
|
8482
8565
|
}
|
|
8483
8566
|
/** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
|
|
8484
8567
|
addonSecretTree() {
|
|
8485
|
-
this._addonSecretTree
|
|
8568
|
+
if (this._addonSecretTree === null) {
|
|
8569
|
+
const tree = new Map(secretTreeFromSchema(this.globalSettingsSchema()));
|
|
8570
|
+
for (const { schema } of this.loadedScopedSchemas()) for (const [key, node] of secretTreeFromSchema(schema)) tree.set(key, node);
|
|
8571
|
+
this._addonSecretTree = mergeSecretStatePaths(tree, this.secretStatePaths);
|
|
8572
|
+
}
|
|
8486
8573
|
return this._addonSecretTree;
|
|
8487
8574
|
}
|
|
8488
8575
|
deviceSecretTree() {
|
|
@@ -8732,6 +8819,23 @@ function dropPerNodeFields(fields) {
|
|
|
8732
8819
|
}
|
|
8733
8820
|
return kept;
|
|
8734
8821
|
}
|
|
8822
|
+
/** The keys of the value-holding fields, recursing into layout containers. */
|
|
8823
|
+
function collectValueFieldKeys(fields) {
|
|
8824
|
+
const collected = [];
|
|
8825
|
+
for (const field of fields) {
|
|
8826
|
+
if (field.type === "group") {
|
|
8827
|
+
collected.push(...collectValueFieldKeys(field.fields));
|
|
8828
|
+
continue;
|
|
8829
|
+
}
|
|
8830
|
+
if (field.type === "sub-tabs") {
|
|
8831
|
+
for (const tab of field.tabs) collected.push(...collectValueFieldKeys(tab.fields));
|
|
8832
|
+
continue;
|
|
8833
|
+
}
|
|
8834
|
+
if (field.type === "separator" || field.type === "info") continue;
|
|
8835
|
+
if ("key" in field && typeof field.key === "string") collected.push(field.key);
|
|
8836
|
+
}
|
|
8837
|
+
return collected;
|
|
8838
|
+
}
|
|
8735
8839
|
function collectFieldKeys(fields) {
|
|
8736
8840
|
const collected = [];
|
|
8737
8841
|
for (const field of fields) {
|
|
@@ -13586,12 +13690,22 @@ var RecordIntegrationInstances = class {
|
|
|
13586
13690
|
...this.hooks.description,
|
|
13587
13691
|
mintsId: true
|
|
13588
13692
|
};
|
|
13589
|
-
|
|
13693
|
+
/**
|
|
13694
|
+
* The record an id names. A caller holding the `integrations` registry id (the Integrations
|
|
13695
|
+
* page) reaches a kind whose body links to it through `integrationId`; an id that is itself a
|
|
13696
|
+
* record id answers as it is. An id that names nothing comes back unchanged.
|
|
13697
|
+
*/
|
|
13698
|
+
async resolveRecordId(id) {
|
|
13699
|
+
if (await this.handle.get(id) !== null) return id;
|
|
13700
|
+
const linked = (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.find((record) => record.data["integrationId"] === id);
|
|
13701
|
+
return linked === void 0 ? id : linked.id;
|
|
13702
|
+
}
|
|
13703
|
+
instanceFormSchema = async ({ id }) => this.hooks.formSchema(id === null ? null : await this.resolveRecordId(id));
|
|
13590
13704
|
listInstances = async () => {
|
|
13591
13705
|
return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
|
|
13592
13706
|
};
|
|
13593
13707
|
getInstance = async ({ id }) => {
|
|
13594
|
-
const record = await this.handle.get(id);
|
|
13708
|
+
const record = await this.handle.get(await this.resolveRecordId(id));
|
|
13595
13709
|
return record === null ? null : this.viewOf(record);
|
|
13596
13710
|
};
|
|
13597
13711
|
/** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
|
|
@@ -13608,7 +13722,8 @@ var RecordIntegrationInstances = class {
|
|
|
13608
13722
|
await this.hooks.onChanged?.(minted, "created");
|
|
13609
13723
|
return { id: minted };
|
|
13610
13724
|
};
|
|
13611
|
-
updateInstance = async ({ id, name, values }) => {
|
|
13725
|
+
updateInstance = async ({ id: askedId, name, values }) => {
|
|
13726
|
+
const id = await this.resolveRecordId(askedId);
|
|
13612
13727
|
const patch = {};
|
|
13613
13728
|
for (const [key, value] of Object.entries(values)) {
|
|
13614
13729
|
if (value === void 0) continue;
|
|
@@ -13643,7 +13758,10 @@ var RecordIntegrationInstances = class {
|
|
|
13643
13758
|
status: "unsupported",
|
|
13644
13759
|
error: "This integration declares no connection test"
|
|
13645
13760
|
};
|
|
13646
|
-
return this.hooks.test(input
|
|
13761
|
+
return this.hooks.test(input.id === null ? input : {
|
|
13762
|
+
...input,
|
|
13763
|
+
id: await this.resolveRecordId(input.id)
|
|
13764
|
+
});
|
|
13647
13765
|
};
|
|
13648
13766
|
/** The flat provider object to hand to `registerProvider`. */
|
|
13649
13767
|
toProvider() {
|
|
@@ -52301,12 +52419,12 @@ var deviceExportCapability = {
|
|
|
52301
52419
|
kind: "poll"
|
|
52302
52420
|
},
|
|
52303
52421
|
/**
|
|
52304
|
-
* Each export provider
|
|
52305
|
-
* (enabled toggle, preferred stream, addon-specific knobs
|
|
52306
|
-
*
|
|
52307
|
-
* `
|
|
52308
|
-
* `
|
|
52309
|
-
*
|
|
52422
|
+
* Each export provider MAY contribute its own per-device "Export" panel
|
|
52423
|
+
* (enabled toggle, preferred stream, addon-specific knobs). The three
|
|
52424
|
+
* framework methods are mounted on the cap router when present.
|
|
52425
|
+
* `IDeviceExportProvider` leaves them optional: an exporter with no
|
|
52426
|
+
* per-device panel omits them (a `deviceConfig` cap's UI is derived and
|
|
52427
|
+
* never hand-written — D14 — and an exporter is not one of those).
|
|
52310
52428
|
*/
|
|
52311
52429
|
exposesDeviceSettings: true,
|
|
52312
52430
|
methods: {
|
|
@@ -56784,11 +56902,7 @@ var CreateIntegrationInputSchema = object({
|
|
|
56784
56902
|
info: record(string(), unknown()).optional(),
|
|
56785
56903
|
/** `stableId` of the discovery candidate the form was seeded from (D984). */
|
|
56786
56904
|
originStableId: string().optional(),
|
|
56787
|
-
settings: record(string(), unknown()).optional()
|
|
56788
|
-
/** Keys of `settings` the form's schema declares secret (by type) — kept in
|
|
56789
|
-
* the keychain, not the table (D787). */
|
|
56790
|
-
secretKeys: array(string()).optional(),
|
|
56791
|
-
skipRestart: boolean().optional()
|
|
56905
|
+
settings: record(string(), unknown()).optional()
|
|
56792
56906
|
});
|
|
56793
56907
|
var UpdateIntegrationInputSchema = object({
|
|
56794
56908
|
id: string(),
|
|
@@ -56809,22 +56923,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
56809
56923
|
}), method(object({ id: string() }), DeleteIntegrationResultSchema, {
|
|
56810
56924
|
kind: "mutation",
|
|
56811
56925
|
auth: "admin"
|
|
56812
|
-
}), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
|
|
56813
|
-
id: string(),
|
|
56814
|
-
settings: record(string(), unknown()),
|
|
56815
|
-
/** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
|
|
56816
|
-
secretKeys: array(string()).optional(),
|
|
56817
|
-
/** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
|
|
56818
|
-
skipRestart: boolean().optional()
|
|
56819
|
-
}), object({ success: boolean() }), {
|
|
56820
|
-
kind: "mutation",
|
|
56821
|
-
auth: "admin"
|
|
56822
|
-
}), method(object({ id: string() }), object({
|
|
56823
|
-
rows: number().int().nonnegative(),
|
|
56824
|
-
secrets: number().int().nonnegative()
|
|
56825
|
-
}), {
|
|
56826
|
-
kind: "mutation",
|
|
56827
|
-
auth: "admin"
|
|
56828
56926
|
}), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
|
|
56829
56927
|
addonId: string(),
|
|
56830
56928
|
settings: record(string(), unknown())
|
|
@@ -59801,6 +59899,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
|
|
|
59801
59899
|
}), {
|
|
59802
59900
|
kind: "mutation",
|
|
59803
59901
|
auth: "admin"
|
|
59902
|
+
}), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
|
|
59903
|
+
rows: number().int().nonnegative(),
|
|
59904
|
+
secrets: number().int().nonnegative()
|
|
59905
|
+
}), {
|
|
59906
|
+
kind: "mutation",
|
|
59907
|
+
auth: "admin"
|
|
59908
|
+
}), method(object({
|
|
59909
|
+
integrationId: string().min(1),
|
|
59910
|
+
patch: record(string(), unknown())
|
|
59911
|
+
}), object({ written: boolean() }), {
|
|
59912
|
+
kind: "mutation",
|
|
59913
|
+
auth: "admin"
|
|
59914
|
+
}), method(object({
|
|
59915
|
+
integrationId: string().min(1),
|
|
59916
|
+
keys: array(string().min(1))
|
|
59917
|
+
}), object({
|
|
59918
|
+
present: boolean(),
|
|
59919
|
+
removed: record(string(), unknown())
|
|
59920
|
+
}), {
|
|
59921
|
+
kind: "mutation",
|
|
59922
|
+
auth: "admin"
|
|
59804
59923
|
});
|
|
59805
59924
|
method(object({
|
|
59806
59925
|
sourceUrl: string(),
|
|
@@ -86621,13 +86740,6 @@ Object.freeze({
|
|
|
86621
86740
|
access: "create",
|
|
86622
86741
|
role: "admin"
|
|
86623
86742
|
},
|
|
86624
|
-
"integrations.clearSettings": {
|
|
86625
|
-
capName: "integrations",
|
|
86626
|
-
capScope: "system",
|
|
86627
|
-
addonId: null,
|
|
86628
|
-
access: "delete",
|
|
86629
|
-
role: "admin"
|
|
86630
|
-
},
|
|
86631
86743
|
"integrations.create": {
|
|
86632
86744
|
capName: "integrations",
|
|
86633
86745
|
capScope: "system",
|
|
@@ -86663,13 +86775,6 @@ Object.freeze({
|
|
|
86663
86775
|
access: "view",
|
|
86664
86776
|
role: "admin"
|
|
86665
86777
|
},
|
|
86666
|
-
"integrations.getSettings": {
|
|
86667
|
-
capName: "integrations",
|
|
86668
|
-
capScope: "system",
|
|
86669
|
-
addonId: null,
|
|
86670
|
-
access: "view",
|
|
86671
|
-
role: "admin"
|
|
86672
|
-
},
|
|
86673
86778
|
"integrations.list": {
|
|
86674
86779
|
capName: "integrations",
|
|
86675
86780
|
capScope: "system",
|
|
@@ -86677,13 +86782,6 @@ Object.freeze({
|
|
|
86677
86782
|
access: "view",
|
|
86678
86783
|
role: "admin"
|
|
86679
86784
|
},
|
|
86680
|
-
"integrations.setSettings": {
|
|
86681
|
-
capName: "integrations",
|
|
86682
|
-
capScope: "system",
|
|
86683
|
-
addonId: null,
|
|
86684
|
-
access: "create",
|
|
86685
|
-
role: "admin"
|
|
86686
|
-
},
|
|
86687
86785
|
"integrations.testConnection": {
|
|
86688
86786
|
capName: "integrations",
|
|
86689
86787
|
capScope: "system",
|
|
@@ -87678,6 +87776,13 @@ Object.freeze({
|
|
|
87678
87776
|
access: "view",
|
|
87679
87777
|
role: "admin"
|
|
87680
87778
|
},
|
|
87779
|
+
"migrations.clearLegacyIntegrationRows": {
|
|
87780
|
+
capName: "migrations",
|
|
87781
|
+
capScope: "system",
|
|
87782
|
+
addonId: null,
|
|
87783
|
+
access: "delete",
|
|
87784
|
+
role: "admin"
|
|
87785
|
+
},
|
|
87681
87786
|
"migrations.declareOwner": {
|
|
87682
87787
|
capName: "migrations",
|
|
87683
87788
|
capScope: "system",
|
|
@@ -87685,6 +87790,13 @@ Object.freeze({
|
|
|
87685
87790
|
access: "create",
|
|
87686
87791
|
role: "admin"
|
|
87687
87792
|
},
|
|
87793
|
+
"migrations.deleteLegacyIntegrationRowKeys": {
|
|
87794
|
+
capName: "migrations",
|
|
87795
|
+
capScope: "system",
|
|
87796
|
+
addonId: null,
|
|
87797
|
+
access: "delete",
|
|
87798
|
+
role: "admin"
|
|
87799
|
+
},
|
|
87688
87800
|
"migrations.dryRun": {
|
|
87689
87801
|
capName: "migrations",
|
|
87690
87802
|
capScope: "system",
|
|
@@ -87720,6 +87832,20 @@ Object.freeze({
|
|
|
87720
87832
|
access: "create",
|
|
87721
87833
|
role: "admin"
|
|
87722
87834
|
},
|
|
87835
|
+
"migrations.patchLegacyIntegrationRows": {
|
|
87836
|
+
capName: "migrations",
|
|
87837
|
+
capScope: "system",
|
|
87838
|
+
addonId: null,
|
|
87839
|
+
access: "create",
|
|
87840
|
+
role: "admin"
|
|
87841
|
+
},
|
|
87842
|
+
"migrations.readLegacyIntegrationRows": {
|
|
87843
|
+
capName: "migrations",
|
|
87844
|
+
capScope: "system",
|
|
87845
|
+
addonId: null,
|
|
87846
|
+
access: "view",
|
|
87847
|
+
role: "admin"
|
|
87848
|
+
},
|
|
87723
87849
|
"migrations.restorePreMigration": {
|
|
87724
87850
|
capName: "migrations",
|
|
87725
87851
|
capScope: "system",
|
|
@@ -98399,13 +98525,18 @@ var HA_BROKER_KIND = "home-assistant";
|
|
|
98399
98525
|
async function listHomeAssistantBrokers(query) {
|
|
98400
98526
|
return (await query({})).filter((broker) => broker.kind === HA_BROKER_KIND);
|
|
98401
98527
|
}
|
|
98402
|
-
function
|
|
98528
|
+
function refsOfKeys(stored, keys) {
|
|
98529
|
+
const picked = {};
|
|
98530
|
+
for (const key of keys) if (Object.prototype.hasOwnProperty.call(stored, key)) picked[key] = stored[key];
|
|
98531
|
+
return collectSecretMarkerRefs(picked);
|
|
98532
|
+
}
|
|
98533
|
+
function haInstanceSettingsPortsOver(api, secrets = null) {
|
|
98403
98534
|
const listInstances = async () => {
|
|
98404
98535
|
const all = await api.list.query();
|
|
98405
98536
|
const rows = [];
|
|
98406
98537
|
for (const integration of all) {
|
|
98407
98538
|
if (integration.addonId !== "provider-homeassistant") continue;
|
|
98408
|
-
const settings = await api.
|
|
98539
|
+
const settings = await api.readLegacyIntegrationRows.query({ integrationId: integration.id });
|
|
98409
98540
|
const brokerId = settings["brokerId"];
|
|
98410
98541
|
rows.push({
|
|
98411
98542
|
integrationId: integration.id,
|
|
@@ -98415,31 +98546,26 @@ function haInstanceSettingsPortsOver(api) {
|
|
|
98415
98546
|
}
|
|
98416
98547
|
return rows;
|
|
98417
98548
|
};
|
|
98549
|
+
const owns = async (integrationId) => {
|
|
98550
|
+
return (await api.list.query()).some((i) => i.id === integrationId && i.addonId === "provider-homeassistant");
|
|
98551
|
+
};
|
|
98418
98552
|
return {
|
|
98419
98553
|
listInstances,
|
|
98420
98554
|
patchInstanceSettings: async (integrationId, patch) => {
|
|
98421
|
-
if (!
|
|
98422
|
-
|
|
98423
|
-
|
|
98424
|
-
|
|
98425
|
-
|
|
98426
|
-
...stored,
|
|
98427
|
-
...patch
|
|
98428
|
-
},
|
|
98429
|
-
skipRestart: true
|
|
98430
|
-
});
|
|
98431
|
-
return true;
|
|
98555
|
+
if (!await owns(integrationId)) return false;
|
|
98556
|
+
return (await api.patchLegacyIntegrationRows.mutate({
|
|
98557
|
+
integrationId,
|
|
98558
|
+
patch: { ...patch }
|
|
98559
|
+
})).written;
|
|
98432
98560
|
},
|
|
98433
98561
|
clearInstanceSettingsKeys: async (integrationId, keys) => {
|
|
98434
|
-
if (!
|
|
98435
|
-
const
|
|
98436
|
-
|
|
98437
|
-
await api.
|
|
98438
|
-
|
|
98439
|
-
|
|
98440
|
-
|
|
98441
|
-
});
|
|
98442
|
-
return true;
|
|
98562
|
+
if (!await owns(integrationId)) return false;
|
|
98563
|
+
const refs = refsOfKeys(await api.readLegacyIntegrationRows.query({ integrationId }), keys);
|
|
98564
|
+
if (refs.length > 0 && secrets !== null) await secrets.delete(refs);
|
|
98565
|
+
return (await api.deleteLegacyIntegrationRowKeys.mutate({
|
|
98566
|
+
integrationId,
|
|
98567
|
+
keys
|
|
98568
|
+
})).present;
|
|
98443
98569
|
}
|
|
98444
98570
|
};
|
|
98445
98571
|
}
|
|
@@ -5980,7 +5980,7 @@ var ZodIssueCode = {
|
|
|
5980
5980
|
var ZodFirstPartyTypeKind;
|
|
5981
5981
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5982
5982
|
//#endregion
|
|
5983
|
-
//#region ../types/dist/sleep-
|
|
5983
|
+
//#region ../types/dist/sleep-DEZaLbiu.mjs
|
|
5984
5984
|
/**
|
|
5985
5985
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5986
5986
|
* window to float samples (D455).
|
|
@@ -6799,6 +6799,12 @@ function projectStore(store, perNodeKeys, nodeId) {
|
|
|
6799
6799
|
}
|
|
6800
6800
|
return out;
|
|
6801
6801
|
}
|
|
6802
|
+
/** Every scope, in the order the guard and the docs list them. */
|
|
6803
|
+
var SCOPED_SETTINGS_SCOPES = [
|
|
6804
|
+
"cluster",
|
|
6805
|
+
"device-defaults",
|
|
6806
|
+
"node"
|
|
6807
|
+
];
|
|
6802
6808
|
/**
|
|
6803
6809
|
* The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
|
|
6804
6810
|
*
|
|
@@ -7690,6 +7696,13 @@ var BaseAddon = class {
|
|
|
7690
7696
|
*/
|
|
7691
7697
|
secretStatePaths = [];
|
|
7692
7698
|
_addonSecretTree = null;
|
|
7699
|
+
/**
|
|
7700
|
+
* D986 M4: the scoped schemas this addon declared, resolved once per capability
|
|
7701
|
+
* (`loadScopedSchemas`) so the sync readers (`perNodeKeys`, the secret tree, the restart check)
|
|
7702
|
+
* see them. Empty until loaded: the readers then fall back to {@link globalSettingsSchema}.
|
|
7703
|
+
*/
|
|
7704
|
+
_scopedByCap = /* @__PURE__ */ new Map();
|
|
7705
|
+
_nodeKeysByCap = /* @__PURE__ */ new Map();
|
|
7693
7706
|
_deviceSecretTree = null;
|
|
7694
7707
|
/** Refs the last resolved config pointed at — a change to one re-resolves. */
|
|
7695
7708
|
_configSecretRefs = /* @__PURE__ */ new Set();
|
|
@@ -7759,6 +7772,7 @@ var BaseAddon = class {
|
|
|
7759
7772
|
this._ctx = context;
|
|
7760
7773
|
this._secretSettings = null;
|
|
7761
7774
|
}
|
|
7775
|
+
await this.loadScopedSchemas();
|
|
7762
7776
|
await this.resolveConfig();
|
|
7763
7777
|
if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
|
|
7764
7778
|
if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
|
|
@@ -7768,6 +7782,7 @@ var BaseAddon = class {
|
|
|
7768
7782
|
const normalized = normalizeAddonInitResult(result);
|
|
7769
7783
|
const providers = normalized && "providers" in normalized && normalized.providers ? normalized.providers : [];
|
|
7770
7784
|
this._registeredCapNames = providers.map((p) => p.capability.name);
|
|
7785
|
+
await this.loadScopedSchemas();
|
|
7771
7786
|
return normalized;
|
|
7772
7787
|
}
|
|
7773
7788
|
/**
|
|
@@ -7856,6 +7871,16 @@ var BaseAddon = class {
|
|
|
7856
7871
|
schema(schema) {
|
|
7857
7872
|
return schema;
|
|
7858
7873
|
}
|
|
7874
|
+
/**
|
|
7875
|
+
* The capability names whose scoped settings schemas ({@link clusterSettingsSchema},
|
|
7876
|
+
* {@link nodeSettingsSchema}, {@link deviceDefaultsSchema}) this addon declares. Read BEFORE
|
|
7877
|
+
* `onInitialize`, when the providers it registers are not known yet: the first config resolve
|
|
7878
|
+
* must already know which keys are per node. Names the providers register at init are added
|
|
7879
|
+
* once known.
|
|
7880
|
+
*/
|
|
7881
|
+
scopedSettingsCapabilities() {
|
|
7882
|
+
return [];
|
|
7883
|
+
}
|
|
7859
7884
|
/** Override to provide global-level settings UI schema. */
|
|
7860
7885
|
globalSettingsSchema(_cap) {
|
|
7861
7886
|
return null;
|
|
@@ -7899,7 +7924,8 @@ var BaseAddon = class {
|
|
|
7899
7924
|
return [];
|
|
7900
7925
|
}
|
|
7901
7926
|
async getGlobalSettings(overlay, cap, nodeId) {
|
|
7902
|
-
|
|
7927
|
+
await this.loadScopedSchemas(cap);
|
|
7928
|
+
const schema = this.globalSettingsSchema(cap) ?? this.scopedFormSchema(cap);
|
|
7903
7929
|
if (!schema) return { sections: [] };
|
|
7904
7930
|
const projected = await this.readRawProjectedStore(nodeId, cap);
|
|
7905
7931
|
return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
|
|
@@ -7934,10 +7960,9 @@ var BaseAddon = class {
|
|
|
7934
7960
|
*
|
|
7935
7961
|
* Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
|
|
7936
7962
|
* needs its connection fields, the default integration the addon's own sections. It is the
|
|
7937
|
-
* SHAPE of the form only — the values live
|
|
7938
|
-
* unchanged (`integrations.setSettings` for an account integration).
|
|
7963
|
+
* SHAPE of the form only — the values live on the instance record.
|
|
7939
7964
|
*/
|
|
7940
|
-
async
|
|
7965
|
+
async integrationFormSchema(_instance) {
|
|
7941
7966
|
return null;
|
|
7942
7967
|
}
|
|
7943
7968
|
/**
|
|
@@ -7983,7 +8008,7 @@ var BaseAddon = class {
|
|
|
7983
8008
|
* per-instance schema when it declares one, else its global schema (what they always showed).
|
|
7984
8009
|
*/
|
|
7985
8010
|
async getIntegrationFormSchema(instance, nodeId) {
|
|
7986
|
-
const scoped = await this.
|
|
8011
|
+
const scoped = await this.integrationFormSchema(instance);
|
|
7987
8012
|
if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
|
|
7988
8013
|
return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
|
|
7989
8014
|
}
|
|
@@ -8031,6 +8056,7 @@ var BaseAddon = class {
|
|
|
8031
8056
|
* resolved blob back is a no-op for every unchanged secret.
|
|
8032
8057
|
*/
|
|
8033
8058
|
async resolveGlobalStore(nodeId, cap) {
|
|
8059
|
+
await this.loadScopedSchemas(cap);
|
|
8034
8060
|
const raw = await this._ctx?.settings?.readAddonStore() ?? {};
|
|
8035
8061
|
return this.projectForNode(raw, nodeId, cap);
|
|
8036
8062
|
}
|
|
@@ -8041,6 +8067,7 @@ var BaseAddon = class {
|
|
|
8041
8067
|
}
|
|
8042
8068
|
/** The PROJECTED store with its markers — the form hydrate only (D798). */
|
|
8043
8069
|
async readRawProjectedStore(nodeId, cap) {
|
|
8070
|
+
await this.loadScopedSchemas(cap);
|
|
8044
8071
|
const raw = await this.rawSettings()?.readAddonStore() ?? {};
|
|
8045
8072
|
return this.projectForNode(raw, nodeId, cap);
|
|
8046
8073
|
}
|
|
@@ -8143,6 +8170,7 @@ var BaseAddon = class {
|
|
|
8143
8170
|
await this.applyGlobalPatch(patch, nodeId);
|
|
8144
8171
|
}
|
|
8145
8172
|
async applyGlobalPatch(barePatch, nodeId) {
|
|
8173
|
+
await this.loadScopedSchemas();
|
|
8146
8174
|
const keys = this.perNodeKeys();
|
|
8147
8175
|
const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
|
|
8148
8176
|
const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
|
|
@@ -8156,7 +8184,8 @@ var BaseAddon = class {
|
|
|
8156
8184
|
await this.resolveConfig();
|
|
8157
8185
|
await this.onConfigChanged();
|
|
8158
8186
|
this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
|
|
8159
|
-
|
|
8187
|
+
const legacy = this.globalSettingsSchema();
|
|
8188
|
+
this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
|
|
8160
8189
|
}
|
|
8161
8190
|
/**
|
|
8162
8191
|
* The set of field keys the global settings schema declares `perNode: true`
|
|
@@ -8164,14 +8193,67 @@ var BaseAddon = class {
|
|
|
8164
8193
|
* declarations). Empty set ⇒ every per-node code path is bypassed and the
|
|
8165
8194
|
* settings API behaves exactly like the legacy node-agnostic one.
|
|
8166
8195
|
*/
|
|
8167
|
-
|
|
8196
|
+
/**
|
|
8197
|
+
* Load the scoped schemas of every capability this addon declares (the override and the
|
|
8198
|
+
* providers registered at init, plus `extraCap`) that is not loaded yet, then drop what was
|
|
8199
|
+
* derived from the previous set. Idempotent.
|
|
8200
|
+
*/
|
|
8201
|
+
async loadScopedSchemas(extraCap) {
|
|
8202
|
+
const caps = new Set([
|
|
8203
|
+
...this.scopedSettingsCapabilities(),
|
|
8204
|
+
...this._registeredCapNames,
|
|
8205
|
+
...extraCap === void 0 ? [] : [extraCap]
|
|
8206
|
+
]);
|
|
8207
|
+
let added = false;
|
|
8208
|
+
for (const cap of caps) {
|
|
8209
|
+
if (this._scopedByCap.has(cap)) continue;
|
|
8210
|
+
const loaded = [];
|
|
8211
|
+
for (const scope of SCOPED_SETTINGS_SCOPES) {
|
|
8212
|
+
const schema = await this.getScopedSettingsSchema(scope, cap);
|
|
8213
|
+
if (schema !== null) loaded.push({
|
|
8214
|
+
scope,
|
|
8215
|
+
schema
|
|
8216
|
+
});
|
|
8217
|
+
}
|
|
8218
|
+
this._scopedByCap.set(cap, loaded);
|
|
8219
|
+
added = true;
|
|
8220
|
+
}
|
|
8221
|
+
if (!added) return;
|
|
8222
|
+
this._nodeKeysByCap = /* @__PURE__ */ new Map();
|
|
8223
|
+
this._addonSecretTree = null;
|
|
8224
|
+
}
|
|
8225
|
+
/**
|
|
8226
|
+
* The form the settings API serves while an addon declares no {@link globalSettingsSchema}: the
|
|
8227
|
+
* sections of its scoped schemas, so dropping the old hook does not blank the form.
|
|
8228
|
+
*/
|
|
8229
|
+
scopedFormSchema(cap) {
|
|
8230
|
+
const byId = /* @__PURE__ */ new Map();
|
|
8231
|
+
for (const entry of this.loadedScopedSchemas(cap)) for (const section of entry.schema.sections) if (!byId.has(section.id)) byId.set(section.id, section);
|
|
8232
|
+
return byId.size === 0 ? null : { sections: [...byId.values()] };
|
|
8233
|
+
}
|
|
8234
|
+
/** Every loaded scoped schema of `cap`, or of every loaded capability. */
|
|
8235
|
+
loadedScopedSchemas(cap) {
|
|
8236
|
+
if (cap !== void 0) return this._scopedByCap.get(cap) ?? [];
|
|
8237
|
+
return [...this._scopedByCap.values()].flat();
|
|
8238
|
+
}
|
|
8239
|
+
/**
|
|
8240
|
+
* The field keys that are stored per node: every field of a {@link nodeSettingsSchema}, plus
|
|
8241
|
+
* the fields the legacy {@link globalSettingsSchema} still marks `perNode: true`. Derived once
|
|
8242
|
+
* per `cap` argument and memoized (schemas are static declarations). Empty set ⇒ every
|
|
8243
|
+
* per-node code path is bypassed and the settings API behaves like the node-agnostic one.
|
|
8244
|
+
* Call {@link loadScopedSchemas} first (the async seams do).
|
|
8245
|
+
*/
|
|
8168
8246
|
perNodeKeys(cap) {
|
|
8169
8247
|
const cacheKey = cap ?? "";
|
|
8170
|
-
const cached = this.
|
|
8248
|
+
const cached = this._nodeKeysByCap.get(cacheKey);
|
|
8171
8249
|
if (cached) return cached;
|
|
8172
|
-
const
|
|
8173
|
-
const keys = new Set(
|
|
8174
|
-
this.
|
|
8250
|
+
const legacy = this.globalSettingsSchema(cap);
|
|
8251
|
+
const keys = new Set(legacy ? legacy.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
|
|
8252
|
+
for (const { scope, schema } of this.loadedScopedSchemas(cap)) {
|
|
8253
|
+
if (scope !== "node") continue;
|
|
8254
|
+
for (const section of schema.sections) for (const key of collectValueFieldKeys(section.fields)) keys.add(key);
|
|
8255
|
+
}
|
|
8256
|
+
this._nodeKeysByCap.set(cacheKey, keys);
|
|
8175
8257
|
return keys;
|
|
8176
8258
|
}
|
|
8177
8259
|
/**
|
|
@@ -8181,10 +8263,9 @@ var BaseAddon = class {
|
|
|
8181
8263
|
* time to return its response before the addon is torn down and
|
|
8182
8264
|
* re-initialised by `AddonRegistryService.restartAddon`.
|
|
8183
8265
|
*/
|
|
8184
|
-
maybeAutoRestart(patchKeys,
|
|
8185
|
-
if (!schema) return;
|
|
8266
|
+
maybeAutoRestart(patchKeys, schemas) {
|
|
8186
8267
|
const restartKeys = /* @__PURE__ */ new Set();
|
|
8187
|
-
for (const section of schema.sections) for (const field of section.fields) {
|
|
8268
|
+
for (const schema of schemas) for (const section of schema.sections) for (const field of section.fields) {
|
|
8188
8269
|
if (field.type === "separator" || field.type === "info") continue;
|
|
8189
8270
|
if (field.requiresRestart) restartKeys.add(field.key);
|
|
8190
8271
|
}
|
|
@@ -8380,12 +8461,14 @@ var BaseAddon = class {
|
|
|
8380
8461
|
* here, in the addon's own process, so the hub keeps no copy of it.
|
|
8381
8462
|
*/
|
|
8382
8463
|
async getDeclaredSettingsKeys() {
|
|
8464
|
+
await this.loadScopedSchemas();
|
|
8383
8465
|
return { keys: [...this.declaredKeySet()].toSorted() };
|
|
8384
8466
|
}
|
|
8385
8467
|
declaredKeySet() {
|
|
8386
8468
|
const keys = new Set(Object.keys(this.defaults));
|
|
8387
8469
|
const schema = this.globalSettingsSchema();
|
|
8388
8470
|
if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
|
|
8471
|
+
for (const { schema: scoped } of this.loadedScopedSchemas()) for (const section of scoped.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
|
|
8389
8472
|
for (const key of this._declaredStateKeys) keys.add(key);
|
|
8390
8473
|
for (const path of this.secretStatePaths) {
|
|
8391
8474
|
const root = path.split(/[.[]/, 1)[0];
|
|
@@ -8481,7 +8564,11 @@ var BaseAddon = class {
|
|
|
8481
8564
|
}
|
|
8482
8565
|
/** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
|
|
8483
8566
|
addonSecretTree() {
|
|
8484
|
-
this._addonSecretTree
|
|
8567
|
+
if (this._addonSecretTree === null) {
|
|
8568
|
+
const tree = new Map(secretTreeFromSchema(this.globalSettingsSchema()));
|
|
8569
|
+
for (const { schema } of this.loadedScopedSchemas()) for (const [key, node] of secretTreeFromSchema(schema)) tree.set(key, node);
|
|
8570
|
+
this._addonSecretTree = mergeSecretStatePaths(tree, this.secretStatePaths);
|
|
8571
|
+
}
|
|
8485
8572
|
return this._addonSecretTree;
|
|
8486
8573
|
}
|
|
8487
8574
|
deviceSecretTree() {
|
|
@@ -8731,6 +8818,23 @@ function dropPerNodeFields(fields) {
|
|
|
8731
8818
|
}
|
|
8732
8819
|
return kept;
|
|
8733
8820
|
}
|
|
8821
|
+
/** The keys of the value-holding fields, recursing into layout containers. */
|
|
8822
|
+
function collectValueFieldKeys(fields) {
|
|
8823
|
+
const collected = [];
|
|
8824
|
+
for (const field of fields) {
|
|
8825
|
+
if (field.type === "group") {
|
|
8826
|
+
collected.push(...collectValueFieldKeys(field.fields));
|
|
8827
|
+
continue;
|
|
8828
|
+
}
|
|
8829
|
+
if (field.type === "sub-tabs") {
|
|
8830
|
+
for (const tab of field.tabs) collected.push(...collectValueFieldKeys(tab.fields));
|
|
8831
|
+
continue;
|
|
8832
|
+
}
|
|
8833
|
+
if (field.type === "separator" || field.type === "info") continue;
|
|
8834
|
+
if ("key" in field && typeof field.key === "string") collected.push(field.key);
|
|
8835
|
+
}
|
|
8836
|
+
return collected;
|
|
8837
|
+
}
|
|
8734
8838
|
function collectFieldKeys(fields) {
|
|
8735
8839
|
const collected = [];
|
|
8736
8840
|
for (const field of fields) {
|
|
@@ -13585,12 +13689,22 @@ var RecordIntegrationInstances = class {
|
|
|
13585
13689
|
...this.hooks.description,
|
|
13586
13690
|
mintsId: true
|
|
13587
13691
|
};
|
|
13588
|
-
|
|
13692
|
+
/**
|
|
13693
|
+
* The record an id names. A caller holding the `integrations` registry id (the Integrations
|
|
13694
|
+
* page) reaches a kind whose body links to it through `integrationId`; an id that is itself a
|
|
13695
|
+
* record id answers as it is. An id that names nothing comes back unchanged.
|
|
13696
|
+
*/
|
|
13697
|
+
async resolveRecordId(id) {
|
|
13698
|
+
if (await this.handle.get(id) !== null) return id;
|
|
13699
|
+
const linked = (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.find((record) => record.data["integrationId"] === id);
|
|
13700
|
+
return linked === void 0 ? id : linked.id;
|
|
13701
|
+
}
|
|
13702
|
+
instanceFormSchema = async ({ id }) => this.hooks.formSchema(id === null ? null : await this.resolveRecordId(id));
|
|
13589
13703
|
listInstances = async () => {
|
|
13590
13704
|
return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
|
|
13591
13705
|
};
|
|
13592
13706
|
getInstance = async ({ id }) => {
|
|
13593
|
-
const record = await this.handle.get(id);
|
|
13707
|
+
const record = await this.handle.get(await this.resolveRecordId(id));
|
|
13594
13708
|
return record === null ? null : this.viewOf(record);
|
|
13595
13709
|
};
|
|
13596
13710
|
/** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
|
|
@@ -13607,7 +13721,8 @@ var RecordIntegrationInstances = class {
|
|
|
13607
13721
|
await this.hooks.onChanged?.(minted, "created");
|
|
13608
13722
|
return { id: minted };
|
|
13609
13723
|
};
|
|
13610
|
-
updateInstance = async ({ id, name, values }) => {
|
|
13724
|
+
updateInstance = async ({ id: askedId, name, values }) => {
|
|
13725
|
+
const id = await this.resolveRecordId(askedId);
|
|
13611
13726
|
const patch = {};
|
|
13612
13727
|
for (const [key, value] of Object.entries(values)) {
|
|
13613
13728
|
if (value === void 0) continue;
|
|
@@ -13642,7 +13757,10 @@ var RecordIntegrationInstances = class {
|
|
|
13642
13757
|
status: "unsupported",
|
|
13643
13758
|
error: "This integration declares no connection test"
|
|
13644
13759
|
};
|
|
13645
|
-
return this.hooks.test(input
|
|
13760
|
+
return this.hooks.test(input.id === null ? input : {
|
|
13761
|
+
...input,
|
|
13762
|
+
id: await this.resolveRecordId(input.id)
|
|
13763
|
+
});
|
|
13646
13764
|
};
|
|
13647
13765
|
/** The flat provider object to hand to `registerProvider`. */
|
|
13648
13766
|
toProvider() {
|
|
@@ -52300,12 +52418,12 @@ var deviceExportCapability = {
|
|
|
52300
52418
|
kind: "poll"
|
|
52301
52419
|
},
|
|
52302
52420
|
/**
|
|
52303
|
-
* Each export provider
|
|
52304
|
-
* (enabled toggle, preferred stream, addon-specific knobs
|
|
52305
|
-
*
|
|
52306
|
-
* `
|
|
52307
|
-
* `
|
|
52308
|
-
*
|
|
52421
|
+
* Each export provider MAY contribute its own per-device "Export" panel
|
|
52422
|
+
* (enabled toggle, preferred stream, addon-specific knobs). The three
|
|
52423
|
+
* framework methods are mounted on the cap router when present.
|
|
52424
|
+
* `IDeviceExportProvider` leaves them optional: an exporter with no
|
|
52425
|
+
* per-device panel omits them (a `deviceConfig` cap's UI is derived and
|
|
52426
|
+
* never hand-written — D14 — and an exporter is not one of those).
|
|
52309
52427
|
*/
|
|
52310
52428
|
exposesDeviceSettings: true,
|
|
52311
52429
|
methods: {
|
|
@@ -56783,11 +56901,7 @@ var CreateIntegrationInputSchema = object({
|
|
|
56783
56901
|
info: record(string(), unknown()).optional(),
|
|
56784
56902
|
/** `stableId` of the discovery candidate the form was seeded from (D984). */
|
|
56785
56903
|
originStableId: string().optional(),
|
|
56786
|
-
settings: record(string(), unknown()).optional()
|
|
56787
|
-
/** Keys of `settings` the form's schema declares secret (by type) — kept in
|
|
56788
|
-
* the keychain, not the table (D787). */
|
|
56789
|
-
secretKeys: array(string()).optional(),
|
|
56790
|
-
skipRestart: boolean().optional()
|
|
56904
|
+
settings: record(string(), unknown()).optional()
|
|
56791
56905
|
});
|
|
56792
56906
|
var UpdateIntegrationInputSchema = object({
|
|
56793
56907
|
id: string(),
|
|
@@ -56808,22 +56922,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
56808
56922
|
}), method(object({ id: string() }), DeleteIntegrationResultSchema, {
|
|
56809
56923
|
kind: "mutation",
|
|
56810
56924
|
auth: "admin"
|
|
56811
|
-
}), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
|
|
56812
|
-
id: string(),
|
|
56813
|
-
settings: record(string(), unknown()),
|
|
56814
|
-
/** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
|
|
56815
|
-
secretKeys: array(string()).optional(),
|
|
56816
|
-
/** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
|
|
56817
|
-
skipRestart: boolean().optional()
|
|
56818
|
-
}), object({ success: boolean() }), {
|
|
56819
|
-
kind: "mutation",
|
|
56820
|
-
auth: "admin"
|
|
56821
|
-
}), method(object({ id: string() }), object({
|
|
56822
|
-
rows: number().int().nonnegative(),
|
|
56823
|
-
secrets: number().int().nonnegative()
|
|
56824
|
-
}), {
|
|
56825
|
-
kind: "mutation",
|
|
56826
|
-
auth: "admin"
|
|
56827
56925
|
}), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
|
|
56828
56926
|
addonId: string(),
|
|
56829
56927
|
settings: record(string(), unknown())
|
|
@@ -59800,6 +59898,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
|
|
|
59800
59898
|
}), {
|
|
59801
59899
|
kind: "mutation",
|
|
59802
59900
|
auth: "admin"
|
|
59901
|
+
}), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
|
|
59902
|
+
rows: number().int().nonnegative(),
|
|
59903
|
+
secrets: number().int().nonnegative()
|
|
59904
|
+
}), {
|
|
59905
|
+
kind: "mutation",
|
|
59906
|
+
auth: "admin"
|
|
59907
|
+
}), method(object({
|
|
59908
|
+
integrationId: string().min(1),
|
|
59909
|
+
patch: record(string(), unknown())
|
|
59910
|
+
}), object({ written: boolean() }), {
|
|
59911
|
+
kind: "mutation",
|
|
59912
|
+
auth: "admin"
|
|
59913
|
+
}), method(object({
|
|
59914
|
+
integrationId: string().min(1),
|
|
59915
|
+
keys: array(string().min(1))
|
|
59916
|
+
}), object({
|
|
59917
|
+
present: boolean(),
|
|
59918
|
+
removed: record(string(), unknown())
|
|
59919
|
+
}), {
|
|
59920
|
+
kind: "mutation",
|
|
59921
|
+
auth: "admin"
|
|
59803
59922
|
});
|
|
59804
59923
|
method(object({
|
|
59805
59924
|
sourceUrl: string(),
|
|
@@ -86620,13 +86739,6 @@ Object.freeze({
|
|
|
86620
86739
|
access: "create",
|
|
86621
86740
|
role: "admin"
|
|
86622
86741
|
},
|
|
86623
|
-
"integrations.clearSettings": {
|
|
86624
|
-
capName: "integrations",
|
|
86625
|
-
capScope: "system",
|
|
86626
|
-
addonId: null,
|
|
86627
|
-
access: "delete",
|
|
86628
|
-
role: "admin"
|
|
86629
|
-
},
|
|
86630
86742
|
"integrations.create": {
|
|
86631
86743
|
capName: "integrations",
|
|
86632
86744
|
capScope: "system",
|
|
@@ -86662,13 +86774,6 @@ Object.freeze({
|
|
|
86662
86774
|
access: "view",
|
|
86663
86775
|
role: "admin"
|
|
86664
86776
|
},
|
|
86665
|
-
"integrations.getSettings": {
|
|
86666
|
-
capName: "integrations",
|
|
86667
|
-
capScope: "system",
|
|
86668
|
-
addonId: null,
|
|
86669
|
-
access: "view",
|
|
86670
|
-
role: "admin"
|
|
86671
|
-
},
|
|
86672
86777
|
"integrations.list": {
|
|
86673
86778
|
capName: "integrations",
|
|
86674
86779
|
capScope: "system",
|
|
@@ -86676,13 +86781,6 @@ Object.freeze({
|
|
|
86676
86781
|
access: "view",
|
|
86677
86782
|
role: "admin"
|
|
86678
86783
|
},
|
|
86679
|
-
"integrations.setSettings": {
|
|
86680
|
-
capName: "integrations",
|
|
86681
|
-
capScope: "system",
|
|
86682
|
-
addonId: null,
|
|
86683
|
-
access: "create",
|
|
86684
|
-
role: "admin"
|
|
86685
|
-
},
|
|
86686
86784
|
"integrations.testConnection": {
|
|
86687
86785
|
capName: "integrations",
|
|
86688
86786
|
capScope: "system",
|
|
@@ -87677,6 +87775,13 @@ Object.freeze({
|
|
|
87677
87775
|
access: "view",
|
|
87678
87776
|
role: "admin"
|
|
87679
87777
|
},
|
|
87778
|
+
"migrations.clearLegacyIntegrationRows": {
|
|
87779
|
+
capName: "migrations",
|
|
87780
|
+
capScope: "system",
|
|
87781
|
+
addonId: null,
|
|
87782
|
+
access: "delete",
|
|
87783
|
+
role: "admin"
|
|
87784
|
+
},
|
|
87680
87785
|
"migrations.declareOwner": {
|
|
87681
87786
|
capName: "migrations",
|
|
87682
87787
|
capScope: "system",
|
|
@@ -87684,6 +87789,13 @@ Object.freeze({
|
|
|
87684
87789
|
access: "create",
|
|
87685
87790
|
role: "admin"
|
|
87686
87791
|
},
|
|
87792
|
+
"migrations.deleteLegacyIntegrationRowKeys": {
|
|
87793
|
+
capName: "migrations",
|
|
87794
|
+
capScope: "system",
|
|
87795
|
+
addonId: null,
|
|
87796
|
+
access: "delete",
|
|
87797
|
+
role: "admin"
|
|
87798
|
+
},
|
|
87687
87799
|
"migrations.dryRun": {
|
|
87688
87800
|
capName: "migrations",
|
|
87689
87801
|
capScope: "system",
|
|
@@ -87719,6 +87831,20 @@ Object.freeze({
|
|
|
87719
87831
|
access: "create",
|
|
87720
87832
|
role: "admin"
|
|
87721
87833
|
},
|
|
87834
|
+
"migrations.patchLegacyIntegrationRows": {
|
|
87835
|
+
capName: "migrations",
|
|
87836
|
+
capScope: "system",
|
|
87837
|
+
addonId: null,
|
|
87838
|
+
access: "create",
|
|
87839
|
+
role: "admin"
|
|
87840
|
+
},
|
|
87841
|
+
"migrations.readLegacyIntegrationRows": {
|
|
87842
|
+
capName: "migrations",
|
|
87843
|
+
capScope: "system",
|
|
87844
|
+
addonId: null,
|
|
87845
|
+
access: "view",
|
|
87846
|
+
role: "admin"
|
|
87847
|
+
},
|
|
87722
87848
|
"migrations.restorePreMigration": {
|
|
87723
87849
|
capName: "migrations",
|
|
87724
87850
|
capScope: "system",
|
|
@@ -98398,13 +98524,18 @@ var HA_BROKER_KIND = "home-assistant";
|
|
|
98398
98524
|
async function listHomeAssistantBrokers(query) {
|
|
98399
98525
|
return (await query({})).filter((broker) => broker.kind === HA_BROKER_KIND);
|
|
98400
98526
|
}
|
|
98401
|
-
function
|
|
98527
|
+
function refsOfKeys(stored, keys) {
|
|
98528
|
+
const picked = {};
|
|
98529
|
+
for (const key of keys) if (Object.prototype.hasOwnProperty.call(stored, key)) picked[key] = stored[key];
|
|
98530
|
+
return collectSecretMarkerRefs(picked);
|
|
98531
|
+
}
|
|
98532
|
+
function haInstanceSettingsPortsOver(api, secrets = null) {
|
|
98402
98533
|
const listInstances = async () => {
|
|
98403
98534
|
const all = await api.list.query();
|
|
98404
98535
|
const rows = [];
|
|
98405
98536
|
for (const integration of all) {
|
|
98406
98537
|
if (integration.addonId !== "provider-homeassistant") continue;
|
|
98407
|
-
const settings = await api.
|
|
98538
|
+
const settings = await api.readLegacyIntegrationRows.query({ integrationId: integration.id });
|
|
98408
98539
|
const brokerId = settings["brokerId"];
|
|
98409
98540
|
rows.push({
|
|
98410
98541
|
integrationId: integration.id,
|
|
@@ -98414,31 +98545,26 @@ function haInstanceSettingsPortsOver(api) {
|
|
|
98414
98545
|
}
|
|
98415
98546
|
return rows;
|
|
98416
98547
|
};
|
|
98548
|
+
const owns = async (integrationId) => {
|
|
98549
|
+
return (await api.list.query()).some((i) => i.id === integrationId && i.addonId === "provider-homeassistant");
|
|
98550
|
+
};
|
|
98417
98551
|
return {
|
|
98418
98552
|
listInstances,
|
|
98419
98553
|
patchInstanceSettings: async (integrationId, patch) => {
|
|
98420
|
-
if (!
|
|
98421
|
-
|
|
98422
|
-
|
|
98423
|
-
|
|
98424
|
-
|
|
98425
|
-
...stored,
|
|
98426
|
-
...patch
|
|
98427
|
-
},
|
|
98428
|
-
skipRestart: true
|
|
98429
|
-
});
|
|
98430
|
-
return true;
|
|
98554
|
+
if (!await owns(integrationId)) return false;
|
|
98555
|
+
return (await api.patchLegacyIntegrationRows.mutate({
|
|
98556
|
+
integrationId,
|
|
98557
|
+
patch: { ...patch }
|
|
98558
|
+
})).written;
|
|
98431
98559
|
},
|
|
98432
98560
|
clearInstanceSettingsKeys: async (integrationId, keys) => {
|
|
98433
|
-
if (!
|
|
98434
|
-
const
|
|
98435
|
-
|
|
98436
|
-
await api.
|
|
98437
|
-
|
|
98438
|
-
|
|
98439
|
-
|
|
98440
|
-
});
|
|
98441
|
-
return true;
|
|
98561
|
+
if (!await owns(integrationId)) return false;
|
|
98562
|
+
const refs = refsOfKeys(await api.readLegacyIntegrationRows.query({ integrationId }), keys);
|
|
98563
|
+
if (refs.length > 0 && secrets !== null) await secrets.delete(refs);
|
|
98564
|
+
return (await api.deleteLegacyIntegrationRowKeys.mutate({
|
|
98565
|
+
integrationId,
|
|
98566
|
+
keys
|
|
98567
|
+
})).present;
|
|
98442
98568
|
}
|
|
98443
98569
|
};
|
|
98444
98570
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-provider-homeassistant",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.662",
|
|
4
4
|
"description": "Home Assistant device provider addon for CamStack",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
"description": "Device provider (import) + native entity export (push). Two addons, one bundle, separate processes."
|
|
138
138
|
},
|
|
139
139
|
"requires": {
|
|
140
|
-
"server": ">=1.2.
|
|
140
|
+
"server": ">=1.2.777"
|
|
141
141
|
}
|
|
142
142
|
},
|
|
143
143
|
"files": [
|