@camstack/addon-import-alexa 0.2.189 → 0.2.191
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 +14 -0
- package/dist/addon.js +1078 -832
- package/dist/addon.mjs +1078 -832
- package/package.json +3 -3
package/dist/addon.js
CHANGED
|
@@ -6044,7 +6044,7 @@ function number(params) {
|
|
|
6044
6044
|
return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
|
|
6045
6045
|
}
|
|
6046
6046
|
//#endregion
|
|
6047
|
-
//#region ../types/dist/sleep-
|
|
6047
|
+
//#region ../types/dist/sleep-BX_MqzUe.mjs
|
|
6048
6048
|
/**
|
|
6049
6049
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
6050
6050
|
* window to float samples (D455).
|
|
@@ -7673,6 +7673,28 @@ var SECRET_RETRY_MAX_MS = 5 * 6e4;
|
|
|
7673
7673
|
/** Coalesces a burst of `keychain.changed` events into one re-resolve. */
|
|
7674
7674
|
var SECRET_CHANGE_DEBOUNCE_MS = 250;
|
|
7675
7675
|
/**
|
|
7676
|
+
* Pair a catalogue with the function that builds its ports FROM THE MIGRATION API. The one
|
|
7677
|
+
* place a catalogue is bound: a migration reaches nothing a ports binder cannot reach
|
|
7678
|
+
* through `api` (guard `check-migration-ports-surface`).
|
|
7679
|
+
*/
|
|
7680
|
+
function migrationSet(catalogue, bindPorts) {
|
|
7681
|
+
return { bind: (api) => defineAddonMigrations(catalogue, bindPorts(api)) };
|
|
7682
|
+
}
|
|
7683
|
+
/** A mixed array is a mistake: the other kind would be dropped without a word. */
|
|
7684
|
+
function refuseMixed(form, kept, dropped) {
|
|
7685
|
+
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`);
|
|
7686
|
+
}
|
|
7687
|
+
function catalogueOf(list) {
|
|
7688
|
+
const kept = list.filter((entry) => !("execute" in entry));
|
|
7689
|
+
refuseMixed("catalogue, (api) => ports", kept.length, list.length - kept.length);
|
|
7690
|
+
return kept;
|
|
7691
|
+
}
|
|
7692
|
+
function boundOf(list) {
|
|
7693
|
+
const kept = list.filter((entry) => "execute" in entry);
|
|
7694
|
+
refuseMixed("bound migrations", kept.length, list.length - kept.length);
|
|
7695
|
+
return kept;
|
|
7696
|
+
}
|
|
7697
|
+
/**
|
|
7676
7698
|
* Base class for CamStack addons. Eliminates settings boilerplate:
|
|
7677
7699
|
*
|
|
7678
7700
|
* - Typed `config` property with automatic resolution from store + defaults
|
|
@@ -7839,23 +7861,21 @@ var BaseAddon = class {
|
|
|
7839
7861
|
await this.loadScopedSchemas();
|
|
7840
7862
|
return normalized;
|
|
7841
7863
|
}
|
|
7864
|
+
async runMigrations(first, second, third) {
|
|
7865
|
+
if (typeof second === "function") return this.runMigrationSets([migrationSet(catalogueOf(first), second)], third);
|
|
7866
|
+
return this.runBound(boundOf(first), second);
|
|
7867
|
+
}
|
|
7842
7868
|
/**
|
|
7843
|
-
*
|
|
7844
|
-
*
|
|
7845
|
-
*
|
|
7846
|
-
* restored devices — with the catalogue bound to the addon's ports
|
|
7847
|
-
* (`defineAddonMigrations(ADDON_MIGRATIONS, ports)`).
|
|
7848
|
-
*
|
|
7849
|
-
* An engine that cannot be reached or refuses the catalogue is logged and runs
|
|
7850
|
-
* nothing; the boot continues. A migration that FAILED (or was blocked by one)
|
|
7851
|
-
* and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
|
|
7852
|
-
* serve data whose shape it cannot trust; `tolerate` migrations never do.
|
|
7853
|
-
*
|
|
7854
|
-
* ALWAYS pass the whole catalogue; a phase that must run only some of it passes
|
|
7855
|
-
* `{ only: [ids] }` — never a filtered catalogue (the engine would judge the
|
|
7856
|
-
* omitted applied ids "newer").
|
|
7869
|
+
* Several catalogues of ONE owner in one declaration (the engine replaces an owner's
|
|
7870
|
+
* catalogue on every declare, so two calls would each judge the other's ids "newer").
|
|
7871
|
+
* Build each entry with `migrationSet(catalogue, (api) => ports)`.
|
|
7857
7872
|
*/
|
|
7858
|
-
async
|
|
7873
|
+
async runMigrationSets(sets, options) {
|
|
7874
|
+
const api = this.ctx.migrationApi;
|
|
7875
|
+
if (api === void 0) throw new Error(`${this.ctx.id}: the migration API is not available on this context`);
|
|
7876
|
+
return this.runBound(sets.flatMap((set) => set.bind(api)), options);
|
|
7877
|
+
}
|
|
7878
|
+
async runBound(migrations, options) {
|
|
7859
7879
|
const api = this.ctx.api.migrations;
|
|
7860
7880
|
const report = await runAddonMigrations({
|
|
7861
7881
|
declareOwner: (input) => api.declareOwner.mutate(input),
|
|
@@ -13557,6 +13577,39 @@ function buildCapturingReply(envelope) {
|
|
|
13557
13577
|
return wrapper;
|
|
13558
13578
|
}
|
|
13559
13579
|
/**
|
|
13580
|
+
* The link primitive's declaration half (D1013 rule 5): what a link field names and how a kind
|
|
13581
|
+
* records it. Split from `instance-link.ts` so `RecordIntegrationInstances` can read the links of
|
|
13582
|
+
* its kind without importing the builders that import it.
|
|
13583
|
+
*/
|
|
13584
|
+
/** The body field of a link: the id of the instance it names; the target rides in the field meta. */
|
|
13585
|
+
function linkField(target) {
|
|
13586
|
+
return string().meta({ link: target });
|
|
13587
|
+
}
|
|
13588
|
+
function linkTargetOf(meta) {
|
|
13589
|
+
if (typeof meta !== "object" || meta === null) return void 0;
|
|
13590
|
+
const link = Reflect.get(meta, "link");
|
|
13591
|
+
const parsed = LinkTargetSchema.safeParse(link);
|
|
13592
|
+
return parsed.success ? parsed.data : void 0;
|
|
13593
|
+
}
|
|
13594
|
+
var LinkTargetSchema = object({
|
|
13595
|
+
cap: string().min(1),
|
|
13596
|
+
kind: string().min(1).optional()
|
|
13597
|
+
});
|
|
13598
|
+
/** The links a kind declares, read from the meta of its body fields. */
|
|
13599
|
+
function linksOfKind(kind) {
|
|
13600
|
+
const schema = kind.schema;
|
|
13601
|
+
if (!(schema instanceof ZodObject)) return [];
|
|
13602
|
+
const links = [];
|
|
13603
|
+
for (const [field, fieldSchema] of Object.entries(schema.shape)) {
|
|
13604
|
+
const to = linkTargetOf(globalRegistry.get(fieldSchema));
|
|
13605
|
+
if (to !== void 0) links.push({
|
|
13606
|
+
field,
|
|
13607
|
+
to
|
|
13608
|
+
});
|
|
13609
|
+
}
|
|
13610
|
+
return links;
|
|
13611
|
+
}
|
|
13612
|
+
/**
|
|
13560
13613
|
* `RecordIntegrationInstances` - the `integration-instances` provider over ONE
|
|
13561
13614
|
* entry kind, so an addon that holds a connection serves it in ~30 lines.
|
|
13562
13615
|
*
|
|
@@ -13677,30 +13730,29 @@ var RecordIntegrationInstances = class {
|
|
|
13677
13730
|
...health?.lastCheckedAt !== void 0 ? { lastCheckedAt: health.lastCheckedAt } : {}
|
|
13678
13731
|
};
|
|
13679
13732
|
}
|
|
13680
|
-
describe = async () =>
|
|
13681
|
-
|
|
13682
|
-
|
|
13733
|
+
describe = async () => {
|
|
13734
|
+
const declared = linksOfKind(this.kind);
|
|
13735
|
+
const links = this.hooks.description.links !== void 0 || declared.length === 0 ? {} : { links: declared.map((link) => ({
|
|
13736
|
+
kind: this.kind.name,
|
|
13737
|
+
field: link.field,
|
|
13738
|
+
to: link.to
|
|
13739
|
+
})) };
|
|
13740
|
+
return {
|
|
13741
|
+
...this.hooks.description,
|
|
13742
|
+
...links,
|
|
13743
|
+
...this.hooks.mintId === void 0 ? {} : { mintsId: true }
|
|
13744
|
+
};
|
|
13683
13745
|
};
|
|
13684
|
-
|
|
13685
|
-
* The record an id names. A caller holding the `integrations` registry id (the Integrations
|
|
13686
|
-
* page) reaches a kind whose body links to it through `integrationId`; an id that is itself a
|
|
13687
|
-
* record id answers as it is. An id that names nothing comes back unchanged.
|
|
13688
|
-
*/
|
|
13689
|
-
async resolveRecordId(id) {
|
|
13690
|
-
if (await this.handle.get(id) !== null) return id;
|
|
13691
|
-
const linked = (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.find((record) => record.data["integrationId"] === id);
|
|
13692
|
-
return linked === void 0 ? id : linked.id;
|
|
13693
|
-
}
|
|
13694
|
-
instanceFormSchema = async ({ id }) => this.hooks.formSchema(id === null ? null : await this.resolveRecordId(id));
|
|
13746
|
+
instanceFormSchema = async ({ id }) => this.hooks.formSchema(id);
|
|
13695
13747
|
listInstances = async () => {
|
|
13696
13748
|
return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
|
|
13697
13749
|
};
|
|
13698
13750
|
getInstance = async ({ id }) => {
|
|
13699
|
-
const record = await this.handle.get(
|
|
13751
|
+
const record = await this.handle.get(id);
|
|
13700
13752
|
return record === null ? null : this.viewOf(record);
|
|
13701
13753
|
};
|
|
13702
13754
|
/** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
|
|
13703
|
-
createInstance = async ({ id, name, kind }) => {
|
|
13755
|
+
createInstance = async ({ id, name, kind, values }) => {
|
|
13704
13756
|
const minted = id ?? this.hooks.mintId?.({
|
|
13705
13757
|
name,
|
|
13706
13758
|
...kind !== void 0 ? { kind } : {}
|
|
@@ -13710,11 +13762,20 @@ var RecordIntegrationInstances = class {
|
|
|
13710
13762
|
id: minted,
|
|
13711
13763
|
data: this.hooks.initialBody(name, kind)
|
|
13712
13764
|
}, { ifRev: 0 });
|
|
13765
|
+
const patch = this.patchOf(values ?? {});
|
|
13766
|
+
if (Object.keys(patch).length > 0) await this.handle.patch(minted, patch);
|
|
13713
13767
|
await this.hooks.onChanged?.(minted, "created");
|
|
13714
13768
|
return { id: minted };
|
|
13715
13769
|
};
|
|
13716
|
-
updateInstance = async ({ id
|
|
13717
|
-
const
|
|
13770
|
+
updateInstance = async ({ id, name, values }) => {
|
|
13771
|
+
const patch = this.patchOf(values);
|
|
13772
|
+
if (name !== void 0) patch[this.nameField] = name;
|
|
13773
|
+
await this.handle.patch(id, patch);
|
|
13774
|
+
await this.hooks.onChanged?.(id, "updated");
|
|
13775
|
+
return { ok: true };
|
|
13776
|
+
};
|
|
13777
|
+
/** The record patch a form `values` object means: secrets kept, cleared or replaced. */
|
|
13778
|
+
patchOf(values) {
|
|
13718
13779
|
const patch = {};
|
|
13719
13780
|
for (const [key, value] of Object.entries(values)) {
|
|
13720
13781
|
if (value === void 0) continue;
|
|
@@ -13725,11 +13786,8 @@ var RecordIntegrationInstances = class {
|
|
|
13725
13786
|
else if (isSecretClear(value)) patch[key] = null;
|
|
13726
13787
|
else if (typeof value === "string" && value !== "" && value !== "__camstack_redacted__" && !isSecretFieldView(value)) patch[key] = value;
|
|
13727
13788
|
}
|
|
13728
|
-
|
|
13729
|
-
|
|
13730
|
-
await this.hooks.onChanged?.(id, "updated");
|
|
13731
|
-
return { ok: true };
|
|
13732
|
-
};
|
|
13789
|
+
return patch;
|
|
13790
|
+
}
|
|
13733
13791
|
/** The switch lives in the record body; the addon is told so it applies it (D996). */
|
|
13734
13792
|
setInstanceEnabled = async ({ id, enabled }) => {
|
|
13735
13793
|
if (this.registryOwnsEnabled) throw new Error(`instance "${id}": the integrations registry owns the on/off switch of this addon; set it with integrations.update`);
|
|
@@ -13749,10 +13807,7 @@ var RecordIntegrationInstances = class {
|
|
|
13749
13807
|
status: "unsupported",
|
|
13750
13808
|
error: "This integration declares no connection test"
|
|
13751
13809
|
};
|
|
13752
|
-
return this.hooks.test(input
|
|
13753
|
-
...input,
|
|
13754
|
-
id: await this.resolveRecordId(input.id)
|
|
13755
|
-
});
|
|
13810
|
+
return this.hooks.test(input);
|
|
13756
13811
|
};
|
|
13757
13812
|
/** The flat provider object to hand to `registerProvider`. */
|
|
13758
13813
|
toProvider() {
|
|
@@ -13769,6 +13824,80 @@ var RecordIntegrationInstances = class {
|
|
|
13769
13824
|
};
|
|
13770
13825
|
}
|
|
13771
13826
|
};
|
|
13827
|
+
/** No part holds the id a call named. */
|
|
13828
|
+
var UnknownInstanceError = class extends Error {
|
|
13829
|
+
instanceId;
|
|
13830
|
+
constructor(instanceId) {
|
|
13831
|
+
super(`Unknown instance '${instanceId}'`);
|
|
13832
|
+
this.instanceId = instanceId;
|
|
13833
|
+
this.name = "UnknownInstanceError";
|
|
13834
|
+
}
|
|
13835
|
+
};
|
|
13836
|
+
/** A create or a form named no kind, or a kind no part serves. */
|
|
13837
|
+
var UnknownInstanceKindError = class extends Error {
|
|
13838
|
+
kindId;
|
|
13839
|
+
constructor(kindId) {
|
|
13840
|
+
super(kindId === void 0 ? "This provider has several kinds: name one" : `Unknown instance kind '${kindId}'`);
|
|
13841
|
+
this.kindId = kindId;
|
|
13842
|
+
this.name = "UnknownInstanceKindError";
|
|
13843
|
+
}
|
|
13844
|
+
};
|
|
13845
|
+
function composeInstanceKinds(parts, base) {
|
|
13846
|
+
const byKind = (kindId) => {
|
|
13847
|
+
if (kindId === void 0) {
|
|
13848
|
+
const only = parts.length === 1 ? parts[0] : void 0;
|
|
13849
|
+
if (only === void 0) throw new UnknownInstanceKindError(void 0);
|
|
13850
|
+
return only;
|
|
13851
|
+
}
|
|
13852
|
+
const part = parts.find((candidate) => candidate.kind.id === kindId);
|
|
13853
|
+
if (part === void 0) throw new UnknownInstanceKindError(kindId);
|
|
13854
|
+
return part;
|
|
13855
|
+
};
|
|
13856
|
+
const holderOf = async (id) => {
|
|
13857
|
+
for (const part of parts) if (await part.provider.getInstance({ id }) !== null) return part;
|
|
13858
|
+
throw new UnknownInstanceError(id);
|
|
13859
|
+
};
|
|
13860
|
+
const tagged = (part, instance) => ({
|
|
13861
|
+
...instance,
|
|
13862
|
+
kind: part.kind.id
|
|
13863
|
+
});
|
|
13864
|
+
return {
|
|
13865
|
+
describe: async () => {
|
|
13866
|
+
const links = (await Promise.all(parts.map(async (part) => (await part.provider.describe()).links ?? []))).flat();
|
|
13867
|
+
return {
|
|
13868
|
+
...base,
|
|
13869
|
+
kinds: parts.map((part) => part.kind),
|
|
13870
|
+
...links.length > 0 ? { links } : {}
|
|
13871
|
+
};
|
|
13872
|
+
},
|
|
13873
|
+
instanceFormSchema: async ({ id, kind }) => {
|
|
13874
|
+
return (id === null ? byKind(kind) : await holderOf(id)).provider.instanceFormSchema({ id });
|
|
13875
|
+
},
|
|
13876
|
+
listInstances: async () => {
|
|
13877
|
+
return (await Promise.all(parts.map(async (part) => (await part.provider.listInstances()).map((instance) => tagged(part, instance))))).flat();
|
|
13878
|
+
},
|
|
13879
|
+
getInstance: async ({ id }) => {
|
|
13880
|
+
for (const part of parts) {
|
|
13881
|
+
const found = await part.provider.getInstance({ id });
|
|
13882
|
+
if (found !== null) return tagged(part, found);
|
|
13883
|
+
}
|
|
13884
|
+
return null;
|
|
13885
|
+
},
|
|
13886
|
+
createInstance: async (input) => byKind(input.kind).provider.createInstance(input),
|
|
13887
|
+
updateInstance: async (input) => (await holderOf(input.id)).provider.updateInstance(input),
|
|
13888
|
+
setInstanceEnabled: async (input) => (await holderOf(input.id)).provider.setInstanceEnabled(input),
|
|
13889
|
+
deleteInstance: async (input) => (await holderOf(input.id)).provider.deleteInstance(input),
|
|
13890
|
+
testInstance: async (input) => {
|
|
13891
|
+
const test = (input.id === null ? byKind(input.kind) : await holderOf(input.id)).provider.testInstance;
|
|
13892
|
+
if (test === void 0) return {
|
|
13893
|
+
success: false,
|
|
13894
|
+
status: "inconclusive",
|
|
13895
|
+
error: "This kind cannot be tested"
|
|
13896
|
+
};
|
|
13897
|
+
return test(input);
|
|
13898
|
+
}
|
|
13899
|
+
};
|
|
13900
|
+
}
|
|
13772
13901
|
var EncodeProfileSchema = object({
|
|
13773
13902
|
video: object({
|
|
13774
13903
|
codec: _enum([
|
|
@@ -16049,6 +16178,39 @@ var UnstampedEventMediaCountSchema = object({
|
|
|
16049
16178
|
/** Sum across both, or `null` when either lane could not be counted. */
|
|
16050
16179
|
total: number$1().int().nonnegative().nullable()
|
|
16051
16180
|
}).nullable();
|
|
16181
|
+
/**
|
|
16182
|
+
* Where the stored event media sits, per storage location: how many media rows
|
|
16183
|
+
* and how many bytes carry each location id, how many carry none (legacy,
|
|
16184
|
+
* resolved to the default `eventMedia` location), and how many name a location
|
|
16185
|
+
* that was not asked about (a deleted or never-listed one).
|
|
16186
|
+
*
|
|
16187
|
+
* One covering-index aggregate per asked location plus the NULL one, and one
|
|
16188
|
+
* unfiltered total that scans the covering index: O(rows), not a walk of the
|
|
16189
|
+
* rows themselves. `unlisted` is total minus the ASKED locations minus NULL, so
|
|
16190
|
+
* it is a deleted location only when every location id was asked.
|
|
16191
|
+
* `null` is "could not be measured", never zero.
|
|
16192
|
+
*/
|
|
16193
|
+
var MediaLocationCensusInputSchema = object({ locationIds: array(string().min(1)).max(50).readonly() });
|
|
16194
|
+
var MediaLocationCensusSchema = object({
|
|
16195
|
+
locations: array(object({
|
|
16196
|
+
locationId: string(),
|
|
16197
|
+
rows: number$1().int().nonnegative(),
|
|
16198
|
+
bytes: number$1().nonnegative()
|
|
16199
|
+
})).readonly(),
|
|
16200
|
+
unstamped: object({
|
|
16201
|
+
rows: number$1().int().nonnegative(),
|
|
16202
|
+
bytes: number$1().nonnegative()
|
|
16203
|
+
}),
|
|
16204
|
+
/** Rows stamped with a location id that was not in the asked list. */
|
|
16205
|
+
unlisted: object({
|
|
16206
|
+
rows: number$1().int().nonnegative(),
|
|
16207
|
+
bytes: number$1().nonnegative()
|
|
16208
|
+
}),
|
|
16209
|
+
total: object({
|
|
16210
|
+
rows: number$1().int().nonnegative(),
|
|
16211
|
+
bytes: number$1().nonnegative()
|
|
16212
|
+
})
|
|
16213
|
+
});
|
|
16052
16214
|
var StorageMigrationMediaMoveInputSchema = RelocateMediaInputSchema.extend({ leaseId: string().min(1) });
|
|
16053
16215
|
/** The independently selectable logical storage classes — every class
|
|
16054
16216
|
* `storage.listLocationDeclarations` reports, so an operator never meets a
|
|
@@ -47126,52 +47288,7 @@ onBrightnessChanged: { data: object({
|
|
|
47126
47288
|
minIntervalMs: 0
|
|
47127
47289
|
} } }
|
|
47128
47290
|
};
|
|
47129
|
-
|
|
47130
|
-
* `broker` — unified pub/sub broker registry, system-scoped collection.
|
|
47131
|
-
*
|
|
47132
|
-
* The cap models any kind-tagged message broker the user wants to
|
|
47133
|
-
* register and that other addons might consume. The first two kinds
|
|
47134
|
-
* are MQTT (mosquitto / aedes embedded / cloud bridge) and
|
|
47135
|
-
* Home Assistant (HA WebSocket — subscribe_entities + call_service).
|
|
47136
|
-
* Future kinds (Zigbee2MQTT bridge, ZHA, KNX, Telegram, …) plug in
|
|
47137
|
-
* the same surface.
|
|
47138
|
-
*
|
|
47139
|
-
* Why one cap, not one cap per kind:
|
|
47140
|
-
* - The integrations page wants a single table of "every broker"
|
|
47141
|
-
* across kinds; a unified cap drives it without join logic.
|
|
47142
|
-
* - Generic admin operations (add / remove / test / status) are
|
|
47143
|
-
* identical across kinds — duplicating them per cap was busywork.
|
|
47144
|
-
* - The `network-access` cap precedent: same interface, many
|
|
47145
|
-
* `providerKind: 'ingress'` implementations (Tailscale, ngrok, …).
|
|
47146
|
-
*
|
|
47147
|
-
* What stays kind-specific:
|
|
47148
|
-
* - `getBrokerConfig` payload (kind decides whether it returns
|
|
47149
|
-
* mqtt URL+creds, HA baseUrl+token, …) — typed as
|
|
47150
|
-
* `Record<string, unknown>` at the wire; consumers narrow.
|
|
47151
|
-
* - `publish` / `subscribe` / `getState` arguments (target / filter /
|
|
47152
|
-
* key) — also typed as `Record<string, unknown>` so each kind can
|
|
47153
|
-
* evolve its surface (MQTT topic+qos vs HA entity_id+domain)
|
|
47154
|
-
* without rebreaking the cap signature.
|
|
47155
|
-
* - `add` `settings` payload (different fields per kind, validated by
|
|
47156
|
-
* the kind-specific provider).
|
|
47157
|
-
*
|
|
47158
|
-
* Bidirectionality:
|
|
47159
|
-
* - `publish` is RPC — synchronous result (provider-defined).
|
|
47160
|
-
* - `subscribe` returns a subscription handle; the actual message
|
|
47161
|
-
* stream flows over the typed event-bus as `broker.message` events
|
|
47162
|
-
* keyed by `(brokerId, subscriptionId)`. Consumers filter in their
|
|
47163
|
-
* event handler. Subscriptions persist across reconnects — the
|
|
47164
|
-
* provider re-subscribes upstream after a transport drop.
|
|
47165
|
-
*
|
|
47166
|
-
* Why event-bus push (not a subscription RPC stream):
|
|
47167
|
-
* - Matches the rest of CamStack's D8 contract — events for telemetry,
|
|
47168
|
-
* RPC for loss-is-a-bug. Broker messages are telemetry (a single
|
|
47169
|
-
* drop is recoverable via `getState({key})`).
|
|
47170
|
-
* - Frees `broker.subscribe` from holding a long-lived RPC channel.
|
|
47171
|
-
* - Cross-process: events already route through `$event-bus`; no
|
|
47172
|
-
* per-broker wire-shaping needed.
|
|
47173
|
-
*/
|
|
47174
|
-
var BrokerStatusEnum = _enum([
|
|
47291
|
+
_enum([
|
|
47175
47292
|
"connected",
|
|
47176
47293
|
"disconnected",
|
|
47177
47294
|
"connecting",
|
|
@@ -47179,141 +47296,12 @@ var BrokerStatusEnum = _enum([
|
|
|
47179
47296
|
"unreachable",
|
|
47180
47297
|
"error"
|
|
47181
47298
|
]);
|
|
47182
|
-
var BrokerInfoSchema = object({
|
|
47183
|
-
/** Stable broker id. Persisted; survives addon restarts. */
|
|
47184
|
-
id: string(),
|
|
47185
|
-
/** Addon id of the provider that OWNS this broker.
|
|
47186
|
-
*
|
|
47187
|
-
* The `broker` cap is a system-scoped collection: several addons
|
|
47188
|
-
* register a `broker` provider, each owning a DISJOINT set of
|
|
47189
|
-
* brokers (mqtt-broker owns `mqtt_*`, provider-homeassistant owns
|
|
47190
|
-
* `ha_*`). A broker therefore belongs to exactly one addon — like a
|
|
47191
|
-
* device belongs to one integration. The admin UI threads this id
|
|
47192
|
-
* back as the `{ addonId }` system-collection selector on every
|
|
47193
|
-
* id-keyed call (`get` / `getSettings` / `setSettings` / `remove` /
|
|
47194
|
-
* `testConnection`), so the call routes to the OWNING provider
|
|
47195
|
-
* instead of defaulting to the first-registered one. */
|
|
47196
|
-
addonId: string(),
|
|
47197
|
-
/** Human-readable name (operator-chosen at add-time). */
|
|
47198
|
-
name: string(),
|
|
47199
|
-
/** Provider-defined kind tag — `mqtt` / `home-assistant` / future. */
|
|
47200
|
-
kind: string(),
|
|
47201
|
-
status: BrokerStatusEnum,
|
|
47202
|
-
/** Free-form provider-specific info (HA version, MQTT broker
|
|
47203
|
-
* flavour, latency, mTLS active, …). */
|
|
47204
|
-
info: record(string(), unknown()),
|
|
47205
|
-
/** Ms epoch of the last connection probe / status update. */
|
|
47206
|
-
lastCheckedAt: number$1().nullable(),
|
|
47207
|
-
/** Last error message, when `status` indicates failure. */
|
|
47208
|
-
error: string().nullable()
|
|
47209
|
-
});
|
|
47210
47299
|
var RegistryStatusSchema = object({
|
|
47211
47300
|
brokerCount: number$1().int().nonnegative(),
|
|
47212
47301
|
connectedCount: number$1().int().nonnegative()
|
|
47213
47302
|
});
|
|
47214
|
-
/**
|
|
47215
|
-
* One entry per `broker` provider — which addon provides which broker
|
|
47216
|
-
* kind(s). The unified create picker reads this to know, for each
|
|
47217
|
-
* (addonId, kind) pair the operator can pick, where to route the
|
|
47218
|
-
* follow-up `getSettingsSchema` / `testSettings` / `add` calls (via the
|
|
47219
|
-
* `{ addonId }` selector). A provider only advertises a kind here when
|
|
47220
|
-
* it implements `getSettingsSchema` for that kind (so the picker never
|
|
47221
|
-
* offers a kind whose creation form can't be rendered).
|
|
47222
|
-
*/
|
|
47223
|
-
var BrokerProviderInfoSchema = object({
|
|
47224
|
-
/** Addon id of the `broker` provider this entry describes. */
|
|
47225
|
-
addonId: string(),
|
|
47226
|
-
/** Broker kinds this provider can create, with a display label. */
|
|
47227
|
-
kinds: array(object({
|
|
47228
|
-
kind: string(),
|
|
47229
|
-
label: string(),
|
|
47230
|
-
/**
|
|
47231
|
-
* Relative hub path to start an interactive account link (OAuth popup).
|
|
47232
|
-
* May include `{{brokerId}}`; admin-ui substitutes and appends `?popup=1`.
|
|
47233
|
-
*/
|
|
47234
|
-
interactiveConnectPath: string().min(1).optional(),
|
|
47235
|
-
/** Relative hub path to disconnect the linked account (same popup pattern). */
|
|
47236
|
-
interactiveDisconnectPath: string().min(1).optional(),
|
|
47237
|
-
/**
|
|
47238
|
-
* The kind signs in through a server-held interactive session instead of
|
|
47239
|
-
* (or before) typed credentials. `basePath` is a hub-relative route set
|
|
47240
|
-
* served by the owning addon (`/addon/<id>/...`) speaking the sign-in
|
|
47241
|
-
* contract: `POST {basePath}/start` (body: the form's settings) answers
|
|
47242
|
-
* `{ sessionId, loginUrl }`; `GET {basePath}/status?sessionId=` answers
|
|
47243
|
-
* `{ phase: 'starting' | 'awaiting-login' | 'captured' | 'error' |
|
|
47244
|
-
* 'cancelled', loginUrl, error }`; `POST {basePath}/commit`
|
|
47245
|
-
* (`{ sessionId, name, brokerId? }`) stores the credentials on a new or
|
|
47246
|
-
* existing broker and answers `{ brokerId }`; `POST {basePath}/cancel`
|
|
47247
|
-
* (`{ sessionId }`) drops the session. The dialog that edits a broker
|
|
47248
|
-
* offers it as the default path; the fields the session fills are
|
|
47249
|
-
* marked `filledBySignIn` in the kind's settings schema.
|
|
47250
|
-
*
|
|
47251
|
-
* `methods` lists the ways the session can sign in, recommended first:
|
|
47252
|
-
* `'device-code'` adds `POST {basePath}/code/start` (body: the form's
|
|
47253
|
-
* settings) answering `{ sessionId, userCode, verificationUrl,
|
|
47254
|
-
* expiresAt }` — the operator types `userCode` at `verificationUrl` on
|
|
47255
|
-
* any device, and the same `status` / `commit` / `cancel` routes follow
|
|
47256
|
-
* (status then carries `method`, `userCode`, `verificationUrl`,
|
|
47257
|
-
* `expiresAt` and the phases `'awaiting-code'` / `'expired'`);
|
|
47258
|
-
* `'proxy'` is the `{basePath}/start` flow above. Absent means
|
|
47259
|
-
* `['proxy']`.
|
|
47260
|
-
*/
|
|
47261
|
-
signIn: object({
|
|
47262
|
-
basePath: string().min(1),
|
|
47263
|
-
methods: array(_enum(["device-code", "proxy"])).optional()
|
|
47264
|
-
}).optional()
|
|
47265
|
-
}))
|
|
47266
|
-
});
|
|
47267
|
-
var ListInputSchema = object({
|
|
47268
|
-
/** Optional kind filter — `list({kind:'home-assistant'})` returns
|
|
47269
|
-
* only HA brokers. Omit to list every broker. */
|
|
47270
|
-
kind: string().optional() });
|
|
47271
47303
|
var GetInputSchema = object({ id: string() });
|
|
47272
|
-
var AddInputSchema = object({
|
|
47273
|
-
kind: string().min(1),
|
|
47274
|
-
name: string().min(1),
|
|
47275
|
-
/**
|
|
47276
|
-
* ADOPT an existing id instead of minting a new one.
|
|
47277
|
-
*
|
|
47278
|
-
* Written the day this cost an outage. A broker's id is not a detail: HA
|
|
47279
|
-
* devices carry it inside their `stableId` (`ha:ha_004:dev:…`), so a broker
|
|
47280
|
-
* lost from config and re-added as `ha_001` leaves every one of its devices
|
|
47281
|
-
* bound to a broker that no longer exists. Re-entering the password under the
|
|
47282
|
-
* ORIGINAL id turns a multi-step device migration back into re-entering a
|
|
47283
|
-
* password.
|
|
47284
|
-
*
|
|
47285
|
-
* A provider MUST refuse an id that is already in use — adopting a live
|
|
47286
|
-
* broker's id would silently take it over.
|
|
47287
|
-
*/
|
|
47288
|
-
id: string().min(1).optional(),
|
|
47289
|
-
/** Kind-specific settings (e.g. MQTT `{url,username,password}` or HA
|
|
47290
|
-
* `{baseUrl,accessToken}`). Validated by the kind-specific provider
|
|
47291
|
-
* branch on receipt — invalid shape rejects the add. */
|
|
47292
|
-
settings: record(string(), unknown())
|
|
47293
|
-
});
|
|
47294
|
-
var AddResultSchema = object({ id: string() });
|
|
47295
|
-
var RemoveInputSchema = object({ id: string() });
|
|
47296
|
-
var TestConnectionResultSchema = discriminatedUnion("ok", [object({
|
|
47297
|
-
ok: literal(true),
|
|
47298
|
-
latencyMs: number$1().nonnegative()
|
|
47299
|
-
}), object({
|
|
47300
|
-
ok: literal(false),
|
|
47301
|
-
error: string()
|
|
47302
|
-
})]);
|
|
47303
47304
|
var SettingsRecordSchema$1 = record(string(), unknown());
|
|
47304
|
-
var SettingsSchemaInputSchema = object({ kind: string() });
|
|
47305
|
-
var TestSettingsInputSchema = object({
|
|
47306
|
-
kind: string(),
|
|
47307
|
-
settings: SettingsRecordSchema$1
|
|
47308
|
-
});
|
|
47309
|
-
var TestSettingsResultSchema = discriminatedUnion("ok", [object({
|
|
47310
|
-
ok: literal(true),
|
|
47311
|
-
latencyMs: number$1().nonnegative().optional()
|
|
47312
|
-
}).strict(), object({
|
|
47313
|
-
ok: literal(false),
|
|
47314
|
-
error: string()
|
|
47315
|
-
})]);
|
|
47316
|
-
var SettingsSchemaResultSchema = unknown().nullable();
|
|
47317
47305
|
var PublishInputSchema = object({
|
|
47318
47306
|
brokerId: string(),
|
|
47319
47307
|
/** Kind-specific routing target.
|
|
@@ -47357,51 +47345,12 @@ var brokerCapability = {
|
|
|
47357
47345
|
kind: "poll"
|
|
47358
47346
|
},
|
|
47359
47347
|
methods: {
|
|
47360
|
-
list: method(ListInputSchema, array(BrokerInfoSchema)),
|
|
47361
|
-
get: method(GetInputSchema, BrokerInfoSchema.nullable()),
|
|
47362
|
-
/** Enumerate which addon provides which broker kind(s) for the
|
|
47363
|
-
* unified create picker. The auto-mount fans this array across
|
|
47364
|
-
* every registered `broker` provider (array-output method), so the
|
|
47365
|
-
* picker sees every kind from every provider in one call. */
|
|
47366
|
-
listProviders: method(_void(), array(BrokerProviderInfoSchema), { auth: "admin" }),
|
|
47367
|
-
add: method(AddInputSchema, AddResultSchema, {
|
|
47368
|
-
kind: "mutation",
|
|
47369
|
-
auth: "admin"
|
|
47370
|
-
}),
|
|
47371
|
-
remove: method(RemoveInputSchema, _void(), {
|
|
47372
|
-
kind: "mutation",
|
|
47373
|
-
auth: "admin"
|
|
47374
|
-
}),
|
|
47375
|
-
testConnection: method(GetInputSchema, TestConnectionResultSchema, {
|
|
47376
|
-
kind: "mutation",
|
|
47377
|
-
auth: "admin"
|
|
47378
|
-
}),
|
|
47379
|
-
/** Read the persisted settings record for a broker (kind-specific
|
|
47380
|
-
* shape). Admin-only — settings may contain secrets. Returns `null`
|
|
47381
|
-
* when the broker id is unknown to the provider (the collection
|
|
47382
|
-
* fallback may route a foreign id to the first provider). */
|
|
47383
|
-
getSettings: method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }),
|
|
47384
|
-
/** Overwrite the persisted settings record. The kind-specific
|
|
47385
|
-
* provider validates the shape and applies the change (reconnects
|
|
47386
|
-
* if credentials changed). */
|
|
47387
|
-
setSettings: method(object({
|
|
47388
|
-
id: string(),
|
|
47389
|
-
settings: SettingsRecordSchema$1
|
|
47390
|
-
}), _void(), {
|
|
47391
|
-
kind: "mutation",
|
|
47392
|
-
auth: "admin"
|
|
47393
|
-
}),
|
|
47394
47348
|
/** Returns the kind-specific connection config the consumer needs
|
|
47395
47349
|
* to open its own client (MQTT pattern: `{url, username, password,
|
|
47396
47350
|
* clientIdPrefix}`). HA providers MAY return the auth envelope
|
|
47397
47351
|
* but typical HA consumers use `publish` / `subscribe` instead.
|
|
47398
47352
|
* Returns `null` when the broker id is unknown to the provider. */
|
|
47399
47353
|
getBrokerConfig: method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }),
|
|
47400
|
-
getSettingsSchema: method(SettingsSchemaInputSchema, SettingsSchemaResultSchema, { auth: "admin" }),
|
|
47401
|
-
testSettings: method(TestSettingsInputSchema, TestSettingsResultSchema, {
|
|
47402
|
-
kind: "mutation",
|
|
47403
|
-
auth: "admin"
|
|
47404
|
-
}),
|
|
47405
47354
|
publish: method(PublishInputSchema, unknown(), {
|
|
47406
47355
|
kind: "mutation",
|
|
47407
47356
|
auth: "admin"
|
|
@@ -52248,7 +52197,14 @@ var DiscoveryCandidateSchema = object({
|
|
|
52248
52197
|
/** `stableId` of the gateway candidate this device was found BEHIND. */
|
|
52249
52198
|
parentStableId: string().optional(),
|
|
52250
52199
|
/** `false` = shown for context but not pickable. Default true. */
|
|
52251
|
-
selectable: boolean().optional()
|
|
52200
|
+
selectable: boolean().optional(),
|
|
52201
|
+
/**
|
|
52202
|
+
* Keys of the provider's creation form (`getChildCreationSchema`) this candidate
|
|
52203
|
+
* cannot be adopted without — a key for an encrypted board, a pairing code. The
|
|
52204
|
+
* admin asks for exactly these, on the creation form prefilled with
|
|
52205
|
+
* `prefilledConfig`, before it adopts. Absent or empty = adopt as it is.
|
|
52206
|
+
*/
|
|
52207
|
+
needsParams: array(string()).optional()
|
|
52252
52208
|
});
|
|
52253
52209
|
/**
|
|
52254
52210
|
* Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
|
|
@@ -52375,8 +52331,14 @@ var deviceProviderCapability = {
|
|
|
52375
52331
|
* provider does not support manual creation. Lets the Add-Device dialog
|
|
52376
52332
|
* pick the right type instead of assuming Camera.
|
|
52377
52333
|
*/
|
|
52378
|
-
getManualCreationType: method(object({
|
|
52379
|
-
|
|
52334
|
+
getManualCreationType: method(object({
|
|
52335
|
+
/** The integration asked about; a provider whose integrations differ answers per one. */
|
|
52336
|
+
integrationId: string().optional() }), object({ deviceType: _enum(DeviceType).nullable() })),
|
|
52337
|
+
adoptDiscoveredDevice: method(object({
|
|
52338
|
+
candidate: DiscoveryCandidateSchema,
|
|
52339
|
+
/** The integration the operator adopts into; a provider with several instances needs it. */
|
|
52340
|
+
integrationId: string().optional()
|
|
52341
|
+
}), DeviceSummarySchema, {
|
|
52380
52342
|
kind: "mutation",
|
|
52381
52343
|
auth: "admin"
|
|
52382
52344
|
}),
|
|
@@ -52387,10 +52349,16 @@ var deviceProviderCapability = {
|
|
|
52387
52349
|
* devices of that type. The output is a `ConfigUISchema` — the
|
|
52388
52350
|
* router type-asserts it at the boundary.
|
|
52389
52351
|
*/
|
|
52390
|
-
getChildCreationSchema: method(object({
|
|
52352
|
+
getChildCreationSchema: method(object({
|
|
52353
|
+
type: _enum(DeviceType),
|
|
52354
|
+
/** The integration asked about; a provider whose integrations differ answers per one. */
|
|
52355
|
+
integrationId: string().optional()
|
|
52356
|
+
}), CreationSchemaOutputSchema),
|
|
52391
52357
|
createDevice: method(object({
|
|
52392
52358
|
type: _enum(DeviceType),
|
|
52393
|
-
config: record(string(), unknown())
|
|
52359
|
+
config: record(string(), unknown()),
|
|
52360
|
+
/** The integration the operator adds the device to; a provider with several instances needs it. */
|
|
52361
|
+
integrationId: string().optional()
|
|
52394
52362
|
}), DeviceSummarySchema, {
|
|
52395
52363
|
kind: "mutation",
|
|
52396
52364
|
auth: "admin"
|
|
@@ -53115,7 +53083,10 @@ method(object({
|
|
|
53115
53083
|
auth: "admin"
|
|
53116
53084
|
}), method(object({
|
|
53117
53085
|
addonId: string(),
|
|
53118
|
-
type: _enum(DeviceType)
|
|
53086
|
+
type: _enum(DeviceType),
|
|
53087
|
+
/** The integration the form is for; a provider whose integrations differ (a hub
|
|
53088
|
+
* integration takes no manual add) answers per integration. Absent = the addon's own. */
|
|
53089
|
+
integrationId: string().optional()
|
|
53119
53090
|
}), unknown().nullable()), method(object({
|
|
53120
53091
|
addonId: string(),
|
|
53121
53092
|
type: _enum(DeviceType),
|
|
@@ -53199,7 +53170,11 @@ method(object({
|
|
|
53199
53170
|
kind: "mutation",
|
|
53200
53171
|
auth: "admin",
|
|
53201
53172
|
timeoutMs: 12e4
|
|
53202
|
-
}), method(object({
|
|
53173
|
+
}), method(object({
|
|
53174
|
+
addonId: string(),
|
|
53175
|
+
/** The integration the Add-Device dialog is open on; absent = the addon's own. */
|
|
53176
|
+
integrationId: string().optional()
|
|
53177
|
+
}), object({ deviceType: _enum(DeviceType).nullable() }), { auth: "admin" }), method(object({ addonId: string() }), unknown(), { auth: "admin" }), method(object({
|
|
53203
53178
|
deviceId: number$1(),
|
|
53204
53179
|
key: string(),
|
|
53205
53180
|
value: unknown()
|
|
@@ -56479,7 +56454,7 @@ var TestConnectionStatusEnum = _enum([
|
|
|
56479
56454
|
"inconclusive",
|
|
56480
56455
|
"unsupported"
|
|
56481
56456
|
]);
|
|
56482
|
-
var TestConnectionResultSchema
|
|
56457
|
+
var TestConnectionResultSchema = object({
|
|
56483
56458
|
/** True ONLY for `validated`. Never true for a test that did not run. */
|
|
56484
56459
|
success: boolean(),
|
|
56485
56460
|
error: string().optional(),
|
|
@@ -56524,7 +56499,7 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
56524
56499
|
}), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
|
|
56525
56500
|
addonId: string(),
|
|
56526
56501
|
settings: record(string(), unknown())
|
|
56527
|
-
}), TestConnectionResultSchema
|
|
56502
|
+
}), TestConnectionResultSchema, {
|
|
56528
56503
|
kind: "mutation",
|
|
56529
56504
|
auth: "admin"
|
|
56530
56505
|
});
|
|
@@ -56551,6 +56526,14 @@ var ConfigSchemaPassthrough$3 = custom();
|
|
|
56551
56526
|
var IntegrationInstanceIdSchema = string().min(1);
|
|
56552
56527
|
var IntegrationInstanceModeSchema = _enum(["single", "multiple"]);
|
|
56553
56528
|
/**
|
|
56529
|
+
* What the addon's instances ARE for the Integrations page (planning#416). `devices`: each
|
|
56530
|
+
* instance is an integration that brings devices (the registry owns its on/off switch);
|
|
56531
|
+
* `services`: a connection configured from the addon's own function page, never listed on
|
|
56532
|
+
* Integrations.
|
|
56533
|
+
* It is the same vocabulary as the manifest's `integrationRole`, which stays as an override.
|
|
56534
|
+
*/
|
|
56535
|
+
var IntegrationInstanceRoleSchema = _enum(["devices", "services"]);
|
|
56536
|
+
/**
|
|
56554
56537
|
* One kind of instance a provider can create (an adapter, an account type). The sign-in and
|
|
56555
56538
|
* connect fields follow the `broker` contract (`broker.cap.ts`): `signIn.basePath` is a
|
|
56556
56539
|
* hub-relative route set speaking the sign-in contract, `connectPath` / `disconnectPath` open an
|
|
@@ -56561,6 +56544,22 @@ var IntegrationInstanceKindSchema = object({
|
|
|
56561
56544
|
/** Stable id of the kind, sent back as `createInstance.kind`. */
|
|
56562
56545
|
id: string().min(1),
|
|
56563
56546
|
label: string().min(1),
|
|
56547
|
+
/**
|
|
56548
|
+
* The function capability this kind is configured for (`broker`, `smtp-provider`,
|
|
56549
|
+
* `device-provider`, ...). A page section is the kinds whose `functionCap` it names (D1013); a
|
|
56550
|
+
* provider with more than one section must name it on every kind.
|
|
56551
|
+
*/
|
|
56552
|
+
functionCap: string().min(1),
|
|
56553
|
+
/**
|
|
56554
|
+
* For `functionCap: 'broker'`: the registry kind of the broker an instance of this kind is
|
|
56555
|
+
* (`matter`, `home-assistant`, ...). A caller that needs a broker of one kind (a link field)
|
|
56556
|
+
* finds the instances whose kind names it; absent takes the kind `id`.
|
|
56557
|
+
*/
|
|
56558
|
+
brokerKind: string().min(1).optional(),
|
|
56559
|
+
/** What instances of this kind are for the Integrations page; absent takes the provider's `role`. */
|
|
56560
|
+
role: IntegrationInstanceRoleSchema.optional(),
|
|
56561
|
+
/** `testInstance` really probes the remote for this kind; absent takes the provider's `canTest`. */
|
|
56562
|
+
canTest: boolean().optional(),
|
|
56564
56563
|
/** The kind signs in through a server-held interactive session. */
|
|
56565
56564
|
signIn: object({
|
|
56566
56565
|
basePath: string().min(1),
|
|
@@ -56571,22 +56570,38 @@ var IntegrationInstanceKindSchema = object({
|
|
|
56571
56570
|
/** Relative hub path to disconnect that account. */
|
|
56572
56571
|
disconnectPath: string().min(1).optional()
|
|
56573
56572
|
});
|
|
56573
|
+
/**
|
|
56574
|
+
* How devices of an instance are found. `none`: the operator adds them by hand; `lan`: the addon
|
|
56575
|
+
* scans the network before any instance exists (the wizard offers the candidates); `account`: the
|
|
56576
|
+
* devices appear after the instance signs in. `scopedToInstance`: a discovery run belongs to ONE
|
|
56577
|
+
* instance and its candidates are claimed by it.
|
|
56578
|
+
*/
|
|
56579
|
+
var IntegrationInstanceDiscoverySchema = object({
|
|
56580
|
+
mode: _enum([
|
|
56581
|
+
"none",
|
|
56582
|
+
"lan",
|
|
56583
|
+
"account"
|
|
56584
|
+
]),
|
|
56585
|
+
scopedToInstance: boolean()
|
|
56586
|
+
});
|
|
56587
|
+
/**
|
|
56588
|
+
* One link a kind of this provider declares (D1013 rule 5): the body `field` of an instance of
|
|
56589
|
+
* `kind` holds the id of an instance of the function cap `to.cap` (of kind `to.kind`, when set).
|
|
56590
|
+
* The hub indexes these so the target's delete is refused while a link names it.
|
|
56591
|
+
*/
|
|
56592
|
+
var IntegrationInstanceLinkSchema = object({
|
|
56593
|
+
kind: string().min(1),
|
|
56594
|
+
field: string().min(1),
|
|
56595
|
+
to: object({
|
|
56596
|
+
cap: string().min(1),
|
|
56597
|
+
kind: string().min(1).optional()
|
|
56598
|
+
})
|
|
56599
|
+
});
|
|
56574
56600
|
var IntegrationInstanceDescriptionSchema = object({
|
|
56575
56601
|
/** What the instances are for the Integrations page; see {@link IntegrationInstanceRoleSchema}. */
|
|
56576
|
-
role:
|
|
56577
|
-
"devices",
|
|
56578
|
-
"services",
|
|
56579
|
-
"both"
|
|
56580
|
-
]),
|
|
56602
|
+
role: IntegrationInstanceRoleSchema,
|
|
56581
56603
|
/** How the devices of an instance are found; absent means `{ mode: 'none' }`. */
|
|
56582
|
-
discovery:
|
|
56583
|
-
mode: _enum([
|
|
56584
|
-
"none",
|
|
56585
|
-
"lan",
|
|
56586
|
-
"account"
|
|
56587
|
-
]),
|
|
56588
|
-
scopedToInstance: boolean()
|
|
56589
|
-
}).optional(),
|
|
56604
|
+
discovery: IntegrationInstanceDiscoverySchema.optional(),
|
|
56590
56605
|
/** `single`: at most one instance can exist (Loki). `multiple`: an account each. */
|
|
56591
56606
|
instanceMode: IntegrationInstanceModeSchema,
|
|
56592
56607
|
/** What the instance is, for the UI (`destination`, `relay`, `account`). */
|
|
@@ -56607,7 +56622,9 @@ var IntegrationInstanceDescriptionSchema = object({
|
|
|
56607
56622
|
* `createInstance` without an `id` works: the provider mints it. Absent means the caller must
|
|
56608
56623
|
* send an id (the UI then mints an opaque one).
|
|
56609
56624
|
*/
|
|
56610
|
-
mintsId: boolean().optional()
|
|
56625
|
+
mintsId: boolean().optional(),
|
|
56626
|
+
/** The links to other instances the provider's kinds declare; absent means none. */
|
|
56627
|
+
links: array(IntegrationInstanceLinkSchema).optional()
|
|
56611
56628
|
});
|
|
56612
56629
|
var IntegrationInstanceStatusSchema = object({
|
|
56613
56630
|
state: _enum([
|
|
@@ -56652,7 +56669,11 @@ var integrationInstancesCapability = {
|
|
|
56652
56669
|
methods: {
|
|
56653
56670
|
describe: method(_void(), IntegrationInstanceDescriptionSchema, { auth: "admin" }),
|
|
56654
56671
|
/** The form; `id: null` is the create form. */
|
|
56655
|
-
instanceFormSchema: method(object({
|
|
56672
|
+
instanceFormSchema: method(object({
|
|
56673
|
+
id: IntegrationInstanceIdSchema.nullable(),
|
|
56674
|
+
/** The kind of the create form (`id: null`) of a provider with several. */
|
|
56675
|
+
kind: string().min(1).optional()
|
|
56676
|
+
}), ConfigSchemaPassthrough$3, { auth: "admin" }),
|
|
56656
56677
|
listInstances: method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }),
|
|
56657
56678
|
getInstance: method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }),
|
|
56658
56679
|
/**
|
|
@@ -56662,7 +56683,9 @@ var integrationInstancesCapability = {
|
|
|
56662
56683
|
createInstance: method(object({
|
|
56663
56684
|
id: IntegrationInstanceIdSchema.optional(),
|
|
56664
56685
|
name: string().min(1),
|
|
56665
|
-
kind: string().min(1).optional()
|
|
56686
|
+
kind: string().min(1).optional(),
|
|
56687
|
+
/** Form values written with the record (a link picked in the create dialog). */
|
|
56688
|
+
values: record(string(), unknown()).optional()
|
|
56666
56689
|
}), object({ id: string() }), {
|
|
56667
56690
|
kind: "mutation",
|
|
56668
56691
|
auth: "admin"
|
|
@@ -56689,8 +56712,10 @@ var integrationInstancesCapability = {
|
|
|
56689
56712
|
}),
|
|
56690
56713
|
testInstance: optionalMethod(object({
|
|
56691
56714
|
id: IntegrationInstanceIdSchema.nullable(),
|
|
56692
|
-
values: record(string(), unknown())
|
|
56693
|
-
|
|
56715
|
+
values: record(string(), unknown()),
|
|
56716
|
+
/** The kind to test when `id` is null and the provider has several. */
|
|
56717
|
+
kind: string().min(1).optional()
|
|
56718
|
+
}), TestConnectionResultSchema, {
|
|
56694
56719
|
kind: "mutation",
|
|
56695
56720
|
auth: "admin"
|
|
56696
56721
|
})
|
|
@@ -59447,6 +59472,380 @@ var SystemMetricsSchema = object({
|
|
|
59447
59472
|
gpuMemoryPercent: number$1().optional()
|
|
59448
59473
|
});
|
|
59449
59474
|
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);
|
|
59475
|
+
/**
|
|
59476
|
+
* records - an addon's own facts and documents (D881): ledgers, audit rows,
|
|
59477
|
+
* config documents, mirrors. The third storage API next to `ctx.settings`
|
|
59478
|
+
* (operator intent, D870) and device-history (numbers over time, D850).
|
|
59479
|
+
*
|
|
59480
|
+
* Who the OWNER is: `callerAddonId`, stamped by the transport on a call that
|
|
59481
|
+
* crossed a process and stated by a hub in-process caller (`ctx.records` does
|
|
59482
|
+
* that with the addon id closed over). It is deliberately NOT in any input
|
|
59483
|
+
* schema, so a session can never supply it, and a call with no stamp is refused
|
|
59484
|
+
* `records/no-caller`. A kind is readable and writable only by its owner; other
|
|
59485
|
+
* addons go through the owner's capability.
|
|
59486
|
+
*
|
|
59487
|
+
* Every read is three-way: a row, `null`, or the call THROWS. A failed read is
|
|
59488
|
+
* never `[]`. Secret fields are never on the wire: a read shows the
|
|
59489
|
+
* redaction token, `secretRefs` hands the owner's `ctx.secrets` the refs it
|
|
59490
|
+
* resolves itself, and a write may carry only a keychain marker bound to the
|
|
59491
|
+
* row's own ref.
|
|
59492
|
+
*
|
|
59493
|
+
* Internal: addons reach it through `ctx.records`, never directly.
|
|
59494
|
+
*/
|
|
59495
|
+
var RecordIdSchema = string().min(1).max(256);
|
|
59496
|
+
/** A JSON object body. Size is the engine's check (`maxDataBytes`), not a schema's. */
|
|
59497
|
+
var RecordBodySchema = record(string(), unknown());
|
|
59498
|
+
var RecordInputSchema = object({
|
|
59499
|
+
id: RecordIdSchema,
|
|
59500
|
+
/** The device this row belongs to (D865 removal, per-device reads). */
|
|
59501
|
+
deviceId: number$1().int().optional(),
|
|
59502
|
+
/** The domain clock, ms: observed / created / opened. */
|
|
59503
|
+
at: number$1().int().optional(),
|
|
59504
|
+
/** Producer-set expiry, ms. Refused on a kind whose retention is an engine TTL. */
|
|
59505
|
+
expiresAt: number$1().int().optional(),
|
|
59506
|
+
data: RecordBodySchema
|
|
59507
|
+
});
|
|
59508
|
+
var RecordViewSchema = object({
|
|
59509
|
+
id: string(),
|
|
59510
|
+
deviceId: number$1().int().nullable(),
|
|
59511
|
+
at: number$1().nullable(),
|
|
59512
|
+
expiresAt: number$1().nullable(),
|
|
59513
|
+
rev: number$1().int(),
|
|
59514
|
+
updatedAt: number$1(),
|
|
59515
|
+
data: RecordBodySchema
|
|
59516
|
+
});
|
|
59517
|
+
/** Declared index fields only. Anything else does not compile, by design. */
|
|
59518
|
+
var RecordWhereSchema = record(RecordFieldPathSchema, union([
|
|
59519
|
+
string(),
|
|
59520
|
+
number$1(),
|
|
59521
|
+
boolean(),
|
|
59522
|
+
object({ in: array(union([
|
|
59523
|
+
string(),
|
|
59524
|
+
number$1(),
|
|
59525
|
+
boolean()
|
|
59526
|
+
])).min(1).max(RECORD_BATCH_MAX) }),
|
|
59527
|
+
object({
|
|
59528
|
+
gt: union([string(), number$1()]).optional(),
|
|
59529
|
+
gte: union([string(), number$1()]).optional(),
|
|
59530
|
+
lt: union([string(), number$1()]).optional(),
|
|
59531
|
+
lte: union([string(), number$1()]).optional()
|
|
59532
|
+
}).refine((r) => Object.keys(r).length > 0, { message: "name at least one bound" })
|
|
59533
|
+
]));
|
|
59534
|
+
var KindShape = { kind: RecordKindNameSchema };
|
|
59535
|
+
var TimeWindowShape = {
|
|
59536
|
+
deviceId: number$1().int().optional(),
|
|
59537
|
+
atFrom: number$1().optional(),
|
|
59538
|
+
atTo: number$1().optional()
|
|
59539
|
+
};
|
|
59540
|
+
var RecordQueryInputSchema = object({
|
|
59541
|
+
...KindShape,
|
|
59542
|
+
...TimeWindowShape,
|
|
59543
|
+
where: RecordWhereSchema.optional(),
|
|
59544
|
+
/** Without `where`: `at` (default for a kind with a clock) or `id`. With `where` the order is the index's. */
|
|
59545
|
+
order: _enum(["at", "id"]).optional(),
|
|
59546
|
+
desc: boolean().optional(),
|
|
59547
|
+
limit: number$1().int().min(1).max(RECORD_QUERY_MAX).default(100),
|
|
59548
|
+
/** Opaque keyset cursor from a previous answer. There is no offset. */
|
|
59549
|
+
cursor: string().min(1).optional()
|
|
59550
|
+
});
|
|
59551
|
+
var RecordQueryResultSchema = object({
|
|
59552
|
+
records: array(RecordViewSchema),
|
|
59553
|
+
nextCursor: string().nullable()
|
|
59554
|
+
});
|
|
59555
|
+
var RecordKindStateSchema = _enum([
|
|
59556
|
+
"created",
|
|
59557
|
+
"unchanged",
|
|
59558
|
+
"updated"
|
|
59559
|
+
]);
|
|
59560
|
+
var DeclareKindResultSchema = object({
|
|
59561
|
+
kindId: number$1().int(),
|
|
59562
|
+
state: RecordKindStateSchema,
|
|
59563
|
+
createdIndexes: array(string())
|
|
59564
|
+
});
|
|
59565
|
+
/** Keychain refs a write released (the old marker is gone from the row): the owner deletes the values. */
|
|
59566
|
+
var SecretRefsList = array(string());
|
|
59567
|
+
/**
|
|
59568
|
+
* One op of a `transact`: the single-row writes, each on a kind of the caller.
|
|
59569
|
+
* All ops of one call share ONE database file and ONE `transactFamily`.
|
|
59570
|
+
*/
|
|
59571
|
+
var RecordTransactOpSchema = discriminatedUnion("op", [
|
|
59572
|
+
object({
|
|
59573
|
+
op: literal("put"),
|
|
59574
|
+
kind: RecordKindNameSchema,
|
|
59575
|
+
record: RecordInputSchema,
|
|
59576
|
+
ifRev: number$1().int().min(0).optional()
|
|
59577
|
+
}),
|
|
59578
|
+
object({
|
|
59579
|
+
op: literal("patch"),
|
|
59580
|
+
kind: RecordKindNameSchema,
|
|
59581
|
+
id: RecordIdSchema,
|
|
59582
|
+
data: RecordBodySchema,
|
|
59583
|
+
at: number$1().int().nullable().optional(),
|
|
59584
|
+
expiresAt: number$1().int().nullable().optional(),
|
|
59585
|
+
ifRev: number$1().int().min(1).optional()
|
|
59586
|
+
}),
|
|
59587
|
+
object({
|
|
59588
|
+
op: literal("delete"),
|
|
59589
|
+
kind: RecordKindNameSchema,
|
|
59590
|
+
id: RecordIdSchema
|
|
59591
|
+
})
|
|
59592
|
+
]);
|
|
59593
|
+
var RecordSecretRefsSchema = record(string(), record(string(), string()));
|
|
59594
|
+
method(object({ kinds: record(RecordKindNameSchema, RecordKindDeclarationSchema) }), object({ kinds: record(string(), DeclareKindResultSchema) }), {
|
|
59595
|
+
kind: "mutation",
|
|
59596
|
+
auth: "admin"
|
|
59597
|
+
}), method(object({
|
|
59598
|
+
...KindShape,
|
|
59599
|
+
id: RecordIdSchema
|
|
59600
|
+
}), RecordViewSchema.nullable(), { auth: "admin" }), method(object({
|
|
59601
|
+
...KindShape,
|
|
59602
|
+
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
59603
|
+
}), array(RecordViewSchema), { auth: "admin" }), method(object({
|
|
59604
|
+
...KindShape,
|
|
59605
|
+
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
59606
|
+
}), RecordSecretRefsSchema, { auth: "admin" }), method(object({
|
|
59607
|
+
...KindShape,
|
|
59608
|
+
record: RecordInputSchema,
|
|
59609
|
+
/** Compare-and-set. `0` means "the row must not exist". */
|
|
59610
|
+
ifRev: number$1().int().min(0).optional()
|
|
59611
|
+
}), object({
|
|
59612
|
+
rev: number$1().int(),
|
|
59613
|
+
releasedRefs: SecretRefsList
|
|
59614
|
+
}), {
|
|
59615
|
+
kind: "mutation",
|
|
59616
|
+
auth: "admin"
|
|
59617
|
+
}), method(object({
|
|
59618
|
+
...KindShape,
|
|
59619
|
+
records: array(RecordInputSchema).min(1).max(RECORD_BATCH_MAX)
|
|
59620
|
+
}), object({
|
|
59621
|
+
count: number$1().int(),
|
|
59622
|
+
releasedRefs: SecretRefsList
|
|
59623
|
+
}), {
|
|
59624
|
+
kind: "mutation",
|
|
59625
|
+
auth: "admin"
|
|
59626
|
+
}), method(object({
|
|
59627
|
+
...KindShape,
|
|
59628
|
+
id: RecordIdSchema,
|
|
59629
|
+
data: RecordBodySchema,
|
|
59630
|
+
at: number$1().int().nullable().optional(),
|
|
59631
|
+
expiresAt: number$1().int().nullable().optional(),
|
|
59632
|
+
ifRev: number$1().int().min(1).optional()
|
|
59633
|
+
}), object({
|
|
59634
|
+
rev: number$1().int(),
|
|
59635
|
+
releasedRefs: SecretRefsList
|
|
59636
|
+
}), {
|
|
59637
|
+
kind: "mutation",
|
|
59638
|
+
auth: "admin"
|
|
59639
|
+
}), method(object({
|
|
59640
|
+
...KindShape,
|
|
59641
|
+
id: RecordIdSchema
|
|
59642
|
+
}), object({
|
|
59643
|
+
deleted: boolean(),
|
|
59644
|
+
secretRefs: SecretRefsList
|
|
59645
|
+
}), {
|
|
59646
|
+
kind: "mutation",
|
|
59647
|
+
auth: "admin"
|
|
59648
|
+
}), method(object({
|
|
59649
|
+
...KindShape,
|
|
59650
|
+
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
59651
|
+
}), object({
|
|
59652
|
+
deleted: number$1().int(),
|
|
59653
|
+
secretRefs: SecretRefsList
|
|
59654
|
+
}), {
|
|
59655
|
+
kind: "mutation",
|
|
59656
|
+
auth: "admin"
|
|
59657
|
+
}), method(RecordQueryInputSchema, RecordQueryResultSchema, { auth: "admin" }), method(object({
|
|
59658
|
+
...KindShape,
|
|
59659
|
+
...TimeWindowShape,
|
|
59660
|
+
where: RecordWhereSchema.optional()
|
|
59661
|
+
}), object({ count: number$1().int() }), { auth: "admin" }), method(object({
|
|
59662
|
+
...KindShape,
|
|
59663
|
+
deviceId: number$1().int().optional(),
|
|
59664
|
+
from: number$1(),
|
|
59665
|
+
to: number$1(),
|
|
59666
|
+
bucketMs: number$1().int().positive()
|
|
59667
|
+
}), object({ buckets: array(object({
|
|
59668
|
+
bucketStart: number$1(),
|
|
59669
|
+
count: number$1().int()
|
|
59670
|
+
})) }), { auth: "admin" }), method(object({
|
|
59671
|
+
...KindShape,
|
|
59672
|
+
op: _enum([
|
|
59673
|
+
"sum",
|
|
59674
|
+
"min",
|
|
59675
|
+
"max",
|
|
59676
|
+
"count"
|
|
59677
|
+
]),
|
|
59678
|
+
field: RecordFieldPathSchema.optional(),
|
|
59679
|
+
groupBy: RecordFieldPathSchema.optional(),
|
|
59680
|
+
where: RecordWhereSchema.optional()
|
|
59681
|
+
}), object({ rows: array(object({
|
|
59682
|
+
group: union([
|
|
59683
|
+
string(),
|
|
59684
|
+
number$1(),
|
|
59685
|
+
_null()
|
|
59686
|
+
]),
|
|
59687
|
+
value: number$1().nullable()
|
|
59688
|
+
})) }), { auth: "admin" }), method(object({
|
|
59689
|
+
...KindShape,
|
|
59690
|
+
deviceId: number$1().int().optional(),
|
|
59691
|
+
atBefore: number$1().optional(),
|
|
59692
|
+
where: RecordWhereSchema.optional()
|
|
59693
|
+
}), object({
|
|
59694
|
+
deleted: number$1().int(),
|
|
59695
|
+
secretRefs: SecretRefsList
|
|
59696
|
+
}), {
|
|
59697
|
+
kind: "mutation",
|
|
59698
|
+
auth: "admin"
|
|
59699
|
+
}), method(object({ ops: array(RecordTransactOpSchema).min(1).max(RECORD_BATCH_MAX) }), object({
|
|
59700
|
+
results: array(object({
|
|
59701
|
+
rev: number$1().int().nullable(),
|
|
59702
|
+
deleted: boolean()
|
|
59703
|
+
})),
|
|
59704
|
+
releasedRefs: SecretRefsList,
|
|
59705
|
+
secretRefs: SecretRefsList
|
|
59706
|
+
}), {
|
|
59707
|
+
kind: "mutation",
|
|
59708
|
+
auth: "admin"
|
|
59709
|
+
}), method(object({
|
|
59710
|
+
deviceId: number$1().int(),
|
|
59711
|
+
kinds: array(RecordKindNameSchema).optional()
|
|
59712
|
+
}), object({
|
|
59713
|
+
deleted: record(string(), number$1().int()),
|
|
59714
|
+
secretRefs: SecretRefsList
|
|
59715
|
+
}), {
|
|
59716
|
+
kind: "mutation",
|
|
59717
|
+
auth: "admin"
|
|
59718
|
+
}), method(object(KindShape), object({ id: number$1().int() }), {
|
|
59719
|
+
kind: "mutation",
|
|
59720
|
+
auth: "admin"
|
|
59721
|
+
});
|
|
59722
|
+
/**
|
|
59723
|
+
* The wire shapes of the migration API, version 1 (planning#447).
|
|
59724
|
+
*
|
|
59725
|
+
* A migration is code that outlives the server it was written against: an addon at
|
|
59726
|
+
* version 1.4 that is brought to 1.9 in one jump still runs its 1.4 catalogue, against
|
|
59727
|
+
* the 1.9 kernel. The only thing that code may call is {@link MigrationApiV1}; this file
|
|
59728
|
+
* is the contract of every method of it, one Zod pair per method.
|
|
59729
|
+
*
|
|
59730
|
+
* APPEND-ONLY. A released method is never removed and never changes meaning: an input
|
|
59731
|
+
* field is never removed or made required, an enum never narrows, an output field is
|
|
59732
|
+
* never removed. A change that cannot be expressed that way is `migration-api.v2`, next
|
|
59733
|
+
* to this file. `scripts/check-migration-api-append-only.ts` holds the line against
|
|
59734
|
+
* `scripts/migration-api-contract.json`, and every `behaviours` id below must have an
|
|
59735
|
+
* `it('<id>'` in `migration-api.contract.spec.ts`.
|
|
59736
|
+
*/
|
|
59737
|
+
var Rows = record(string(), unknown());
|
|
59738
|
+
var IntegrationId = object({ integrationId: string().min(1) });
|
|
59739
|
+
var RecordId = object({ id: string().min(1) });
|
|
59740
|
+
var RecordRowSchema = object({
|
|
59741
|
+
id: string(),
|
|
59742
|
+
deviceId: number$1().int().nullable(),
|
|
59743
|
+
at: number$1().nullable(),
|
|
59744
|
+
expiresAt: number$1().nullable(),
|
|
59745
|
+
rev: number$1().int(),
|
|
59746
|
+
updatedAt: number$1(),
|
|
59747
|
+
data: Rows
|
|
59748
|
+
});
|
|
59749
|
+
var RecordPutShape = {
|
|
59750
|
+
id: string().min(1),
|
|
59751
|
+
deviceId: number$1().int().optional(),
|
|
59752
|
+
at: number$1().int().optional(),
|
|
59753
|
+
expiresAt: number$1().int().optional(),
|
|
59754
|
+
data: Rows
|
|
59755
|
+
};
|
|
59756
|
+
var MigrationIntegrationSchema = object({
|
|
59757
|
+
id: string(),
|
|
59758
|
+
addonId: string(),
|
|
59759
|
+
name: string(),
|
|
59760
|
+
info: Rows,
|
|
59761
|
+
originStableId: string().optional()
|
|
59762
|
+
});
|
|
59763
|
+
var MigrationDeviceSchema = object({
|
|
59764
|
+
id: number$1().int(),
|
|
59765
|
+
integrationId: string().nullable().optional(),
|
|
59766
|
+
/** The owning device; null or absent for a root. */
|
|
59767
|
+
parentDeviceId: number$1().int().nullable().optional(),
|
|
59768
|
+
type: string().optional(),
|
|
59769
|
+
name: string().optional(),
|
|
59770
|
+
stableId: string().optional()
|
|
59771
|
+
});
|
|
59772
|
+
/** What the engine says about the migration API it serves: lets an addon notice an older server. */
|
|
59773
|
+
var MigrationApiInfoSchema = object({
|
|
59774
|
+
version: number$1().int(),
|
|
59775
|
+
methods: array(string())
|
|
59776
|
+
});
|
|
59777
|
+
object({
|
|
59778
|
+
message: string(),
|
|
59779
|
+
meta: Rows.optional(),
|
|
59780
|
+
/** Grouping tags of the line; a line about a device carries `{ deviceId }`. */
|
|
59781
|
+
tags: object({ deviceId: number$1().int().optional() }).optional()
|
|
59782
|
+
});
|
|
59783
|
+
object({
|
|
59784
|
+
owner: string().min(1),
|
|
59785
|
+
metas: array(MigrationMetaSchema)
|
|
59786
|
+
}), object({
|
|
59787
|
+
bootId: string(),
|
|
59788
|
+
snapshot: _enum([
|
|
59789
|
+
"none",
|
|
59790
|
+
"written",
|
|
59791
|
+
"refused"
|
|
59792
|
+
]),
|
|
59793
|
+
steps: array(MigrationOwnerStepSchema),
|
|
59794
|
+
/** The migration API the engine serves; absent from a server that predates it. */
|
|
59795
|
+
migrationApi: MigrationApiInfoSchema.optional()
|
|
59796
|
+
}), object({ id: string().min(1) }), object({
|
|
59797
|
+
proceed: boolean(),
|
|
59798
|
+
state: union([literal("pending"), MigrationStatusSchema]),
|
|
59799
|
+
reason: string().nullable()
|
|
59800
|
+
}), object({
|
|
59801
|
+
id: string().min(1),
|
|
59802
|
+
result: MigrationOwnerResultSchema
|
|
59803
|
+
}), object({
|
|
59804
|
+
status: MigrationStatusSchema,
|
|
59805
|
+
error: string().nullable(),
|
|
59806
|
+
rowsChanged: number$1().int().nonnegative()
|
|
59807
|
+
}), object({
|
|
59808
|
+
rows: number$1().int().nonnegative(),
|
|
59809
|
+
secrets: number$1().int().nonnegative()
|
|
59810
|
+
}), IntegrationId.extend({ patch: Rows }), object({ written: boolean() }), IntegrationId.extend({ keys: array(string().min(1)) }), object({
|
|
59811
|
+
present: boolean(),
|
|
59812
|
+
removed: Rows
|
|
59813
|
+
}), RecordRowSchema.nullable(), RecordRowSchema.nullable(), object({
|
|
59814
|
+
...RecordPutShape,
|
|
59815
|
+
ifRev: number$1().int().optional()
|
|
59816
|
+
}), object({ rev: number$1().int() }), object({ records: array(object(RecordPutShape)) }), object({ count: number$1().int().nonnegative() }), RecordId.extend({
|
|
59817
|
+
partial: Rows,
|
|
59818
|
+
ifRev: number$1().int().optional()
|
|
59819
|
+
}), object({ rev: number$1().int() }), object({ deleted: boolean() }), object({
|
|
59820
|
+
deviceId: number$1().int().optional(),
|
|
59821
|
+
atFrom: number$1().optional(),
|
|
59822
|
+
atTo: number$1().optional(),
|
|
59823
|
+
where: RecordWhereSchema.optional(),
|
|
59824
|
+
limit: number$1().int().min(1).optional(),
|
|
59825
|
+
cursor: string().min(1).optional(),
|
|
59826
|
+
resolveSecrets: boolean().optional()
|
|
59827
|
+
}), object({
|
|
59828
|
+
records: array(RecordRowSchema),
|
|
59829
|
+
nextCursor: string().nullable()
|
|
59830
|
+
}), object({
|
|
59831
|
+
deviceId: number$1().int().optional(),
|
|
59832
|
+
atBefore: number$1().optional(),
|
|
59833
|
+
where: RecordWhereSchema.optional()
|
|
59834
|
+
}), 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({
|
|
59835
|
+
ref: string().min(1),
|
|
59836
|
+
value: string()
|
|
59837
|
+
}), object({}), object({ refs: array(string()) }), object({ deleted: number$1().int().nonnegative() }), object({}), object({ integrations: array(MigrationIntegrationSchema) }), object({ id: string().min(1) }), MigrationIntegrationSchema.nullable(), object({
|
|
59838
|
+
addonId: string().min(1),
|
|
59839
|
+
integrationId: string().min(1).optional()
|
|
59840
|
+
}), object({ devices: array(MigrationDeviceSchema) }), object({ deviceId: number$1().int() }), object({
|
|
59841
|
+
id: number$1().int(),
|
|
59842
|
+
integrationId: string().nullable(),
|
|
59843
|
+
/** Whether the device carries any stored config; its content is not offered. */
|
|
59844
|
+
hasConfig: boolean()
|
|
59845
|
+
}).nullable(), object({
|
|
59846
|
+
deviceId: number$1().int(),
|
|
59847
|
+
integrationId: string().min(1)
|
|
59848
|
+
}), object({}), object({}), object({ nodeId: string() }), object({}), object({});
|
|
59450
59849
|
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({
|
|
59451
59850
|
id: string().min(1),
|
|
59452
59851
|
error: string().min(1)
|
|
@@ -59476,7 +59875,9 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
|
|
|
59476
59875
|
"written",
|
|
59477
59876
|
"refused"
|
|
59478
59877
|
]),
|
|
59479
|
-
steps: array(MigrationOwnerStepSchema)
|
|
59878
|
+
steps: array(MigrationOwnerStepSchema),
|
|
59879
|
+
/** The migration API this engine serves (additive: a server that predates it omits it). */
|
|
59880
|
+
migrationApi: MigrationApiInfoSchema.optional()
|
|
59480
59881
|
}), {
|
|
59481
59882
|
kind: "mutation",
|
|
59482
59883
|
auth: "admin"
|
|
@@ -62143,7 +62544,10 @@ DeviceType.Camera, method(object({ deviceId: number$1() }), array(TrackSchema).r
|
|
|
62143
62544
|
}), method(RelocateMediaInputSchema, object({ jobId: string() }), {
|
|
62144
62545
|
kind: "mutation",
|
|
62145
62546
|
auth: "admin"
|
|
62146
|
-
}), method(object({}), UnstampedEventMediaCountSchema, { auth: "admin" }), method(
|
|
62547
|
+
}), method(object({}), UnstampedEventMediaCountSchema, { auth: "admin" }), method(MediaLocationCensusInputSchema, MediaLocationCensusSchema, {
|
|
62548
|
+
kind: "query",
|
|
62549
|
+
auth: "admin"
|
|
62550
|
+
}), method(RelocatableMediaCountInputSchema, RelocatableMediaCountSchema, {
|
|
62147
62551
|
kind: "query",
|
|
62148
62552
|
auth: "admin"
|
|
62149
62553
|
}), method(object({}), array(RelocateJobSchema).readonly(), {
|
|
@@ -64798,7 +65202,7 @@ var BrokerStatusSchema$1 = _enum([
|
|
|
64798
65202
|
"unreachable",
|
|
64799
65203
|
"tls-error"
|
|
64800
65204
|
]);
|
|
64801
|
-
var BrokerInfoSchema
|
|
65205
|
+
var BrokerInfoSchema = object({
|
|
64802
65206
|
id: string(),
|
|
64803
65207
|
name: string(),
|
|
64804
65208
|
url: string(),
|
|
@@ -64861,7 +65265,7 @@ var StatusSchema = object({
|
|
|
64861
65265
|
brokerCount: number$1(),
|
|
64862
65266
|
embeddedRunning: boolean()
|
|
64863
65267
|
});
|
|
64864
|
-
method(_void(), array(BrokerInfoSchema
|
|
65268
|
+
method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
|
|
64865
65269
|
kind: "mutation",
|
|
64866
65270
|
auth: "admin"
|
|
64867
65271
|
}), method(IdInputSchema, _void(), {
|
|
@@ -72043,253 +72447,6 @@ DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, DeviceType.Gateway, met
|
|
|
72043
72447
|
auth: "admin"
|
|
72044
72448
|
});
|
|
72045
72449
|
/**
|
|
72046
|
-
* records - an addon's own facts and documents (D881): ledgers, audit rows,
|
|
72047
|
-
* config documents, mirrors. The third storage API next to `ctx.settings`
|
|
72048
|
-
* (operator intent, D870) and device-history (numbers over time, D850).
|
|
72049
|
-
*
|
|
72050
|
-
* Who the OWNER is: `callerAddonId`, stamped by the transport on a call that
|
|
72051
|
-
* crossed a process and stated by a hub in-process caller (`ctx.records` does
|
|
72052
|
-
* that with the addon id closed over). It is deliberately NOT in any input
|
|
72053
|
-
* schema, so a session can never supply it, and a call with no stamp is refused
|
|
72054
|
-
* `records/no-caller`. A kind is readable and writable only by its owner; other
|
|
72055
|
-
* addons go through the owner's capability.
|
|
72056
|
-
*
|
|
72057
|
-
* Every read is three-way: a row, `null`, or the call THROWS. A failed read is
|
|
72058
|
-
* never `[]`. Secret fields are never on the wire: a read shows the
|
|
72059
|
-
* redaction token, `secretRefs` hands the owner's `ctx.secrets` the refs it
|
|
72060
|
-
* resolves itself, and a write may carry only a keychain marker bound to the
|
|
72061
|
-
* row's own ref.
|
|
72062
|
-
*
|
|
72063
|
-
* Internal: addons reach it through `ctx.records`, never directly.
|
|
72064
|
-
*/
|
|
72065
|
-
var RecordIdSchema = string().min(1).max(256);
|
|
72066
|
-
/** A JSON object body. Size is the engine's check (`maxDataBytes`), not a schema's. */
|
|
72067
|
-
var RecordBodySchema = record(string(), unknown());
|
|
72068
|
-
var RecordInputSchema = object({
|
|
72069
|
-
id: RecordIdSchema,
|
|
72070
|
-
/** The device this row belongs to (D865 removal, per-device reads). */
|
|
72071
|
-
deviceId: number$1().int().optional(),
|
|
72072
|
-
/** The domain clock, ms: observed / created / opened. */
|
|
72073
|
-
at: number$1().int().optional(),
|
|
72074
|
-
/** Producer-set expiry, ms. Refused on a kind whose retention is an engine TTL. */
|
|
72075
|
-
expiresAt: number$1().int().optional(),
|
|
72076
|
-
data: RecordBodySchema
|
|
72077
|
-
});
|
|
72078
|
-
var RecordViewSchema = object({
|
|
72079
|
-
id: string(),
|
|
72080
|
-
deviceId: number$1().int().nullable(),
|
|
72081
|
-
at: number$1().nullable(),
|
|
72082
|
-
expiresAt: number$1().nullable(),
|
|
72083
|
-
rev: number$1().int(),
|
|
72084
|
-
updatedAt: number$1(),
|
|
72085
|
-
data: RecordBodySchema
|
|
72086
|
-
});
|
|
72087
|
-
/** Declared index fields only. Anything else does not compile, by design. */
|
|
72088
|
-
var RecordWhereSchema = record(RecordFieldPathSchema, union([
|
|
72089
|
-
string(),
|
|
72090
|
-
number$1(),
|
|
72091
|
-
boolean(),
|
|
72092
|
-
object({ in: array(union([
|
|
72093
|
-
string(),
|
|
72094
|
-
number$1(),
|
|
72095
|
-
boolean()
|
|
72096
|
-
])).min(1).max(RECORD_BATCH_MAX) }),
|
|
72097
|
-
object({
|
|
72098
|
-
gt: union([string(), number$1()]).optional(),
|
|
72099
|
-
gte: union([string(), number$1()]).optional(),
|
|
72100
|
-
lt: union([string(), number$1()]).optional(),
|
|
72101
|
-
lte: union([string(), number$1()]).optional()
|
|
72102
|
-
}).refine((r) => Object.keys(r).length > 0, { message: "name at least one bound" })
|
|
72103
|
-
]));
|
|
72104
|
-
var KindShape = { kind: RecordKindNameSchema };
|
|
72105
|
-
var TimeWindowShape = {
|
|
72106
|
-
deviceId: number$1().int().optional(),
|
|
72107
|
-
atFrom: number$1().optional(),
|
|
72108
|
-
atTo: number$1().optional()
|
|
72109
|
-
};
|
|
72110
|
-
var RecordQueryInputSchema = object({
|
|
72111
|
-
...KindShape,
|
|
72112
|
-
...TimeWindowShape,
|
|
72113
|
-
where: RecordWhereSchema.optional(),
|
|
72114
|
-
/** Without `where`: `at` (default for a kind with a clock) or `id`. With `where` the order is the index's. */
|
|
72115
|
-
order: _enum(["at", "id"]).optional(),
|
|
72116
|
-
desc: boolean().optional(),
|
|
72117
|
-
limit: number$1().int().min(1).max(RECORD_QUERY_MAX).default(100),
|
|
72118
|
-
/** Opaque keyset cursor from a previous answer. There is no offset. */
|
|
72119
|
-
cursor: string().min(1).optional()
|
|
72120
|
-
});
|
|
72121
|
-
var RecordQueryResultSchema = object({
|
|
72122
|
-
records: array(RecordViewSchema),
|
|
72123
|
-
nextCursor: string().nullable()
|
|
72124
|
-
});
|
|
72125
|
-
var RecordKindStateSchema = _enum([
|
|
72126
|
-
"created",
|
|
72127
|
-
"unchanged",
|
|
72128
|
-
"updated"
|
|
72129
|
-
]);
|
|
72130
|
-
var DeclareKindResultSchema = object({
|
|
72131
|
-
kindId: number$1().int(),
|
|
72132
|
-
state: RecordKindStateSchema,
|
|
72133
|
-
createdIndexes: array(string())
|
|
72134
|
-
});
|
|
72135
|
-
/** Keychain refs a write released (the old marker is gone from the row): the owner deletes the values. */
|
|
72136
|
-
var SecretRefsList = array(string());
|
|
72137
|
-
/**
|
|
72138
|
-
* One op of a `transact`: the single-row writes, each on a kind of the caller.
|
|
72139
|
-
* All ops of one call share ONE database file and ONE `transactFamily`.
|
|
72140
|
-
*/
|
|
72141
|
-
var RecordTransactOpSchema = discriminatedUnion("op", [
|
|
72142
|
-
object({
|
|
72143
|
-
op: literal("put"),
|
|
72144
|
-
kind: RecordKindNameSchema,
|
|
72145
|
-
record: RecordInputSchema,
|
|
72146
|
-
ifRev: number$1().int().min(0).optional()
|
|
72147
|
-
}),
|
|
72148
|
-
object({
|
|
72149
|
-
op: literal("patch"),
|
|
72150
|
-
kind: RecordKindNameSchema,
|
|
72151
|
-
id: RecordIdSchema,
|
|
72152
|
-
data: RecordBodySchema,
|
|
72153
|
-
at: number$1().int().nullable().optional(),
|
|
72154
|
-
expiresAt: number$1().int().nullable().optional(),
|
|
72155
|
-
ifRev: number$1().int().min(1).optional()
|
|
72156
|
-
}),
|
|
72157
|
-
object({
|
|
72158
|
-
op: literal("delete"),
|
|
72159
|
-
kind: RecordKindNameSchema,
|
|
72160
|
-
id: RecordIdSchema
|
|
72161
|
-
})
|
|
72162
|
-
]);
|
|
72163
|
-
var RecordSecretRefsSchema = record(string(), record(string(), string()));
|
|
72164
|
-
method(object({ kinds: record(RecordKindNameSchema, RecordKindDeclarationSchema) }), object({ kinds: record(string(), DeclareKindResultSchema) }), {
|
|
72165
|
-
kind: "mutation",
|
|
72166
|
-
auth: "admin"
|
|
72167
|
-
}), method(object({
|
|
72168
|
-
...KindShape,
|
|
72169
|
-
id: RecordIdSchema
|
|
72170
|
-
}), RecordViewSchema.nullable(), { auth: "admin" }), method(object({
|
|
72171
|
-
...KindShape,
|
|
72172
|
-
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
72173
|
-
}), array(RecordViewSchema), { auth: "admin" }), method(object({
|
|
72174
|
-
...KindShape,
|
|
72175
|
-
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
72176
|
-
}), RecordSecretRefsSchema, { auth: "admin" }), method(object({
|
|
72177
|
-
...KindShape,
|
|
72178
|
-
record: RecordInputSchema,
|
|
72179
|
-
/** Compare-and-set. `0` means "the row must not exist". */
|
|
72180
|
-
ifRev: number$1().int().min(0).optional()
|
|
72181
|
-
}), object({
|
|
72182
|
-
rev: number$1().int(),
|
|
72183
|
-
releasedRefs: SecretRefsList
|
|
72184
|
-
}), {
|
|
72185
|
-
kind: "mutation",
|
|
72186
|
-
auth: "admin"
|
|
72187
|
-
}), method(object({
|
|
72188
|
-
...KindShape,
|
|
72189
|
-
records: array(RecordInputSchema).min(1).max(RECORD_BATCH_MAX)
|
|
72190
|
-
}), object({
|
|
72191
|
-
count: number$1().int(),
|
|
72192
|
-
releasedRefs: SecretRefsList
|
|
72193
|
-
}), {
|
|
72194
|
-
kind: "mutation",
|
|
72195
|
-
auth: "admin"
|
|
72196
|
-
}), method(object({
|
|
72197
|
-
...KindShape,
|
|
72198
|
-
id: RecordIdSchema,
|
|
72199
|
-
data: RecordBodySchema,
|
|
72200
|
-
at: number$1().int().nullable().optional(),
|
|
72201
|
-
expiresAt: number$1().int().nullable().optional(),
|
|
72202
|
-
ifRev: number$1().int().min(1).optional()
|
|
72203
|
-
}), object({
|
|
72204
|
-
rev: number$1().int(),
|
|
72205
|
-
releasedRefs: SecretRefsList
|
|
72206
|
-
}), {
|
|
72207
|
-
kind: "mutation",
|
|
72208
|
-
auth: "admin"
|
|
72209
|
-
}), method(object({
|
|
72210
|
-
...KindShape,
|
|
72211
|
-
id: RecordIdSchema
|
|
72212
|
-
}), object({
|
|
72213
|
-
deleted: boolean(),
|
|
72214
|
-
secretRefs: SecretRefsList
|
|
72215
|
-
}), {
|
|
72216
|
-
kind: "mutation",
|
|
72217
|
-
auth: "admin"
|
|
72218
|
-
}), method(object({
|
|
72219
|
-
...KindShape,
|
|
72220
|
-
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
72221
|
-
}), object({
|
|
72222
|
-
deleted: number$1().int(),
|
|
72223
|
-
secretRefs: SecretRefsList
|
|
72224
|
-
}), {
|
|
72225
|
-
kind: "mutation",
|
|
72226
|
-
auth: "admin"
|
|
72227
|
-
}), method(RecordQueryInputSchema, RecordQueryResultSchema, { auth: "admin" }), method(object({
|
|
72228
|
-
...KindShape,
|
|
72229
|
-
...TimeWindowShape,
|
|
72230
|
-
where: RecordWhereSchema.optional()
|
|
72231
|
-
}), object({ count: number$1().int() }), { auth: "admin" }), method(object({
|
|
72232
|
-
...KindShape,
|
|
72233
|
-
deviceId: number$1().int().optional(),
|
|
72234
|
-
from: number$1(),
|
|
72235
|
-
to: number$1(),
|
|
72236
|
-
bucketMs: number$1().int().positive()
|
|
72237
|
-
}), object({ buckets: array(object({
|
|
72238
|
-
bucketStart: number$1(),
|
|
72239
|
-
count: number$1().int()
|
|
72240
|
-
})) }), { auth: "admin" }), method(object({
|
|
72241
|
-
...KindShape,
|
|
72242
|
-
op: _enum([
|
|
72243
|
-
"sum",
|
|
72244
|
-
"min",
|
|
72245
|
-
"max",
|
|
72246
|
-
"count"
|
|
72247
|
-
]),
|
|
72248
|
-
field: RecordFieldPathSchema.optional(),
|
|
72249
|
-
groupBy: RecordFieldPathSchema.optional(),
|
|
72250
|
-
where: RecordWhereSchema.optional()
|
|
72251
|
-
}), object({ rows: array(object({
|
|
72252
|
-
group: union([
|
|
72253
|
-
string(),
|
|
72254
|
-
number$1(),
|
|
72255
|
-
_null()
|
|
72256
|
-
]),
|
|
72257
|
-
value: number$1().nullable()
|
|
72258
|
-
})) }), { auth: "admin" }), method(object({
|
|
72259
|
-
...KindShape,
|
|
72260
|
-
deviceId: number$1().int().optional(),
|
|
72261
|
-
atBefore: number$1().optional(),
|
|
72262
|
-
where: RecordWhereSchema.optional()
|
|
72263
|
-
}), object({
|
|
72264
|
-
deleted: number$1().int(),
|
|
72265
|
-
secretRefs: SecretRefsList
|
|
72266
|
-
}), {
|
|
72267
|
-
kind: "mutation",
|
|
72268
|
-
auth: "admin"
|
|
72269
|
-
}), method(object({ ops: array(RecordTransactOpSchema).min(1).max(RECORD_BATCH_MAX) }), object({
|
|
72270
|
-
results: array(object({
|
|
72271
|
-
rev: number$1().int().nullable(),
|
|
72272
|
-
deleted: boolean()
|
|
72273
|
-
})),
|
|
72274
|
-
releasedRefs: SecretRefsList,
|
|
72275
|
-
secretRefs: SecretRefsList
|
|
72276
|
-
}), {
|
|
72277
|
-
kind: "mutation",
|
|
72278
|
-
auth: "admin"
|
|
72279
|
-
}), method(object({
|
|
72280
|
-
deviceId: number$1().int(),
|
|
72281
|
-
kinds: array(RecordKindNameSchema).optional()
|
|
72282
|
-
}), object({
|
|
72283
|
-
deleted: record(string(), number$1().int()),
|
|
72284
|
-
secretRefs: SecretRefsList
|
|
72285
|
-
}), {
|
|
72286
|
-
kind: "mutation",
|
|
72287
|
-
auth: "admin"
|
|
72288
|
-
}), method(object(KindShape), object({ id: number$1().int() }), {
|
|
72289
|
-
kind: "mutation",
|
|
72290
|
-
auth: "admin"
|
|
72291
|
-
});
|
|
72292
|
-
/**
|
|
72293
72450
|
* record-entries - the ONE admin surface for every list of user-created entries
|
|
72294
72451
|
* (D881): notification targets, brokers, LLM profiles, anything an addon
|
|
72295
72452
|
* declares as an ENTRY kind (`entry: { title }` in `defineRecordKinds`).
|
|
@@ -82017,7 +82174,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
82017
82174
|
* `deviceClasses` map (first registered type). `null` when manual creation is
|
|
82018
82175
|
* unsupported. Lets the Add-Device dialog pick the right type per provider.
|
|
82019
82176
|
*/
|
|
82020
|
-
async getManualCreationType() {
|
|
82177
|
+
async getManualCreationType(_input) {
|
|
82021
82178
|
if (!await this.supportsManualCreation()) return { deviceType: null };
|
|
82022
82179
|
return { deviceType: Object.values(DeviceType).find((t) => this.deviceClasses[t] !== void 0) ?? null };
|
|
82023
82180
|
}
|
|
@@ -82028,7 +82185,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
82028
82185
|
return true;
|
|
82029
82186
|
}
|
|
82030
82187
|
async getChildCreationSchema(input) {
|
|
82031
|
-
return this.onGetCreationSchema(input.type);
|
|
82188
|
+
return this.onGetCreationSchema(input.type, { integrationId: input.integrationId });
|
|
82032
82189
|
}
|
|
82033
82190
|
/**
|
|
82034
82191
|
* Default kernel-orchestrated `createDevice` implementation. The
|
|
@@ -82040,7 +82197,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
82040
82197
|
* `deviceClasses` instead.
|
|
82041
82198
|
*/
|
|
82042
82199
|
async createDevice(input) {
|
|
82043
|
-
const spec = await this.onCreateDevice(input.type, input.config);
|
|
82200
|
+
const spec = await this.onCreateDevice(input.type, input.config, { ...input.integrationId !== void 0 ? { integrationId: input.integrationId } : {} });
|
|
82044
82201
|
const Class = this.deviceClasses[spec.meta.type];
|
|
82045
82202
|
if (!Class) throw new Error(`${this.providerName} provider: no device class registered for type "${spec.meta.type}" — add it to the deviceClasses map`);
|
|
82046
82203
|
const stableId = this.generateStableId(spec.meta.type, spec.config);
|
|
@@ -83449,20 +83606,6 @@ Object.freeze({
|
|
|
83449
83606
|
access: "create",
|
|
83450
83607
|
role: "viewer"
|
|
83451
83608
|
},
|
|
83452
|
-
"broker.add": {
|
|
83453
|
-
capName: "broker",
|
|
83454
|
-
capScope: "system",
|
|
83455
|
-
addonId: null,
|
|
83456
|
-
access: "create",
|
|
83457
|
-
role: "admin"
|
|
83458
|
-
},
|
|
83459
|
-
"broker.get": {
|
|
83460
|
-
capName: "broker",
|
|
83461
|
-
capScope: "system",
|
|
83462
|
-
addonId: null,
|
|
83463
|
-
access: "view",
|
|
83464
|
-
role: "admin"
|
|
83465
|
-
},
|
|
83466
83609
|
"broker.getBrokerConfig": {
|
|
83467
83610
|
capName: "broker",
|
|
83468
83611
|
capScope: "system",
|
|
@@ -83470,20 +83613,6 @@ Object.freeze({
|
|
|
83470
83613
|
access: "view",
|
|
83471
83614
|
role: "admin"
|
|
83472
83615
|
},
|
|
83473
|
-
"broker.getSettings": {
|
|
83474
|
-
capName: "broker",
|
|
83475
|
-
capScope: "system",
|
|
83476
|
-
addonId: null,
|
|
83477
|
-
access: "view",
|
|
83478
|
-
role: "admin"
|
|
83479
|
-
},
|
|
83480
|
-
"broker.getSettingsSchema": {
|
|
83481
|
-
capName: "broker",
|
|
83482
|
-
capScope: "system",
|
|
83483
|
-
addonId: null,
|
|
83484
|
-
access: "view",
|
|
83485
|
-
role: "admin"
|
|
83486
|
-
},
|
|
83487
83616
|
"broker.getState": {
|
|
83488
83617
|
capName: "broker",
|
|
83489
83618
|
capScope: "system",
|
|
@@ -83498,20 +83627,6 @@ Object.freeze({
|
|
|
83498
83627
|
access: "view",
|
|
83499
83628
|
role: "admin"
|
|
83500
83629
|
},
|
|
83501
|
-
"broker.list": {
|
|
83502
|
-
capName: "broker",
|
|
83503
|
-
capScope: "system",
|
|
83504
|
-
addonId: null,
|
|
83505
|
-
access: "view",
|
|
83506
|
-
role: "admin"
|
|
83507
|
-
},
|
|
83508
|
-
"broker.listProviders": {
|
|
83509
|
-
capName: "broker",
|
|
83510
|
-
capScope: "system",
|
|
83511
|
-
addonId: null,
|
|
83512
|
-
access: "view",
|
|
83513
|
-
role: "admin"
|
|
83514
|
-
},
|
|
83515
83630
|
"broker.publish": {
|
|
83516
83631
|
capName: "broker",
|
|
83517
83632
|
capScope: "system",
|
|
@@ -83519,20 +83634,6 @@ Object.freeze({
|
|
|
83519
83634
|
access: "create",
|
|
83520
83635
|
role: "admin"
|
|
83521
83636
|
},
|
|
83522
|
-
"broker.remove": {
|
|
83523
|
-
capName: "broker",
|
|
83524
|
-
capScope: "system",
|
|
83525
|
-
addonId: null,
|
|
83526
|
-
access: "delete",
|
|
83527
|
-
role: "admin"
|
|
83528
|
-
},
|
|
83529
|
-
"broker.setSettings": {
|
|
83530
|
-
capName: "broker",
|
|
83531
|
-
capScope: "system",
|
|
83532
|
-
addonId: null,
|
|
83533
|
-
access: "create",
|
|
83534
|
-
role: "admin"
|
|
83535
|
-
},
|
|
83536
83637
|
"broker.subscribe": {
|
|
83537
83638
|
capName: "broker",
|
|
83538
83639
|
capScope: "system",
|
|
@@ -83540,20 +83641,6 @@ Object.freeze({
|
|
|
83540
83641
|
access: "create",
|
|
83541
83642
|
role: "admin"
|
|
83542
83643
|
},
|
|
83543
|
-
"broker.testConnection": {
|
|
83544
|
-
capName: "broker",
|
|
83545
|
-
capScope: "system",
|
|
83546
|
-
addonId: null,
|
|
83547
|
-
access: "create",
|
|
83548
|
-
role: "admin"
|
|
83549
|
-
},
|
|
83550
|
-
"broker.testSettings": {
|
|
83551
|
-
capName: "broker",
|
|
83552
|
-
capScope: "system",
|
|
83553
|
-
addonId: null,
|
|
83554
|
-
access: "create",
|
|
83555
|
-
role: "admin"
|
|
83556
|
-
},
|
|
83557
83644
|
"broker.unsubscribe": {
|
|
83558
83645
|
capName: "broker",
|
|
83559
83646
|
capScope: "system",
|
|
@@ -88517,6 +88604,13 @@ Object.freeze({
|
|
|
88517
88604
|
access: "view",
|
|
88518
88605
|
role: "viewer"
|
|
88519
88606
|
},
|
|
88607
|
+
"pipelineAnalytics.getMediaLocationCensus": {
|
|
88608
|
+
capName: "pipeline-analytics",
|
|
88609
|
+
capScope: "device",
|
|
88610
|
+
addonId: null,
|
|
88611
|
+
access: "view",
|
|
88612
|
+
role: "admin"
|
|
88613
|
+
},
|
|
88520
88614
|
"pipelineAnalytics.getMediaReclaimStatus": {
|
|
88521
88615
|
capName: "pipeline-analytics",
|
|
88522
88616
|
capScope: "device",
|
|
@@ -97767,6 +97861,15 @@ function rewriteLocation(location, currentUpstream, route) {
|
|
|
97767
97861
|
/** Characters that may precede a URL that STARTS a value (never `=`, `?`, `&`, `%3D`). */
|
|
97768
97862
|
var VALUE_START = String.raw`(?<=^|["'(\s>,\[{])`;
|
|
97769
97863
|
/**
|
|
97864
|
+
* Subresource Integrity pins the bytes of a script or stylesheet. The proxy rewrites vendor
|
|
97865
|
+
* URLs inside those bytes (and re-serves them from another origin), so the hash can no longer
|
|
97866
|
+
* match and the browser refuses the file: the page renders with no script at all. The hub
|
|
97867
|
+
* already drops the CSP that would have enforced the same pin; the attribute goes with it.
|
|
97868
|
+
*/
|
|
97869
|
+
function stripIntegrity(html) {
|
|
97870
|
+
return html.replace(/\s+integrity\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, "");
|
|
97871
|
+
}
|
|
97872
|
+
/**
|
|
97770
97873
|
* Rewrite vendor URLs in a text body: absolute (`https://host/…`), protocol-relative
|
|
97771
97874
|
* (`//host/…`) and JSON-escaped (`https:\/\/host\/…`) when they start a value; and, in
|
|
97772
97875
|
* HTML/CSS, root-relative references (`href="/x"`, `url(/x)`) which would otherwise
|
|
@@ -97782,6 +97885,7 @@ function rewriteBody(body, kind, currentUpstream, route) {
|
|
|
97782
97885
|
const escaped = new RegExp(`${VALUE_START}(?:https?:)?\\\\/\\\\/${host}(?=\\\\/|["'?#\\s)\\]},<]|$)`, "g");
|
|
97783
97886
|
out = out.replace(escaped, escapedBase);
|
|
97784
97887
|
}
|
|
97888
|
+
if (kind === "html") out = stripIntegrity(out);
|
|
97785
97889
|
const current = `${route.basePath}/${currentUpstream.host}/`;
|
|
97786
97890
|
const notMounted = `(?!/)(?!${escapeRegex(route.basePath.slice(1))}/)`;
|
|
97787
97891
|
if (kind === "html") {
|
|
@@ -98406,6 +98510,232 @@ object({
|
|
|
98406
98510
|
delay: number$1().nonnegative().optional(),
|
|
98407
98511
|
commands: record(string(), unknown())
|
|
98408
98512
|
});
|
|
98513
|
+
var InstanceInUseError = class extends Error {
|
|
98514
|
+
instanceId;
|
|
98515
|
+
users;
|
|
98516
|
+
name = "InstanceInUseError";
|
|
98517
|
+
constructor(instanceId, users) {
|
|
98518
|
+
super(`instance '${instanceId}' is still in use by ${users.map((u) => `${u.kind} '${u.name ?? u.id}' (${u.id})`).join(", ")}; unlink them first`);
|
|
98519
|
+
this.instanceId = instanceId;
|
|
98520
|
+
this.users = users;
|
|
98521
|
+
}
|
|
98522
|
+
};
|
|
98523
|
+
/** Throw {@link InstanceInUseError} when any finder names a user of `instanceId`. */
|
|
98524
|
+
async function assertInstanceUnused(instanceId, finders) {
|
|
98525
|
+
const users = [];
|
|
98526
|
+
for (const finder of finders) users.push(...await finder.find(instanceId));
|
|
98527
|
+
if (users.length > 0) throw new InstanceInUseError(instanceId, users);
|
|
98528
|
+
}
|
|
98529
|
+
/** The `instance-select` field of a link, keyed like the body field it fills. */
|
|
98530
|
+
function linkFieldDescriptor(key, target, labels) {
|
|
98531
|
+
return {
|
|
98532
|
+
type: "instance-select",
|
|
98533
|
+
key,
|
|
98534
|
+
label: labels.label,
|
|
98535
|
+
...labels.description !== void 0 ? { description: labels.description } : {},
|
|
98536
|
+
required: labels.required ?? true,
|
|
98537
|
+
...labels.noneLabel !== void 0 ? { noneLabel: labels.noneLabel } : {},
|
|
98538
|
+
functionCap: target.cap,
|
|
98539
|
+
...target.kind !== void 0 ? { kind: target.kind } : {}
|
|
98540
|
+
};
|
|
98541
|
+
}
|
|
98542
|
+
/** @deprecated use {@link InstanceInUseError}; this one only adds the old `brokerId` name. */
|
|
98543
|
+
var BrokerInUseError = class extends InstanceInUseError {
|
|
98544
|
+
get brokerId() {
|
|
98545
|
+
return this.instanceId;
|
|
98546
|
+
}
|
|
98547
|
+
};
|
|
98548
|
+
/** @deprecated use {@link assertInstanceUnused}. */
|
|
98549
|
+
async function assertBrokerUnused(brokerId, finders) {
|
|
98550
|
+
try {
|
|
98551
|
+
await assertInstanceUnused(brokerId, finders);
|
|
98552
|
+
} catch (err) {
|
|
98553
|
+
if (err instanceof InstanceInUseError) throw new BrokerInUseError(err.instanceId, err.users);
|
|
98554
|
+
throw err;
|
|
98555
|
+
}
|
|
98556
|
+
}
|
|
98557
|
+
/** @deprecated use `guardInstanceDelete`. */
|
|
98558
|
+
function guardBrokerDelete(base, users) {
|
|
98559
|
+
return {
|
|
98560
|
+
...base,
|
|
98561
|
+
deleteInstance: async (input) => {
|
|
98562
|
+
await assertBrokerUnused(input.id, users());
|
|
98563
|
+
return base.deleteInstance(input);
|
|
98564
|
+
}
|
|
98565
|
+
};
|
|
98566
|
+
}
|
|
98567
|
+
/**
|
|
98568
|
+
* The generic binding of a service to a broker (D1012). A service (an integration today; a
|
|
98569
|
+
* notifier target, a calendar source or an exporter later) names the broker it runs through by id
|
|
98570
|
+
* and keeps only its OWN link settings: never a copy of the broker's host, sign-in or secrets,
|
|
98571
|
+
* which stay with the broker and are edited on the Brokers page.
|
|
98572
|
+
*
|
|
98573
|
+
* One primitive serves every consumer:
|
|
98574
|
+
* - {@link declareBrokerLinkKind}: the record kind, body `{ name, brokerId, ...linkFields }`;
|
|
98575
|
+
* - {@link buildBrokerLinkForm}: the form, a `broker-select` picker plus the service's link
|
|
98576
|
+
* sections, which may be DYNAMIC (a list read from the chosen broker, like HA calendars);
|
|
98577
|
+
* - {@link buildBrokerLinkInstances}: the `integration-instances` provider of an integration;
|
|
98578
|
+
* - {@link resolveBrokerId} / {@link brokerLinkUserFinder}: which broker a record names and which
|
|
98579
|
+
* records name a broker, so a delete of a broker still in use can name its users generically.
|
|
98580
|
+
*/
|
|
98581
|
+
/** The record kind an integration is stored under. */
|
|
98582
|
+
var BROKER_LINK_KIND_NAME = "connections";
|
|
98583
|
+
/** 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. */
|
|
98584
|
+
var BrokerLinkBodySchema = entryBody({ brokerId: string() });
|
|
98585
|
+
function declareBrokerLinkKind(spec) {
|
|
98586
|
+
return defineEntryKind(BROKER_LINK_KIND_NAME, {
|
|
98587
|
+
title: spec.title,
|
|
98588
|
+
schema: BrokerLinkBodySchema.extend(spec.linkFields ?? {}),
|
|
98589
|
+
writeTrigger: spec.writeTrigger
|
|
98590
|
+
});
|
|
98591
|
+
}
|
|
98592
|
+
/** The form: a picker over the brokers of `brokerKind`, then the service's link sections. */
|
|
98593
|
+
async function buildBrokerLinkForm(spec, brokerId) {
|
|
98594
|
+
const dynamic = brokerId !== void 0 && spec.dynamicSections !== void 0 ? await spec.dynamicSections(brokerId) : [];
|
|
98595
|
+
return { sections: [
|
|
98596
|
+
{
|
|
98597
|
+
id: "broker-link",
|
|
98598
|
+
title: spec.brokerLabel,
|
|
98599
|
+
description: `Each integration links to a ${spec.brokerLabel} connection (Connectivity → Brokers). The connection and its sign-in are edited there.`,
|
|
98600
|
+
columns: 1,
|
|
98601
|
+
fields: [{
|
|
98602
|
+
type: "broker-select",
|
|
98603
|
+
key: "brokerId",
|
|
98604
|
+
label: spec.brokerLabel,
|
|
98605
|
+
brokerKind: spec.brokerKind
|
|
98606
|
+
}]
|
|
98607
|
+
},
|
|
98608
|
+
...spec.linkSections ?? [],
|
|
98609
|
+
...dynamic
|
|
98610
|
+
] };
|
|
98611
|
+
}
|
|
98612
|
+
/** The broker a record names; undefined while it names none. */
|
|
98613
|
+
function resolveBrokerId(record) {
|
|
98614
|
+
return record.brokerId === void 0 || record.brokerId === "" ? void 0 : record.brokerId;
|
|
98615
|
+
}
|
|
98616
|
+
/** The broker an integration links to, from its record. */
|
|
98617
|
+
async function brokerIdOf(instances, integrationId) {
|
|
98618
|
+
const body = await instances.getResolved(integrationId);
|
|
98619
|
+
return body === null ? void 0 : resolveBrokerId(body);
|
|
98620
|
+
}
|
|
98621
|
+
/** Every record of the kind that names `brokerId`, as users of that broker. */
|
|
98622
|
+
function brokerLinkUserFinder(instances, userKind = "integration") {
|
|
98623
|
+
return { find: async (brokerId) => (await instances.listResolved()).filter((entry) => resolveBrokerId(entry.body) === brokerId).map((entry) => ({
|
|
98624
|
+
kind: userKind,
|
|
98625
|
+
id: entry.id,
|
|
98626
|
+
name: entry.body.name
|
|
98627
|
+
})) };
|
|
98628
|
+
}
|
|
98629
|
+
/** The `integration-instances` provider of an addon whose integrations link to a broker. */
|
|
98630
|
+
function buildBrokerLinkInstances(records, spec) {
|
|
98631
|
+
const instances = new RecordIntegrationInstances(records, spec.kind, {
|
|
98632
|
+
description: {
|
|
98633
|
+
role: "devices",
|
|
98634
|
+
instanceMode: "multiple",
|
|
98635
|
+
kind: "connection",
|
|
98636
|
+
canTest: false
|
|
98637
|
+
},
|
|
98638
|
+
formSchema: async (id) => buildBrokerLinkForm(spec, id === null ? void 0 : await brokerIdOf(instances, id)),
|
|
98639
|
+
initialBody: (name) => spec.kind.schema.parse({
|
|
98640
|
+
name,
|
|
98641
|
+
brokerId: ""
|
|
98642
|
+
}),
|
|
98643
|
+
configured: (body) => resolveBrokerId(body) !== void 0,
|
|
98644
|
+
...spec.onChanged !== void 0 ? { onChanged: spec.onChanged } : {}
|
|
98645
|
+
});
|
|
98646
|
+
return instances;
|
|
98647
|
+
}
|
|
98648
|
+
var BROKER_ROWS_MAX = 1e3;
|
|
98649
|
+
var STRAY_REGISTRY_ID = /^int_\d+$/;
|
|
98650
|
+
async function moveBrokerLinksToConnections(ports) {
|
|
98651
|
+
const brokers = await ports.listBrokers();
|
|
98652
|
+
const strays = brokers.filter((b) => STRAY_REGISTRY_ID.test(b.id));
|
|
98653
|
+
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(", ")}`);
|
|
98654
|
+
let moved = 0;
|
|
98655
|
+
for (const broker of brokers) {
|
|
98656
|
+
const integrationId = broker.integrationId;
|
|
98657
|
+
if (integrationId === void 0 || integrationId === "") continue;
|
|
98658
|
+
const existing = await ports.readConnection(integrationId);
|
|
98659
|
+
if (existing === null) await ports.putConnection(integrationId, {
|
|
98660
|
+
name: broker.name,
|
|
98661
|
+
brokerId: broker.id
|
|
98662
|
+
});
|
|
98663
|
+
else if (existing.brokerId !== broker.id) {
|
|
98664
|
+
ports.warn(`integration '${integrationId}' already names broker '${existing.brokerId ?? "none"}', not '${broker.id}'; the link stays on the broker`);
|
|
98665
|
+
continue;
|
|
98666
|
+
}
|
|
98667
|
+
if ((await ports.readConnection(integrationId))?.brokerId !== broker.id) throw new Error(`integration '${integrationId}': the link did not read back - broker '${broker.id}' keeps it`);
|
|
98668
|
+
await ports.clearBrokerLink(broker.id);
|
|
98669
|
+
moved += 1;
|
|
98670
|
+
}
|
|
98671
|
+
return {
|
|
98672
|
+
outcome: moved === 0 ? "already-done" : "done",
|
|
98673
|
+
summary: {
|
|
98674
|
+
brokers: brokers.length,
|
|
98675
|
+
moved,
|
|
98676
|
+
strayIntegrationRows: strays.length
|
|
98677
|
+
}
|
|
98678
|
+
};
|
|
98679
|
+
}
|
|
98680
|
+
/** The ports over the two record kinds of the addon. */
|
|
98681
|
+
function brokerLinkPortsOver(deps) {
|
|
98682
|
+
return {
|
|
98683
|
+
warn: deps.warn,
|
|
98684
|
+
listBrokers: async () => {
|
|
98685
|
+
return (await deps.brokers.query({ limit: BROKER_ROWS_MAX })).records.map((row) => {
|
|
98686
|
+
const name = row.data["name"];
|
|
98687
|
+
const link = row.data["integrationId"];
|
|
98688
|
+
return {
|
|
98689
|
+
id: row.id,
|
|
98690
|
+
name: typeof name === "string" && name.length > 0 ? name : row.id,
|
|
98691
|
+
integrationId: typeof link === "string" ? link : void 0
|
|
98692
|
+
};
|
|
98693
|
+
});
|
|
98694
|
+
},
|
|
98695
|
+
readConnection: async (id) => {
|
|
98696
|
+
const row = await deps.connections.get(id);
|
|
98697
|
+
return row === null ? null : { brokerId: row.data.brokerId };
|
|
98698
|
+
},
|
|
98699
|
+
putConnection: async (id, body) => {
|
|
98700
|
+
await deps.connections.put({
|
|
98701
|
+
id,
|
|
98702
|
+
data: body
|
|
98703
|
+
}, { ifRev: 0 });
|
|
98704
|
+
},
|
|
98705
|
+
clearBrokerLink: async (brokerId) => {
|
|
98706
|
+
await deps.brokers.patch(brokerId, { integrationId: null });
|
|
98707
|
+
}
|
|
98708
|
+
};
|
|
98709
|
+
}
|
|
98710
|
+
//#endregion
|
|
98711
|
+
//#region src/speak/constants.ts
|
|
98712
|
+
/** Echo speech limits and the worker restart bound (same window as CrashSupervisor). */
|
|
98713
|
+
var ALEXA_SPEAK_ADDON_ID = "import-alexa";
|
|
98714
|
+
var ALEXA_SPEAK_KIND = "alexa-echo";
|
|
98715
|
+
/** The kind of the Amazon-account broker the Echoes are read from. */
|
|
98716
|
+
var ALEXA_BROKER_KIND = "alexa";
|
|
98717
|
+
/** The Amazon account an Echo target speaks through. */
|
|
98718
|
+
var ALEXA_ACCOUNT_LINK = {
|
|
98719
|
+
cap: "broker",
|
|
98720
|
+
kind: ALEXA_BROKER_KIND
|
|
98721
|
+
};
|
|
98722
|
+
var DEVICE_REFRESH_MS = 5 * 6e4;
|
|
98723
|
+
/** Where the addon serves the Alexa mark; `listTargetKinds` stamps it as `iconUrl`. */
|
|
98724
|
+
var ALEXA_SPEAK_ICON_URL = `/addon/${ALEXA_SPEAK_ADDON_ID}/icons/${ALEXA_SPEAK_KIND}`;
|
|
98725
|
+
/** How often an adopted Echo's player state is read (a push channel is a later phase). */
|
|
98726
|
+
var ECHO_POLL_MS = 3e4;
|
|
98727
|
+
/** One more read after a command, once Amazon has had time to apply it. */
|
|
98728
|
+
var ECHO_REREAD_MS = 1500;
|
|
98729
|
+
/** After a 429 the account is left alone for this long, doubling up to the cap. */
|
|
98730
|
+
var ECHO_RATE_BACKOFF_BASE_MS = 3e4;
|
|
98731
|
+
var ECHO_RATE_BACKOFF_MAX_MS = 5 * 6e4;
|
|
98732
|
+
/**
|
|
98733
|
+
* A command (volume, play, Do Not Disturb) that has not started within this, or not
|
|
98734
|
+
* finished, is answered with an error instead of running minutes late.
|
|
98735
|
+
*/
|
|
98736
|
+
var ECHO_COMMAND_DEADLINE_MS = 12e3;
|
|
98737
|
+
/** A poll read is stale after this; it is dropped from the queue rather than sent late. */
|
|
98738
|
+
var ECHO_READ_DEADLINE_MS = 25e3;
|
|
98409
98739
|
//#endregion
|
|
98410
98740
|
//#region src/config.ts
|
|
98411
98741
|
/**
|
|
@@ -98451,7 +98781,6 @@ var AlexaBrokerEntrySchema = object({
|
|
|
98451
98781
|
id: string().min(1),
|
|
98452
98782
|
name: string().min(1),
|
|
98453
98783
|
connection: alexaConnectionSchema,
|
|
98454
|
-
integrationId: string().optional(),
|
|
98455
98784
|
/** Absent means on; the instances page switches a connection off without deleting it. */
|
|
98456
98785
|
enabled: boolean().optional()
|
|
98457
98786
|
});
|
|
@@ -98464,7 +98793,7 @@ var alexaEchoTargetSchema = object({
|
|
|
98464
98793
|
name: string(),
|
|
98465
98794
|
enabled: boolean(),
|
|
98466
98795
|
/** The broker (Amazon account) the Echo belongs to; '' until one is chosen. */
|
|
98467
|
-
brokerId:
|
|
98796
|
+
brokerId: linkField(ALEXA_ACCOUNT_LINK).default(""),
|
|
98468
98797
|
echoId: string(),
|
|
98469
98798
|
respectDnd: boolean()
|
|
98470
98799
|
});
|
|
@@ -142697,7 +143026,6 @@ var AlexaBrokerRegistry = class {
|
|
|
142697
143026
|
#onStatesPolled;
|
|
142698
143027
|
#makeManager;
|
|
142699
143028
|
#managers = /* @__PURE__ */ new Map();
|
|
142700
|
-
#integrationToBroker = /* @__PURE__ */ new Map();
|
|
142701
143029
|
#nextId = 1;
|
|
142702
143030
|
constructor(logger, deps = {}) {
|
|
142703
143031
|
this.#logger = logger;
|
|
@@ -142748,7 +143076,6 @@ var AlexaBrokerRegistry = class {
|
|
|
142748
143076
|
}
|
|
142749
143077
|
}));
|
|
142750
143078
|
this.#managers.clear();
|
|
142751
|
-
this.#integrationToBroker.clear();
|
|
142752
143079
|
}
|
|
142753
143080
|
setOnBrokerConnected(cb) {
|
|
142754
143081
|
this.#onBrokerConnected = cb;
|
|
@@ -142771,17 +143098,14 @@ var AlexaBrokerRegistry = class {
|
|
|
142771
143098
|
const entry = {
|
|
142772
143099
|
id,
|
|
142773
143100
|
name,
|
|
142774
|
-
connection
|
|
142775
|
-
...opts.integrationId !== void 0 ? { integrationId: opts.integrationId } : {}
|
|
143101
|
+
connection
|
|
142776
143102
|
};
|
|
142777
143103
|
await this.#startManager(entry);
|
|
142778
|
-
if (opts.integrationId !== void 0) this.#integrationToBroker.set(opts.integrationId, id);
|
|
142779
143104
|
return entry;
|
|
142780
143105
|
}
|
|
142781
143106
|
async removeEntry(id) {
|
|
142782
143107
|
const mgr = this.#managers.get(id);
|
|
142783
143108
|
if (!mgr) return;
|
|
142784
|
-
for (const [integrationId, brokerId] of this.#integrationToBroker.entries()) if (brokerId === id) this.#integrationToBroker.delete(integrationId);
|
|
142785
143109
|
this.#managers.delete(id);
|
|
142786
143110
|
try {
|
|
142787
143111
|
await mgr.stop();
|
|
@@ -142807,9 +143131,6 @@ var AlexaBrokerRegistry = class {
|
|
|
142807
143131
|
if (!mgr) throw new Error(`AlexaBrokerRegistry: unknown broker id "${brokerId}"`);
|
|
142808
143132
|
await mgr.sendCommand(command);
|
|
142809
143133
|
}
|
|
142810
|
-
linkIntegration(integrationId, brokerId) {
|
|
142811
|
-
this.#integrationToBroker.set(integrationId, brokerId);
|
|
142812
|
-
}
|
|
142813
143134
|
list() {
|
|
142814
143135
|
return Array.from(this.#managers.values()).map((m) => m.getInfo());
|
|
142815
143136
|
}
|
|
@@ -142828,10 +143149,7 @@ var AlexaBrokerRegistry = class {
|
|
|
142828
143149
|
return count;
|
|
142829
143150
|
}
|
|
142830
143151
|
async #startManager(entry) {
|
|
142831
|
-
if (this.#managers.has(entry.id))
|
|
142832
|
-
if (entry.integrationId !== void 0) this.#integrationToBroker.set(entry.integrationId, entry.id);
|
|
142833
|
-
return;
|
|
142834
|
-
}
|
|
143152
|
+
if (this.#managers.has(entry.id)) return;
|
|
142835
143153
|
const mgr = this.#makeManager({
|
|
142836
143154
|
id: entry.id,
|
|
142837
143155
|
name: entry.name,
|
|
@@ -142844,7 +143162,6 @@ var AlexaBrokerRegistry = class {
|
|
|
142844
143162
|
onStatesPolled: (id, values) => this.#onStatesPolled(id, values)
|
|
142845
143163
|
});
|
|
142846
143164
|
this.#managers.set(entry.id, mgr);
|
|
142847
|
-
if (entry.integrationId !== void 0) this.#integrationToBroker.set(entry.integrationId, entry.id);
|
|
142848
143165
|
await mgr.start();
|
|
142849
143166
|
}
|
|
142850
143167
|
/** The next free broker id, for a record minted before its manager exists. */
|
|
@@ -142908,40 +143225,15 @@ async function removeSessionFile(dataDir, brokerId) {
|
|
|
142908
143225
|
await node_fs_promises.rm(sessionPath(dataDir, brokerId), { force: true });
|
|
142909
143226
|
}
|
|
142910
143227
|
//#endregion
|
|
142911
|
-
//#region src/speak/constants.ts
|
|
142912
|
-
/** Echo speech limits and the worker restart bound (same window as CrashSupervisor). */
|
|
142913
|
-
var ALEXA_SPEAK_ADDON_ID = "import-alexa";
|
|
142914
|
-
var ALEXA_SPEAK_KIND = "alexa-echo";
|
|
142915
|
-
/** The kind of the Amazon-account broker the Echoes are read from. */
|
|
142916
|
-
var ALEXA_BROKER_KIND = "alexa";
|
|
142917
|
-
var DEVICE_REFRESH_MS = 5 * 6e4;
|
|
142918
|
-
/** Where the addon serves the Alexa mark; `listTargetKinds` stamps it as `iconUrl`. */
|
|
142919
|
-
var ALEXA_SPEAK_ICON_URL = `/addon/${ALEXA_SPEAK_ADDON_ID}/icons/${ALEXA_SPEAK_KIND}`;
|
|
142920
|
-
/** How often an adopted Echo's player state is read (a push channel is a later phase). */
|
|
142921
|
-
var ECHO_POLL_MS = 3e4;
|
|
142922
|
-
/** One more read after a command, once Amazon has had time to apply it. */
|
|
142923
|
-
var ECHO_REREAD_MS = 1500;
|
|
142924
|
-
/** After a 429 the account is left alone for this long, doubling up to the cap. */
|
|
142925
|
-
var ECHO_RATE_BACKOFF_BASE_MS = 3e4;
|
|
142926
|
-
var ECHO_RATE_BACKOFF_MAX_MS = 5 * 6e4;
|
|
142927
|
-
/**
|
|
142928
|
-
* A command (volume, play, Do Not Disturb) that has not started within this, or not
|
|
142929
|
-
* finished, is answered with an error instead of running minutes late.
|
|
142930
|
-
*/
|
|
142931
|
-
var ECHO_COMMAND_DEADLINE_MS = 12e3;
|
|
142932
|
-
/** A poll read is stale after this; it is dropped from the queue rather than sent late. */
|
|
142933
|
-
var ECHO_READ_DEADLINE_MS = 25e3;
|
|
142934
|
-
//#endregion
|
|
142935
143228
|
//#region src/alexa-broker-provider.ts
|
|
142936
143229
|
/**
|
|
142937
143230
|
* Construct the `broker` cap provider for the Alexa import addon. Pure builder.
|
|
142938
|
-
*
|
|
142939
|
-
*
|
|
142940
|
-
* poll-based (no pub/sub) — `publish`/`subscribe`/`getState` are no-ops.
|
|
143231
|
+
* The bus half only (D1013): creating, listing, editing and removing an account is
|
|
143232
|
+
* `integration-instances`. The cookie + refresh token are secrets, redacted on the config
|
|
143233
|
+
* read. State sync is poll-based (no pub/sub) — `publish`/`subscribe`/`getState` are no-ops.
|
|
142941
143234
|
*/
|
|
142942
143235
|
function buildAlexaBrokerProvider(deps) {
|
|
142943
|
-
const {
|
|
142944
|
-
const owns = (id) => getBrokers().some((b) => b.id === id);
|
|
143236
|
+
const { registry, getBrokers } = deps;
|
|
142945
143237
|
const redact = (entry) => ({
|
|
142946
143238
|
...entry.connection,
|
|
142947
143239
|
cookie: "",
|
|
@@ -142949,108 +143241,10 @@ function buildAlexaBrokerProvider(deps) {
|
|
|
142949
143241
|
macDms: ""
|
|
142950
143242
|
});
|
|
142951
143243
|
return {
|
|
142952
|
-
list: async ({ kind }) => {
|
|
142953
|
-
if (kind && kind !== "alexa") return [];
|
|
142954
|
-
return registry.list().map((info) => ({
|
|
142955
|
-
...info,
|
|
142956
|
-
addonId: ownerAddonId
|
|
142957
|
-
}));
|
|
142958
|
-
},
|
|
142959
|
-
get: async ({ id }) => {
|
|
142960
|
-
const info = registry.get(id);
|
|
142961
|
-
return info ? {
|
|
142962
|
-
...info,
|
|
142963
|
-
addonId: ownerAddonId
|
|
142964
|
-
} : null;
|
|
142965
|
-
},
|
|
142966
|
-
listProviders: async () => [{
|
|
142967
|
-
addonId: ownerAddonId,
|
|
142968
|
-
kinds: [{
|
|
142969
|
-
kind: ALEXA_BROKER_KIND,
|
|
142970
|
-
label: "Alexa",
|
|
142971
|
-
signIn: {
|
|
142972
|
-
basePath: "/addon/import-alexa/login",
|
|
142973
|
-
methods: ["device-code", "proxy"]
|
|
142974
|
-
}
|
|
142975
|
-
}]
|
|
142976
|
-
}],
|
|
142977
|
-
add: async ({ kind, name, settings }) => {
|
|
142978
|
-
if (kind !== "alexa") throw new Error(`import-alexa: only kind '${ALEXA_BROKER_KIND}' is handled here (got '${kind}')`);
|
|
142979
|
-
const entry = await registry.createEntry(name, settingsToAlexaConnection(settings));
|
|
142980
|
-
await persistBrokers([...getBrokers(), entry]);
|
|
142981
|
-
return { id: entry.id };
|
|
142982
|
-
},
|
|
142983
|
-
remove: async ({ id }) => {
|
|
142984
|
-
if (!owns(id)) return;
|
|
142985
|
-
await registry.removeEntry(id);
|
|
142986
|
-
await cascadeRemoveDevices(id).catch((err) => {
|
|
142987
|
-
logger.warn("alexa: broker cascade-remove threw", {
|
|
142988
|
-
tags: { brokerId: id },
|
|
142989
|
-
meta: { error: errMsg(err) }
|
|
142990
|
-
});
|
|
142991
|
-
});
|
|
142992
|
-
await persistBrokers(getBrokers().filter((b) => b.id !== id));
|
|
142993
|
-
},
|
|
142994
|
-
testConnection: async ({ id }) => {
|
|
142995
|
-
if (!owns(id)) return {
|
|
142996
|
-
ok: false,
|
|
142997
|
-
error: "unknown broker"
|
|
142998
|
-
};
|
|
142999
|
-
const info = registry.get(id);
|
|
143000
|
-
if (!info) return {
|
|
143001
|
-
ok: false,
|
|
143002
|
-
error: "unknown broker"
|
|
143003
|
-
};
|
|
143004
|
-
if (info.status === "connected") return {
|
|
143005
|
-
ok: true,
|
|
143006
|
-
latencyMs: info.lastCheckedAt ? Math.max(0, Date.now() - info.lastCheckedAt) : 0
|
|
143007
|
-
};
|
|
143008
|
-
return {
|
|
143009
|
-
ok: false,
|
|
143010
|
-
error: info.error ?? `status: ${info.status}`
|
|
143011
|
-
};
|
|
143012
|
-
},
|
|
143013
|
-
getSettings: async ({ id }) => {
|
|
143014
|
-
const entry = getBrokers().find((b) => b.id === id);
|
|
143015
|
-
return entry ? redact(entry) : null;
|
|
143016
|
-
},
|
|
143017
|
-
setSettings: async ({ id, settings }) => {
|
|
143018
|
-
if (!owns(id)) return;
|
|
143019
|
-
const existing = getBrokers().find((b) => b.id === id);
|
|
143020
|
-
if (!existing) return;
|
|
143021
|
-
const parsed = settingsToAlexaConnection(settings);
|
|
143022
|
-
const merged = {
|
|
143023
|
-
...parsed,
|
|
143024
|
-
cookie: parsed.cookie.length > 0 ? parsed.cookie : existing.connection.cookie,
|
|
143025
|
-
refreshToken: parsed.refreshToken.length > 0 ? parsed.refreshToken : existing.connection.refreshToken,
|
|
143026
|
-
macDms: parsed.macDms.length > 0 ? parsed.macDms : existing.connection.macDms
|
|
143027
|
-
};
|
|
143028
|
-
const updated = await registry.updateEntry(id, merged);
|
|
143029
|
-
await persistBrokers(getBrokers().map((b) => b.id === id ? updated : b));
|
|
143030
|
-
},
|
|
143031
143244
|
getBrokerConfig: async ({ id }) => {
|
|
143032
143245
|
const entry = getBrokers().find((b) => b.id === id);
|
|
143033
143246
|
return entry ? redact(entry) : null;
|
|
143034
143247
|
},
|
|
143035
|
-
getSettingsSchema: async ({ kind }) => {
|
|
143036
|
-
if (kind !== "alexa") return null;
|
|
143037
|
-
return buildConnectionFormSchema();
|
|
143038
|
-
},
|
|
143039
|
-
testSettings: async ({ kind, settings }) => {
|
|
143040
|
-
if (kind !== "alexa") return {
|
|
143041
|
-
ok: false,
|
|
143042
|
-
error: `unsupported kind: ${kind}`
|
|
143043
|
-
};
|
|
143044
|
-
try {
|
|
143045
|
-
settingsToAlexaConnection(settings);
|
|
143046
|
-
return { ok: true };
|
|
143047
|
-
} catch (err) {
|
|
143048
|
-
return {
|
|
143049
|
-
ok: false,
|
|
143050
|
-
error: errMsg(err)
|
|
143051
|
-
};
|
|
143052
|
-
}
|
|
143053
|
-
},
|
|
143054
143248
|
publish: async () => null,
|
|
143055
143249
|
subscribe: async () => ({ subscriptionId: "" }),
|
|
143056
143250
|
unsubscribe: async () => void 0,
|
|
@@ -143376,7 +143570,7 @@ function buildAlexaAdoptionProvider(deps) {
|
|
|
143376
143570
|
//#region src/alexa-broker-device-cascade.ts
|
|
143377
143571
|
/**
|
|
143378
143572
|
* Remove every adopted Alexa device whose persisted config carries
|
|
143379
|
-
* `{ system: 'alexa', brokerId }`. Used
|
|
143573
|
+
* `{ system: 'alexa', brokerId }`. Used when an account instance is deleted and when an integration is deleted.
|
|
143380
143574
|
* Alexa appliances are flat (standalone) devices, so this removes top-level
|
|
143381
143575
|
* devices directly. Best-effort per device; returns the count removed. Mirrors
|
|
143382
143576
|
* the Ecowitt cascade helper.
|
|
@@ -144340,6 +144534,7 @@ var alexaBrokersEntryKind = defineEntryKind("brokers", {
|
|
|
144340
144534
|
title: "Alexa import brokers",
|
|
144341
144535
|
schema: entryBody({
|
|
144342
144536
|
...alexaConnectionSchema.shape,
|
|
144537
|
+
/** Retired (D1012): the link lives on the integration's `connections` record. Only the migration reads it. */
|
|
144343
144538
|
integrationId: string().optional(),
|
|
144344
144539
|
enabled: boolean().optional()
|
|
144345
144540
|
}),
|
|
@@ -144351,20 +144546,27 @@ var alexaSpeakSessionsEntryKind = defineEntryKind("speak-sessions", {
|
|
|
144351
144546
|
schema: entryBody({ session: string().default("").meta({ secret: true }) }),
|
|
144352
144547
|
writeTrigger: "an Echo worker signs in as an Alexa account broker"
|
|
144353
144548
|
});
|
|
144549
|
+
/** An Alexa integration: the link to the Alexa account (broker) it imports devices from. */
|
|
144550
|
+
var alexaConnectionsEntryKind = declareBrokerLinkKind({
|
|
144551
|
+
title: "Alexa integrations",
|
|
144552
|
+
writeTrigger: "an operator links an Alexa integration to an Alexa account"
|
|
144553
|
+
});
|
|
144354
144554
|
/** Every kind this addon declares, keyed by its name: `records.declare` refuses any other key. */
|
|
144355
144555
|
var alexaRecordKinds = {
|
|
144356
144556
|
brokers: alexaBrokersEntryKind,
|
|
144557
|
+
connections: alexaConnectionsEntryKind,
|
|
144357
144558
|
"speak-sessions": alexaSpeakSessionsEntryKind
|
|
144358
144559
|
};
|
|
144359
144560
|
//#endregion
|
|
144360
|
-
//#region src/alexa-instances.ts
|
|
144561
|
+
//#region src/alexa-account-instances.ts
|
|
144361
144562
|
/**
|
|
144362
|
-
* The Alexa accounts as `integration-instances` (planning#408 P3): one instance per `brokers`
|
|
144363
|
-
* record, over the SAME records and ids (`alexa_NNN`) the `broker` cap serves
|
|
144563
|
+
* The Alexa accounts as `integration-instances` (planning#408 P3, D1013): one instance per `brokers`
|
|
144564
|
+
* record, over the SAME records and ids (`alexa_NNN`) the `broker` cap serves; the `account` kind of
|
|
144565
|
+
* the composed provider (the integrations are the `integration` kind, `alexa-instances.ts`). The body is flat, so
|
|
144364
144566
|
* the form the instances page edits is the stored shape. Signing in (device code or the Amazon
|
|
144365
144567
|
* login proxy) goes through the routes the addon already serves under `/addon/import-alexa/login`.
|
|
144366
144568
|
*/
|
|
144367
|
-
var
|
|
144569
|
+
var ALEXA_ACCOUNT_KIND = "account";
|
|
144368
144570
|
var ALEXA_SIGN_IN_PATH = "/addon/import-alexa/login";
|
|
144369
144571
|
function isAlexaBodyConfigured(body) {
|
|
144370
144572
|
return body.cookie.length > 0 || body.refreshToken.length > 0;
|
|
@@ -144392,21 +144594,13 @@ function statusOf(info, configured) {
|
|
|
144392
144594
|
...info.error !== null ? { detail: info.error } : {}
|
|
144393
144595
|
};
|
|
144394
144596
|
}
|
|
144395
|
-
function
|
|
144597
|
+
function buildAlexaAccountInstances(records, deps) {
|
|
144396
144598
|
return new RecordIntegrationInstances(records, alexaBrokersEntryKind, {
|
|
144397
144599
|
description: {
|
|
144398
|
-
role: "
|
|
144600
|
+
role: "services",
|
|
144399
144601
|
instanceMode: "multiple",
|
|
144400
144602
|
kind: "account",
|
|
144401
144603
|
canTest: true,
|
|
144402
|
-
kinds: [{
|
|
144403
|
-
id: ALEXA_INSTANCE_KIND,
|
|
144404
|
-
label: "Alexa",
|
|
144405
|
-
signIn: {
|
|
144406
|
-
basePath: ALEXA_SIGN_IN_PATH,
|
|
144407
|
-
methods: ["device-code", "proxy"]
|
|
144408
|
-
}
|
|
144409
|
-
}],
|
|
144410
144604
|
allowRestoreId: true
|
|
144411
144605
|
},
|
|
144412
144606
|
formSchema: () => buildConnectionFormSchema(),
|
|
@@ -144419,7 +144613,7 @@ function buildAlexaInstances(records, deps) {
|
|
|
144419
144613
|
pollIntervalMs: 6e4
|
|
144420
144614
|
}),
|
|
144421
144615
|
mintId: () => deps.mintId(),
|
|
144422
|
-
kindOf: () =>
|
|
144616
|
+
kindOf: () => ALEXA_ACCOUNT_KIND,
|
|
144423
144617
|
health: (body, id) => {
|
|
144424
144618
|
const info = deps.infoOf(id);
|
|
144425
144619
|
return {
|
|
@@ -144448,6 +144642,21 @@ function buildAlexaInstances(records, deps) {
|
|
|
144448
144642
|
});
|
|
144449
144643
|
}
|
|
144450
144644
|
//#endregion
|
|
144645
|
+
//#region src/alexa-instances.ts
|
|
144646
|
+
/**
|
|
144647
|
+
* The Alexa integrations as `integration-instances` (D1012): one `connections` record per
|
|
144648
|
+
* integration, keyed by the registry id, whose body is the link to an Alexa account (a `brokers`
|
|
144649
|
+
* record, edited on the Brokers page). The account's sign-in and secrets are never copied here.
|
|
144650
|
+
*/
|
|
144651
|
+
function buildAlexaInstances(records) {
|
|
144652
|
+
return buildBrokerLinkInstances(records, {
|
|
144653
|
+
kind: alexaConnectionsEntryKind,
|
|
144654
|
+
brokerKind: ALEXA_BROKER_KIND,
|
|
144655
|
+
brokerLabel: "Alexa account",
|
|
144656
|
+
onChanged: () => void 0
|
|
144657
|
+
});
|
|
144658
|
+
}
|
|
144659
|
+
//#endregion
|
|
144451
144660
|
//#region src/alexa-broker-store.ts
|
|
144452
144661
|
function rowToEntry(id, data) {
|
|
144453
144662
|
const parsed = AlexaBrokerEntrySchema.safeParse({
|
|
@@ -144460,7 +144669,6 @@ function rowToEntry(id, data) {
|
|
|
144460
144669
|
macDms: data.macDms,
|
|
144461
144670
|
pollIntervalMs: data.pollIntervalMs
|
|
144462
144671
|
},
|
|
144463
|
-
integrationId: data.integrationId,
|
|
144464
144672
|
enabled: data.enabled
|
|
144465
144673
|
});
|
|
144466
144674
|
return parsed.success ? parsed.data : null;
|
|
@@ -144497,11 +144705,10 @@ var AlexaBrokerStore = class AlexaBrokerStore {
|
|
|
144497
144705
|
const nextIds = new Set(entries.map((e) => e.id));
|
|
144498
144706
|
for (const row of page.records) if (!nextIds.has(row.id)) await this.kind.delete(row.id);
|
|
144499
144707
|
for (const entry of entries) {
|
|
144500
|
-
const { id, name, connection,
|
|
144708
|
+
const { id, name, connection, enabled } = entry;
|
|
144501
144709
|
const body = {
|
|
144502
144710
|
name,
|
|
144503
144711
|
...connection,
|
|
144504
|
-
...integrationId === void 0 ? {} : { integrationId },
|
|
144505
144712
|
...enabled === void 0 ? {} : { enabled }
|
|
144506
144713
|
};
|
|
144507
144714
|
const parsed = alexaBrokersEntryKind.schema.safeParse(body);
|
|
@@ -145028,6 +145235,13 @@ function speechFromNotification(notification) {
|
|
|
145028
145235
|
}
|
|
145029
145236
|
//#endregion
|
|
145030
145237
|
//#region src/speak/provider.ts
|
|
145238
|
+
/**
|
|
145239
|
+
* `notification-output` provider for Echo devices. Text is the rule template
|
|
145240
|
+
* as plain speech. A burst collapses or waits; it is not queued. A transport
|
|
145241
|
+
* failure is `success: false` so the notification outbox retries. A collapse,
|
|
145242
|
+
* a rate limit, or Do Not Disturb is a recorded skip (`success: true`) so the
|
|
145243
|
+
* outbox does not speak the same sentence again later.
|
|
145244
|
+
*/
|
|
145031
145245
|
function quietLogger$1() {
|
|
145032
145246
|
const logger = {
|
|
145033
145247
|
debug() {},
|
|
@@ -145083,11 +145297,8 @@ function configSchema() {
|
|
|
145083
145297
|
title: "Echo",
|
|
145084
145298
|
titleKey: "import-alexa:echoTarget.s_echo.title",
|
|
145085
145299
|
fields: [...[{
|
|
145086
|
-
|
|
145087
|
-
|
|
145088
|
-
label: "Amazon account",
|
|
145089
|
-
labelKey: "import-alexa:echoTarget.s_echo.brokerId.label",
|
|
145090
|
-
brokerKind: ALEXA_BROKER_KIND
|
|
145300
|
+
...linkFieldDescriptor("brokerId", ALEXA_ACCOUNT_LINK, { label: "Amazon account" }),
|
|
145301
|
+
labelKey: "import-alexa:echoTarget.s_echo.brokerId.label"
|
|
145091
145302
|
}, {
|
|
145092
145303
|
type: "select",
|
|
145093
145304
|
key: "echoId",
|
|
@@ -146084,6 +146295,21 @@ async function flattenBrokerRecords(ports) {
|
|
|
146084
146295
|
});
|
|
146085
146296
|
}
|
|
146086
146297
|
//#endregion
|
|
146298
|
+
//#region src/migrations/broker-links-to-connections.ts
|
|
146299
|
+
var BROKER_LINKS_TO_CONNECTIONS_ID = "import-alexa/2026-11-05-broker-links-to-connections";
|
|
146300
|
+
/** The broker records and the integration (`connections`) records of this addon. */
|
|
146301
|
+
function alexaBrokerLinkPorts(records, warn) {
|
|
146302
|
+
return brokerLinkPortsOver({
|
|
146303
|
+
brokers: records.kind({
|
|
146304
|
+
name: alexaBrokersEntryKind.name,
|
|
146305
|
+
declaration: alexaBrokersEntryKind.declaration,
|
|
146306
|
+
schema: record(string(), unknown())
|
|
146307
|
+
}),
|
|
146308
|
+
connections: records.kind(alexaConnectionsEntryKind),
|
|
146309
|
+
warn
|
|
146310
|
+
});
|
|
146311
|
+
}
|
|
146312
|
+
//#endregion
|
|
146087
146313
|
//#region src/migrations/strip-echo-target-volume-and-mode.ts
|
|
146088
146314
|
var RETIRED_KEYS = ["volume", "mode"];
|
|
146089
146315
|
function isRecord(value) {
|
|
@@ -146166,8 +146392,30 @@ var ADDON_MIGRATIONS = [
|
|
|
146166
146392
|
fixtureMark: "the name of the broker and the integration it already names"
|
|
146167
146393
|
},
|
|
146168
146394
|
procedure: moveAlexaIntegrationRows
|
|
146395
|
+
},
|
|
146396
|
+
{
|
|
146397
|
+
meta: {
|
|
146398
|
+
id: BROKER_LINKS_TO_CONNECTIONS_ID,
|
|
146399
|
+
owner: "import-alexa",
|
|
146400
|
+
level: "addon-state",
|
|
146401
|
+
seq: 4,
|
|
146402
|
+
dependsOn: [INTEGRATION_ROWS_TO_RECORD_ID],
|
|
146403
|
+
store: "settings",
|
|
146404
|
+
scope: "cluster",
|
|
146405
|
+
onFailure: "block-owner",
|
|
146406
|
+
bodyKind: "procedure",
|
|
146407
|
+
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",
|
|
146408
|
+
fixtureMark: "the name and the extra field of the account that keeps its record"
|
|
146409
|
+
},
|
|
146410
|
+
procedure: moveBrokerLinksToConnections
|
|
146169
146411
|
}
|
|
146170
146412
|
];
|
|
146413
|
+
/** The migrations that rewrite the broker records: they run BEFORE anything reads or persists one. */
|
|
146414
|
+
var BROKER_RECORD_MIGRATION_IDS = [
|
|
146415
|
+
FLATTEN_BROKER_RECORDS_ID,
|
|
146416
|
+
INTEGRATION_ROWS_TO_RECORD_ID,
|
|
146417
|
+
BROKER_LINKS_TO_CONNECTIONS_ID
|
|
146418
|
+
];
|
|
146171
146419
|
//#endregion
|
|
146172
146420
|
//#region src/speak/icon-routes.ts
|
|
146173
146421
|
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>";
|
|
@@ -146266,6 +146514,7 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
146266
146514
|
loginProxy = null;
|
|
146267
146515
|
brokerStore = null;
|
|
146268
146516
|
instances = null;
|
|
146517
|
+
accountInstances = null;
|
|
146269
146518
|
activeBrokers = [];
|
|
146270
146519
|
speak = null;
|
|
146271
146520
|
constructor() {
|
|
@@ -146277,12 +146526,13 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
146277
146526
|
if (records === void 0) throw new Error("import-alexa: ctx.records is required (hub-only addon)");
|
|
146278
146527
|
await records.declare(alexaRecordKinds);
|
|
146279
146528
|
this.brokerStore = AlexaBrokerStore.of(records);
|
|
146280
|
-
this.instances = buildAlexaInstances(records
|
|
146529
|
+
this.instances = buildAlexaInstances(records);
|
|
146530
|
+
this.accountInstances = buildAlexaAccountInstances(records, {
|
|
146281
146531
|
mintId: () => this.requireRegistry().allocateId(),
|
|
146282
146532
|
infoOf: (id) => this.registry?.get(id) ?? null,
|
|
146283
146533
|
onChanged: (id) => this.syncBrokerRuntime(id)
|
|
146284
146534
|
});
|
|
146285
|
-
await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, this.migrationPorts(records)), { only:
|
|
146535
|
+
await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, this.migrationPorts(records)), { only: BROKER_RECORD_MIGRATION_IDS });
|
|
146286
146536
|
this.activeBrokers = await this.loadPersistedBrokers();
|
|
146287
146537
|
await this.adoptSessionFiles();
|
|
146288
146538
|
this.registry = new AlexaBrokerRegistry(this.ctx.logger, {
|
|
@@ -146317,15 +146567,13 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
146317
146567
|
});
|
|
146318
146568
|
this.deviceCodeLogin = new AlexaDeviceCodeManager({ logger: this.ctx.logger });
|
|
146319
146569
|
this.ctx.logger.info("Alexa: import addon initialised", { meta: { brokerCount: this.activeBrokers.length } });
|
|
146320
|
-
await this.reconcileIntegrationsToBrokers();
|
|
146321
|
-
this.subscribeIntegrationLifecycle();
|
|
146322
146570
|
const loginRoutes = [...buildAlexaLoginRoutes({
|
|
146323
146571
|
logger: this.ctx.logger,
|
|
146324
146572
|
proxyLogin: this.proxyLogin,
|
|
146325
146573
|
deviceCode: this.deviceCodeLogin,
|
|
146326
146574
|
commitCookie: (input) => this.commitCapturedCookie(input)
|
|
146327
146575
|
}), ...speakIconRoutes()];
|
|
146328
|
-
await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, this.migrationPorts(records)), { only: ADDON_MIGRATIONS.map((m) => m.meta.id).filter((id) => id
|
|
146576
|
+
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)) });
|
|
146329
146577
|
const speak = new AlexaSpeakController({
|
|
146330
146578
|
logger: this.ctx.logger.child("speak"),
|
|
146331
146579
|
readConfig: () => this.config,
|
|
@@ -146357,7 +146605,7 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
146357
146605
|
},
|
|
146358
146606
|
{
|
|
146359
146607
|
capability: integrationInstancesCapability,
|
|
146360
|
-
provider: this.
|
|
146608
|
+
provider: this.buildInstancesProvider()
|
|
146361
146609
|
},
|
|
146362
146610
|
{
|
|
146363
146611
|
capability: deviceAdoptionCapability,
|
|
@@ -146382,6 +146630,7 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
146382
146630
|
await this.updateGlobalSettings({ echoTargets: [...readEchoTargets({ echoTargets: rows })] });
|
|
146383
146631
|
},
|
|
146384
146632
|
...flattenPortsOver(records.kind(alexaBrokersEntryKind)),
|
|
146633
|
+
...alexaBrokerLinkPorts(records, (message) => this.ctx.logger.warn(message)),
|
|
146385
146634
|
...alexaIntegrationRowsPorts({
|
|
146386
146635
|
api: defaultIntegrationApiOver(this.ctx.api),
|
|
146387
146636
|
records,
|
|
@@ -146390,10 +146639,42 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
146390
146639
|
})
|
|
146391
146640
|
};
|
|
146392
146641
|
}
|
|
146642
|
+
/** One provider over the two record kinds: the account (broker) and the integrations using it. */
|
|
146643
|
+
buildInstancesProvider() {
|
|
146644
|
+
if (this.accountInstances === null) throw new Error("import-alexa: account instances not ready");
|
|
146645
|
+
return composeInstanceKinds([{
|
|
146646
|
+
kind: {
|
|
146647
|
+
id: "account",
|
|
146648
|
+
label: "Alexa account",
|
|
146649
|
+
functionCap: "broker",
|
|
146650
|
+
brokerKind: ALEXA_BROKER_KIND,
|
|
146651
|
+
role: "services",
|
|
146652
|
+
signIn: {
|
|
146653
|
+
basePath: ALEXA_SIGN_IN_PATH,
|
|
146654
|
+
methods: ["device-code", "proxy"]
|
|
146655
|
+
}
|
|
146656
|
+
},
|
|
146657
|
+
provider: guardBrokerDelete(this.accountInstances.toProvider(), () => this.brokerUsers())
|
|
146658
|
+
}, {
|
|
146659
|
+
kind: {
|
|
146660
|
+
id: "integration",
|
|
146661
|
+
label: "Alexa",
|
|
146662
|
+
functionCap: "device-provider",
|
|
146663
|
+
role: "devices"
|
|
146664
|
+
},
|
|
146665
|
+
provider: this.requireInstances().toProvider()
|
|
146666
|
+
}], {
|
|
146667
|
+
role: "devices",
|
|
146668
|
+
instanceMode: "multiple",
|
|
146669
|
+
kind: "account",
|
|
146670
|
+
canTest: true,
|
|
146671
|
+
allowRestoreId: true
|
|
146672
|
+
});
|
|
146673
|
+
}
|
|
146393
146674
|
/**
|
|
146394
|
-
* Bring the runtime in line with one record after the instances page changed it: a
|
|
146395
|
-
* record stops its manager and cascades its devices, an off record only stops the
|
|
146396
|
-
* live one re-applies its connection or starts under the record's own id.
|
|
146675
|
+
* Bring the runtime in line with one account record after the instances page changed it: a
|
|
146676
|
+
* removed record stops its manager and cascades its devices, an off record only stops the
|
|
146677
|
+
* manager, a live one re-applies its connection or starts under the record's own id.
|
|
146397
146678
|
*/
|
|
146398
146679
|
async syncBrokerRuntime(id) {
|
|
146399
146680
|
const reg = this.requireRegistry();
|
|
@@ -146414,10 +146695,7 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
146414
146695
|
try {
|
|
146415
146696
|
if (reg.get(id) !== null) {
|
|
146416
146697
|
if (!sameConnection(entry.connection, reg.getConnection(id))) await reg.updateEntry(id, entry.connection);
|
|
146417
|
-
} else await reg.createEntry(entry.name, entry.connection, {
|
|
146418
|
-
id,
|
|
146419
|
-
...entry.integrationId !== void 0 ? { integrationId: entry.integrationId } : {}
|
|
146420
|
-
});
|
|
146698
|
+
} else await reg.createEntry(entry.name, entry.connection, { id });
|
|
146421
146699
|
} catch (err) {
|
|
146422
146700
|
this.ctx.logger.warn("alexa instance: could not start the account", { meta: {
|
|
146423
146701
|
brokerId: id,
|
|
@@ -146636,12 +146914,8 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
146636
146914
|
}
|
|
146637
146915
|
buildBrokerProvider() {
|
|
146638
146916
|
return buildAlexaBrokerProvider({
|
|
146639
|
-
ownerAddonId: this.ctx.id,
|
|
146640
146917
|
registry: this.requireRegistry(),
|
|
146641
|
-
getBrokers: () => this.activeBrokers
|
|
146642
|
-
persistBrokers: (brokers) => this.persistBrokerEntries(brokers),
|
|
146643
|
-
cascadeRemoveDevices: (brokerId) => this.cascadeRemoveDevicesForBroker(brokerId),
|
|
146644
|
-
logger: this.ctx.logger
|
|
146918
|
+
getBrokers: () => this.activeBrokers
|
|
146645
146919
|
});
|
|
146646
146920
|
}
|
|
146647
146921
|
/**
|
|
@@ -146678,6 +146952,14 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
146678
146952
|
} });
|
|
146679
146953
|
return { brokerId: entry.id };
|
|
146680
146954
|
}
|
|
146955
|
+
/** Who still points at an Alexa account: its integrations and the Echo targets that speak through it. */
|
|
146956
|
+
brokerUsers() {
|
|
146957
|
+
return [brokerLinkUserFinder(this.requireInstances()), { find: async (brokerId) => readEchoTargets(this.config).filter((target) => target.brokerId === brokerId).map((target) => ({
|
|
146958
|
+
kind: "notification-target",
|
|
146959
|
+
id: target.id,
|
|
146960
|
+
name: target.name
|
|
146961
|
+
})) }];
|
|
146962
|
+
}
|
|
146681
146963
|
async cascadeRemoveDevicesForBroker(brokerId) {
|
|
146682
146964
|
const reg = this.ctx.kernel.deviceRegistry;
|
|
146683
146965
|
const devices = this.ctx.kernel.devices;
|
|
@@ -146690,42 +146972,6 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
146690
146972
|
logger: this.ctx.logger
|
|
146691
146973
|
});
|
|
146692
146974
|
}
|
|
146693
|
-
async reconcileIntegrationsToBrokers() {
|
|
146694
|
-
try {
|
|
146695
|
-
const mine = (await this.ctx.api.integrations.list.query()).filter((i) => i.addonId === this.ctx.id);
|
|
146696
|
-
const surviving = new Set(mine.map((i) => i.id));
|
|
146697
|
-
for (const broker of this.activeBrokers) {
|
|
146698
|
-
const integrationId = broker.integrationId;
|
|
146699
|
-
if (integrationId === void 0 || !surviving.has(integrationId)) continue;
|
|
146700
|
-
this.requireRegistry().linkIntegration(integrationId, broker.id);
|
|
146701
|
-
}
|
|
146702
|
-
const toRemove = this.activeBrokers.filter((b) => b.integrationId !== void 0 && !surviving.has(b.integrationId)).map((b) => b.id);
|
|
146703
|
-
if (toRemove.length === 0) return;
|
|
146704
|
-
for (const id of toRemove) try {
|
|
146705
|
-
await this.requireRegistry().removeEntry(id);
|
|
146706
|
-
await this.cascadeRemoveDevicesForBroker(id);
|
|
146707
|
-
} catch (err) {
|
|
146708
|
-
this.ctx.logger.warn("Alexa integration→broker: broker cleanup failed", { meta: {
|
|
146709
|
-
brokerId: id,
|
|
146710
|
-
error: errMsg(err)
|
|
146711
|
-
} });
|
|
146712
|
-
}
|
|
146713
|
-
const nextBrokers = this.activeBrokers.filter((b) => !toRemove.includes(b.id));
|
|
146714
|
-
await this.persistBrokerEntries(nextBrokers);
|
|
146715
|
-
} catch (err) {
|
|
146716
|
-
this.ctx.logger.warn("Alexa integration→broker reconcile failed", { meta: { error: errMsg(err) } });
|
|
146717
|
-
}
|
|
146718
|
-
}
|
|
146719
|
-
subscribeIntegrationLifecycle() {
|
|
146720
|
-
const handler = (event) => {
|
|
146721
|
-
const addonId = event.data["addonId"];
|
|
146722
|
-
if (typeof addonId === "string" && addonId !== this.ctx.id) return;
|
|
146723
|
-
this.reconcileIntegrationsToBrokers();
|
|
146724
|
-
};
|
|
146725
|
-
this.ctx.eventBus.subscribe({ category: EventCategory.IntegrationEnabled }, handler);
|
|
146726
|
-
this.ctx.eventBus.subscribe({ category: EventCategory.IntegrationDisabled }, handler);
|
|
146727
|
-
this.ctx.eventBus.subscribe({ category: EventCategory.IntegrationDeleted }, handler);
|
|
146728
|
-
}
|
|
146729
146975
|
buildAdoptionProvider() {
|
|
146730
146976
|
return buildAlexaAdoptionProvider({
|
|
146731
146977
|
registry: {
|
|
@@ -146739,9 +146985,9 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
146739
146985
|
},
|
|
146740
146986
|
logger: this.ctx.logger,
|
|
146741
146987
|
getBrokerIdForIntegration: async (id) => {
|
|
146742
|
-
const
|
|
146743
|
-
if (
|
|
146744
|
-
return
|
|
146988
|
+
const brokerId = await brokerIdOf(this.requireInstances(), id);
|
|
146989
|
+
if (brokerId === void 0) throw new Error(`integration ${id} has no broker`);
|
|
146990
|
+
return brokerId;
|
|
146745
146991
|
},
|
|
146746
146992
|
listAdoptedAlexa: async () => {
|
|
146747
146993
|
const reg = this.ctx.kernel.deviceRegistry;
|