@camstack/addon-import-alexa 0.2.189 → 0.2.190
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 +8 -0
- package/dist/addon.js +959 -814
- package/dist/addon.mjs +959 -814
- package/package.json +3 -3
package/dist/addon.mjs
CHANGED
|
@@ -6042,7 +6042,7 @@ function number(params) {
|
|
|
6042
6042
|
return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
|
|
6043
6043
|
}
|
|
6044
6044
|
//#endregion
|
|
6045
|
-
//#region ../types/dist/sleep-
|
|
6045
|
+
//#region ../types/dist/sleep-BX_MqzUe.mjs
|
|
6046
6046
|
/**
|
|
6047
6047
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
6048
6048
|
* window to float samples (D455).
|
|
@@ -7671,6 +7671,28 @@ var SECRET_RETRY_MAX_MS = 5 * 6e4;
|
|
|
7671
7671
|
/** Coalesces a burst of `keychain.changed` events into one re-resolve. */
|
|
7672
7672
|
var SECRET_CHANGE_DEBOUNCE_MS = 250;
|
|
7673
7673
|
/**
|
|
7674
|
+
* Pair a catalogue with the function that builds its ports FROM THE MIGRATION API. The one
|
|
7675
|
+
* place a catalogue is bound: a migration reaches nothing a ports binder cannot reach
|
|
7676
|
+
* through `api` (guard `check-migration-ports-surface`).
|
|
7677
|
+
*/
|
|
7678
|
+
function migrationSet(catalogue, bindPorts) {
|
|
7679
|
+
return { bind: (api) => defineAddonMigrations(catalogue, bindPorts(api)) };
|
|
7680
|
+
}
|
|
7681
|
+
/** A mixed array is a mistake: the other kind would be dropped without a word. */
|
|
7682
|
+
function refuseMixed(form, kept, dropped) {
|
|
7683
|
+
if (dropped > 0) throw new Error(`runMigrations(${form}): ${dropped} of ${kept + dropped} entries are of the other kind and would be dropped; pass one kind per call`);
|
|
7684
|
+
}
|
|
7685
|
+
function catalogueOf(list) {
|
|
7686
|
+
const kept = list.filter((entry) => !("execute" in entry));
|
|
7687
|
+
refuseMixed("catalogue, (api) => ports", kept.length, list.length - kept.length);
|
|
7688
|
+
return kept;
|
|
7689
|
+
}
|
|
7690
|
+
function boundOf(list) {
|
|
7691
|
+
const kept = list.filter((entry) => "execute" in entry);
|
|
7692
|
+
refuseMixed("bound migrations", kept.length, list.length - kept.length);
|
|
7693
|
+
return kept;
|
|
7694
|
+
}
|
|
7695
|
+
/**
|
|
7674
7696
|
* Base class for CamStack addons. Eliminates settings boilerplate:
|
|
7675
7697
|
*
|
|
7676
7698
|
* - Typed `config` property with automatic resolution from store + defaults
|
|
@@ -7837,23 +7859,21 @@ var BaseAddon = class {
|
|
|
7837
7859
|
await this.loadScopedSchemas();
|
|
7838
7860
|
return normalized;
|
|
7839
7861
|
}
|
|
7862
|
+
async runMigrations(first, second, third) {
|
|
7863
|
+
if (typeof second === "function") return this.runMigrationSets([migrationSet(catalogueOf(first), second)], third);
|
|
7864
|
+
return this.runBound(boundOf(first), second);
|
|
7865
|
+
}
|
|
7840
7866
|
/**
|
|
7841
|
-
*
|
|
7842
|
-
*
|
|
7843
|
-
*
|
|
7844
|
-
* restored devices — with the catalogue bound to the addon's ports
|
|
7845
|
-
* (`defineAddonMigrations(ADDON_MIGRATIONS, ports)`).
|
|
7846
|
-
*
|
|
7847
|
-
* An engine that cannot be reached or refuses the catalogue is logged and runs
|
|
7848
|
-
* nothing; the boot continues. A migration that FAILED (or was blocked by one)
|
|
7849
|
-
* and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
|
|
7850
|
-
* serve data whose shape it cannot trust; `tolerate` migrations never do.
|
|
7851
|
-
*
|
|
7852
|
-
* ALWAYS pass the whole catalogue; a phase that must run only some of it passes
|
|
7853
|
-
* `{ only: [ids] }` — never a filtered catalogue (the engine would judge the
|
|
7854
|
-
* omitted applied ids "newer").
|
|
7867
|
+
* Several catalogues of ONE owner in one declaration (the engine replaces an owner's
|
|
7868
|
+
* catalogue on every declare, so two calls would each judge the other's ids "newer").
|
|
7869
|
+
* Build each entry with `migrationSet(catalogue, (api) => ports)`.
|
|
7855
7870
|
*/
|
|
7856
|
-
async
|
|
7871
|
+
async runMigrationSets(sets, options) {
|
|
7872
|
+
const api = this.ctx.migrationApi;
|
|
7873
|
+
if (api === void 0) throw new Error(`${this.ctx.id}: the migration API is not available on this context`);
|
|
7874
|
+
return this.runBound(sets.flatMap((set) => set.bind(api)), options);
|
|
7875
|
+
}
|
|
7876
|
+
async runBound(migrations, options) {
|
|
7857
7877
|
const api = this.ctx.api.migrations;
|
|
7858
7878
|
const report = await runAddonMigrations({
|
|
7859
7879
|
declareOwner: (input) => api.declareOwner.mutate(input),
|
|
@@ -13679,26 +13699,16 @@ var RecordIntegrationInstances = class {
|
|
|
13679
13699
|
...this.hooks.description,
|
|
13680
13700
|
mintsId: true
|
|
13681
13701
|
};
|
|
13682
|
-
|
|
13683
|
-
* The record an id names. A caller holding the `integrations` registry id (the Integrations
|
|
13684
|
-
* page) reaches a kind whose body links to it through `integrationId`; an id that is itself a
|
|
13685
|
-
* record id answers as it is. An id that names nothing comes back unchanged.
|
|
13686
|
-
*/
|
|
13687
|
-
async resolveRecordId(id) {
|
|
13688
|
-
if (await this.handle.get(id) !== null) return id;
|
|
13689
|
-
const linked = (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.find((record) => record.data["integrationId"] === id);
|
|
13690
|
-
return linked === void 0 ? id : linked.id;
|
|
13691
|
-
}
|
|
13692
|
-
instanceFormSchema = async ({ id }) => this.hooks.formSchema(id === null ? null : await this.resolveRecordId(id));
|
|
13702
|
+
instanceFormSchema = async ({ id }) => this.hooks.formSchema(id);
|
|
13693
13703
|
listInstances = async () => {
|
|
13694
13704
|
return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
|
|
13695
13705
|
};
|
|
13696
13706
|
getInstance = async ({ id }) => {
|
|
13697
|
-
const record = await this.handle.get(
|
|
13707
|
+
const record = await this.handle.get(id);
|
|
13698
13708
|
return record === null ? null : this.viewOf(record);
|
|
13699
13709
|
};
|
|
13700
13710
|
/** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
|
|
13701
|
-
createInstance = async ({ id, name, kind }) => {
|
|
13711
|
+
createInstance = async ({ id, name, kind, values }) => {
|
|
13702
13712
|
const minted = id ?? this.hooks.mintId?.({
|
|
13703
13713
|
name,
|
|
13704
13714
|
...kind !== void 0 ? { kind } : {}
|
|
@@ -13708,11 +13718,20 @@ var RecordIntegrationInstances = class {
|
|
|
13708
13718
|
id: minted,
|
|
13709
13719
|
data: this.hooks.initialBody(name, kind)
|
|
13710
13720
|
}, { ifRev: 0 });
|
|
13721
|
+
const patch = this.patchOf(values ?? {});
|
|
13722
|
+
if (Object.keys(patch).length > 0) await this.handle.patch(minted, patch);
|
|
13711
13723
|
await this.hooks.onChanged?.(minted, "created");
|
|
13712
13724
|
return { id: minted };
|
|
13713
13725
|
};
|
|
13714
|
-
updateInstance = async ({ id
|
|
13715
|
-
const
|
|
13726
|
+
updateInstance = async ({ id, name, values }) => {
|
|
13727
|
+
const patch = this.patchOf(values);
|
|
13728
|
+
if (name !== void 0) patch[this.nameField] = name;
|
|
13729
|
+
await this.handle.patch(id, patch);
|
|
13730
|
+
await this.hooks.onChanged?.(id, "updated");
|
|
13731
|
+
return { ok: true };
|
|
13732
|
+
};
|
|
13733
|
+
/** The record patch a form `values` object means: secrets kept, cleared or replaced. */
|
|
13734
|
+
patchOf(values) {
|
|
13716
13735
|
const patch = {};
|
|
13717
13736
|
for (const [key, value] of Object.entries(values)) {
|
|
13718
13737
|
if (value === void 0) continue;
|
|
@@ -13723,11 +13742,8 @@ var RecordIntegrationInstances = class {
|
|
|
13723
13742
|
else if (isSecretClear(value)) patch[key] = null;
|
|
13724
13743
|
else if (typeof value === "string" && value !== "" && value !== "__camstack_redacted__" && !isSecretFieldView(value)) patch[key] = value;
|
|
13725
13744
|
}
|
|
13726
|
-
|
|
13727
|
-
|
|
13728
|
-
await this.hooks.onChanged?.(id, "updated");
|
|
13729
|
-
return { ok: true };
|
|
13730
|
-
};
|
|
13745
|
+
return patch;
|
|
13746
|
+
}
|
|
13731
13747
|
/** The switch lives in the record body; the addon is told so it applies it (D996). */
|
|
13732
13748
|
setInstanceEnabled = async ({ id, enabled }) => {
|
|
13733
13749
|
if (this.registryOwnsEnabled) throw new Error(`instance "${id}": the integrations registry owns the on/off switch of this addon; set it with integrations.update`);
|
|
@@ -13747,10 +13763,7 @@ var RecordIntegrationInstances = class {
|
|
|
13747
13763
|
status: "unsupported",
|
|
13748
13764
|
error: "This integration declares no connection test"
|
|
13749
13765
|
};
|
|
13750
|
-
return this.hooks.test(input
|
|
13751
|
-
...input,
|
|
13752
|
-
id: await this.resolveRecordId(input.id)
|
|
13753
|
-
});
|
|
13766
|
+
return this.hooks.test(input);
|
|
13754
13767
|
};
|
|
13755
13768
|
/** The flat provider object to hand to `registerProvider`. */
|
|
13756
13769
|
toProvider() {
|
|
@@ -13767,6 +13780,76 @@ var RecordIntegrationInstances = class {
|
|
|
13767
13780
|
};
|
|
13768
13781
|
}
|
|
13769
13782
|
};
|
|
13783
|
+
/** No part holds the id a call named. */
|
|
13784
|
+
var UnknownInstanceError = class extends Error {
|
|
13785
|
+
instanceId;
|
|
13786
|
+
constructor(instanceId) {
|
|
13787
|
+
super(`Unknown instance '${instanceId}'`);
|
|
13788
|
+
this.instanceId = instanceId;
|
|
13789
|
+
this.name = "UnknownInstanceError";
|
|
13790
|
+
}
|
|
13791
|
+
};
|
|
13792
|
+
/** A create or a form named no kind, or a kind no part serves. */
|
|
13793
|
+
var UnknownInstanceKindError = class extends Error {
|
|
13794
|
+
kindId;
|
|
13795
|
+
constructor(kindId) {
|
|
13796
|
+
super(kindId === void 0 ? "This provider has several kinds: name one" : `Unknown instance kind '${kindId}'`);
|
|
13797
|
+
this.kindId = kindId;
|
|
13798
|
+
this.name = "UnknownInstanceKindError";
|
|
13799
|
+
}
|
|
13800
|
+
};
|
|
13801
|
+
function composeInstanceKinds(parts, base) {
|
|
13802
|
+
const byKind = (kindId) => {
|
|
13803
|
+
if (kindId === void 0) {
|
|
13804
|
+
const only = parts.length === 1 ? parts[0] : void 0;
|
|
13805
|
+
if (only === void 0) throw new UnknownInstanceKindError(void 0);
|
|
13806
|
+
return only;
|
|
13807
|
+
}
|
|
13808
|
+
const part = parts.find((candidate) => candidate.kind.id === kindId);
|
|
13809
|
+
if (part === void 0) throw new UnknownInstanceKindError(kindId);
|
|
13810
|
+
return part;
|
|
13811
|
+
};
|
|
13812
|
+
const holderOf = async (id) => {
|
|
13813
|
+
for (const part of parts) if (await part.provider.getInstance({ id }) !== null) return part;
|
|
13814
|
+
throw new UnknownInstanceError(id);
|
|
13815
|
+
};
|
|
13816
|
+
const tagged = (part, instance) => ({
|
|
13817
|
+
...instance,
|
|
13818
|
+
kind: part.kind.id
|
|
13819
|
+
});
|
|
13820
|
+
return {
|
|
13821
|
+
describe: async () => ({
|
|
13822
|
+
...base,
|
|
13823
|
+
kinds: parts.map((part) => part.kind)
|
|
13824
|
+
}),
|
|
13825
|
+
instanceFormSchema: async ({ id, kind }) => {
|
|
13826
|
+
return (id === null ? byKind(kind) : await holderOf(id)).provider.instanceFormSchema({ id });
|
|
13827
|
+
},
|
|
13828
|
+
listInstances: async () => {
|
|
13829
|
+
return (await Promise.all(parts.map(async (part) => (await part.provider.listInstances()).map((instance) => tagged(part, instance))))).flat();
|
|
13830
|
+
},
|
|
13831
|
+
getInstance: async ({ id }) => {
|
|
13832
|
+
for (const part of parts) {
|
|
13833
|
+
const found = await part.provider.getInstance({ id });
|
|
13834
|
+
if (found !== null) return tagged(part, found);
|
|
13835
|
+
}
|
|
13836
|
+
return null;
|
|
13837
|
+
},
|
|
13838
|
+
createInstance: async (input) => byKind(input.kind).provider.createInstance(input),
|
|
13839
|
+
updateInstance: async (input) => (await holderOf(input.id)).provider.updateInstance(input),
|
|
13840
|
+
setInstanceEnabled: async (input) => (await holderOf(input.id)).provider.setInstanceEnabled(input),
|
|
13841
|
+
deleteInstance: async (input) => (await holderOf(input.id)).provider.deleteInstance(input),
|
|
13842
|
+
testInstance: async (input) => {
|
|
13843
|
+
const test = (input.id === null ? byKind(input.kind) : await holderOf(input.id)).provider.testInstance;
|
|
13844
|
+
if (test === void 0) return {
|
|
13845
|
+
success: false,
|
|
13846
|
+
status: "inconclusive",
|
|
13847
|
+
error: "This kind cannot be tested"
|
|
13848
|
+
};
|
|
13849
|
+
return test(input);
|
|
13850
|
+
}
|
|
13851
|
+
};
|
|
13852
|
+
}
|
|
13770
13853
|
var EncodeProfileSchema = object({
|
|
13771
13854
|
video: object({
|
|
13772
13855
|
codec: _enum([
|
|
@@ -16047,6 +16130,39 @@ var UnstampedEventMediaCountSchema = object({
|
|
|
16047
16130
|
/** Sum across both, or `null` when either lane could not be counted. */
|
|
16048
16131
|
total: number$1().int().nonnegative().nullable()
|
|
16049
16132
|
}).nullable();
|
|
16133
|
+
/**
|
|
16134
|
+
* Where the stored event media sits, per storage location: how many media rows
|
|
16135
|
+
* and how many bytes carry each location id, how many carry none (legacy,
|
|
16136
|
+
* resolved to the default `eventMedia` location), and how many name a location
|
|
16137
|
+
* that was not asked about (a deleted or never-listed one).
|
|
16138
|
+
*
|
|
16139
|
+
* One covering-index aggregate per asked location plus the NULL one, and one
|
|
16140
|
+
* unfiltered total that scans the covering index: O(rows), not a walk of the
|
|
16141
|
+
* rows themselves. `unlisted` is total minus the ASKED locations minus NULL, so
|
|
16142
|
+
* it is a deleted location only when every location id was asked.
|
|
16143
|
+
* `null` is "could not be measured", never zero.
|
|
16144
|
+
*/
|
|
16145
|
+
var MediaLocationCensusInputSchema = object({ locationIds: array(string().min(1)).max(50).readonly() });
|
|
16146
|
+
var MediaLocationCensusSchema = object({
|
|
16147
|
+
locations: array(object({
|
|
16148
|
+
locationId: string(),
|
|
16149
|
+
rows: number$1().int().nonnegative(),
|
|
16150
|
+
bytes: number$1().nonnegative()
|
|
16151
|
+
})).readonly(),
|
|
16152
|
+
unstamped: object({
|
|
16153
|
+
rows: number$1().int().nonnegative(),
|
|
16154
|
+
bytes: number$1().nonnegative()
|
|
16155
|
+
}),
|
|
16156
|
+
/** Rows stamped with a location id that was not in the asked list. */
|
|
16157
|
+
unlisted: object({
|
|
16158
|
+
rows: number$1().int().nonnegative(),
|
|
16159
|
+
bytes: number$1().nonnegative()
|
|
16160
|
+
}),
|
|
16161
|
+
total: object({
|
|
16162
|
+
rows: number$1().int().nonnegative(),
|
|
16163
|
+
bytes: number$1().nonnegative()
|
|
16164
|
+
})
|
|
16165
|
+
});
|
|
16050
16166
|
var StorageMigrationMediaMoveInputSchema = RelocateMediaInputSchema.extend({ leaseId: string().min(1) });
|
|
16051
16167
|
/** The independently selectable logical storage classes — every class
|
|
16052
16168
|
* `storage.listLocationDeclarations` reports, so an operator never meets a
|
|
@@ -47124,52 +47240,7 @@ onBrightnessChanged: { data: object({
|
|
|
47124
47240
|
minIntervalMs: 0
|
|
47125
47241
|
} } }
|
|
47126
47242
|
};
|
|
47127
|
-
|
|
47128
|
-
* `broker` — unified pub/sub broker registry, system-scoped collection.
|
|
47129
|
-
*
|
|
47130
|
-
* The cap models any kind-tagged message broker the user wants to
|
|
47131
|
-
* register and that other addons might consume. The first two kinds
|
|
47132
|
-
* are MQTT (mosquitto / aedes embedded / cloud bridge) and
|
|
47133
|
-
* Home Assistant (HA WebSocket — subscribe_entities + call_service).
|
|
47134
|
-
* Future kinds (Zigbee2MQTT bridge, ZHA, KNX, Telegram, …) plug in
|
|
47135
|
-
* the same surface.
|
|
47136
|
-
*
|
|
47137
|
-
* Why one cap, not one cap per kind:
|
|
47138
|
-
* - The integrations page wants a single table of "every broker"
|
|
47139
|
-
* across kinds; a unified cap drives it without join logic.
|
|
47140
|
-
* - Generic admin operations (add / remove / test / status) are
|
|
47141
|
-
* identical across kinds — duplicating them per cap was busywork.
|
|
47142
|
-
* - The `network-access` cap precedent: same interface, many
|
|
47143
|
-
* `providerKind: 'ingress'` implementations (Tailscale, ngrok, …).
|
|
47144
|
-
*
|
|
47145
|
-
* What stays kind-specific:
|
|
47146
|
-
* - `getBrokerConfig` payload (kind decides whether it returns
|
|
47147
|
-
* mqtt URL+creds, HA baseUrl+token, …) — typed as
|
|
47148
|
-
* `Record<string, unknown>` at the wire; consumers narrow.
|
|
47149
|
-
* - `publish` / `subscribe` / `getState` arguments (target / filter /
|
|
47150
|
-
* key) — also typed as `Record<string, unknown>` so each kind can
|
|
47151
|
-
* evolve its surface (MQTT topic+qos vs HA entity_id+domain)
|
|
47152
|
-
* without rebreaking the cap signature.
|
|
47153
|
-
* - `add` `settings` payload (different fields per kind, validated by
|
|
47154
|
-
* the kind-specific provider).
|
|
47155
|
-
*
|
|
47156
|
-
* Bidirectionality:
|
|
47157
|
-
* - `publish` is RPC — synchronous result (provider-defined).
|
|
47158
|
-
* - `subscribe` returns a subscription handle; the actual message
|
|
47159
|
-
* stream flows over the typed event-bus as `broker.message` events
|
|
47160
|
-
* keyed by `(brokerId, subscriptionId)`. Consumers filter in their
|
|
47161
|
-
* event handler. Subscriptions persist across reconnects — the
|
|
47162
|
-
* provider re-subscribes upstream after a transport drop.
|
|
47163
|
-
*
|
|
47164
|
-
* Why event-bus push (not a subscription RPC stream):
|
|
47165
|
-
* - Matches the rest of CamStack's D8 contract — events for telemetry,
|
|
47166
|
-
* RPC for loss-is-a-bug. Broker messages are telemetry (a single
|
|
47167
|
-
* drop is recoverable via `getState({key})`).
|
|
47168
|
-
* - Frees `broker.subscribe` from holding a long-lived RPC channel.
|
|
47169
|
-
* - Cross-process: events already route through `$event-bus`; no
|
|
47170
|
-
* per-broker wire-shaping needed.
|
|
47171
|
-
*/
|
|
47172
|
-
var BrokerStatusEnum = _enum([
|
|
47243
|
+
_enum([
|
|
47173
47244
|
"connected",
|
|
47174
47245
|
"disconnected",
|
|
47175
47246
|
"connecting",
|
|
@@ -47177,141 +47248,12 @@ var BrokerStatusEnum = _enum([
|
|
|
47177
47248
|
"unreachable",
|
|
47178
47249
|
"error"
|
|
47179
47250
|
]);
|
|
47180
|
-
var BrokerInfoSchema = object({
|
|
47181
|
-
/** Stable broker id. Persisted; survives addon restarts. */
|
|
47182
|
-
id: string(),
|
|
47183
|
-
/** Addon id of the provider that OWNS this broker.
|
|
47184
|
-
*
|
|
47185
|
-
* The `broker` cap is a system-scoped collection: several addons
|
|
47186
|
-
* register a `broker` provider, each owning a DISJOINT set of
|
|
47187
|
-
* brokers (mqtt-broker owns `mqtt_*`, provider-homeassistant owns
|
|
47188
|
-
* `ha_*`). A broker therefore belongs to exactly one addon — like a
|
|
47189
|
-
* device belongs to one integration. The admin UI threads this id
|
|
47190
|
-
* back as the `{ addonId }` system-collection selector on every
|
|
47191
|
-
* id-keyed call (`get` / `getSettings` / `setSettings` / `remove` /
|
|
47192
|
-
* `testConnection`), so the call routes to the OWNING provider
|
|
47193
|
-
* instead of defaulting to the first-registered one. */
|
|
47194
|
-
addonId: string(),
|
|
47195
|
-
/** Human-readable name (operator-chosen at add-time). */
|
|
47196
|
-
name: string(),
|
|
47197
|
-
/** Provider-defined kind tag — `mqtt` / `home-assistant` / future. */
|
|
47198
|
-
kind: string(),
|
|
47199
|
-
status: BrokerStatusEnum,
|
|
47200
|
-
/** Free-form provider-specific info (HA version, MQTT broker
|
|
47201
|
-
* flavour, latency, mTLS active, …). */
|
|
47202
|
-
info: record(string(), unknown()),
|
|
47203
|
-
/** Ms epoch of the last connection probe / status update. */
|
|
47204
|
-
lastCheckedAt: number$1().nullable(),
|
|
47205
|
-
/** Last error message, when `status` indicates failure. */
|
|
47206
|
-
error: string().nullable()
|
|
47207
|
-
});
|
|
47208
47251
|
var RegistryStatusSchema = object({
|
|
47209
47252
|
brokerCount: number$1().int().nonnegative(),
|
|
47210
47253
|
connectedCount: number$1().int().nonnegative()
|
|
47211
47254
|
});
|
|
47212
|
-
/**
|
|
47213
|
-
* One entry per `broker` provider — which addon provides which broker
|
|
47214
|
-
* kind(s). The unified create picker reads this to know, for each
|
|
47215
|
-
* (addonId, kind) pair the operator can pick, where to route the
|
|
47216
|
-
* follow-up `getSettingsSchema` / `testSettings` / `add` calls (via the
|
|
47217
|
-
* `{ addonId }` selector). A provider only advertises a kind here when
|
|
47218
|
-
* it implements `getSettingsSchema` for that kind (so the picker never
|
|
47219
|
-
* offers a kind whose creation form can't be rendered).
|
|
47220
|
-
*/
|
|
47221
|
-
var BrokerProviderInfoSchema = object({
|
|
47222
|
-
/** Addon id of the `broker` provider this entry describes. */
|
|
47223
|
-
addonId: string(),
|
|
47224
|
-
/** Broker kinds this provider can create, with a display label. */
|
|
47225
|
-
kinds: array(object({
|
|
47226
|
-
kind: string(),
|
|
47227
|
-
label: string(),
|
|
47228
|
-
/**
|
|
47229
|
-
* Relative hub path to start an interactive account link (OAuth popup).
|
|
47230
|
-
* May include `{{brokerId}}`; admin-ui substitutes and appends `?popup=1`.
|
|
47231
|
-
*/
|
|
47232
|
-
interactiveConnectPath: string().min(1).optional(),
|
|
47233
|
-
/** Relative hub path to disconnect the linked account (same popup pattern). */
|
|
47234
|
-
interactiveDisconnectPath: string().min(1).optional(),
|
|
47235
|
-
/**
|
|
47236
|
-
* The kind signs in through a server-held interactive session instead of
|
|
47237
|
-
* (or before) typed credentials. `basePath` is a hub-relative route set
|
|
47238
|
-
* served by the owning addon (`/addon/<id>/...`) speaking the sign-in
|
|
47239
|
-
* contract: `POST {basePath}/start` (body: the form's settings) answers
|
|
47240
|
-
* `{ sessionId, loginUrl }`; `GET {basePath}/status?sessionId=` answers
|
|
47241
|
-
* `{ phase: 'starting' | 'awaiting-login' | 'captured' | 'error' |
|
|
47242
|
-
* 'cancelled', loginUrl, error }`; `POST {basePath}/commit`
|
|
47243
|
-
* (`{ sessionId, name, brokerId? }`) stores the credentials on a new or
|
|
47244
|
-
* existing broker and answers `{ brokerId }`; `POST {basePath}/cancel`
|
|
47245
|
-
* (`{ sessionId }`) drops the session. The dialog that edits a broker
|
|
47246
|
-
* offers it as the default path; the fields the session fills are
|
|
47247
|
-
* marked `filledBySignIn` in the kind's settings schema.
|
|
47248
|
-
*
|
|
47249
|
-
* `methods` lists the ways the session can sign in, recommended first:
|
|
47250
|
-
* `'device-code'` adds `POST {basePath}/code/start` (body: the form's
|
|
47251
|
-
* settings) answering `{ sessionId, userCode, verificationUrl,
|
|
47252
|
-
* expiresAt }` — the operator types `userCode` at `verificationUrl` on
|
|
47253
|
-
* any device, and the same `status` / `commit` / `cancel` routes follow
|
|
47254
|
-
* (status then carries `method`, `userCode`, `verificationUrl`,
|
|
47255
|
-
* `expiresAt` and the phases `'awaiting-code'` / `'expired'`);
|
|
47256
|
-
* `'proxy'` is the `{basePath}/start` flow above. Absent means
|
|
47257
|
-
* `['proxy']`.
|
|
47258
|
-
*/
|
|
47259
|
-
signIn: object({
|
|
47260
|
-
basePath: string().min(1),
|
|
47261
|
-
methods: array(_enum(["device-code", "proxy"])).optional()
|
|
47262
|
-
}).optional()
|
|
47263
|
-
}))
|
|
47264
|
-
});
|
|
47265
|
-
var ListInputSchema = object({
|
|
47266
|
-
/** Optional kind filter — `list({kind:'home-assistant'})` returns
|
|
47267
|
-
* only HA brokers. Omit to list every broker. */
|
|
47268
|
-
kind: string().optional() });
|
|
47269
47255
|
var GetInputSchema = object({ id: string() });
|
|
47270
|
-
var AddInputSchema = object({
|
|
47271
|
-
kind: string().min(1),
|
|
47272
|
-
name: string().min(1),
|
|
47273
|
-
/**
|
|
47274
|
-
* ADOPT an existing id instead of minting a new one.
|
|
47275
|
-
*
|
|
47276
|
-
* Written the day this cost an outage. A broker's id is not a detail: HA
|
|
47277
|
-
* devices carry it inside their `stableId` (`ha:ha_004:dev:…`), so a broker
|
|
47278
|
-
* lost from config and re-added as `ha_001` leaves every one of its devices
|
|
47279
|
-
* bound to a broker that no longer exists. Re-entering the password under the
|
|
47280
|
-
* ORIGINAL id turns a multi-step device migration back into re-entering a
|
|
47281
|
-
* password.
|
|
47282
|
-
*
|
|
47283
|
-
* A provider MUST refuse an id that is already in use — adopting a live
|
|
47284
|
-
* broker's id would silently take it over.
|
|
47285
|
-
*/
|
|
47286
|
-
id: string().min(1).optional(),
|
|
47287
|
-
/** Kind-specific settings (e.g. MQTT `{url,username,password}` or HA
|
|
47288
|
-
* `{baseUrl,accessToken}`). Validated by the kind-specific provider
|
|
47289
|
-
* branch on receipt — invalid shape rejects the add. */
|
|
47290
|
-
settings: record(string(), unknown())
|
|
47291
|
-
});
|
|
47292
|
-
var AddResultSchema = object({ id: string() });
|
|
47293
|
-
var RemoveInputSchema = object({ id: string() });
|
|
47294
|
-
var TestConnectionResultSchema = discriminatedUnion("ok", [object({
|
|
47295
|
-
ok: literal(true),
|
|
47296
|
-
latencyMs: number$1().nonnegative()
|
|
47297
|
-
}), object({
|
|
47298
|
-
ok: literal(false),
|
|
47299
|
-
error: string()
|
|
47300
|
-
})]);
|
|
47301
47256
|
var SettingsRecordSchema$1 = record(string(), unknown());
|
|
47302
|
-
var SettingsSchemaInputSchema = object({ kind: string() });
|
|
47303
|
-
var TestSettingsInputSchema = object({
|
|
47304
|
-
kind: string(),
|
|
47305
|
-
settings: SettingsRecordSchema$1
|
|
47306
|
-
});
|
|
47307
|
-
var TestSettingsResultSchema = discriminatedUnion("ok", [object({
|
|
47308
|
-
ok: literal(true),
|
|
47309
|
-
latencyMs: number$1().nonnegative().optional()
|
|
47310
|
-
}).strict(), object({
|
|
47311
|
-
ok: literal(false),
|
|
47312
|
-
error: string()
|
|
47313
|
-
})]);
|
|
47314
|
-
var SettingsSchemaResultSchema = unknown().nullable();
|
|
47315
47257
|
var PublishInputSchema = object({
|
|
47316
47258
|
brokerId: string(),
|
|
47317
47259
|
/** Kind-specific routing target.
|
|
@@ -47355,51 +47297,12 @@ var brokerCapability = {
|
|
|
47355
47297
|
kind: "poll"
|
|
47356
47298
|
},
|
|
47357
47299
|
methods: {
|
|
47358
|
-
list: method(ListInputSchema, array(BrokerInfoSchema)),
|
|
47359
|
-
get: method(GetInputSchema, BrokerInfoSchema.nullable()),
|
|
47360
|
-
/** Enumerate which addon provides which broker kind(s) for the
|
|
47361
|
-
* unified create picker. The auto-mount fans this array across
|
|
47362
|
-
* every registered `broker` provider (array-output method), so the
|
|
47363
|
-
* picker sees every kind from every provider in one call. */
|
|
47364
|
-
listProviders: method(_void(), array(BrokerProviderInfoSchema), { auth: "admin" }),
|
|
47365
|
-
add: method(AddInputSchema, AddResultSchema, {
|
|
47366
|
-
kind: "mutation",
|
|
47367
|
-
auth: "admin"
|
|
47368
|
-
}),
|
|
47369
|
-
remove: method(RemoveInputSchema, _void(), {
|
|
47370
|
-
kind: "mutation",
|
|
47371
|
-
auth: "admin"
|
|
47372
|
-
}),
|
|
47373
|
-
testConnection: method(GetInputSchema, TestConnectionResultSchema, {
|
|
47374
|
-
kind: "mutation",
|
|
47375
|
-
auth: "admin"
|
|
47376
|
-
}),
|
|
47377
|
-
/** Read the persisted settings record for a broker (kind-specific
|
|
47378
|
-
* shape). Admin-only — settings may contain secrets. Returns `null`
|
|
47379
|
-
* when the broker id is unknown to the provider (the collection
|
|
47380
|
-
* fallback may route a foreign id to the first provider). */
|
|
47381
|
-
getSettings: method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }),
|
|
47382
|
-
/** Overwrite the persisted settings record. The kind-specific
|
|
47383
|
-
* provider validates the shape and applies the change (reconnects
|
|
47384
|
-
* if credentials changed). */
|
|
47385
|
-
setSettings: method(object({
|
|
47386
|
-
id: string(),
|
|
47387
|
-
settings: SettingsRecordSchema$1
|
|
47388
|
-
}), _void(), {
|
|
47389
|
-
kind: "mutation",
|
|
47390
|
-
auth: "admin"
|
|
47391
|
-
}),
|
|
47392
47300
|
/** Returns the kind-specific connection config the consumer needs
|
|
47393
47301
|
* to open its own client (MQTT pattern: `{url, username, password,
|
|
47394
47302
|
* clientIdPrefix}`). HA providers MAY return the auth envelope
|
|
47395
47303
|
* but typical HA consumers use `publish` / `subscribe` instead.
|
|
47396
47304
|
* Returns `null` when the broker id is unknown to the provider. */
|
|
47397
47305
|
getBrokerConfig: method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }),
|
|
47398
|
-
getSettingsSchema: method(SettingsSchemaInputSchema, SettingsSchemaResultSchema, { auth: "admin" }),
|
|
47399
|
-
testSettings: method(TestSettingsInputSchema, TestSettingsResultSchema, {
|
|
47400
|
-
kind: "mutation",
|
|
47401
|
-
auth: "admin"
|
|
47402
|
-
}),
|
|
47403
47306
|
publish: method(PublishInputSchema, unknown(), {
|
|
47404
47307
|
kind: "mutation",
|
|
47405
47308
|
auth: "admin"
|
|
@@ -52246,7 +52149,14 @@ var DiscoveryCandidateSchema = object({
|
|
|
52246
52149
|
/** `stableId` of the gateway candidate this device was found BEHIND. */
|
|
52247
52150
|
parentStableId: string().optional(),
|
|
52248
52151
|
/** `false` = shown for context but not pickable. Default true. */
|
|
52249
|
-
selectable: boolean().optional()
|
|
52152
|
+
selectable: boolean().optional(),
|
|
52153
|
+
/**
|
|
52154
|
+
* Keys of the provider's creation form (`getChildCreationSchema`) this candidate
|
|
52155
|
+
* cannot be adopted without — a key for an encrypted board, a pairing code. The
|
|
52156
|
+
* admin asks for exactly these, on the creation form prefilled with
|
|
52157
|
+
* `prefilledConfig`, before it adopts. Absent or empty = adopt as it is.
|
|
52158
|
+
*/
|
|
52159
|
+
needsParams: array(string()).optional()
|
|
52250
52160
|
});
|
|
52251
52161
|
/**
|
|
52252
52162
|
* Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
|
|
@@ -52373,8 +52283,14 @@ var deviceProviderCapability = {
|
|
|
52373
52283
|
* provider does not support manual creation. Lets the Add-Device dialog
|
|
52374
52284
|
* pick the right type instead of assuming Camera.
|
|
52375
52285
|
*/
|
|
52376
|
-
getManualCreationType: method(object({
|
|
52377
|
-
|
|
52286
|
+
getManualCreationType: method(object({
|
|
52287
|
+
/** The integration asked about; a provider whose integrations differ answers per one. */
|
|
52288
|
+
integrationId: string().optional() }), object({ deviceType: _enum(DeviceType).nullable() })),
|
|
52289
|
+
adoptDiscoveredDevice: method(object({
|
|
52290
|
+
candidate: DiscoveryCandidateSchema,
|
|
52291
|
+
/** The integration the operator adopts into; a provider with several instances needs it. */
|
|
52292
|
+
integrationId: string().optional()
|
|
52293
|
+
}), DeviceSummarySchema, {
|
|
52378
52294
|
kind: "mutation",
|
|
52379
52295
|
auth: "admin"
|
|
52380
52296
|
}),
|
|
@@ -52385,10 +52301,16 @@ var deviceProviderCapability = {
|
|
|
52385
52301
|
* devices of that type. The output is a `ConfigUISchema` — the
|
|
52386
52302
|
* router type-asserts it at the boundary.
|
|
52387
52303
|
*/
|
|
52388
|
-
getChildCreationSchema: method(object({
|
|
52304
|
+
getChildCreationSchema: method(object({
|
|
52305
|
+
type: _enum(DeviceType),
|
|
52306
|
+
/** The integration asked about; a provider whose integrations differ answers per one. */
|
|
52307
|
+
integrationId: string().optional()
|
|
52308
|
+
}), CreationSchemaOutputSchema),
|
|
52389
52309
|
createDevice: method(object({
|
|
52390
52310
|
type: _enum(DeviceType),
|
|
52391
|
-
config: record(string(), unknown())
|
|
52311
|
+
config: record(string(), unknown()),
|
|
52312
|
+
/** The integration the operator adds the device to; a provider with several instances needs it. */
|
|
52313
|
+
integrationId: string().optional()
|
|
52392
52314
|
}), DeviceSummarySchema, {
|
|
52393
52315
|
kind: "mutation",
|
|
52394
52316
|
auth: "admin"
|
|
@@ -53113,7 +53035,10 @@ method(object({
|
|
|
53113
53035
|
auth: "admin"
|
|
53114
53036
|
}), method(object({
|
|
53115
53037
|
addonId: string(),
|
|
53116
|
-
type: _enum(DeviceType)
|
|
53038
|
+
type: _enum(DeviceType),
|
|
53039
|
+
/** The integration the form is for; a provider whose integrations differ (a hub
|
|
53040
|
+
* integration takes no manual add) answers per integration. Absent = the addon's own. */
|
|
53041
|
+
integrationId: string().optional()
|
|
53117
53042
|
}), unknown().nullable()), method(object({
|
|
53118
53043
|
addonId: string(),
|
|
53119
53044
|
type: _enum(DeviceType),
|
|
@@ -53197,7 +53122,11 @@ method(object({
|
|
|
53197
53122
|
kind: "mutation",
|
|
53198
53123
|
auth: "admin",
|
|
53199
53124
|
timeoutMs: 12e4
|
|
53200
|
-
}), method(object({
|
|
53125
|
+
}), method(object({
|
|
53126
|
+
addonId: string(),
|
|
53127
|
+
/** The integration the Add-Device dialog is open on; absent = the addon's own. */
|
|
53128
|
+
integrationId: string().optional()
|
|
53129
|
+
}), object({ deviceType: _enum(DeviceType).nullable() }), { auth: "admin" }), method(object({ addonId: string() }), unknown(), { auth: "admin" }), method(object({
|
|
53201
53130
|
deviceId: number$1(),
|
|
53202
53131
|
key: string(),
|
|
53203
53132
|
value: unknown()
|
|
@@ -56477,7 +56406,7 @@ var TestConnectionStatusEnum = _enum([
|
|
|
56477
56406
|
"inconclusive",
|
|
56478
56407
|
"unsupported"
|
|
56479
56408
|
]);
|
|
56480
|
-
var TestConnectionResultSchema
|
|
56409
|
+
var TestConnectionResultSchema = object({
|
|
56481
56410
|
/** True ONLY for `validated`. Never true for a test that did not run. */
|
|
56482
56411
|
success: boolean(),
|
|
56483
56412
|
error: string().optional(),
|
|
@@ -56522,7 +56451,7 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
56522
56451
|
}), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
|
|
56523
56452
|
addonId: string(),
|
|
56524
56453
|
settings: record(string(), unknown())
|
|
56525
|
-
}), TestConnectionResultSchema
|
|
56454
|
+
}), TestConnectionResultSchema, {
|
|
56526
56455
|
kind: "mutation",
|
|
56527
56456
|
auth: "admin"
|
|
56528
56457
|
});
|
|
@@ -56549,6 +56478,14 @@ var ConfigSchemaPassthrough$3 = custom();
|
|
|
56549
56478
|
var IntegrationInstanceIdSchema = string().min(1);
|
|
56550
56479
|
var IntegrationInstanceModeSchema = _enum(["single", "multiple"]);
|
|
56551
56480
|
/**
|
|
56481
|
+
* What the addon's instances ARE for the Integrations page (planning#416). `devices`: each
|
|
56482
|
+
* instance is an integration that brings devices (the registry owns its on/off switch);
|
|
56483
|
+
* `services`: a connection configured from the addon's own function page, never listed on
|
|
56484
|
+
* Integrations.
|
|
56485
|
+
* It is the same vocabulary as the manifest's `integrationRole`, which stays as an override.
|
|
56486
|
+
*/
|
|
56487
|
+
var IntegrationInstanceRoleSchema = _enum(["devices", "services"]);
|
|
56488
|
+
/**
|
|
56552
56489
|
* One kind of instance a provider can create (an adapter, an account type). The sign-in and
|
|
56553
56490
|
* connect fields follow the `broker` contract (`broker.cap.ts`): `signIn.basePath` is a
|
|
56554
56491
|
* hub-relative route set speaking the sign-in contract, `connectPath` / `disconnectPath` open an
|
|
@@ -56559,6 +56496,22 @@ var IntegrationInstanceKindSchema = object({
|
|
|
56559
56496
|
/** Stable id of the kind, sent back as `createInstance.kind`. */
|
|
56560
56497
|
id: string().min(1),
|
|
56561
56498
|
label: string().min(1),
|
|
56499
|
+
/**
|
|
56500
|
+
* The function capability this kind is configured for (`broker`, `smtp-provider`,
|
|
56501
|
+
* `device-provider`, ...). A page section is the kinds whose `functionCap` it names (D1013); a
|
|
56502
|
+
* provider with more than one section must name it on every kind.
|
|
56503
|
+
*/
|
|
56504
|
+
functionCap: string().min(1),
|
|
56505
|
+
/**
|
|
56506
|
+
* For `functionCap: 'broker'`: the registry kind of the broker an instance of this kind is
|
|
56507
|
+
* (`matter`, `home-assistant`, ...). A caller that needs a broker of one kind (a link field)
|
|
56508
|
+
* finds the instances whose kind names it; absent takes the kind `id`.
|
|
56509
|
+
*/
|
|
56510
|
+
brokerKind: string().min(1).optional(),
|
|
56511
|
+
/** What instances of this kind are for the Integrations page; absent takes the provider's `role`. */
|
|
56512
|
+
role: IntegrationInstanceRoleSchema.optional(),
|
|
56513
|
+
/** `testInstance` really probes the remote for this kind; absent takes the provider's `canTest`. */
|
|
56514
|
+
canTest: boolean().optional(),
|
|
56562
56515
|
/** The kind signs in through a server-held interactive session. */
|
|
56563
56516
|
signIn: object({
|
|
56564
56517
|
basePath: string().min(1),
|
|
@@ -56571,11 +56524,7 @@ var IntegrationInstanceKindSchema = object({
|
|
|
56571
56524
|
});
|
|
56572
56525
|
var IntegrationInstanceDescriptionSchema = object({
|
|
56573
56526
|
/** What the instances are for the Integrations page; see {@link IntegrationInstanceRoleSchema}. */
|
|
56574
|
-
role:
|
|
56575
|
-
"devices",
|
|
56576
|
-
"services",
|
|
56577
|
-
"both"
|
|
56578
|
-
]),
|
|
56527
|
+
role: IntegrationInstanceRoleSchema,
|
|
56579
56528
|
/** How the devices of an instance are found; absent means `{ mode: 'none' }`. */
|
|
56580
56529
|
discovery: object({
|
|
56581
56530
|
mode: _enum([
|
|
@@ -56650,7 +56599,11 @@ var integrationInstancesCapability = {
|
|
|
56650
56599
|
methods: {
|
|
56651
56600
|
describe: method(_void(), IntegrationInstanceDescriptionSchema, { auth: "admin" }),
|
|
56652
56601
|
/** The form; `id: null` is the create form. */
|
|
56653
|
-
instanceFormSchema: method(object({
|
|
56602
|
+
instanceFormSchema: method(object({
|
|
56603
|
+
id: IntegrationInstanceIdSchema.nullable(),
|
|
56604
|
+
/** The kind of the create form (`id: null`) of a provider with several. */
|
|
56605
|
+
kind: string().min(1).optional()
|
|
56606
|
+
}), ConfigSchemaPassthrough$3, { auth: "admin" }),
|
|
56654
56607
|
listInstances: method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }),
|
|
56655
56608
|
getInstance: method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }),
|
|
56656
56609
|
/**
|
|
@@ -56660,7 +56613,9 @@ var integrationInstancesCapability = {
|
|
|
56660
56613
|
createInstance: method(object({
|
|
56661
56614
|
id: IntegrationInstanceIdSchema.optional(),
|
|
56662
56615
|
name: string().min(1),
|
|
56663
|
-
kind: string().min(1).optional()
|
|
56616
|
+
kind: string().min(1).optional(),
|
|
56617
|
+
/** Form values written with the record (a link picked in the create dialog). */
|
|
56618
|
+
values: record(string(), unknown()).optional()
|
|
56664
56619
|
}), object({ id: string() }), {
|
|
56665
56620
|
kind: "mutation",
|
|
56666
56621
|
auth: "admin"
|
|
@@ -56687,8 +56642,10 @@ var integrationInstancesCapability = {
|
|
|
56687
56642
|
}),
|
|
56688
56643
|
testInstance: optionalMethod(object({
|
|
56689
56644
|
id: IntegrationInstanceIdSchema.nullable(),
|
|
56690
|
-
values: record(string(), unknown())
|
|
56691
|
-
|
|
56645
|
+
values: record(string(), unknown()),
|
|
56646
|
+
/** The kind to test when `id` is null and the provider has several. */
|
|
56647
|
+
kind: string().min(1).optional()
|
|
56648
|
+
}), TestConnectionResultSchema, {
|
|
56692
56649
|
kind: "mutation",
|
|
56693
56650
|
auth: "admin"
|
|
56694
56651
|
})
|
|
@@ -59445,6 +59402,380 @@ var SystemMetricsSchema = object({
|
|
|
59445
59402
|
gpuMemoryPercent: number$1().optional()
|
|
59446
59403
|
});
|
|
59447
59404
|
method(_void(), SystemResourceSnapshotSchema), method(_void(), SystemResourceSnapshotSchema.nullable()), method(_void(), SystemMetricsSchema), method(object({ dirPath: string() }), DiskSpaceInfoSchema), method(_void(), MetricsGpuInfoSchema.nullable()), method(_void(), number$1().nullable()), method(object({ pids: array(number$1()) }), array(PidResourceStatsSchema)), method(_void(), array(AddonInstanceSchema).readonly()), method(object({ addonId: string() }), PidResourceStatsSchema.nullable()), method(_void(), array(NodeProcessSchema).readonly()), method(GetLoadSeriesInputSchema, NodeLoadSeriesSchema);
|
|
59405
|
+
/**
|
|
59406
|
+
* records - an addon's own facts and documents (D881): ledgers, audit rows,
|
|
59407
|
+
* config documents, mirrors. The third storage API next to `ctx.settings`
|
|
59408
|
+
* (operator intent, D870) and device-history (numbers over time, D850).
|
|
59409
|
+
*
|
|
59410
|
+
* Who the OWNER is: `callerAddonId`, stamped by the transport on a call that
|
|
59411
|
+
* crossed a process and stated by a hub in-process caller (`ctx.records` does
|
|
59412
|
+
* that with the addon id closed over). It is deliberately NOT in any input
|
|
59413
|
+
* schema, so a session can never supply it, and a call with no stamp is refused
|
|
59414
|
+
* `records/no-caller`. A kind is readable and writable only by its owner; other
|
|
59415
|
+
* addons go through the owner's capability.
|
|
59416
|
+
*
|
|
59417
|
+
* Every read is three-way: a row, `null`, or the call THROWS. A failed read is
|
|
59418
|
+
* never `[]`. Secret fields are never on the wire: a read shows the
|
|
59419
|
+
* redaction token, `secretRefs` hands the owner's `ctx.secrets` the refs it
|
|
59420
|
+
* resolves itself, and a write may carry only a keychain marker bound to the
|
|
59421
|
+
* row's own ref.
|
|
59422
|
+
*
|
|
59423
|
+
* Internal: addons reach it through `ctx.records`, never directly.
|
|
59424
|
+
*/
|
|
59425
|
+
var RecordIdSchema = string().min(1).max(256);
|
|
59426
|
+
/** A JSON object body. Size is the engine's check (`maxDataBytes`), not a schema's. */
|
|
59427
|
+
var RecordBodySchema = record(string(), unknown());
|
|
59428
|
+
var RecordInputSchema = object({
|
|
59429
|
+
id: RecordIdSchema,
|
|
59430
|
+
/** The device this row belongs to (D865 removal, per-device reads). */
|
|
59431
|
+
deviceId: number$1().int().optional(),
|
|
59432
|
+
/** The domain clock, ms: observed / created / opened. */
|
|
59433
|
+
at: number$1().int().optional(),
|
|
59434
|
+
/** Producer-set expiry, ms. Refused on a kind whose retention is an engine TTL. */
|
|
59435
|
+
expiresAt: number$1().int().optional(),
|
|
59436
|
+
data: RecordBodySchema
|
|
59437
|
+
});
|
|
59438
|
+
var RecordViewSchema = object({
|
|
59439
|
+
id: string(),
|
|
59440
|
+
deviceId: number$1().int().nullable(),
|
|
59441
|
+
at: number$1().nullable(),
|
|
59442
|
+
expiresAt: number$1().nullable(),
|
|
59443
|
+
rev: number$1().int(),
|
|
59444
|
+
updatedAt: number$1(),
|
|
59445
|
+
data: RecordBodySchema
|
|
59446
|
+
});
|
|
59447
|
+
/** Declared index fields only. Anything else does not compile, by design. */
|
|
59448
|
+
var RecordWhereSchema = record(RecordFieldPathSchema, union([
|
|
59449
|
+
string(),
|
|
59450
|
+
number$1(),
|
|
59451
|
+
boolean(),
|
|
59452
|
+
object({ in: array(union([
|
|
59453
|
+
string(),
|
|
59454
|
+
number$1(),
|
|
59455
|
+
boolean()
|
|
59456
|
+
])).min(1).max(RECORD_BATCH_MAX) }),
|
|
59457
|
+
object({
|
|
59458
|
+
gt: union([string(), number$1()]).optional(),
|
|
59459
|
+
gte: union([string(), number$1()]).optional(),
|
|
59460
|
+
lt: union([string(), number$1()]).optional(),
|
|
59461
|
+
lte: union([string(), number$1()]).optional()
|
|
59462
|
+
}).refine((r) => Object.keys(r).length > 0, { message: "name at least one bound" })
|
|
59463
|
+
]));
|
|
59464
|
+
var KindShape = { kind: RecordKindNameSchema };
|
|
59465
|
+
var TimeWindowShape = {
|
|
59466
|
+
deviceId: number$1().int().optional(),
|
|
59467
|
+
atFrom: number$1().optional(),
|
|
59468
|
+
atTo: number$1().optional()
|
|
59469
|
+
};
|
|
59470
|
+
var RecordQueryInputSchema = object({
|
|
59471
|
+
...KindShape,
|
|
59472
|
+
...TimeWindowShape,
|
|
59473
|
+
where: RecordWhereSchema.optional(),
|
|
59474
|
+
/** Without `where`: `at` (default for a kind with a clock) or `id`. With `where` the order is the index's. */
|
|
59475
|
+
order: _enum(["at", "id"]).optional(),
|
|
59476
|
+
desc: boolean().optional(),
|
|
59477
|
+
limit: number$1().int().min(1).max(RECORD_QUERY_MAX).default(100),
|
|
59478
|
+
/** Opaque keyset cursor from a previous answer. There is no offset. */
|
|
59479
|
+
cursor: string().min(1).optional()
|
|
59480
|
+
});
|
|
59481
|
+
var RecordQueryResultSchema = object({
|
|
59482
|
+
records: array(RecordViewSchema),
|
|
59483
|
+
nextCursor: string().nullable()
|
|
59484
|
+
});
|
|
59485
|
+
var RecordKindStateSchema = _enum([
|
|
59486
|
+
"created",
|
|
59487
|
+
"unchanged",
|
|
59488
|
+
"updated"
|
|
59489
|
+
]);
|
|
59490
|
+
var DeclareKindResultSchema = object({
|
|
59491
|
+
kindId: number$1().int(),
|
|
59492
|
+
state: RecordKindStateSchema,
|
|
59493
|
+
createdIndexes: array(string())
|
|
59494
|
+
});
|
|
59495
|
+
/** Keychain refs a write released (the old marker is gone from the row): the owner deletes the values. */
|
|
59496
|
+
var SecretRefsList = array(string());
|
|
59497
|
+
/**
|
|
59498
|
+
* One op of a `transact`: the single-row writes, each on a kind of the caller.
|
|
59499
|
+
* All ops of one call share ONE database file and ONE `transactFamily`.
|
|
59500
|
+
*/
|
|
59501
|
+
var RecordTransactOpSchema = discriminatedUnion("op", [
|
|
59502
|
+
object({
|
|
59503
|
+
op: literal("put"),
|
|
59504
|
+
kind: RecordKindNameSchema,
|
|
59505
|
+
record: RecordInputSchema,
|
|
59506
|
+
ifRev: number$1().int().min(0).optional()
|
|
59507
|
+
}),
|
|
59508
|
+
object({
|
|
59509
|
+
op: literal("patch"),
|
|
59510
|
+
kind: RecordKindNameSchema,
|
|
59511
|
+
id: RecordIdSchema,
|
|
59512
|
+
data: RecordBodySchema,
|
|
59513
|
+
at: number$1().int().nullable().optional(),
|
|
59514
|
+
expiresAt: number$1().int().nullable().optional(),
|
|
59515
|
+
ifRev: number$1().int().min(1).optional()
|
|
59516
|
+
}),
|
|
59517
|
+
object({
|
|
59518
|
+
op: literal("delete"),
|
|
59519
|
+
kind: RecordKindNameSchema,
|
|
59520
|
+
id: RecordIdSchema
|
|
59521
|
+
})
|
|
59522
|
+
]);
|
|
59523
|
+
var RecordSecretRefsSchema = record(string(), record(string(), string()));
|
|
59524
|
+
method(object({ kinds: record(RecordKindNameSchema, RecordKindDeclarationSchema) }), object({ kinds: record(string(), DeclareKindResultSchema) }), {
|
|
59525
|
+
kind: "mutation",
|
|
59526
|
+
auth: "admin"
|
|
59527
|
+
}), method(object({
|
|
59528
|
+
...KindShape,
|
|
59529
|
+
id: RecordIdSchema
|
|
59530
|
+
}), RecordViewSchema.nullable(), { auth: "admin" }), method(object({
|
|
59531
|
+
...KindShape,
|
|
59532
|
+
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
59533
|
+
}), array(RecordViewSchema), { auth: "admin" }), method(object({
|
|
59534
|
+
...KindShape,
|
|
59535
|
+
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
59536
|
+
}), RecordSecretRefsSchema, { auth: "admin" }), method(object({
|
|
59537
|
+
...KindShape,
|
|
59538
|
+
record: RecordInputSchema,
|
|
59539
|
+
/** Compare-and-set. `0` means "the row must not exist". */
|
|
59540
|
+
ifRev: number$1().int().min(0).optional()
|
|
59541
|
+
}), object({
|
|
59542
|
+
rev: number$1().int(),
|
|
59543
|
+
releasedRefs: SecretRefsList
|
|
59544
|
+
}), {
|
|
59545
|
+
kind: "mutation",
|
|
59546
|
+
auth: "admin"
|
|
59547
|
+
}), method(object({
|
|
59548
|
+
...KindShape,
|
|
59549
|
+
records: array(RecordInputSchema).min(1).max(RECORD_BATCH_MAX)
|
|
59550
|
+
}), object({
|
|
59551
|
+
count: number$1().int(),
|
|
59552
|
+
releasedRefs: SecretRefsList
|
|
59553
|
+
}), {
|
|
59554
|
+
kind: "mutation",
|
|
59555
|
+
auth: "admin"
|
|
59556
|
+
}), method(object({
|
|
59557
|
+
...KindShape,
|
|
59558
|
+
id: RecordIdSchema,
|
|
59559
|
+
data: RecordBodySchema,
|
|
59560
|
+
at: number$1().int().nullable().optional(),
|
|
59561
|
+
expiresAt: number$1().int().nullable().optional(),
|
|
59562
|
+
ifRev: number$1().int().min(1).optional()
|
|
59563
|
+
}), object({
|
|
59564
|
+
rev: number$1().int(),
|
|
59565
|
+
releasedRefs: SecretRefsList
|
|
59566
|
+
}), {
|
|
59567
|
+
kind: "mutation",
|
|
59568
|
+
auth: "admin"
|
|
59569
|
+
}), method(object({
|
|
59570
|
+
...KindShape,
|
|
59571
|
+
id: RecordIdSchema
|
|
59572
|
+
}), object({
|
|
59573
|
+
deleted: boolean(),
|
|
59574
|
+
secretRefs: SecretRefsList
|
|
59575
|
+
}), {
|
|
59576
|
+
kind: "mutation",
|
|
59577
|
+
auth: "admin"
|
|
59578
|
+
}), method(object({
|
|
59579
|
+
...KindShape,
|
|
59580
|
+
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
59581
|
+
}), object({
|
|
59582
|
+
deleted: number$1().int(),
|
|
59583
|
+
secretRefs: SecretRefsList
|
|
59584
|
+
}), {
|
|
59585
|
+
kind: "mutation",
|
|
59586
|
+
auth: "admin"
|
|
59587
|
+
}), method(RecordQueryInputSchema, RecordQueryResultSchema, { auth: "admin" }), method(object({
|
|
59588
|
+
...KindShape,
|
|
59589
|
+
...TimeWindowShape,
|
|
59590
|
+
where: RecordWhereSchema.optional()
|
|
59591
|
+
}), object({ count: number$1().int() }), { auth: "admin" }), method(object({
|
|
59592
|
+
...KindShape,
|
|
59593
|
+
deviceId: number$1().int().optional(),
|
|
59594
|
+
from: number$1(),
|
|
59595
|
+
to: number$1(),
|
|
59596
|
+
bucketMs: number$1().int().positive()
|
|
59597
|
+
}), object({ buckets: array(object({
|
|
59598
|
+
bucketStart: number$1(),
|
|
59599
|
+
count: number$1().int()
|
|
59600
|
+
})) }), { auth: "admin" }), method(object({
|
|
59601
|
+
...KindShape,
|
|
59602
|
+
op: _enum([
|
|
59603
|
+
"sum",
|
|
59604
|
+
"min",
|
|
59605
|
+
"max",
|
|
59606
|
+
"count"
|
|
59607
|
+
]),
|
|
59608
|
+
field: RecordFieldPathSchema.optional(),
|
|
59609
|
+
groupBy: RecordFieldPathSchema.optional(),
|
|
59610
|
+
where: RecordWhereSchema.optional()
|
|
59611
|
+
}), object({ rows: array(object({
|
|
59612
|
+
group: union([
|
|
59613
|
+
string(),
|
|
59614
|
+
number$1(),
|
|
59615
|
+
_null()
|
|
59616
|
+
]),
|
|
59617
|
+
value: number$1().nullable()
|
|
59618
|
+
})) }), { auth: "admin" }), method(object({
|
|
59619
|
+
...KindShape,
|
|
59620
|
+
deviceId: number$1().int().optional(),
|
|
59621
|
+
atBefore: number$1().optional(),
|
|
59622
|
+
where: RecordWhereSchema.optional()
|
|
59623
|
+
}), object({
|
|
59624
|
+
deleted: number$1().int(),
|
|
59625
|
+
secretRefs: SecretRefsList
|
|
59626
|
+
}), {
|
|
59627
|
+
kind: "mutation",
|
|
59628
|
+
auth: "admin"
|
|
59629
|
+
}), method(object({ ops: array(RecordTransactOpSchema).min(1).max(RECORD_BATCH_MAX) }), object({
|
|
59630
|
+
results: array(object({
|
|
59631
|
+
rev: number$1().int().nullable(),
|
|
59632
|
+
deleted: boolean()
|
|
59633
|
+
})),
|
|
59634
|
+
releasedRefs: SecretRefsList,
|
|
59635
|
+
secretRefs: SecretRefsList
|
|
59636
|
+
}), {
|
|
59637
|
+
kind: "mutation",
|
|
59638
|
+
auth: "admin"
|
|
59639
|
+
}), method(object({
|
|
59640
|
+
deviceId: number$1().int(),
|
|
59641
|
+
kinds: array(RecordKindNameSchema).optional()
|
|
59642
|
+
}), object({
|
|
59643
|
+
deleted: record(string(), number$1().int()),
|
|
59644
|
+
secretRefs: SecretRefsList
|
|
59645
|
+
}), {
|
|
59646
|
+
kind: "mutation",
|
|
59647
|
+
auth: "admin"
|
|
59648
|
+
}), method(object(KindShape), object({ id: number$1().int() }), {
|
|
59649
|
+
kind: "mutation",
|
|
59650
|
+
auth: "admin"
|
|
59651
|
+
});
|
|
59652
|
+
/**
|
|
59653
|
+
* The wire shapes of the migration API, version 1 (planning#447).
|
|
59654
|
+
*
|
|
59655
|
+
* A migration is code that outlives the server it was written against: an addon at
|
|
59656
|
+
* version 1.4 that is brought to 1.9 in one jump still runs its 1.4 catalogue, against
|
|
59657
|
+
* the 1.9 kernel. The only thing that code may call is {@link MigrationApiV1}; this file
|
|
59658
|
+
* is the contract of every method of it, one Zod pair per method.
|
|
59659
|
+
*
|
|
59660
|
+
* APPEND-ONLY. A released method is never removed and never changes meaning: an input
|
|
59661
|
+
* field is never removed or made required, an enum never narrows, an output field is
|
|
59662
|
+
* never removed. A change that cannot be expressed that way is `migration-api.v2`, next
|
|
59663
|
+
* to this file. `scripts/check-migration-api-append-only.ts` holds the line against
|
|
59664
|
+
* `scripts/migration-api-contract.json`, and every `behaviours` id below must have an
|
|
59665
|
+
* `it('<id>'` in `migration-api.contract.spec.ts`.
|
|
59666
|
+
*/
|
|
59667
|
+
var Rows = record(string(), unknown());
|
|
59668
|
+
var IntegrationId = object({ integrationId: string().min(1) });
|
|
59669
|
+
var RecordId = object({ id: string().min(1) });
|
|
59670
|
+
var RecordRowSchema = object({
|
|
59671
|
+
id: string(),
|
|
59672
|
+
deviceId: number$1().int().nullable(),
|
|
59673
|
+
at: number$1().nullable(),
|
|
59674
|
+
expiresAt: number$1().nullable(),
|
|
59675
|
+
rev: number$1().int(),
|
|
59676
|
+
updatedAt: number$1(),
|
|
59677
|
+
data: Rows
|
|
59678
|
+
});
|
|
59679
|
+
var RecordPutShape = {
|
|
59680
|
+
id: string().min(1),
|
|
59681
|
+
deviceId: number$1().int().optional(),
|
|
59682
|
+
at: number$1().int().optional(),
|
|
59683
|
+
expiresAt: number$1().int().optional(),
|
|
59684
|
+
data: Rows
|
|
59685
|
+
};
|
|
59686
|
+
var MigrationIntegrationSchema = object({
|
|
59687
|
+
id: string(),
|
|
59688
|
+
addonId: string(),
|
|
59689
|
+
name: string(),
|
|
59690
|
+
info: Rows,
|
|
59691
|
+
originStableId: string().optional()
|
|
59692
|
+
});
|
|
59693
|
+
var MigrationDeviceSchema = object({
|
|
59694
|
+
id: number$1().int(),
|
|
59695
|
+
integrationId: string().nullable().optional(),
|
|
59696
|
+
/** The owning device; null or absent for a root. */
|
|
59697
|
+
parentDeviceId: number$1().int().nullable().optional(),
|
|
59698
|
+
type: string().optional(),
|
|
59699
|
+
name: string().optional(),
|
|
59700
|
+
stableId: string().optional()
|
|
59701
|
+
});
|
|
59702
|
+
/** What the engine says about the migration API it serves: lets an addon notice an older server. */
|
|
59703
|
+
var MigrationApiInfoSchema = object({
|
|
59704
|
+
version: number$1().int(),
|
|
59705
|
+
methods: array(string())
|
|
59706
|
+
});
|
|
59707
|
+
object({
|
|
59708
|
+
message: string(),
|
|
59709
|
+
meta: Rows.optional(),
|
|
59710
|
+
/** Grouping tags of the line; a line about a device carries `{ deviceId }`. */
|
|
59711
|
+
tags: object({ deviceId: number$1().int().optional() }).optional()
|
|
59712
|
+
});
|
|
59713
|
+
object({
|
|
59714
|
+
owner: string().min(1),
|
|
59715
|
+
metas: array(MigrationMetaSchema)
|
|
59716
|
+
}), object({
|
|
59717
|
+
bootId: string(),
|
|
59718
|
+
snapshot: _enum([
|
|
59719
|
+
"none",
|
|
59720
|
+
"written",
|
|
59721
|
+
"refused"
|
|
59722
|
+
]),
|
|
59723
|
+
steps: array(MigrationOwnerStepSchema),
|
|
59724
|
+
/** The migration API the engine serves; absent from a server that predates it. */
|
|
59725
|
+
migrationApi: MigrationApiInfoSchema.optional()
|
|
59726
|
+
}), object({ id: string().min(1) }), object({
|
|
59727
|
+
proceed: boolean(),
|
|
59728
|
+
state: union([literal("pending"), MigrationStatusSchema]),
|
|
59729
|
+
reason: string().nullable()
|
|
59730
|
+
}), object({
|
|
59731
|
+
id: string().min(1),
|
|
59732
|
+
result: MigrationOwnerResultSchema
|
|
59733
|
+
}), object({
|
|
59734
|
+
status: MigrationStatusSchema,
|
|
59735
|
+
error: string().nullable(),
|
|
59736
|
+
rowsChanged: number$1().int().nonnegative()
|
|
59737
|
+
}), object({
|
|
59738
|
+
rows: number$1().int().nonnegative(),
|
|
59739
|
+
secrets: number$1().int().nonnegative()
|
|
59740
|
+
}), IntegrationId.extend({ patch: Rows }), object({ written: boolean() }), IntegrationId.extend({ keys: array(string().min(1)) }), object({
|
|
59741
|
+
present: boolean(),
|
|
59742
|
+
removed: Rows
|
|
59743
|
+
}), RecordRowSchema.nullable(), RecordRowSchema.nullable(), object({
|
|
59744
|
+
...RecordPutShape,
|
|
59745
|
+
ifRev: number$1().int().optional()
|
|
59746
|
+
}), object({ rev: number$1().int() }), object({ records: array(object(RecordPutShape)) }), object({ count: number$1().int().nonnegative() }), RecordId.extend({
|
|
59747
|
+
partial: Rows,
|
|
59748
|
+
ifRev: number$1().int().optional()
|
|
59749
|
+
}), object({ rev: number$1().int() }), object({ deleted: boolean() }), object({
|
|
59750
|
+
deviceId: number$1().int().optional(),
|
|
59751
|
+
atFrom: number$1().optional(),
|
|
59752
|
+
atTo: number$1().optional(),
|
|
59753
|
+
where: RecordWhereSchema.optional(),
|
|
59754
|
+
limit: number$1().int().min(1).optional(),
|
|
59755
|
+
cursor: string().min(1).optional(),
|
|
59756
|
+
resolveSecrets: boolean().optional()
|
|
59757
|
+
}), object({
|
|
59758
|
+
records: array(RecordRowSchema),
|
|
59759
|
+
nextCursor: string().nullable()
|
|
59760
|
+
}), object({
|
|
59761
|
+
deviceId: number$1().int().optional(),
|
|
59762
|
+
atBefore: number$1().optional(),
|
|
59763
|
+
where: RecordWhereSchema.optional()
|
|
59764
|
+
}), object({ count: number$1().int().nonnegative() }), object({}), object({ store: Rows }), object({ patch: Rows }), object({}), object({ keys: array(string().min(1)) }), object({ removed: array(string()) }), object({ refs: array(string()) }), object({ values: record(string(), string().nullable()) }), object({
|
|
59765
|
+
ref: string().min(1),
|
|
59766
|
+
value: string()
|
|
59767
|
+
}), object({}), object({ refs: array(string()) }), object({ deleted: number$1().int().nonnegative() }), object({}), object({ integrations: array(MigrationIntegrationSchema) }), object({ id: string().min(1) }), MigrationIntegrationSchema.nullable(), object({
|
|
59768
|
+
addonId: string().min(1),
|
|
59769
|
+
integrationId: string().min(1).optional()
|
|
59770
|
+
}), object({ devices: array(MigrationDeviceSchema) }), object({ deviceId: number$1().int() }), object({
|
|
59771
|
+
id: number$1().int(),
|
|
59772
|
+
integrationId: string().nullable(),
|
|
59773
|
+
/** Whether the device carries any stored config; its content is not offered. */
|
|
59774
|
+
hasConfig: boolean()
|
|
59775
|
+
}).nullable(), object({
|
|
59776
|
+
deviceId: number$1().int(),
|
|
59777
|
+
integrationId: string().min(1)
|
|
59778
|
+
}), object({}), object({}), object({ nodeId: string() }), object({}), object({});
|
|
59448
59779
|
method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { auth: "admin" }), method(object({ owner: string().optional() }), object({ pending: array(MigrationPendingEntrySchema) }), { auth: "admin" }), method(object({ id: string().min(1) }), MigrationDryRunResultSchema, { auth: "admin" }), method(object({
|
|
59449
59780
|
id: string().min(1),
|
|
59450
59781
|
error: string().min(1)
|
|
@@ -59474,7 +59805,9 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
|
|
|
59474
59805
|
"written",
|
|
59475
59806
|
"refused"
|
|
59476
59807
|
]),
|
|
59477
|
-
steps: array(MigrationOwnerStepSchema)
|
|
59808
|
+
steps: array(MigrationOwnerStepSchema),
|
|
59809
|
+
/** The migration API this engine serves (additive: a server that predates it omits it). */
|
|
59810
|
+
migrationApi: MigrationApiInfoSchema.optional()
|
|
59478
59811
|
}), {
|
|
59479
59812
|
kind: "mutation",
|
|
59480
59813
|
auth: "admin"
|
|
@@ -62141,7 +62474,10 @@ DeviceType.Camera, method(object({ deviceId: number$1() }), array(TrackSchema).r
|
|
|
62141
62474
|
}), method(RelocateMediaInputSchema, object({ jobId: string() }), {
|
|
62142
62475
|
kind: "mutation",
|
|
62143
62476
|
auth: "admin"
|
|
62144
|
-
}), method(object({}), UnstampedEventMediaCountSchema, { auth: "admin" }), method(
|
|
62477
|
+
}), method(object({}), UnstampedEventMediaCountSchema, { auth: "admin" }), method(MediaLocationCensusInputSchema, MediaLocationCensusSchema, {
|
|
62478
|
+
kind: "query",
|
|
62479
|
+
auth: "admin"
|
|
62480
|
+
}), method(RelocatableMediaCountInputSchema, RelocatableMediaCountSchema, {
|
|
62145
62481
|
kind: "query",
|
|
62146
62482
|
auth: "admin"
|
|
62147
62483
|
}), method(object({}), array(RelocateJobSchema).readonly(), {
|
|
@@ -64796,7 +65132,7 @@ var BrokerStatusSchema$1 = _enum([
|
|
|
64796
65132
|
"unreachable",
|
|
64797
65133
|
"tls-error"
|
|
64798
65134
|
]);
|
|
64799
|
-
var BrokerInfoSchema
|
|
65135
|
+
var BrokerInfoSchema = object({
|
|
64800
65136
|
id: string(),
|
|
64801
65137
|
name: string(),
|
|
64802
65138
|
url: string(),
|
|
@@ -64859,7 +65195,7 @@ var StatusSchema = object({
|
|
|
64859
65195
|
brokerCount: number$1(),
|
|
64860
65196
|
embeddedRunning: boolean()
|
|
64861
65197
|
});
|
|
64862
|
-
method(_void(), array(BrokerInfoSchema
|
|
65198
|
+
method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
|
|
64863
65199
|
kind: "mutation",
|
|
64864
65200
|
auth: "admin"
|
|
64865
65201
|
}), method(IdInputSchema, _void(), {
|
|
@@ -72041,253 +72377,6 @@ DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, DeviceType.Gateway, met
|
|
|
72041
72377
|
auth: "admin"
|
|
72042
72378
|
});
|
|
72043
72379
|
/**
|
|
72044
|
-
* records - an addon's own facts and documents (D881): ledgers, audit rows,
|
|
72045
|
-
* config documents, mirrors. The third storage API next to `ctx.settings`
|
|
72046
|
-
* (operator intent, D870) and device-history (numbers over time, D850).
|
|
72047
|
-
*
|
|
72048
|
-
* Who the OWNER is: `callerAddonId`, stamped by the transport on a call that
|
|
72049
|
-
* crossed a process and stated by a hub in-process caller (`ctx.records` does
|
|
72050
|
-
* that with the addon id closed over). It is deliberately NOT in any input
|
|
72051
|
-
* schema, so a session can never supply it, and a call with no stamp is refused
|
|
72052
|
-
* `records/no-caller`. A kind is readable and writable only by its owner; other
|
|
72053
|
-
* addons go through the owner's capability.
|
|
72054
|
-
*
|
|
72055
|
-
* Every read is three-way: a row, `null`, or the call THROWS. A failed read is
|
|
72056
|
-
* never `[]`. Secret fields are never on the wire: a read shows the
|
|
72057
|
-
* redaction token, `secretRefs` hands the owner's `ctx.secrets` the refs it
|
|
72058
|
-
* resolves itself, and a write may carry only a keychain marker bound to the
|
|
72059
|
-
* row's own ref.
|
|
72060
|
-
*
|
|
72061
|
-
* Internal: addons reach it through `ctx.records`, never directly.
|
|
72062
|
-
*/
|
|
72063
|
-
var RecordIdSchema = string().min(1).max(256);
|
|
72064
|
-
/** A JSON object body. Size is the engine's check (`maxDataBytes`), not a schema's. */
|
|
72065
|
-
var RecordBodySchema = record(string(), unknown());
|
|
72066
|
-
var RecordInputSchema = object({
|
|
72067
|
-
id: RecordIdSchema,
|
|
72068
|
-
/** The device this row belongs to (D865 removal, per-device reads). */
|
|
72069
|
-
deviceId: number$1().int().optional(),
|
|
72070
|
-
/** The domain clock, ms: observed / created / opened. */
|
|
72071
|
-
at: number$1().int().optional(),
|
|
72072
|
-
/** Producer-set expiry, ms. Refused on a kind whose retention is an engine TTL. */
|
|
72073
|
-
expiresAt: number$1().int().optional(),
|
|
72074
|
-
data: RecordBodySchema
|
|
72075
|
-
});
|
|
72076
|
-
var RecordViewSchema = object({
|
|
72077
|
-
id: string(),
|
|
72078
|
-
deviceId: number$1().int().nullable(),
|
|
72079
|
-
at: number$1().nullable(),
|
|
72080
|
-
expiresAt: number$1().nullable(),
|
|
72081
|
-
rev: number$1().int(),
|
|
72082
|
-
updatedAt: number$1(),
|
|
72083
|
-
data: RecordBodySchema
|
|
72084
|
-
});
|
|
72085
|
-
/** Declared index fields only. Anything else does not compile, by design. */
|
|
72086
|
-
var RecordWhereSchema = record(RecordFieldPathSchema, union([
|
|
72087
|
-
string(),
|
|
72088
|
-
number$1(),
|
|
72089
|
-
boolean(),
|
|
72090
|
-
object({ in: array(union([
|
|
72091
|
-
string(),
|
|
72092
|
-
number$1(),
|
|
72093
|
-
boolean()
|
|
72094
|
-
])).min(1).max(RECORD_BATCH_MAX) }),
|
|
72095
|
-
object({
|
|
72096
|
-
gt: union([string(), number$1()]).optional(),
|
|
72097
|
-
gte: union([string(), number$1()]).optional(),
|
|
72098
|
-
lt: union([string(), number$1()]).optional(),
|
|
72099
|
-
lte: union([string(), number$1()]).optional()
|
|
72100
|
-
}).refine((r) => Object.keys(r).length > 0, { message: "name at least one bound" })
|
|
72101
|
-
]));
|
|
72102
|
-
var KindShape = { kind: RecordKindNameSchema };
|
|
72103
|
-
var TimeWindowShape = {
|
|
72104
|
-
deviceId: number$1().int().optional(),
|
|
72105
|
-
atFrom: number$1().optional(),
|
|
72106
|
-
atTo: number$1().optional()
|
|
72107
|
-
};
|
|
72108
|
-
var RecordQueryInputSchema = object({
|
|
72109
|
-
...KindShape,
|
|
72110
|
-
...TimeWindowShape,
|
|
72111
|
-
where: RecordWhereSchema.optional(),
|
|
72112
|
-
/** Without `where`: `at` (default for a kind with a clock) or `id`. With `where` the order is the index's. */
|
|
72113
|
-
order: _enum(["at", "id"]).optional(),
|
|
72114
|
-
desc: boolean().optional(),
|
|
72115
|
-
limit: number$1().int().min(1).max(RECORD_QUERY_MAX).default(100),
|
|
72116
|
-
/** Opaque keyset cursor from a previous answer. There is no offset. */
|
|
72117
|
-
cursor: string().min(1).optional()
|
|
72118
|
-
});
|
|
72119
|
-
var RecordQueryResultSchema = object({
|
|
72120
|
-
records: array(RecordViewSchema),
|
|
72121
|
-
nextCursor: string().nullable()
|
|
72122
|
-
});
|
|
72123
|
-
var RecordKindStateSchema = _enum([
|
|
72124
|
-
"created",
|
|
72125
|
-
"unchanged",
|
|
72126
|
-
"updated"
|
|
72127
|
-
]);
|
|
72128
|
-
var DeclareKindResultSchema = object({
|
|
72129
|
-
kindId: number$1().int(),
|
|
72130
|
-
state: RecordKindStateSchema,
|
|
72131
|
-
createdIndexes: array(string())
|
|
72132
|
-
});
|
|
72133
|
-
/** Keychain refs a write released (the old marker is gone from the row): the owner deletes the values. */
|
|
72134
|
-
var SecretRefsList = array(string());
|
|
72135
|
-
/**
|
|
72136
|
-
* One op of a `transact`: the single-row writes, each on a kind of the caller.
|
|
72137
|
-
* All ops of one call share ONE database file and ONE `transactFamily`.
|
|
72138
|
-
*/
|
|
72139
|
-
var RecordTransactOpSchema = discriminatedUnion("op", [
|
|
72140
|
-
object({
|
|
72141
|
-
op: literal("put"),
|
|
72142
|
-
kind: RecordKindNameSchema,
|
|
72143
|
-
record: RecordInputSchema,
|
|
72144
|
-
ifRev: number$1().int().min(0).optional()
|
|
72145
|
-
}),
|
|
72146
|
-
object({
|
|
72147
|
-
op: literal("patch"),
|
|
72148
|
-
kind: RecordKindNameSchema,
|
|
72149
|
-
id: RecordIdSchema,
|
|
72150
|
-
data: RecordBodySchema,
|
|
72151
|
-
at: number$1().int().nullable().optional(),
|
|
72152
|
-
expiresAt: number$1().int().nullable().optional(),
|
|
72153
|
-
ifRev: number$1().int().min(1).optional()
|
|
72154
|
-
}),
|
|
72155
|
-
object({
|
|
72156
|
-
op: literal("delete"),
|
|
72157
|
-
kind: RecordKindNameSchema,
|
|
72158
|
-
id: RecordIdSchema
|
|
72159
|
-
})
|
|
72160
|
-
]);
|
|
72161
|
-
var RecordSecretRefsSchema = record(string(), record(string(), string()));
|
|
72162
|
-
method(object({ kinds: record(RecordKindNameSchema, RecordKindDeclarationSchema) }), object({ kinds: record(string(), DeclareKindResultSchema) }), {
|
|
72163
|
-
kind: "mutation",
|
|
72164
|
-
auth: "admin"
|
|
72165
|
-
}), method(object({
|
|
72166
|
-
...KindShape,
|
|
72167
|
-
id: RecordIdSchema
|
|
72168
|
-
}), RecordViewSchema.nullable(), { auth: "admin" }), method(object({
|
|
72169
|
-
...KindShape,
|
|
72170
|
-
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
72171
|
-
}), array(RecordViewSchema), { auth: "admin" }), method(object({
|
|
72172
|
-
...KindShape,
|
|
72173
|
-
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
72174
|
-
}), RecordSecretRefsSchema, { auth: "admin" }), method(object({
|
|
72175
|
-
...KindShape,
|
|
72176
|
-
record: RecordInputSchema,
|
|
72177
|
-
/** Compare-and-set. `0` means "the row must not exist". */
|
|
72178
|
-
ifRev: number$1().int().min(0).optional()
|
|
72179
|
-
}), object({
|
|
72180
|
-
rev: number$1().int(),
|
|
72181
|
-
releasedRefs: SecretRefsList
|
|
72182
|
-
}), {
|
|
72183
|
-
kind: "mutation",
|
|
72184
|
-
auth: "admin"
|
|
72185
|
-
}), method(object({
|
|
72186
|
-
...KindShape,
|
|
72187
|
-
records: array(RecordInputSchema).min(1).max(RECORD_BATCH_MAX)
|
|
72188
|
-
}), object({
|
|
72189
|
-
count: number$1().int(),
|
|
72190
|
-
releasedRefs: SecretRefsList
|
|
72191
|
-
}), {
|
|
72192
|
-
kind: "mutation",
|
|
72193
|
-
auth: "admin"
|
|
72194
|
-
}), method(object({
|
|
72195
|
-
...KindShape,
|
|
72196
|
-
id: RecordIdSchema,
|
|
72197
|
-
data: RecordBodySchema,
|
|
72198
|
-
at: number$1().int().nullable().optional(),
|
|
72199
|
-
expiresAt: number$1().int().nullable().optional(),
|
|
72200
|
-
ifRev: number$1().int().min(1).optional()
|
|
72201
|
-
}), object({
|
|
72202
|
-
rev: number$1().int(),
|
|
72203
|
-
releasedRefs: SecretRefsList
|
|
72204
|
-
}), {
|
|
72205
|
-
kind: "mutation",
|
|
72206
|
-
auth: "admin"
|
|
72207
|
-
}), method(object({
|
|
72208
|
-
...KindShape,
|
|
72209
|
-
id: RecordIdSchema
|
|
72210
|
-
}), object({
|
|
72211
|
-
deleted: boolean(),
|
|
72212
|
-
secretRefs: SecretRefsList
|
|
72213
|
-
}), {
|
|
72214
|
-
kind: "mutation",
|
|
72215
|
-
auth: "admin"
|
|
72216
|
-
}), method(object({
|
|
72217
|
-
...KindShape,
|
|
72218
|
-
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
72219
|
-
}), object({
|
|
72220
|
-
deleted: number$1().int(),
|
|
72221
|
-
secretRefs: SecretRefsList
|
|
72222
|
-
}), {
|
|
72223
|
-
kind: "mutation",
|
|
72224
|
-
auth: "admin"
|
|
72225
|
-
}), method(RecordQueryInputSchema, RecordQueryResultSchema, { auth: "admin" }), method(object({
|
|
72226
|
-
...KindShape,
|
|
72227
|
-
...TimeWindowShape,
|
|
72228
|
-
where: RecordWhereSchema.optional()
|
|
72229
|
-
}), object({ count: number$1().int() }), { auth: "admin" }), method(object({
|
|
72230
|
-
...KindShape,
|
|
72231
|
-
deviceId: number$1().int().optional(),
|
|
72232
|
-
from: number$1(),
|
|
72233
|
-
to: number$1(),
|
|
72234
|
-
bucketMs: number$1().int().positive()
|
|
72235
|
-
}), object({ buckets: array(object({
|
|
72236
|
-
bucketStart: number$1(),
|
|
72237
|
-
count: number$1().int()
|
|
72238
|
-
})) }), { auth: "admin" }), method(object({
|
|
72239
|
-
...KindShape,
|
|
72240
|
-
op: _enum([
|
|
72241
|
-
"sum",
|
|
72242
|
-
"min",
|
|
72243
|
-
"max",
|
|
72244
|
-
"count"
|
|
72245
|
-
]),
|
|
72246
|
-
field: RecordFieldPathSchema.optional(),
|
|
72247
|
-
groupBy: RecordFieldPathSchema.optional(),
|
|
72248
|
-
where: RecordWhereSchema.optional()
|
|
72249
|
-
}), object({ rows: array(object({
|
|
72250
|
-
group: union([
|
|
72251
|
-
string(),
|
|
72252
|
-
number$1(),
|
|
72253
|
-
_null()
|
|
72254
|
-
]),
|
|
72255
|
-
value: number$1().nullable()
|
|
72256
|
-
})) }), { auth: "admin" }), method(object({
|
|
72257
|
-
...KindShape,
|
|
72258
|
-
deviceId: number$1().int().optional(),
|
|
72259
|
-
atBefore: number$1().optional(),
|
|
72260
|
-
where: RecordWhereSchema.optional()
|
|
72261
|
-
}), object({
|
|
72262
|
-
deleted: number$1().int(),
|
|
72263
|
-
secretRefs: SecretRefsList
|
|
72264
|
-
}), {
|
|
72265
|
-
kind: "mutation",
|
|
72266
|
-
auth: "admin"
|
|
72267
|
-
}), method(object({ ops: array(RecordTransactOpSchema).min(1).max(RECORD_BATCH_MAX) }), object({
|
|
72268
|
-
results: array(object({
|
|
72269
|
-
rev: number$1().int().nullable(),
|
|
72270
|
-
deleted: boolean()
|
|
72271
|
-
})),
|
|
72272
|
-
releasedRefs: SecretRefsList,
|
|
72273
|
-
secretRefs: SecretRefsList
|
|
72274
|
-
}), {
|
|
72275
|
-
kind: "mutation",
|
|
72276
|
-
auth: "admin"
|
|
72277
|
-
}), method(object({
|
|
72278
|
-
deviceId: number$1().int(),
|
|
72279
|
-
kinds: array(RecordKindNameSchema).optional()
|
|
72280
|
-
}), object({
|
|
72281
|
-
deleted: record(string(), number$1().int()),
|
|
72282
|
-
secretRefs: SecretRefsList
|
|
72283
|
-
}), {
|
|
72284
|
-
kind: "mutation",
|
|
72285
|
-
auth: "admin"
|
|
72286
|
-
}), method(object(KindShape), object({ id: number$1().int() }), {
|
|
72287
|
-
kind: "mutation",
|
|
72288
|
-
auth: "admin"
|
|
72289
|
-
});
|
|
72290
|
-
/**
|
|
72291
72380
|
* record-entries - the ONE admin surface for every list of user-created entries
|
|
72292
72381
|
* (D881): notification targets, brokers, LLM profiles, anything an addon
|
|
72293
72382
|
* declares as an ENTRY kind (`entry: { title }` in `defineRecordKinds`).
|
|
@@ -82015,7 +82104,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
82015
82104
|
* `deviceClasses` map (first registered type). `null` when manual creation is
|
|
82016
82105
|
* unsupported. Lets the Add-Device dialog pick the right type per provider.
|
|
82017
82106
|
*/
|
|
82018
|
-
async getManualCreationType() {
|
|
82107
|
+
async getManualCreationType(_input) {
|
|
82019
82108
|
if (!await this.supportsManualCreation()) return { deviceType: null };
|
|
82020
82109
|
return { deviceType: Object.values(DeviceType).find((t) => this.deviceClasses[t] !== void 0) ?? null };
|
|
82021
82110
|
}
|
|
@@ -82026,7 +82115,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
82026
82115
|
return true;
|
|
82027
82116
|
}
|
|
82028
82117
|
async getChildCreationSchema(input) {
|
|
82029
|
-
return this.onGetCreationSchema(input.type);
|
|
82118
|
+
return this.onGetCreationSchema(input.type, { integrationId: input.integrationId });
|
|
82030
82119
|
}
|
|
82031
82120
|
/**
|
|
82032
82121
|
* Default kernel-orchestrated `createDevice` implementation. The
|
|
@@ -82038,7 +82127,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
82038
82127
|
* `deviceClasses` instead.
|
|
82039
82128
|
*/
|
|
82040
82129
|
async createDevice(input) {
|
|
82041
|
-
const spec = await this.onCreateDevice(input.type, input.config);
|
|
82130
|
+
const spec = await this.onCreateDevice(input.type, input.config, { ...input.integrationId !== void 0 ? { integrationId: input.integrationId } : {} });
|
|
82042
82131
|
const Class = this.deviceClasses[spec.meta.type];
|
|
82043
82132
|
if (!Class) throw new Error(`${this.providerName} provider: no device class registered for type "${spec.meta.type}" — add it to the deviceClasses map`);
|
|
82044
82133
|
const stableId = this.generateStableId(spec.meta.type, spec.config);
|
|
@@ -83447,20 +83536,6 @@ Object.freeze({
|
|
|
83447
83536
|
access: "create",
|
|
83448
83537
|
role: "viewer"
|
|
83449
83538
|
},
|
|
83450
|
-
"broker.add": {
|
|
83451
|
-
capName: "broker",
|
|
83452
|
-
capScope: "system",
|
|
83453
|
-
addonId: null,
|
|
83454
|
-
access: "create",
|
|
83455
|
-
role: "admin"
|
|
83456
|
-
},
|
|
83457
|
-
"broker.get": {
|
|
83458
|
-
capName: "broker",
|
|
83459
|
-
capScope: "system",
|
|
83460
|
-
addonId: null,
|
|
83461
|
-
access: "view",
|
|
83462
|
-
role: "admin"
|
|
83463
|
-
},
|
|
83464
83539
|
"broker.getBrokerConfig": {
|
|
83465
83540
|
capName: "broker",
|
|
83466
83541
|
capScope: "system",
|
|
@@ -83468,20 +83543,6 @@ Object.freeze({
|
|
|
83468
83543
|
access: "view",
|
|
83469
83544
|
role: "admin"
|
|
83470
83545
|
},
|
|
83471
|
-
"broker.getSettings": {
|
|
83472
|
-
capName: "broker",
|
|
83473
|
-
capScope: "system",
|
|
83474
|
-
addonId: null,
|
|
83475
|
-
access: "view",
|
|
83476
|
-
role: "admin"
|
|
83477
|
-
},
|
|
83478
|
-
"broker.getSettingsSchema": {
|
|
83479
|
-
capName: "broker",
|
|
83480
|
-
capScope: "system",
|
|
83481
|
-
addonId: null,
|
|
83482
|
-
access: "view",
|
|
83483
|
-
role: "admin"
|
|
83484
|
-
},
|
|
83485
83546
|
"broker.getState": {
|
|
83486
83547
|
capName: "broker",
|
|
83487
83548
|
capScope: "system",
|
|
@@ -83496,20 +83557,6 @@ Object.freeze({
|
|
|
83496
83557
|
access: "view",
|
|
83497
83558
|
role: "admin"
|
|
83498
83559
|
},
|
|
83499
|
-
"broker.list": {
|
|
83500
|
-
capName: "broker",
|
|
83501
|
-
capScope: "system",
|
|
83502
|
-
addonId: null,
|
|
83503
|
-
access: "view",
|
|
83504
|
-
role: "admin"
|
|
83505
|
-
},
|
|
83506
|
-
"broker.listProviders": {
|
|
83507
|
-
capName: "broker",
|
|
83508
|
-
capScope: "system",
|
|
83509
|
-
addonId: null,
|
|
83510
|
-
access: "view",
|
|
83511
|
-
role: "admin"
|
|
83512
|
-
},
|
|
83513
83560
|
"broker.publish": {
|
|
83514
83561
|
capName: "broker",
|
|
83515
83562
|
capScope: "system",
|
|
@@ -83517,20 +83564,6 @@ Object.freeze({
|
|
|
83517
83564
|
access: "create",
|
|
83518
83565
|
role: "admin"
|
|
83519
83566
|
},
|
|
83520
|
-
"broker.remove": {
|
|
83521
|
-
capName: "broker",
|
|
83522
|
-
capScope: "system",
|
|
83523
|
-
addonId: null,
|
|
83524
|
-
access: "delete",
|
|
83525
|
-
role: "admin"
|
|
83526
|
-
},
|
|
83527
|
-
"broker.setSettings": {
|
|
83528
|
-
capName: "broker",
|
|
83529
|
-
capScope: "system",
|
|
83530
|
-
addonId: null,
|
|
83531
|
-
access: "create",
|
|
83532
|
-
role: "admin"
|
|
83533
|
-
},
|
|
83534
83567
|
"broker.subscribe": {
|
|
83535
83568
|
capName: "broker",
|
|
83536
83569
|
capScope: "system",
|
|
@@ -83538,20 +83571,6 @@ Object.freeze({
|
|
|
83538
83571
|
access: "create",
|
|
83539
83572
|
role: "admin"
|
|
83540
83573
|
},
|
|
83541
|
-
"broker.testConnection": {
|
|
83542
|
-
capName: "broker",
|
|
83543
|
-
capScope: "system",
|
|
83544
|
-
addonId: null,
|
|
83545
|
-
access: "create",
|
|
83546
|
-
role: "admin"
|
|
83547
|
-
},
|
|
83548
|
-
"broker.testSettings": {
|
|
83549
|
-
capName: "broker",
|
|
83550
|
-
capScope: "system",
|
|
83551
|
-
addonId: null,
|
|
83552
|
-
access: "create",
|
|
83553
|
-
role: "admin"
|
|
83554
|
-
},
|
|
83555
83574
|
"broker.unsubscribe": {
|
|
83556
83575
|
capName: "broker",
|
|
83557
83576
|
capScope: "system",
|
|
@@ -88515,6 +88534,13 @@ Object.freeze({
|
|
|
88515
88534
|
access: "view",
|
|
88516
88535
|
role: "viewer"
|
|
88517
88536
|
},
|
|
88537
|
+
"pipelineAnalytics.getMediaLocationCensus": {
|
|
88538
|
+
capName: "pipeline-analytics",
|
|
88539
|
+
capScope: "device",
|
|
88540
|
+
addonId: null,
|
|
88541
|
+
access: "view",
|
|
88542
|
+
role: "admin"
|
|
88543
|
+
},
|
|
88518
88544
|
"pipelineAnalytics.getMediaReclaimStatus": {
|
|
88519
88545
|
capName: "pipeline-analytics",
|
|
88520
88546
|
capScope: "device",
|
|
@@ -97765,6 +97791,15 @@ function rewriteLocation(location, currentUpstream, route) {
|
|
|
97765
97791
|
/** Characters that may precede a URL that STARTS a value (never `=`, `?`, `&`, `%3D`). */
|
|
97766
97792
|
var VALUE_START = String.raw`(?<=^|["'(\s>,\[{])`;
|
|
97767
97793
|
/**
|
|
97794
|
+
* Subresource Integrity pins the bytes of a script or stylesheet. The proxy rewrites vendor
|
|
97795
|
+
* URLs inside those bytes (and re-serves them from another origin), so the hash can no longer
|
|
97796
|
+
* match and the browser refuses the file: the page renders with no script at all. The hub
|
|
97797
|
+
* already drops the CSP that would have enforced the same pin; the attribute goes with it.
|
|
97798
|
+
*/
|
|
97799
|
+
function stripIntegrity(html) {
|
|
97800
|
+
return html.replace(/\s+integrity\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, "");
|
|
97801
|
+
}
|
|
97802
|
+
/**
|
|
97768
97803
|
* Rewrite vendor URLs in a text body: absolute (`https://host/…`), protocol-relative
|
|
97769
97804
|
* (`//host/…`) and JSON-escaped (`https:\/\/host\/…`) when they start a value; and, in
|
|
97770
97805
|
* HTML/CSS, root-relative references (`href="/x"`, `url(/x)`) which would otherwise
|
|
@@ -97780,6 +97815,7 @@ function rewriteBody(body, kind, currentUpstream, route) {
|
|
|
97780
97815
|
const escaped = new RegExp(`${VALUE_START}(?:https?:)?\\\\/\\\\/${host}(?=\\\\/|["'?#\\s)\\]},<]|$)`, "g");
|
|
97781
97816
|
out = out.replace(escaped, escapedBase);
|
|
97782
97817
|
}
|
|
97818
|
+
if (kind === "html") out = stripIntegrity(out);
|
|
97783
97819
|
const current = `${route.basePath}/${currentUpstream.host}/`;
|
|
97784
97820
|
const notMounted = `(?!/)(?!${escapeRegex(route.basePath.slice(1))}/)`;
|
|
97785
97821
|
if (kind === "html") {
|
|
@@ -98404,6 +98440,179 @@ object({
|
|
|
98404
98440
|
delay: number$1().nonnegative().optional(),
|
|
98405
98441
|
commands: record(string(), unknown())
|
|
98406
98442
|
});
|
|
98443
|
+
var BrokerInUseError = class extends Error {
|
|
98444
|
+
brokerId;
|
|
98445
|
+
users;
|
|
98446
|
+
name = "BrokerInUseError";
|
|
98447
|
+
constructor(brokerId, users) {
|
|
98448
|
+
super(`broker '${brokerId}' is still in use by ${users.map((u) => `${u.kind} '${u.name ?? u.id}' (${u.id})`).join(", ")}; unlink them first`);
|
|
98449
|
+
this.brokerId = brokerId;
|
|
98450
|
+
this.users = users;
|
|
98451
|
+
}
|
|
98452
|
+
};
|
|
98453
|
+
/** Throw {@link BrokerInUseError} when any finder names a user of `brokerId`. */
|
|
98454
|
+
async function assertBrokerUnused(brokerId, finders) {
|
|
98455
|
+
const users = [];
|
|
98456
|
+
for (const finder of finders) users.push(...await finder.find(brokerId));
|
|
98457
|
+
if (users.length > 0) throw new BrokerInUseError(brokerId, users);
|
|
98458
|
+
}
|
|
98459
|
+
/**
|
|
98460
|
+
* An account provider whose `deleteInstance` is refused while anything still uses the account
|
|
98461
|
+
* (D1012). The account is an `integration-instances` kind (D1013), so the refusal lives on the
|
|
98462
|
+
* delete the instances page calls, not on a separate broker cap.
|
|
98463
|
+
*/
|
|
98464
|
+
function guardBrokerDelete(base, users) {
|
|
98465
|
+
return {
|
|
98466
|
+
...base,
|
|
98467
|
+
deleteInstance: async (input) => {
|
|
98468
|
+
await assertBrokerUnused(input.id, users());
|
|
98469
|
+
return base.deleteInstance(input);
|
|
98470
|
+
}
|
|
98471
|
+
};
|
|
98472
|
+
}
|
|
98473
|
+
/**
|
|
98474
|
+
* The generic binding of a service to a broker (D1012). A service (an integration today; a
|
|
98475
|
+
* notifier target, a calendar source or an exporter later) names the broker it runs through by id
|
|
98476
|
+
* and keeps only its OWN link settings: never a copy of the broker's host, sign-in or secrets,
|
|
98477
|
+
* which stay with the broker and are edited on the Brokers page.
|
|
98478
|
+
*
|
|
98479
|
+
* One primitive serves every consumer:
|
|
98480
|
+
* - {@link declareBrokerLinkKind}: the record kind, body `{ name, brokerId, ...linkFields }`;
|
|
98481
|
+
* - {@link buildBrokerLinkForm}: the form, a `broker-select` picker plus the service's link
|
|
98482
|
+
* sections, which may be DYNAMIC (a list read from the chosen broker, like HA calendars);
|
|
98483
|
+
* - {@link buildBrokerLinkInstances}: the `integration-instances` provider of an integration;
|
|
98484
|
+
* - {@link resolveBrokerId} / {@link brokerLinkUserFinder}: which broker a record names and which
|
|
98485
|
+
* records name a broker, so a delete of a broker still in use can name its users generically.
|
|
98486
|
+
*/
|
|
98487
|
+
/** The record kind an integration is stored under. */
|
|
98488
|
+
var BROKER_LINK_KIND_NAME = "connections";
|
|
98489
|
+
/** What every broker-linked record carries; `brokerId` is always present, `''` is a row whose broker is not picked yet; link fields of the service come on top. */
|
|
98490
|
+
var BrokerLinkBodySchema = entryBody({ brokerId: string() });
|
|
98491
|
+
function declareBrokerLinkKind(spec) {
|
|
98492
|
+
return defineEntryKind(BROKER_LINK_KIND_NAME, {
|
|
98493
|
+
title: spec.title,
|
|
98494
|
+
schema: BrokerLinkBodySchema.extend(spec.linkFields ?? {}),
|
|
98495
|
+
writeTrigger: spec.writeTrigger
|
|
98496
|
+
});
|
|
98497
|
+
}
|
|
98498
|
+
/** The form: a picker over the brokers of `brokerKind`, then the service's link sections. */
|
|
98499
|
+
async function buildBrokerLinkForm(spec, brokerId) {
|
|
98500
|
+
const dynamic = brokerId !== void 0 && spec.dynamicSections !== void 0 ? await spec.dynamicSections(brokerId) : [];
|
|
98501
|
+
return { sections: [
|
|
98502
|
+
{
|
|
98503
|
+
id: "broker-link",
|
|
98504
|
+
title: spec.brokerLabel,
|
|
98505
|
+
description: `Each integration links to a ${spec.brokerLabel} connection (Connectivity → Brokers). The connection and its sign-in are edited there.`,
|
|
98506
|
+
columns: 1,
|
|
98507
|
+
fields: [{
|
|
98508
|
+
type: "broker-select",
|
|
98509
|
+
key: "brokerId",
|
|
98510
|
+
label: spec.brokerLabel,
|
|
98511
|
+
brokerKind: spec.brokerKind
|
|
98512
|
+
}]
|
|
98513
|
+
},
|
|
98514
|
+
...spec.linkSections ?? [],
|
|
98515
|
+
...dynamic
|
|
98516
|
+
] };
|
|
98517
|
+
}
|
|
98518
|
+
/** The broker a record names; undefined while it names none. */
|
|
98519
|
+
function resolveBrokerId(record) {
|
|
98520
|
+
return record.brokerId === void 0 || record.brokerId === "" ? void 0 : record.brokerId;
|
|
98521
|
+
}
|
|
98522
|
+
/** The broker an integration links to, from its record. */
|
|
98523
|
+
async function brokerIdOf(instances, integrationId) {
|
|
98524
|
+
const body = await instances.getResolved(integrationId);
|
|
98525
|
+
return body === null ? void 0 : resolveBrokerId(body);
|
|
98526
|
+
}
|
|
98527
|
+
/** Every record of the kind that names `brokerId`, as users of that broker. */
|
|
98528
|
+
function brokerLinkUserFinder(instances, userKind = "integration") {
|
|
98529
|
+
return { find: async (brokerId) => (await instances.listResolved()).filter((entry) => resolveBrokerId(entry.body) === brokerId).map((entry) => ({
|
|
98530
|
+
kind: userKind,
|
|
98531
|
+
id: entry.id,
|
|
98532
|
+
name: entry.body.name
|
|
98533
|
+
})) };
|
|
98534
|
+
}
|
|
98535
|
+
/** The `integration-instances` provider of an addon whose integrations link to a broker. */
|
|
98536
|
+
function buildBrokerLinkInstances(records, spec) {
|
|
98537
|
+
const instances = new RecordIntegrationInstances(records, spec.kind, {
|
|
98538
|
+
description: {
|
|
98539
|
+
role: "devices",
|
|
98540
|
+
instanceMode: "multiple",
|
|
98541
|
+
kind: "connection",
|
|
98542
|
+
canTest: false
|
|
98543
|
+
},
|
|
98544
|
+
formSchema: async (id) => buildBrokerLinkForm(spec, id === null ? void 0 : await brokerIdOf(instances, id)),
|
|
98545
|
+
initialBody: (name) => spec.kind.schema.parse({
|
|
98546
|
+
name,
|
|
98547
|
+
brokerId: ""
|
|
98548
|
+
}),
|
|
98549
|
+
configured: (body) => resolveBrokerId(body) !== void 0,
|
|
98550
|
+
...spec.onChanged !== void 0 ? { onChanged: spec.onChanged } : {}
|
|
98551
|
+
});
|
|
98552
|
+
return instances;
|
|
98553
|
+
}
|
|
98554
|
+
var BROKER_ROWS_MAX = 1e3;
|
|
98555
|
+
var STRAY_REGISTRY_ID = /^int_\d+$/;
|
|
98556
|
+
async function moveBrokerLinksToConnections(ports) {
|
|
98557
|
+
const brokers = await ports.listBrokers();
|
|
98558
|
+
const strays = brokers.filter((b) => STRAY_REGISTRY_ID.test(b.id));
|
|
98559
|
+
if (strays.length > 0) ports.warn(`${strays.length} broker row(s) are keyed like a registry integration and are left as they are: ${strays.map((s) => s.id).join(", ")}`);
|
|
98560
|
+
let moved = 0;
|
|
98561
|
+
for (const broker of brokers) {
|
|
98562
|
+
const integrationId = broker.integrationId;
|
|
98563
|
+
if (integrationId === void 0 || integrationId === "") continue;
|
|
98564
|
+
const existing = await ports.readConnection(integrationId);
|
|
98565
|
+
if (existing === null) await ports.putConnection(integrationId, {
|
|
98566
|
+
name: broker.name,
|
|
98567
|
+
brokerId: broker.id
|
|
98568
|
+
});
|
|
98569
|
+
else if (existing.brokerId !== broker.id) {
|
|
98570
|
+
ports.warn(`integration '${integrationId}' already names broker '${existing.brokerId ?? "none"}', not '${broker.id}'; the link stays on the broker`);
|
|
98571
|
+
continue;
|
|
98572
|
+
}
|
|
98573
|
+
if ((await ports.readConnection(integrationId))?.brokerId !== broker.id) throw new Error(`integration '${integrationId}': the link did not read back - broker '${broker.id}' keeps it`);
|
|
98574
|
+
await ports.clearBrokerLink(broker.id);
|
|
98575
|
+
moved += 1;
|
|
98576
|
+
}
|
|
98577
|
+
return {
|
|
98578
|
+
outcome: moved === 0 ? "already-done" : "done",
|
|
98579
|
+
summary: {
|
|
98580
|
+
brokers: brokers.length,
|
|
98581
|
+
moved,
|
|
98582
|
+
strayIntegrationRows: strays.length
|
|
98583
|
+
}
|
|
98584
|
+
};
|
|
98585
|
+
}
|
|
98586
|
+
/** The ports over the two record kinds of the addon. */
|
|
98587
|
+
function brokerLinkPortsOver(deps) {
|
|
98588
|
+
return {
|
|
98589
|
+
warn: deps.warn,
|
|
98590
|
+
listBrokers: async () => {
|
|
98591
|
+
return (await deps.brokers.query({ limit: BROKER_ROWS_MAX })).records.map((row) => {
|
|
98592
|
+
const name = row.data["name"];
|
|
98593
|
+
const link = row.data["integrationId"];
|
|
98594
|
+
return {
|
|
98595
|
+
id: row.id,
|
|
98596
|
+
name: typeof name === "string" && name.length > 0 ? name : row.id,
|
|
98597
|
+
integrationId: typeof link === "string" ? link : void 0
|
|
98598
|
+
};
|
|
98599
|
+
});
|
|
98600
|
+
},
|
|
98601
|
+
readConnection: async (id) => {
|
|
98602
|
+
const row = await deps.connections.get(id);
|
|
98603
|
+
return row === null ? null : { brokerId: row.data.brokerId };
|
|
98604
|
+
},
|
|
98605
|
+
putConnection: async (id, body) => {
|
|
98606
|
+
await deps.connections.put({
|
|
98607
|
+
id,
|
|
98608
|
+
data: body
|
|
98609
|
+
}, { ifRev: 0 });
|
|
98610
|
+
},
|
|
98611
|
+
clearBrokerLink: async (brokerId) => {
|
|
98612
|
+
await deps.brokers.patch(brokerId, { integrationId: null });
|
|
98613
|
+
}
|
|
98614
|
+
};
|
|
98615
|
+
}
|
|
98407
98616
|
//#endregion
|
|
98408
98617
|
//#region src/config.ts
|
|
98409
98618
|
/**
|
|
@@ -98449,7 +98658,6 @@ var AlexaBrokerEntrySchema = object({
|
|
|
98449
98658
|
id: string().min(1),
|
|
98450
98659
|
name: string().min(1),
|
|
98451
98660
|
connection: alexaConnectionSchema,
|
|
98452
|
-
integrationId: string().optional(),
|
|
98453
98661
|
/** Absent means on; the instances page switches a connection off without deleting it. */
|
|
98454
98662
|
enabled: boolean().optional()
|
|
98455
98663
|
});
|
|
@@ -142695,7 +142903,6 @@ var AlexaBrokerRegistry = class {
|
|
|
142695
142903
|
#onStatesPolled;
|
|
142696
142904
|
#makeManager;
|
|
142697
142905
|
#managers = /* @__PURE__ */ new Map();
|
|
142698
|
-
#integrationToBroker = /* @__PURE__ */ new Map();
|
|
142699
142906
|
#nextId = 1;
|
|
142700
142907
|
constructor(logger, deps = {}) {
|
|
142701
142908
|
this.#logger = logger;
|
|
@@ -142746,7 +142953,6 @@ var AlexaBrokerRegistry = class {
|
|
|
142746
142953
|
}
|
|
142747
142954
|
}));
|
|
142748
142955
|
this.#managers.clear();
|
|
142749
|
-
this.#integrationToBroker.clear();
|
|
142750
142956
|
}
|
|
142751
142957
|
setOnBrokerConnected(cb) {
|
|
142752
142958
|
this.#onBrokerConnected = cb;
|
|
@@ -142769,17 +142975,14 @@ var AlexaBrokerRegistry = class {
|
|
|
142769
142975
|
const entry = {
|
|
142770
142976
|
id,
|
|
142771
142977
|
name,
|
|
142772
|
-
connection
|
|
142773
|
-
...opts.integrationId !== void 0 ? { integrationId: opts.integrationId } : {}
|
|
142978
|
+
connection
|
|
142774
142979
|
};
|
|
142775
142980
|
await this.#startManager(entry);
|
|
142776
|
-
if (opts.integrationId !== void 0) this.#integrationToBroker.set(opts.integrationId, id);
|
|
142777
142981
|
return entry;
|
|
142778
142982
|
}
|
|
142779
142983
|
async removeEntry(id) {
|
|
142780
142984
|
const mgr = this.#managers.get(id);
|
|
142781
142985
|
if (!mgr) return;
|
|
142782
|
-
for (const [integrationId, brokerId] of this.#integrationToBroker.entries()) if (brokerId === id) this.#integrationToBroker.delete(integrationId);
|
|
142783
142986
|
this.#managers.delete(id);
|
|
142784
142987
|
try {
|
|
142785
142988
|
await mgr.stop();
|
|
@@ -142805,9 +143008,6 @@ var AlexaBrokerRegistry = class {
|
|
|
142805
143008
|
if (!mgr) throw new Error(`AlexaBrokerRegistry: unknown broker id "${brokerId}"`);
|
|
142806
143009
|
await mgr.sendCommand(command);
|
|
142807
143010
|
}
|
|
142808
|
-
linkIntegration(integrationId, brokerId) {
|
|
142809
|
-
this.#integrationToBroker.set(integrationId, brokerId);
|
|
142810
|
-
}
|
|
142811
143011
|
list() {
|
|
142812
143012
|
return Array.from(this.#managers.values()).map((m) => m.getInfo());
|
|
142813
143013
|
}
|
|
@@ -142826,10 +143026,7 @@ var AlexaBrokerRegistry = class {
|
|
|
142826
143026
|
return count;
|
|
142827
143027
|
}
|
|
142828
143028
|
async #startManager(entry) {
|
|
142829
|
-
if (this.#managers.has(entry.id))
|
|
142830
|
-
if (entry.integrationId !== void 0) this.#integrationToBroker.set(entry.integrationId, entry.id);
|
|
142831
|
-
return;
|
|
142832
|
-
}
|
|
143029
|
+
if (this.#managers.has(entry.id)) return;
|
|
142833
143030
|
const mgr = this.#makeManager({
|
|
142834
143031
|
id: entry.id,
|
|
142835
143032
|
name: entry.name,
|
|
@@ -142842,7 +143039,6 @@ var AlexaBrokerRegistry = class {
|
|
|
142842
143039
|
onStatesPolled: (id, values) => this.#onStatesPolled(id, values)
|
|
142843
143040
|
});
|
|
142844
143041
|
this.#managers.set(entry.id, mgr);
|
|
142845
|
-
if (entry.integrationId !== void 0) this.#integrationToBroker.set(entry.integrationId, entry.id);
|
|
142846
143042
|
await mgr.start();
|
|
142847
143043
|
}
|
|
142848
143044
|
/** The next free broker id, for a record minted before its manager exists. */
|
|
@@ -142906,40 +143102,15 @@ async function removeSessionFile(dataDir, brokerId) {
|
|
|
142906
143102
|
await fs$1.rm(sessionPath(dataDir, brokerId), { force: true });
|
|
142907
143103
|
}
|
|
142908
143104
|
//#endregion
|
|
142909
|
-
//#region src/speak/constants.ts
|
|
142910
|
-
/** Echo speech limits and the worker restart bound (same window as CrashSupervisor). */
|
|
142911
|
-
var ALEXA_SPEAK_ADDON_ID = "import-alexa";
|
|
142912
|
-
var ALEXA_SPEAK_KIND = "alexa-echo";
|
|
142913
|
-
/** The kind of the Amazon-account broker the Echoes are read from. */
|
|
142914
|
-
var ALEXA_BROKER_KIND = "alexa";
|
|
142915
|
-
var DEVICE_REFRESH_MS = 5 * 6e4;
|
|
142916
|
-
/** Where the addon serves the Alexa mark; `listTargetKinds` stamps it as `iconUrl`. */
|
|
142917
|
-
var ALEXA_SPEAK_ICON_URL = `/addon/${ALEXA_SPEAK_ADDON_ID}/icons/${ALEXA_SPEAK_KIND}`;
|
|
142918
|
-
/** How often an adopted Echo's player state is read (a push channel is a later phase). */
|
|
142919
|
-
var ECHO_POLL_MS = 3e4;
|
|
142920
|
-
/** One more read after a command, once Amazon has had time to apply it. */
|
|
142921
|
-
var ECHO_REREAD_MS = 1500;
|
|
142922
|
-
/** After a 429 the account is left alone for this long, doubling up to the cap. */
|
|
142923
|
-
var ECHO_RATE_BACKOFF_BASE_MS = 3e4;
|
|
142924
|
-
var ECHO_RATE_BACKOFF_MAX_MS = 5 * 6e4;
|
|
142925
|
-
/**
|
|
142926
|
-
* A command (volume, play, Do Not Disturb) that has not started within this, or not
|
|
142927
|
-
* finished, is answered with an error instead of running minutes late.
|
|
142928
|
-
*/
|
|
142929
|
-
var ECHO_COMMAND_DEADLINE_MS = 12e3;
|
|
142930
|
-
/** A poll read is stale after this; it is dropped from the queue rather than sent late. */
|
|
142931
|
-
var ECHO_READ_DEADLINE_MS = 25e3;
|
|
142932
|
-
//#endregion
|
|
142933
143105
|
//#region src/alexa-broker-provider.ts
|
|
142934
143106
|
/**
|
|
142935
143107
|
* Construct the `broker` cap provider for the Alexa import addon. Pure builder.
|
|
142936
|
-
*
|
|
142937
|
-
*
|
|
142938
|
-
* poll-based (no pub/sub) — `publish`/`subscribe`/`getState` are no-ops.
|
|
143108
|
+
* The bus half only (D1013): creating, listing, editing and removing an account is
|
|
143109
|
+
* `integration-instances`. The cookie + refresh token are secrets, redacted on the config
|
|
143110
|
+
* read. State sync is poll-based (no pub/sub) — `publish`/`subscribe`/`getState` are no-ops.
|
|
142939
143111
|
*/
|
|
142940
143112
|
function buildAlexaBrokerProvider(deps) {
|
|
142941
|
-
const {
|
|
142942
|
-
const owns = (id) => getBrokers().some((b) => b.id === id);
|
|
143113
|
+
const { registry, getBrokers } = deps;
|
|
142943
143114
|
const redact = (entry) => ({
|
|
142944
143115
|
...entry.connection,
|
|
142945
143116
|
cookie: "",
|
|
@@ -142947,108 +143118,10 @@ function buildAlexaBrokerProvider(deps) {
|
|
|
142947
143118
|
macDms: ""
|
|
142948
143119
|
});
|
|
142949
143120
|
return {
|
|
142950
|
-
list: async ({ kind }) => {
|
|
142951
|
-
if (kind && kind !== "alexa") return [];
|
|
142952
|
-
return registry.list().map((info) => ({
|
|
142953
|
-
...info,
|
|
142954
|
-
addonId: ownerAddonId
|
|
142955
|
-
}));
|
|
142956
|
-
},
|
|
142957
|
-
get: async ({ id }) => {
|
|
142958
|
-
const info = registry.get(id);
|
|
142959
|
-
return info ? {
|
|
142960
|
-
...info,
|
|
142961
|
-
addonId: ownerAddonId
|
|
142962
|
-
} : null;
|
|
142963
|
-
},
|
|
142964
|
-
listProviders: async () => [{
|
|
142965
|
-
addonId: ownerAddonId,
|
|
142966
|
-
kinds: [{
|
|
142967
|
-
kind: ALEXA_BROKER_KIND,
|
|
142968
|
-
label: "Alexa",
|
|
142969
|
-
signIn: {
|
|
142970
|
-
basePath: "/addon/import-alexa/login",
|
|
142971
|
-
methods: ["device-code", "proxy"]
|
|
142972
|
-
}
|
|
142973
|
-
}]
|
|
142974
|
-
}],
|
|
142975
|
-
add: async ({ kind, name, settings }) => {
|
|
142976
|
-
if (kind !== "alexa") throw new Error(`import-alexa: only kind '${ALEXA_BROKER_KIND}' is handled here (got '${kind}')`);
|
|
142977
|
-
const entry = await registry.createEntry(name, settingsToAlexaConnection(settings));
|
|
142978
|
-
await persistBrokers([...getBrokers(), entry]);
|
|
142979
|
-
return { id: entry.id };
|
|
142980
|
-
},
|
|
142981
|
-
remove: async ({ id }) => {
|
|
142982
|
-
if (!owns(id)) return;
|
|
142983
|
-
await registry.removeEntry(id);
|
|
142984
|
-
await cascadeRemoveDevices(id).catch((err) => {
|
|
142985
|
-
logger.warn("alexa: broker cascade-remove threw", {
|
|
142986
|
-
tags: { brokerId: id },
|
|
142987
|
-
meta: { error: errMsg(err) }
|
|
142988
|
-
});
|
|
142989
|
-
});
|
|
142990
|
-
await persistBrokers(getBrokers().filter((b) => b.id !== id));
|
|
142991
|
-
},
|
|
142992
|
-
testConnection: async ({ id }) => {
|
|
142993
|
-
if (!owns(id)) return {
|
|
142994
|
-
ok: false,
|
|
142995
|
-
error: "unknown broker"
|
|
142996
|
-
};
|
|
142997
|
-
const info = registry.get(id);
|
|
142998
|
-
if (!info) return {
|
|
142999
|
-
ok: false,
|
|
143000
|
-
error: "unknown broker"
|
|
143001
|
-
};
|
|
143002
|
-
if (info.status === "connected") return {
|
|
143003
|
-
ok: true,
|
|
143004
|
-
latencyMs: info.lastCheckedAt ? Math.max(0, Date.now() - info.lastCheckedAt) : 0
|
|
143005
|
-
};
|
|
143006
|
-
return {
|
|
143007
|
-
ok: false,
|
|
143008
|
-
error: info.error ?? `status: ${info.status}`
|
|
143009
|
-
};
|
|
143010
|
-
},
|
|
143011
|
-
getSettings: async ({ id }) => {
|
|
143012
|
-
const entry = getBrokers().find((b) => b.id === id);
|
|
143013
|
-
return entry ? redact(entry) : null;
|
|
143014
|
-
},
|
|
143015
|
-
setSettings: async ({ id, settings }) => {
|
|
143016
|
-
if (!owns(id)) return;
|
|
143017
|
-
const existing = getBrokers().find((b) => b.id === id);
|
|
143018
|
-
if (!existing) return;
|
|
143019
|
-
const parsed = settingsToAlexaConnection(settings);
|
|
143020
|
-
const merged = {
|
|
143021
|
-
...parsed,
|
|
143022
|
-
cookie: parsed.cookie.length > 0 ? parsed.cookie : existing.connection.cookie,
|
|
143023
|
-
refreshToken: parsed.refreshToken.length > 0 ? parsed.refreshToken : existing.connection.refreshToken,
|
|
143024
|
-
macDms: parsed.macDms.length > 0 ? parsed.macDms : existing.connection.macDms
|
|
143025
|
-
};
|
|
143026
|
-
const updated = await registry.updateEntry(id, merged);
|
|
143027
|
-
await persistBrokers(getBrokers().map((b) => b.id === id ? updated : b));
|
|
143028
|
-
},
|
|
143029
143121
|
getBrokerConfig: async ({ id }) => {
|
|
143030
143122
|
const entry = getBrokers().find((b) => b.id === id);
|
|
143031
143123
|
return entry ? redact(entry) : null;
|
|
143032
143124
|
},
|
|
143033
|
-
getSettingsSchema: async ({ kind }) => {
|
|
143034
|
-
if (kind !== "alexa") return null;
|
|
143035
|
-
return buildConnectionFormSchema();
|
|
143036
|
-
},
|
|
143037
|
-
testSettings: async ({ kind, settings }) => {
|
|
143038
|
-
if (kind !== "alexa") return {
|
|
143039
|
-
ok: false,
|
|
143040
|
-
error: `unsupported kind: ${kind}`
|
|
143041
|
-
};
|
|
143042
|
-
try {
|
|
143043
|
-
settingsToAlexaConnection(settings);
|
|
143044
|
-
return { ok: true };
|
|
143045
|
-
} catch (err) {
|
|
143046
|
-
return {
|
|
143047
|
-
ok: false,
|
|
143048
|
-
error: errMsg(err)
|
|
143049
|
-
};
|
|
143050
|
-
}
|
|
143051
|
-
},
|
|
143052
143125
|
publish: async () => null,
|
|
143053
143126
|
subscribe: async () => ({ subscriptionId: "" }),
|
|
143054
143127
|
unsubscribe: async () => void 0,
|
|
@@ -143374,7 +143447,7 @@ function buildAlexaAdoptionProvider(deps) {
|
|
|
143374
143447
|
//#region src/alexa-broker-device-cascade.ts
|
|
143375
143448
|
/**
|
|
143376
143449
|
* Remove every adopted Alexa device whose persisted config carries
|
|
143377
|
-
* `{ system: 'alexa', brokerId }`. Used
|
|
143450
|
+
* `{ system: 'alexa', brokerId }`. Used when an account instance is deleted and when an integration is deleted.
|
|
143378
143451
|
* Alexa appliances are flat (standalone) devices, so this removes top-level
|
|
143379
143452
|
* devices directly. Best-effort per device; returns the count removed. Mirrors
|
|
143380
143453
|
* the Ecowitt cascade helper.
|
|
@@ -143962,6 +144035,30 @@ function withListedVolume(status, listed) {
|
|
|
143962
144035
|
};
|
|
143963
144036
|
}
|
|
143964
144037
|
//#endregion
|
|
144038
|
+
//#region src/speak/constants.ts
|
|
144039
|
+
/** Echo speech limits and the worker restart bound (same window as CrashSupervisor). */
|
|
144040
|
+
var ALEXA_SPEAK_ADDON_ID = "import-alexa";
|
|
144041
|
+
var ALEXA_SPEAK_KIND = "alexa-echo";
|
|
144042
|
+
/** The kind of the Amazon-account broker the Echoes are read from. */
|
|
144043
|
+
var ALEXA_BROKER_KIND = "alexa";
|
|
144044
|
+
var DEVICE_REFRESH_MS = 5 * 6e4;
|
|
144045
|
+
/** Where the addon serves the Alexa mark; `listTargetKinds` stamps it as `iconUrl`. */
|
|
144046
|
+
var ALEXA_SPEAK_ICON_URL = `/addon/${ALEXA_SPEAK_ADDON_ID}/icons/${ALEXA_SPEAK_KIND}`;
|
|
144047
|
+
/** How often an adopted Echo's player state is read (a push channel is a later phase). */
|
|
144048
|
+
var ECHO_POLL_MS = 3e4;
|
|
144049
|
+
/** One more read after a command, once Amazon has had time to apply it. */
|
|
144050
|
+
var ECHO_REREAD_MS = 1500;
|
|
144051
|
+
/** After a 429 the account is left alone for this long, doubling up to the cap. */
|
|
144052
|
+
var ECHO_RATE_BACKOFF_BASE_MS = 3e4;
|
|
144053
|
+
var ECHO_RATE_BACKOFF_MAX_MS = 5 * 6e4;
|
|
144054
|
+
/**
|
|
144055
|
+
* A command (volume, play, Do Not Disturb) that has not started within this, or not
|
|
144056
|
+
* finished, is answered with an error instead of running minutes late.
|
|
144057
|
+
*/
|
|
144058
|
+
var ECHO_COMMAND_DEADLINE_MS = 12e3;
|
|
144059
|
+
/** A poll read is stale after this; it is dropped from the queue rather than sent late. */
|
|
144060
|
+
var ECHO_READ_DEADLINE_MS = 25e3;
|
|
144061
|
+
//#endregion
|
|
143965
144062
|
//#region src/speak/worker-protocol.ts
|
|
143966
144063
|
/** Line-delimited JSON the long-lived Python worker speaks. One object per line. */
|
|
143967
144064
|
var EchoDeviceSchema = object({
|
|
@@ -144338,6 +144435,7 @@ var alexaBrokersEntryKind = defineEntryKind("brokers", {
|
|
|
144338
144435
|
title: "Alexa import brokers",
|
|
144339
144436
|
schema: entryBody({
|
|
144340
144437
|
...alexaConnectionSchema.shape,
|
|
144438
|
+
/** Retired (D1012): the link lives on the integration's `connections` record. Only the migration reads it. */
|
|
144341
144439
|
integrationId: string().optional(),
|
|
144342
144440
|
enabled: boolean().optional()
|
|
144343
144441
|
}),
|
|
@@ -144349,20 +144447,27 @@ var alexaSpeakSessionsEntryKind = defineEntryKind("speak-sessions", {
|
|
|
144349
144447
|
schema: entryBody({ session: string().default("").meta({ secret: true }) }),
|
|
144350
144448
|
writeTrigger: "an Echo worker signs in as an Alexa account broker"
|
|
144351
144449
|
});
|
|
144450
|
+
/** An Alexa integration: the link to the Alexa account (broker) it imports devices from. */
|
|
144451
|
+
var alexaConnectionsEntryKind = declareBrokerLinkKind({
|
|
144452
|
+
title: "Alexa integrations",
|
|
144453
|
+
writeTrigger: "an operator links an Alexa integration to an Alexa account"
|
|
144454
|
+
});
|
|
144352
144455
|
/** Every kind this addon declares, keyed by its name: `records.declare` refuses any other key. */
|
|
144353
144456
|
var alexaRecordKinds = {
|
|
144354
144457
|
brokers: alexaBrokersEntryKind,
|
|
144458
|
+
connections: alexaConnectionsEntryKind,
|
|
144355
144459
|
"speak-sessions": alexaSpeakSessionsEntryKind
|
|
144356
144460
|
};
|
|
144357
144461
|
//#endregion
|
|
144358
|
-
//#region src/alexa-instances.ts
|
|
144462
|
+
//#region src/alexa-account-instances.ts
|
|
144359
144463
|
/**
|
|
144360
|
-
* The Alexa accounts as `integration-instances` (planning#408 P3): one instance per `brokers`
|
|
144361
|
-
* record, over the SAME records and ids (`alexa_NNN`) the `broker` cap serves
|
|
144464
|
+
* The Alexa accounts as `integration-instances` (planning#408 P3, D1013): one instance per `brokers`
|
|
144465
|
+
* record, over the SAME records and ids (`alexa_NNN`) the `broker` cap serves; the `account` kind of
|
|
144466
|
+
* the composed provider (the integrations are the `integration` kind, `alexa-instances.ts`). The body is flat, so
|
|
144362
144467
|
* the form the instances page edits is the stored shape. Signing in (device code or the Amazon
|
|
144363
144468
|
* login proxy) goes through the routes the addon already serves under `/addon/import-alexa/login`.
|
|
144364
144469
|
*/
|
|
144365
|
-
var
|
|
144470
|
+
var ALEXA_ACCOUNT_KIND = "account";
|
|
144366
144471
|
var ALEXA_SIGN_IN_PATH = "/addon/import-alexa/login";
|
|
144367
144472
|
function isAlexaBodyConfigured(body) {
|
|
144368
144473
|
return body.cookie.length > 0 || body.refreshToken.length > 0;
|
|
@@ -144390,21 +144495,13 @@ function statusOf(info, configured) {
|
|
|
144390
144495
|
...info.error !== null ? { detail: info.error } : {}
|
|
144391
144496
|
};
|
|
144392
144497
|
}
|
|
144393
|
-
function
|
|
144498
|
+
function buildAlexaAccountInstances(records, deps) {
|
|
144394
144499
|
return new RecordIntegrationInstances(records, alexaBrokersEntryKind, {
|
|
144395
144500
|
description: {
|
|
144396
|
-
role: "
|
|
144501
|
+
role: "services",
|
|
144397
144502
|
instanceMode: "multiple",
|
|
144398
144503
|
kind: "account",
|
|
144399
144504
|
canTest: true,
|
|
144400
|
-
kinds: [{
|
|
144401
|
-
id: ALEXA_INSTANCE_KIND,
|
|
144402
|
-
label: "Alexa",
|
|
144403
|
-
signIn: {
|
|
144404
|
-
basePath: ALEXA_SIGN_IN_PATH,
|
|
144405
|
-
methods: ["device-code", "proxy"]
|
|
144406
|
-
}
|
|
144407
|
-
}],
|
|
144408
144505
|
allowRestoreId: true
|
|
144409
144506
|
},
|
|
144410
144507
|
formSchema: () => buildConnectionFormSchema(),
|
|
@@ -144417,7 +144514,7 @@ function buildAlexaInstances(records, deps) {
|
|
|
144417
144514
|
pollIntervalMs: 6e4
|
|
144418
144515
|
}),
|
|
144419
144516
|
mintId: () => deps.mintId(),
|
|
144420
|
-
kindOf: () =>
|
|
144517
|
+
kindOf: () => ALEXA_ACCOUNT_KIND,
|
|
144421
144518
|
health: (body, id) => {
|
|
144422
144519
|
const info = deps.infoOf(id);
|
|
144423
144520
|
return {
|
|
@@ -144446,6 +144543,21 @@ function buildAlexaInstances(records, deps) {
|
|
|
144446
144543
|
});
|
|
144447
144544
|
}
|
|
144448
144545
|
//#endregion
|
|
144546
|
+
//#region src/alexa-instances.ts
|
|
144547
|
+
/**
|
|
144548
|
+
* The Alexa integrations as `integration-instances` (D1012): one `connections` record per
|
|
144549
|
+
* integration, keyed by the registry id, whose body is the link to an Alexa account (a `brokers`
|
|
144550
|
+
* record, edited on the Brokers page). The account's sign-in and secrets are never copied here.
|
|
144551
|
+
*/
|
|
144552
|
+
function buildAlexaInstances(records) {
|
|
144553
|
+
return buildBrokerLinkInstances(records, {
|
|
144554
|
+
kind: alexaConnectionsEntryKind,
|
|
144555
|
+
brokerKind: ALEXA_BROKER_KIND,
|
|
144556
|
+
brokerLabel: "Alexa account",
|
|
144557
|
+
onChanged: () => void 0
|
|
144558
|
+
});
|
|
144559
|
+
}
|
|
144560
|
+
//#endregion
|
|
144449
144561
|
//#region src/alexa-broker-store.ts
|
|
144450
144562
|
function rowToEntry(id, data) {
|
|
144451
144563
|
const parsed = AlexaBrokerEntrySchema.safeParse({
|
|
@@ -144458,7 +144570,6 @@ function rowToEntry(id, data) {
|
|
|
144458
144570
|
macDms: data.macDms,
|
|
144459
144571
|
pollIntervalMs: data.pollIntervalMs
|
|
144460
144572
|
},
|
|
144461
|
-
integrationId: data.integrationId,
|
|
144462
144573
|
enabled: data.enabled
|
|
144463
144574
|
});
|
|
144464
144575
|
return parsed.success ? parsed.data : null;
|
|
@@ -144495,11 +144606,10 @@ var AlexaBrokerStore = class AlexaBrokerStore {
|
|
|
144495
144606
|
const nextIds = new Set(entries.map((e) => e.id));
|
|
144496
144607
|
for (const row of page.records) if (!nextIds.has(row.id)) await this.kind.delete(row.id);
|
|
144497
144608
|
for (const entry of entries) {
|
|
144498
|
-
const { id, name, connection,
|
|
144609
|
+
const { id, name, connection, enabled } = entry;
|
|
144499
144610
|
const body = {
|
|
144500
144611
|
name,
|
|
144501
144612
|
...connection,
|
|
144502
|
-
...integrationId === void 0 ? {} : { integrationId },
|
|
144503
144613
|
...enabled === void 0 ? {} : { enabled }
|
|
144504
144614
|
};
|
|
144505
144615
|
const parsed = alexaBrokersEntryKind.schema.safeParse(body);
|
|
@@ -146082,6 +146192,21 @@ async function flattenBrokerRecords(ports) {
|
|
|
146082
146192
|
});
|
|
146083
146193
|
}
|
|
146084
146194
|
//#endregion
|
|
146195
|
+
//#region src/migrations/broker-links-to-connections.ts
|
|
146196
|
+
var BROKER_LINKS_TO_CONNECTIONS_ID = "import-alexa/2026-11-05-broker-links-to-connections";
|
|
146197
|
+
/** The broker records and the integration (`connections`) records of this addon. */
|
|
146198
|
+
function alexaBrokerLinkPorts(records, warn) {
|
|
146199
|
+
return brokerLinkPortsOver({
|
|
146200
|
+
brokers: records.kind({
|
|
146201
|
+
name: alexaBrokersEntryKind.name,
|
|
146202
|
+
declaration: alexaBrokersEntryKind.declaration,
|
|
146203
|
+
schema: record(string(), unknown())
|
|
146204
|
+
}),
|
|
146205
|
+
connections: records.kind(alexaConnectionsEntryKind),
|
|
146206
|
+
warn
|
|
146207
|
+
});
|
|
146208
|
+
}
|
|
146209
|
+
//#endregion
|
|
146085
146210
|
//#region src/migrations/strip-echo-target-volume-and-mode.ts
|
|
146086
146211
|
var RETIRED_KEYS = ["volume", "mode"];
|
|
146087
146212
|
function isRecord(value) {
|
|
@@ -146164,8 +146289,30 @@ var ADDON_MIGRATIONS = [
|
|
|
146164
146289
|
fixtureMark: "the name of the broker and the integration it already names"
|
|
146165
146290
|
},
|
|
146166
146291
|
procedure: moveAlexaIntegrationRows
|
|
146292
|
+
},
|
|
146293
|
+
{
|
|
146294
|
+
meta: {
|
|
146295
|
+
id: BROKER_LINKS_TO_CONNECTIONS_ID,
|
|
146296
|
+
owner: "import-alexa",
|
|
146297
|
+
level: "addon-state",
|
|
146298
|
+
seq: 4,
|
|
146299
|
+
dependsOn: [INTEGRATION_ROWS_TO_RECORD_ID],
|
|
146300
|
+
store: "settings",
|
|
146301
|
+
scope: "cluster",
|
|
146302
|
+
onFailure: "block-owner",
|
|
146303
|
+
bodyKind: "procedure",
|
|
146304
|
+
description: "Write each integration a connections record naming its Alexa account (keyed by the integration id), then clear the integration link on the account; stray registry-keyed account rows are counted and logged, never deleted",
|
|
146305
|
+
fixtureMark: "the name and the extra field of the account that keeps its record"
|
|
146306
|
+
},
|
|
146307
|
+
procedure: moveBrokerLinksToConnections
|
|
146167
146308
|
}
|
|
146168
146309
|
];
|
|
146310
|
+
/** The migrations that rewrite the broker records: they run BEFORE anything reads or persists one. */
|
|
146311
|
+
var BROKER_RECORD_MIGRATION_IDS = [
|
|
146312
|
+
FLATTEN_BROKER_RECORDS_ID,
|
|
146313
|
+
INTEGRATION_ROWS_TO_RECORD_ID,
|
|
146314
|
+
BROKER_LINKS_TO_CONNECTIONS_ID
|
|
146315
|
+
];
|
|
146169
146316
|
//#endregion
|
|
146170
146317
|
//#region src/speak/icon-routes.ts
|
|
146171
146318
|
var ALEXA_ICON_SVG = "<svg role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"#00CAFF\"><title>Amazon Alexa</title><path d=\"M12 0C5.37 0 0 5.37 0 12C0 18.09 4.53 23.11 10.4 23.9V21.5A1.59 1.59 0 0 0 9.32 19.97A8.41 8.41 0 0 1 3.6 11.8A8.37 8.37 0 0 1 12.09 3.6A8.4 8.4 0 0 1 20.4 12.31L20.39 12.38A8.68 8.68 0 0 1 20.36 12.76C20.36 12.83 20.35 12.9 20.34 12.96C20.34 13.04 20.33 13.12 20.32 13.19L20.3 13.29C19.27 20.07 10.45 23.87 10.4 23.9C10.92 23.97 11.46 24 12 24C18.63 24 24 18.63 24 12S18.63 0 12 0Z\"/></svg>";
|
|
@@ -146264,6 +146411,7 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
146264
146411
|
loginProxy = null;
|
|
146265
146412
|
brokerStore = null;
|
|
146266
146413
|
instances = null;
|
|
146414
|
+
accountInstances = null;
|
|
146267
146415
|
activeBrokers = [];
|
|
146268
146416
|
speak = null;
|
|
146269
146417
|
constructor() {
|
|
@@ -146275,12 +146423,13 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
146275
146423
|
if (records === void 0) throw new Error("import-alexa: ctx.records is required (hub-only addon)");
|
|
146276
146424
|
await records.declare(alexaRecordKinds);
|
|
146277
146425
|
this.brokerStore = AlexaBrokerStore.of(records);
|
|
146278
|
-
this.instances = buildAlexaInstances(records
|
|
146426
|
+
this.instances = buildAlexaInstances(records);
|
|
146427
|
+
this.accountInstances = buildAlexaAccountInstances(records, {
|
|
146279
146428
|
mintId: () => this.requireRegistry().allocateId(),
|
|
146280
146429
|
infoOf: (id) => this.registry?.get(id) ?? null,
|
|
146281
146430
|
onChanged: (id) => this.syncBrokerRuntime(id)
|
|
146282
146431
|
});
|
|
146283
|
-
await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, this.migrationPorts(records)), { only:
|
|
146432
|
+
await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, this.migrationPorts(records)), { only: BROKER_RECORD_MIGRATION_IDS });
|
|
146284
146433
|
this.activeBrokers = await this.loadPersistedBrokers();
|
|
146285
146434
|
await this.adoptSessionFiles();
|
|
146286
146435
|
this.registry = new AlexaBrokerRegistry(this.ctx.logger, {
|
|
@@ -146315,15 +146464,13 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
146315
146464
|
});
|
|
146316
146465
|
this.deviceCodeLogin = new AlexaDeviceCodeManager({ logger: this.ctx.logger });
|
|
146317
146466
|
this.ctx.logger.info("Alexa: import addon initialised", { meta: { brokerCount: this.activeBrokers.length } });
|
|
146318
|
-
await this.reconcileIntegrationsToBrokers();
|
|
146319
|
-
this.subscribeIntegrationLifecycle();
|
|
146320
146467
|
const loginRoutes = [...buildAlexaLoginRoutes({
|
|
146321
146468
|
logger: this.ctx.logger,
|
|
146322
146469
|
proxyLogin: this.proxyLogin,
|
|
146323
146470
|
deviceCode: this.deviceCodeLogin,
|
|
146324
146471
|
commitCookie: (input) => this.commitCapturedCookie(input)
|
|
146325
146472
|
}), ...speakIconRoutes()];
|
|
146326
|
-
await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, this.migrationPorts(records)), { only: ADDON_MIGRATIONS.map((m) => m.meta.id).filter((id) => id
|
|
146473
|
+
await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, this.migrationPorts(records)), { only: ADDON_MIGRATIONS.map((m) => m.meta.id).filter((id) => !BROKER_RECORD_MIGRATION_IDS.includes(id)) });
|
|
146327
146474
|
const speak = new AlexaSpeakController({
|
|
146328
146475
|
logger: this.ctx.logger.child("speak"),
|
|
146329
146476
|
readConfig: () => this.config,
|
|
@@ -146355,7 +146502,7 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
146355
146502
|
},
|
|
146356
146503
|
{
|
|
146357
146504
|
capability: integrationInstancesCapability,
|
|
146358
|
-
provider: this.
|
|
146505
|
+
provider: this.buildInstancesProvider()
|
|
146359
146506
|
},
|
|
146360
146507
|
{
|
|
146361
146508
|
capability: deviceAdoptionCapability,
|
|
@@ -146380,6 +146527,7 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
146380
146527
|
await this.updateGlobalSettings({ echoTargets: [...readEchoTargets({ echoTargets: rows })] });
|
|
146381
146528
|
},
|
|
146382
146529
|
...flattenPortsOver(records.kind(alexaBrokersEntryKind)),
|
|
146530
|
+
...alexaBrokerLinkPorts(records, (message) => this.ctx.logger.warn(message)),
|
|
146383
146531
|
...alexaIntegrationRowsPorts({
|
|
146384
146532
|
api: defaultIntegrationApiOver(this.ctx.api),
|
|
146385
146533
|
records,
|
|
@@ -146388,10 +146536,42 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
146388
146536
|
})
|
|
146389
146537
|
};
|
|
146390
146538
|
}
|
|
146539
|
+
/** One provider over the two record kinds: the account (broker) and the integrations using it. */
|
|
146540
|
+
buildInstancesProvider() {
|
|
146541
|
+
if (this.accountInstances === null) throw new Error("import-alexa: account instances not ready");
|
|
146542
|
+
return composeInstanceKinds([{
|
|
146543
|
+
kind: {
|
|
146544
|
+
id: "account",
|
|
146545
|
+
label: "Alexa account",
|
|
146546
|
+
functionCap: "broker",
|
|
146547
|
+
brokerKind: ALEXA_BROKER_KIND,
|
|
146548
|
+
role: "services",
|
|
146549
|
+
signIn: {
|
|
146550
|
+
basePath: ALEXA_SIGN_IN_PATH,
|
|
146551
|
+
methods: ["device-code", "proxy"]
|
|
146552
|
+
}
|
|
146553
|
+
},
|
|
146554
|
+
provider: guardBrokerDelete(this.accountInstances.toProvider(), () => this.brokerUsers())
|
|
146555
|
+
}, {
|
|
146556
|
+
kind: {
|
|
146557
|
+
id: "integration",
|
|
146558
|
+
label: "Alexa",
|
|
146559
|
+
functionCap: "device-provider",
|
|
146560
|
+
role: "devices"
|
|
146561
|
+
},
|
|
146562
|
+
provider: this.requireInstances().toProvider()
|
|
146563
|
+
}], {
|
|
146564
|
+
role: "devices",
|
|
146565
|
+
instanceMode: "multiple",
|
|
146566
|
+
kind: "account",
|
|
146567
|
+
canTest: true,
|
|
146568
|
+
allowRestoreId: true
|
|
146569
|
+
});
|
|
146570
|
+
}
|
|
146391
146571
|
/**
|
|
146392
|
-
* Bring the runtime in line with one record after the instances page changed it: a
|
|
146393
|
-
* record stops its manager and cascades its devices, an off record only stops the
|
|
146394
|
-
* live one re-applies its connection or starts under the record's own id.
|
|
146572
|
+
* Bring the runtime in line with one account record after the instances page changed it: a
|
|
146573
|
+
* removed record stops its manager and cascades its devices, an off record only stops the
|
|
146574
|
+
* manager, a live one re-applies its connection or starts under the record's own id.
|
|
146395
146575
|
*/
|
|
146396
146576
|
async syncBrokerRuntime(id) {
|
|
146397
146577
|
const reg = this.requireRegistry();
|
|
@@ -146412,10 +146592,7 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
146412
146592
|
try {
|
|
146413
146593
|
if (reg.get(id) !== null) {
|
|
146414
146594
|
if (!sameConnection(entry.connection, reg.getConnection(id))) await reg.updateEntry(id, entry.connection);
|
|
146415
|
-
} else await reg.createEntry(entry.name, entry.connection, {
|
|
146416
|
-
id,
|
|
146417
|
-
...entry.integrationId !== void 0 ? { integrationId: entry.integrationId } : {}
|
|
146418
|
-
});
|
|
146595
|
+
} else await reg.createEntry(entry.name, entry.connection, { id });
|
|
146419
146596
|
} catch (err) {
|
|
146420
146597
|
this.ctx.logger.warn("alexa instance: could not start the account", { meta: {
|
|
146421
146598
|
brokerId: id,
|
|
@@ -146634,12 +146811,8 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
146634
146811
|
}
|
|
146635
146812
|
buildBrokerProvider() {
|
|
146636
146813
|
return buildAlexaBrokerProvider({
|
|
146637
|
-
ownerAddonId: this.ctx.id,
|
|
146638
146814
|
registry: this.requireRegistry(),
|
|
146639
|
-
getBrokers: () => this.activeBrokers
|
|
146640
|
-
persistBrokers: (brokers) => this.persistBrokerEntries(brokers),
|
|
146641
|
-
cascadeRemoveDevices: (brokerId) => this.cascadeRemoveDevicesForBroker(brokerId),
|
|
146642
|
-
logger: this.ctx.logger
|
|
146815
|
+
getBrokers: () => this.activeBrokers
|
|
146643
146816
|
});
|
|
146644
146817
|
}
|
|
146645
146818
|
/**
|
|
@@ -146676,6 +146849,14 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
146676
146849
|
} });
|
|
146677
146850
|
return { brokerId: entry.id };
|
|
146678
146851
|
}
|
|
146852
|
+
/** Who still points at an Alexa account: its integrations and the Echo targets that speak through it. */
|
|
146853
|
+
brokerUsers() {
|
|
146854
|
+
return [brokerLinkUserFinder(this.requireInstances()), { find: async (brokerId) => readEchoTargets(this.config).filter((target) => target.brokerId === brokerId).map((target) => ({
|
|
146855
|
+
kind: "notification-target",
|
|
146856
|
+
id: target.id,
|
|
146857
|
+
name: target.name
|
|
146858
|
+
})) }];
|
|
146859
|
+
}
|
|
146679
146860
|
async cascadeRemoveDevicesForBroker(brokerId) {
|
|
146680
146861
|
const reg = this.ctx.kernel.deviceRegistry;
|
|
146681
146862
|
const devices = this.ctx.kernel.devices;
|
|
@@ -146688,42 +146869,6 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
146688
146869
|
logger: this.ctx.logger
|
|
146689
146870
|
});
|
|
146690
146871
|
}
|
|
146691
|
-
async reconcileIntegrationsToBrokers() {
|
|
146692
|
-
try {
|
|
146693
|
-
const mine = (await this.ctx.api.integrations.list.query()).filter((i) => i.addonId === this.ctx.id);
|
|
146694
|
-
const surviving = new Set(mine.map((i) => i.id));
|
|
146695
|
-
for (const broker of this.activeBrokers) {
|
|
146696
|
-
const integrationId = broker.integrationId;
|
|
146697
|
-
if (integrationId === void 0 || !surviving.has(integrationId)) continue;
|
|
146698
|
-
this.requireRegistry().linkIntegration(integrationId, broker.id);
|
|
146699
|
-
}
|
|
146700
|
-
const toRemove = this.activeBrokers.filter((b) => b.integrationId !== void 0 && !surviving.has(b.integrationId)).map((b) => b.id);
|
|
146701
|
-
if (toRemove.length === 0) return;
|
|
146702
|
-
for (const id of toRemove) try {
|
|
146703
|
-
await this.requireRegistry().removeEntry(id);
|
|
146704
|
-
await this.cascadeRemoveDevicesForBroker(id);
|
|
146705
|
-
} catch (err) {
|
|
146706
|
-
this.ctx.logger.warn("Alexa integration→broker: broker cleanup failed", { meta: {
|
|
146707
|
-
brokerId: id,
|
|
146708
|
-
error: errMsg(err)
|
|
146709
|
-
} });
|
|
146710
|
-
}
|
|
146711
|
-
const nextBrokers = this.activeBrokers.filter((b) => !toRemove.includes(b.id));
|
|
146712
|
-
await this.persistBrokerEntries(nextBrokers);
|
|
146713
|
-
} catch (err) {
|
|
146714
|
-
this.ctx.logger.warn("Alexa integration→broker reconcile failed", { meta: { error: errMsg(err) } });
|
|
146715
|
-
}
|
|
146716
|
-
}
|
|
146717
|
-
subscribeIntegrationLifecycle() {
|
|
146718
|
-
const handler = (event) => {
|
|
146719
|
-
const addonId = event.data["addonId"];
|
|
146720
|
-
if (typeof addonId === "string" && addonId !== this.ctx.id) return;
|
|
146721
|
-
this.reconcileIntegrationsToBrokers();
|
|
146722
|
-
};
|
|
146723
|
-
this.ctx.eventBus.subscribe({ category: EventCategory.IntegrationEnabled }, handler);
|
|
146724
|
-
this.ctx.eventBus.subscribe({ category: EventCategory.IntegrationDisabled }, handler);
|
|
146725
|
-
this.ctx.eventBus.subscribe({ category: EventCategory.IntegrationDeleted }, handler);
|
|
146726
|
-
}
|
|
146727
146872
|
buildAdoptionProvider() {
|
|
146728
146873
|
return buildAlexaAdoptionProvider({
|
|
146729
146874
|
registry: {
|
|
@@ -146737,9 +146882,9 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
146737
146882
|
},
|
|
146738
146883
|
logger: this.ctx.logger,
|
|
146739
146884
|
getBrokerIdForIntegration: async (id) => {
|
|
146740
|
-
const
|
|
146741
|
-
if (
|
|
146742
|
-
return
|
|
146885
|
+
const brokerId = await brokerIdOf(this.requireInstances(), id);
|
|
146886
|
+
if (brokerId === void 0) throw new Error(`integration ${id} has no broker`);
|
|
146887
|
+
return brokerId;
|
|
146743
146888
|
},
|
|
146744
146889
|
listAdoptedAlexa: async () => {
|
|
146745
146890
|
const reg = this.ctx.kernel.deviceRegistry;
|