@camstack/addon-import-alexa 0.2.184 → 0.2.186
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 +13 -0
- package/dist/addon.js +115 -63
- package/dist/addon.mjs +115 -63
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.186] - 2026-11-05
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- An Alexa integration links to its account through the account record
|
|
15
|
+
|
|
16
|
+
## [0.2.185] - 2026-11-04
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Alexa starts without waiting for the first appliance list and state poll, so a large account no longer exceeds the runner init budget during an update
|
|
21
|
+
- Alexa broker migrations no longer read as 'skipped-newer' (and no false 'Database is newer' alert) after the addon boots
|
|
22
|
+
|
|
10
23
|
## [0.2.184] - 2026-11-04
|
|
11
24
|
|
|
12
25
|
### Changed
|
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-9TIHFj1w.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).
|
|
@@ -6644,7 +6644,7 @@ var NOT_RUN = {
|
|
|
6644
6644
|
* throws: an unreachable or refusing engine is logged and reported `declared: false`,
|
|
6645
6645
|
* because the hub (and this addon's boot) must not hang on a migration.
|
|
6646
6646
|
*/
|
|
6647
|
-
async function runAddonMigrations(host, migrations, logger) {
|
|
6647
|
+
async function runAddonMigrations(host, migrations, logger, options = {}) {
|
|
6648
6648
|
const first = migrations[0];
|
|
6649
6649
|
if (first === void 0) return NOT_RUN;
|
|
6650
6650
|
const owner = first.meta.owner;
|
|
@@ -6673,7 +6673,9 @@ async function runAddonMigrations(host, migrations, logger) {
|
|
|
6673
6673
|
else if (status === "blocked") blocked.push(id);
|
|
6674
6674
|
else skipped.push(id);
|
|
6675
6675
|
};
|
|
6676
|
+
const selected = options.only === void 0 ? null : new Set(options.only);
|
|
6676
6677
|
for (const step of declaration.steps) {
|
|
6678
|
+
if (selected !== null && !selected.has(step.id)) continue;
|
|
6677
6679
|
const bound = byId.get(step.id);
|
|
6678
6680
|
if (bound === void 0 || step.action === "skip") {
|
|
6679
6681
|
skipped.push(step.id);
|
|
@@ -7833,14 +7835,18 @@ var BaseAddon = class {
|
|
|
7833
7835
|
* nothing; the boot continues. A migration that FAILED (or was blocked by one)
|
|
7834
7836
|
* and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
|
|
7835
7837
|
* serve data whose shape it cannot trust; `tolerate` migrations never do.
|
|
7838
|
+
*
|
|
7839
|
+
* ALWAYS pass the whole catalogue; a phase that must run only some of it passes
|
|
7840
|
+
* `{ only: [ids] }` — never a filtered catalogue (the engine would judge the
|
|
7841
|
+
* omitted applied ids "newer").
|
|
7836
7842
|
*/
|
|
7837
|
-
async runMigrations(migrations) {
|
|
7843
|
+
async runMigrations(migrations, options) {
|
|
7838
7844
|
const api = this.ctx.api.migrations;
|
|
7839
7845
|
const report = await runAddonMigrations({
|
|
7840
7846
|
declareOwner: (input) => api.declareOwner.mutate(input),
|
|
7841
7847
|
startMigration: (input) => api.startMigration.mutate(input),
|
|
7842
7848
|
finishMigration: (input) => api.finishMigration.mutate(input)
|
|
7843
|
-
}, migrations, this.ctx.logger);
|
|
7849
|
+
}, migrations, this.ctx.logger, options);
|
|
7844
7850
|
if (report.blockOwner.length > 0) throw new AddonMigrationsBlockedError(report.blockOwner);
|
|
7845
7851
|
return report;
|
|
7846
7852
|
}
|
|
@@ -7982,10 +7988,9 @@ var BaseAddon = class {
|
|
|
7982
7988
|
*
|
|
7983
7989
|
* Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
|
|
7984
7990
|
* needs its connection fields, the default integration the addon's own sections. It is the
|
|
7985
|
-
* SHAPE of the form only — the values live
|
|
7986
|
-
* unchanged (`integrations.setSettings` for an account integration).
|
|
7991
|
+
* SHAPE of the form only — the values live on the instance record.
|
|
7987
7992
|
*/
|
|
7988
|
-
async
|
|
7993
|
+
async integrationFormSchema(_instance) {
|
|
7989
7994
|
return null;
|
|
7990
7995
|
}
|
|
7991
7996
|
/**
|
|
@@ -8031,7 +8036,7 @@ var BaseAddon = class {
|
|
|
8031
8036
|
* per-instance schema when it declares one, else its global schema (what they always showed).
|
|
8032
8037
|
*/
|
|
8033
8038
|
async getIntegrationFormSchema(instance, nodeId) {
|
|
8034
|
-
const scoped = await this.
|
|
8039
|
+
const scoped = await this.integrationFormSchema(instance);
|
|
8035
8040
|
if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
|
|
8036
8041
|
return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
|
|
8037
8042
|
}
|
|
@@ -9305,6 +9310,7 @@ function defineEntryKind(name, spec) {
|
|
|
9305
9310
|
class: "config",
|
|
9306
9311
|
writeTrigger: spec.writeTrigger ?? "an operator adds or edits an entry in the entries UI",
|
|
9307
9312
|
retention: { none: "an entry goes only when the operator deletes it" },
|
|
9313
|
+
...spec.maxDataBytes === void 0 ? {} : { maxDataBytes: spec.maxDataBytes },
|
|
9308
9314
|
...spec.database === void 0 ? {} : { database: spec.database },
|
|
9309
9315
|
...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
|
|
9310
9316
|
...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
|
|
@@ -11974,7 +11980,7 @@ object({
|
|
|
11974
11980
|
params: record(string(), union([string(), number$1()])).optional()
|
|
11975
11981
|
});
|
|
11976
11982
|
//#endregion
|
|
11977
|
-
//#region ../types/dist/composition-
|
|
11983
|
+
//#region ../types/dist/composition-pOHbTzRB.mjs
|
|
11978
11984
|
/**
|
|
11979
11985
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11980
11986
|
*
|
|
@@ -14672,7 +14678,13 @@ var MigrationLedgerEntrySchema = object({
|
|
|
14672
14678
|
* joined by the engine from the catalogues it knows this boot. Absent when no
|
|
14673
14679
|
* catalogue lists the id (a newer database).
|
|
14674
14680
|
*/
|
|
14675
|
-
meta: MigrationMetaSchema.optional()
|
|
14681
|
+
meta: MigrationMetaSchema.optional(),
|
|
14682
|
+
/**
|
|
14683
|
+
* Set when this is an applied row of an addon owner whose catalogue the answering
|
|
14684
|
+
* process has not loaded (the owner has not declared yet): the row is applied, the
|
|
14685
|
+
* process just cannot judge it. Never `skipped-newer` — that needs the owner loaded.
|
|
14686
|
+
*/
|
|
14687
|
+
ownerNotLoaded: boolean().optional()
|
|
14676
14688
|
});
|
|
14677
14689
|
/** A catalogued migration that is not applied, with where it stands. */
|
|
14678
14690
|
var MigrationPendingEntrySchema = object({
|
|
@@ -50448,6 +50460,9 @@ var RunningProposalEvidenceSchema = object({
|
|
|
50448
50460
|
runsFound: number$1().int().nonnegative(),
|
|
50449
50461
|
pausesFound: number$1().int().nonnegative(),
|
|
50450
50462
|
longestPauseMs: number$1().nonnegative(),
|
|
50463
|
+
/** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
|
|
50464
|
+
pulsesFound: number$1().int().nonnegative(),
|
|
50465
|
+
longestPulseMs: number$1().nonnegative(),
|
|
50451
50466
|
idleFloorW: number$1(),
|
|
50452
50467
|
noiseCeilingW: number$1(),
|
|
50453
50468
|
plateauW: number$1(),
|
|
@@ -56293,11 +56308,7 @@ var CreateIntegrationInputSchema = object({
|
|
|
56293
56308
|
info: record(string(), unknown()).optional(),
|
|
56294
56309
|
/** `stableId` of the discovery candidate the form was seeded from (D984). */
|
|
56295
56310
|
originStableId: string().optional(),
|
|
56296
|
-
settings: record(string(), unknown()).optional()
|
|
56297
|
-
/** Keys of `settings` the form's schema declares secret (by type) — kept in
|
|
56298
|
-
* the keychain, not the table (D787). */
|
|
56299
|
-
secretKeys: array(string()).optional(),
|
|
56300
|
-
skipRestart: boolean().optional()
|
|
56311
|
+
settings: record(string(), unknown()).optional()
|
|
56301
56312
|
});
|
|
56302
56313
|
var UpdateIntegrationInputSchema = object({
|
|
56303
56314
|
id: string(),
|
|
@@ -56318,16 +56329,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
56318
56329
|
}), method(object({ id: string() }), DeleteIntegrationResultSchema, {
|
|
56319
56330
|
kind: "mutation",
|
|
56320
56331
|
auth: "admin"
|
|
56321
|
-
}), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
|
|
56322
|
-
id: string(),
|
|
56323
|
-
settings: record(string(), unknown()),
|
|
56324
|
-
/** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
|
|
56325
|
-
secretKeys: array(string()).optional(),
|
|
56326
|
-
/** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
|
|
56327
|
-
skipRestart: boolean().optional()
|
|
56328
|
-
}), object({ success: boolean() }), {
|
|
56329
|
-
kind: "mutation",
|
|
56330
|
-
auth: "admin"
|
|
56331
56332
|
}), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
|
|
56332
56333
|
addonId: string(),
|
|
56333
56334
|
settings: record(string(), unknown())
|
|
@@ -59304,6 +59305,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
|
|
|
59304
59305
|
}), {
|
|
59305
59306
|
kind: "mutation",
|
|
59306
59307
|
auth: "admin"
|
|
59308
|
+
}), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
|
|
59309
|
+
rows: number$1().int().nonnegative(),
|
|
59310
|
+
secrets: number$1().int().nonnegative()
|
|
59311
|
+
}), {
|
|
59312
|
+
kind: "mutation",
|
|
59313
|
+
auth: "admin"
|
|
59314
|
+
}), method(object({
|
|
59315
|
+
integrationId: string().min(1),
|
|
59316
|
+
patch: record(string(), unknown())
|
|
59317
|
+
}), object({ written: boolean() }), {
|
|
59318
|
+
kind: "mutation",
|
|
59319
|
+
auth: "admin"
|
|
59320
|
+
}), method(object({
|
|
59321
|
+
integrationId: string().min(1),
|
|
59322
|
+
keys: array(string().min(1))
|
|
59323
|
+
}), object({
|
|
59324
|
+
present: boolean(),
|
|
59325
|
+
removed: record(string(), unknown())
|
|
59326
|
+
}), {
|
|
59327
|
+
kind: "mutation",
|
|
59328
|
+
auth: "admin"
|
|
59307
59329
|
});
|
|
59308
59330
|
method(object({
|
|
59309
59331
|
sourceUrl: string(),
|
|
@@ -80630,11 +80652,15 @@ discriminatedUnion("kind", [object({
|
|
|
80630
80652
|
* 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
|
|
80631
80653
|
* near F. A device that is not near one level most of the time has no floor.
|
|
80632
80654
|
* 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
|
|
80633
|
-
* 3.
|
|
80634
|
-
*
|
|
80655
|
+
* 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
|
|
80656
|
+
* display waking): it is neither a run nor a pause. Among the remaining
|
|
80657
|
+
* episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
|
|
80658
|
+
* two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
|
|
80659
|
+
* a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
|
|
80660
|
+
* is no run.
|
|
80635
80661
|
* 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
|
|
80636
|
-
* grid a person would type; stop dwell covers the longest pause
|
|
80637
|
-
* covers the longest standby blip above T.
|
|
80662
|
+
* grid a person would type; stop dwell covers the longest pause and the
|
|
80663
|
+
* kind's floor; start dwell covers the longest pulse and standby blip above T.
|
|
80638
80664
|
* 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
|
|
80639
80665
|
* run must hold exactly one true interval and nothing may flap outside.
|
|
80640
80666
|
*/
|
|
@@ -80645,6 +80671,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
|
|
|
80645
80671
|
30 * SECOND_MS;
|
|
80646
80672
|
60 * SECOND_MS;
|
|
80647
80673
|
20 * MINUTE_MS$1;
|
|
80674
|
+
90 * SECOND_MS;
|
|
80675
|
+
30 * MINUTE_MS$1;
|
|
80676
|
+
10 * MINUTE_MS$1;
|
|
80677
|
+
DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
|
|
80648
80678
|
10 * SECOND_MS;
|
|
80649
80679
|
10 * SECOND_MS;
|
|
80650
80680
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
@@ -85964,13 +85994,6 @@ Object.freeze({
|
|
|
85964
85994
|
access: "view",
|
|
85965
85995
|
role: "admin"
|
|
85966
85996
|
},
|
|
85967
|
-
"integrations.getSettings": {
|
|
85968
|
-
capName: "integrations",
|
|
85969
|
-
capScope: "system",
|
|
85970
|
-
addonId: null,
|
|
85971
|
-
access: "view",
|
|
85972
|
-
role: "admin"
|
|
85973
|
-
},
|
|
85974
85997
|
"integrations.list": {
|
|
85975
85998
|
capName: "integrations",
|
|
85976
85999
|
capScope: "system",
|
|
@@ -85978,13 +86001,6 @@ Object.freeze({
|
|
|
85978
86001
|
access: "view",
|
|
85979
86002
|
role: "admin"
|
|
85980
86003
|
},
|
|
85981
|
-
"integrations.setSettings": {
|
|
85982
|
-
capName: "integrations",
|
|
85983
|
-
capScope: "system",
|
|
85984
|
-
addonId: null,
|
|
85985
|
-
access: "create",
|
|
85986
|
-
role: "admin"
|
|
85987
|
-
},
|
|
85988
86004
|
"integrations.testConnection": {
|
|
85989
86005
|
capName: "integrations",
|
|
85990
86006
|
capScope: "system",
|
|
@@ -86979,6 +86995,13 @@ Object.freeze({
|
|
|
86979
86995
|
access: "view",
|
|
86980
86996
|
role: "admin"
|
|
86981
86997
|
},
|
|
86998
|
+
"migrations.clearLegacyIntegrationRows": {
|
|
86999
|
+
capName: "migrations",
|
|
87000
|
+
capScope: "system",
|
|
87001
|
+
addonId: null,
|
|
87002
|
+
access: "delete",
|
|
87003
|
+
role: "admin"
|
|
87004
|
+
},
|
|
86982
87005
|
"migrations.declareOwner": {
|
|
86983
87006
|
capName: "migrations",
|
|
86984
87007
|
capScope: "system",
|
|
@@ -86986,6 +87009,13 @@ Object.freeze({
|
|
|
86986
87009
|
access: "create",
|
|
86987
87010
|
role: "admin"
|
|
86988
87011
|
},
|
|
87012
|
+
"migrations.deleteLegacyIntegrationRowKeys": {
|
|
87013
|
+
capName: "migrations",
|
|
87014
|
+
capScope: "system",
|
|
87015
|
+
addonId: null,
|
|
87016
|
+
access: "delete",
|
|
87017
|
+
role: "admin"
|
|
87018
|
+
},
|
|
86989
87019
|
"migrations.dryRun": {
|
|
86990
87020
|
capName: "migrations",
|
|
86991
87021
|
capScope: "system",
|
|
@@ -87021,6 +87051,20 @@ Object.freeze({
|
|
|
87021
87051
|
access: "create",
|
|
87022
87052
|
role: "admin"
|
|
87023
87053
|
},
|
|
87054
|
+
"migrations.patchLegacyIntegrationRows": {
|
|
87055
|
+
capName: "migrations",
|
|
87056
|
+
capScope: "system",
|
|
87057
|
+
addonId: null,
|
|
87058
|
+
access: "create",
|
|
87059
|
+
role: "admin"
|
|
87060
|
+
},
|
|
87061
|
+
"migrations.readLegacyIntegrationRows": {
|
|
87062
|
+
capName: "migrations",
|
|
87063
|
+
capScope: "system",
|
|
87064
|
+
addonId: null,
|
|
87065
|
+
access: "view",
|
|
87066
|
+
role: "admin"
|
|
87067
|
+
},
|
|
87024
87068
|
"migrations.restorePreMigration": {
|
|
87025
87069
|
capName: "migrations",
|
|
87026
87070
|
capScope: "system",
|
|
@@ -141638,10 +141682,8 @@ var AlexaIntegrationManager = class {
|
|
|
141638
141682
|
this.#status = "connected";
|
|
141639
141683
|
this.#lastCheckedAt = Date.now();
|
|
141640
141684
|
this.#onConnected(this.#id);
|
|
141641
|
-
await this.refreshAppliances();
|
|
141642
|
-
await this.poll();
|
|
141643
|
-
this.#schedulePoll();
|
|
141644
141685
|
this.#logger.info("AlexaIntegrationManager: started", { tags: { brokerId: this.#id } });
|
|
141686
|
+
this.#firstSync(client);
|
|
141645
141687
|
} catch (err) {
|
|
141646
141688
|
if (err instanceof AlexaAuthError) {
|
|
141647
141689
|
this.#status = "auth-failed";
|
|
@@ -141662,6 +141704,24 @@ var AlexaIntegrationManager = class {
|
|
|
141662
141704
|
});
|
|
141663
141705
|
}
|
|
141664
141706
|
}
|
|
141707
|
+
/** Background first refresh + poll, then the poll loop. Does nothing once `client` is stale. */
|
|
141708
|
+
async #firstSync(client) {
|
|
141709
|
+
let refreshed = false;
|
|
141710
|
+
try {
|
|
141711
|
+
await this.refreshAppliances();
|
|
141712
|
+
refreshed = true;
|
|
141713
|
+
} catch (err) {
|
|
141714
|
+
this.#error = errMsg(err);
|
|
141715
|
+
this.#logger.warn("AlexaIntegrationManager: first appliance refresh failed", {
|
|
141716
|
+
tags: { brokerId: this.#id },
|
|
141717
|
+
meta: { error: errMsg(err) }
|
|
141718
|
+
});
|
|
141719
|
+
}
|
|
141720
|
+
if (this.#client !== client) return;
|
|
141721
|
+
if (refreshed) await this.poll();
|
|
141722
|
+
if (this.#client !== client) return;
|
|
141723
|
+
this.#schedulePoll();
|
|
141724
|
+
}
|
|
141665
141725
|
/** List the account's appliances + publish them to the resolver. */
|
|
141666
141726
|
async refreshAppliances() {
|
|
141667
141727
|
const client = this.#client;
|
|
@@ -145116,7 +145176,7 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
145116
145176
|
infoOf: (id) => this.registry?.get(id) ?? null,
|
|
145117
145177
|
onChanged: (id) => this.syncBrokerRuntime(id)
|
|
145118
145178
|
});
|
|
145119
|
-
await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS
|
|
145179
|
+
await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, this.migrationPorts(records)), { only: [FLATTEN_BROKER_RECORDS_ID] });
|
|
145120
145180
|
this.activeBrokers = await this.loadPersistedBrokers();
|
|
145121
145181
|
this.registry = new AlexaBrokerRegistry(this.ctx.logger, {
|
|
145122
145182
|
dataDir: this.ctx.dataDir,
|
|
@@ -145161,7 +145221,7 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
145161
145221
|
deviceCode: this.deviceCodeLogin,
|
|
145162
145222
|
commitCookie: (input) => this.commitCapturedCookie(input)
|
|
145163
145223
|
}), ...speakIconRoutes()];
|
|
145164
|
-
await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS.
|
|
145224
|
+
await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, this.migrationPorts(records)), { only: ADDON_MIGRATIONS.map((m) => m.meta.id).filter((id) => id !== FLATTEN_BROKER_RECORDS_ID) });
|
|
145165
145225
|
const speak = new AlexaSpeakController({
|
|
145166
145226
|
logger: this.ctx.logger.child("speak"),
|
|
145167
145227
|
readConfig: () => this.config,
|
|
@@ -145479,18 +145539,10 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
145479
145539
|
try {
|
|
145480
145540
|
const mine = (await this.ctx.api.integrations.list.query()).filter((i) => i.addonId === this.ctx.id);
|
|
145481
145541
|
const surviving = new Set(mine.map((i) => i.id));
|
|
145482
|
-
for (const
|
|
145483
|
-
const
|
|
145484
|
-
|
|
145485
|
-
|
|
145486
|
-
if (!this.activeBrokers.some((b) => b.id === brokerId)) {
|
|
145487
|
-
this.ctx.logger.warn("Alexa integration→broker: linked broker not found", { meta: {
|
|
145488
|
-
integrationId: integration.id,
|
|
145489
|
-
brokerId
|
|
145490
|
-
} });
|
|
145491
|
-
continue;
|
|
145492
|
-
}
|
|
145493
|
-
this.requireRegistry().linkIntegration(integration.id, brokerId);
|
|
145542
|
+
for (const broker of this.activeBrokers) {
|
|
145543
|
+
const integrationId = broker.integrationId;
|
|
145544
|
+
if (integrationId === void 0 || !surviving.has(integrationId)) continue;
|
|
145545
|
+
this.requireRegistry().linkIntegration(integrationId, broker.id);
|
|
145494
145546
|
}
|
|
145495
145547
|
const toRemove = this.activeBrokers.filter((b) => b.integrationId !== void 0 && !surviving.has(b.integrationId)).map((b) => b.id);
|
|
145496
145548
|
if (toRemove.length === 0) return;
|
|
@@ -145532,9 +145584,9 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
145532
145584
|
},
|
|
145533
145585
|
logger: this.ctx.logger,
|
|
145534
145586
|
getBrokerIdForIntegration: async (id) => {
|
|
145535
|
-
const
|
|
145536
|
-
if (
|
|
145537
|
-
return
|
|
145587
|
+
const broker = this.activeBrokers.find((entry) => entry.integrationId === id);
|
|
145588
|
+
if (broker === void 0) throw new Error(`integration ${id} has no linked brokerId`);
|
|
145589
|
+
return broker.id;
|
|
145538
145590
|
},
|
|
145539
145591
|
listAdoptedAlexa: async () => {
|
|
145540
145592
|
const reg = this.ctx.kernel.deviceRegistry;
|
package/dist/addon.mjs
CHANGED
|
@@ -6042,7 +6042,7 @@ function number(params) {
|
|
|
6042
6042
|
return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
|
|
6043
6043
|
}
|
|
6044
6044
|
//#endregion
|
|
6045
|
-
//#region ../types/dist/sleep-
|
|
6045
|
+
//#region ../types/dist/sleep-9TIHFj1w.mjs
|
|
6046
6046
|
/**
|
|
6047
6047
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
6048
6048
|
* window to float samples (D455).
|
|
@@ -6642,7 +6642,7 @@ var NOT_RUN = {
|
|
|
6642
6642
|
* throws: an unreachable or refusing engine is logged and reported `declared: false`,
|
|
6643
6643
|
* because the hub (and this addon's boot) must not hang on a migration.
|
|
6644
6644
|
*/
|
|
6645
|
-
async function runAddonMigrations(host, migrations, logger) {
|
|
6645
|
+
async function runAddonMigrations(host, migrations, logger, options = {}) {
|
|
6646
6646
|
const first = migrations[0];
|
|
6647
6647
|
if (first === void 0) return NOT_RUN;
|
|
6648
6648
|
const owner = first.meta.owner;
|
|
@@ -6671,7 +6671,9 @@ async function runAddonMigrations(host, migrations, logger) {
|
|
|
6671
6671
|
else if (status === "blocked") blocked.push(id);
|
|
6672
6672
|
else skipped.push(id);
|
|
6673
6673
|
};
|
|
6674
|
+
const selected = options.only === void 0 ? null : new Set(options.only);
|
|
6674
6675
|
for (const step of declaration.steps) {
|
|
6676
|
+
if (selected !== null && !selected.has(step.id)) continue;
|
|
6675
6677
|
const bound = byId.get(step.id);
|
|
6676
6678
|
if (bound === void 0 || step.action === "skip") {
|
|
6677
6679
|
skipped.push(step.id);
|
|
@@ -7831,14 +7833,18 @@ var BaseAddon = class {
|
|
|
7831
7833
|
* nothing; the boot continues. A migration that FAILED (or was blocked by one)
|
|
7832
7834
|
* and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
|
|
7833
7835
|
* serve data whose shape it cannot trust; `tolerate` migrations never do.
|
|
7836
|
+
*
|
|
7837
|
+
* ALWAYS pass the whole catalogue; a phase that must run only some of it passes
|
|
7838
|
+
* `{ only: [ids] }` — never a filtered catalogue (the engine would judge the
|
|
7839
|
+
* omitted applied ids "newer").
|
|
7834
7840
|
*/
|
|
7835
|
-
async runMigrations(migrations) {
|
|
7841
|
+
async runMigrations(migrations, options) {
|
|
7836
7842
|
const api = this.ctx.api.migrations;
|
|
7837
7843
|
const report = await runAddonMigrations({
|
|
7838
7844
|
declareOwner: (input) => api.declareOwner.mutate(input),
|
|
7839
7845
|
startMigration: (input) => api.startMigration.mutate(input),
|
|
7840
7846
|
finishMigration: (input) => api.finishMigration.mutate(input)
|
|
7841
|
-
}, migrations, this.ctx.logger);
|
|
7847
|
+
}, migrations, this.ctx.logger, options);
|
|
7842
7848
|
if (report.blockOwner.length > 0) throw new AddonMigrationsBlockedError(report.blockOwner);
|
|
7843
7849
|
return report;
|
|
7844
7850
|
}
|
|
@@ -7980,10 +7986,9 @@ var BaseAddon = class {
|
|
|
7980
7986
|
*
|
|
7981
7987
|
* Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
|
|
7982
7988
|
* needs its connection fields, the default integration the addon's own sections. It is the
|
|
7983
|
-
* SHAPE of the form only — the values live
|
|
7984
|
-
* unchanged (`integrations.setSettings` for an account integration).
|
|
7989
|
+
* SHAPE of the form only — the values live on the instance record.
|
|
7985
7990
|
*/
|
|
7986
|
-
async
|
|
7991
|
+
async integrationFormSchema(_instance) {
|
|
7987
7992
|
return null;
|
|
7988
7993
|
}
|
|
7989
7994
|
/**
|
|
@@ -8029,7 +8034,7 @@ var BaseAddon = class {
|
|
|
8029
8034
|
* per-instance schema when it declares one, else its global schema (what they always showed).
|
|
8030
8035
|
*/
|
|
8031
8036
|
async getIntegrationFormSchema(instance, nodeId) {
|
|
8032
|
-
const scoped = await this.
|
|
8037
|
+
const scoped = await this.integrationFormSchema(instance);
|
|
8033
8038
|
if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
|
|
8034
8039
|
return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
|
|
8035
8040
|
}
|
|
@@ -9303,6 +9308,7 @@ function defineEntryKind(name, spec) {
|
|
|
9303
9308
|
class: "config",
|
|
9304
9309
|
writeTrigger: spec.writeTrigger ?? "an operator adds or edits an entry in the entries UI",
|
|
9305
9310
|
retention: { none: "an entry goes only when the operator deletes it" },
|
|
9311
|
+
...spec.maxDataBytes === void 0 ? {} : { maxDataBytes: spec.maxDataBytes },
|
|
9306
9312
|
...spec.database === void 0 ? {} : { database: spec.database },
|
|
9307
9313
|
...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
|
|
9308
9314
|
...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
|
|
@@ -11972,7 +11978,7 @@ object({
|
|
|
11972
11978
|
params: record(string(), union([string(), number$1()])).optional()
|
|
11973
11979
|
});
|
|
11974
11980
|
//#endregion
|
|
11975
|
-
//#region ../types/dist/composition-
|
|
11981
|
+
//#region ../types/dist/composition-pOHbTzRB.mjs
|
|
11976
11982
|
/**
|
|
11977
11983
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11978
11984
|
*
|
|
@@ -14670,7 +14676,13 @@ var MigrationLedgerEntrySchema = object({
|
|
|
14670
14676
|
* joined by the engine from the catalogues it knows this boot. Absent when no
|
|
14671
14677
|
* catalogue lists the id (a newer database).
|
|
14672
14678
|
*/
|
|
14673
|
-
meta: MigrationMetaSchema.optional()
|
|
14679
|
+
meta: MigrationMetaSchema.optional(),
|
|
14680
|
+
/**
|
|
14681
|
+
* Set when this is an applied row of an addon owner whose catalogue the answering
|
|
14682
|
+
* process has not loaded (the owner has not declared yet): the row is applied, the
|
|
14683
|
+
* process just cannot judge it. Never `skipped-newer` — that needs the owner loaded.
|
|
14684
|
+
*/
|
|
14685
|
+
ownerNotLoaded: boolean().optional()
|
|
14674
14686
|
});
|
|
14675
14687
|
/** A catalogued migration that is not applied, with where it stands. */
|
|
14676
14688
|
var MigrationPendingEntrySchema = object({
|
|
@@ -50446,6 +50458,9 @@ var RunningProposalEvidenceSchema = object({
|
|
|
50446
50458
|
runsFound: number$1().int().nonnegative(),
|
|
50447
50459
|
pausesFound: number$1().int().nonnegative(),
|
|
50448
50460
|
longestPauseMs: number$1().nonnegative(),
|
|
50461
|
+
/** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
|
|
50462
|
+
pulsesFound: number$1().int().nonnegative(),
|
|
50463
|
+
longestPulseMs: number$1().nonnegative(),
|
|
50449
50464
|
idleFloorW: number$1(),
|
|
50450
50465
|
noiseCeilingW: number$1(),
|
|
50451
50466
|
plateauW: number$1(),
|
|
@@ -56291,11 +56306,7 @@ var CreateIntegrationInputSchema = object({
|
|
|
56291
56306
|
info: record(string(), unknown()).optional(),
|
|
56292
56307
|
/** `stableId` of the discovery candidate the form was seeded from (D984). */
|
|
56293
56308
|
originStableId: string().optional(),
|
|
56294
|
-
settings: record(string(), unknown()).optional()
|
|
56295
|
-
/** Keys of `settings` the form's schema declares secret (by type) — kept in
|
|
56296
|
-
* the keychain, not the table (D787). */
|
|
56297
|
-
secretKeys: array(string()).optional(),
|
|
56298
|
-
skipRestart: boolean().optional()
|
|
56309
|
+
settings: record(string(), unknown()).optional()
|
|
56299
56310
|
});
|
|
56300
56311
|
var UpdateIntegrationInputSchema = object({
|
|
56301
56312
|
id: string(),
|
|
@@ -56316,16 +56327,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
56316
56327
|
}), method(object({ id: string() }), DeleteIntegrationResultSchema, {
|
|
56317
56328
|
kind: "mutation",
|
|
56318
56329
|
auth: "admin"
|
|
56319
|
-
}), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
|
|
56320
|
-
id: string(),
|
|
56321
|
-
settings: record(string(), unknown()),
|
|
56322
|
-
/** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
|
|
56323
|
-
secretKeys: array(string()).optional(),
|
|
56324
|
-
/** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
|
|
56325
|
-
skipRestart: boolean().optional()
|
|
56326
|
-
}), object({ success: boolean() }), {
|
|
56327
|
-
kind: "mutation",
|
|
56328
|
-
auth: "admin"
|
|
56329
56330
|
}), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
|
|
56330
56331
|
addonId: string(),
|
|
56331
56332
|
settings: record(string(), unknown())
|
|
@@ -59302,6 +59303,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
|
|
|
59302
59303
|
}), {
|
|
59303
59304
|
kind: "mutation",
|
|
59304
59305
|
auth: "admin"
|
|
59306
|
+
}), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
|
|
59307
|
+
rows: number$1().int().nonnegative(),
|
|
59308
|
+
secrets: number$1().int().nonnegative()
|
|
59309
|
+
}), {
|
|
59310
|
+
kind: "mutation",
|
|
59311
|
+
auth: "admin"
|
|
59312
|
+
}), method(object({
|
|
59313
|
+
integrationId: string().min(1),
|
|
59314
|
+
patch: record(string(), unknown())
|
|
59315
|
+
}), object({ written: boolean() }), {
|
|
59316
|
+
kind: "mutation",
|
|
59317
|
+
auth: "admin"
|
|
59318
|
+
}), method(object({
|
|
59319
|
+
integrationId: string().min(1),
|
|
59320
|
+
keys: array(string().min(1))
|
|
59321
|
+
}), object({
|
|
59322
|
+
present: boolean(),
|
|
59323
|
+
removed: record(string(), unknown())
|
|
59324
|
+
}), {
|
|
59325
|
+
kind: "mutation",
|
|
59326
|
+
auth: "admin"
|
|
59305
59327
|
});
|
|
59306
59328
|
method(object({
|
|
59307
59329
|
sourceUrl: string(),
|
|
@@ -80628,11 +80650,15 @@ discriminatedUnion("kind", [object({
|
|
|
80628
80650
|
* 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
|
|
80629
80651
|
* near F. A device that is not near one level most of the time has no floor.
|
|
80630
80652
|
* 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
|
|
80631
|
-
* 3.
|
|
80632
|
-
*
|
|
80653
|
+
* 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
|
|
80654
|
+
* display waking): it is neither a run nor a pause. Among the remaining
|
|
80655
|
+
* episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
|
|
80656
|
+
* two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
|
|
80657
|
+
* a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
|
|
80658
|
+
* is no run.
|
|
80633
80659
|
* 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
|
|
80634
|
-
* grid a person would type; stop dwell covers the longest pause
|
|
80635
|
-
* covers the longest standby blip above T.
|
|
80660
|
+
* grid a person would type; stop dwell covers the longest pause and the
|
|
80661
|
+
* kind's floor; start dwell covers the longest pulse and standby blip above T.
|
|
80636
80662
|
* 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
|
|
80637
80663
|
* run must hold exactly one true interval and nothing may flap outside.
|
|
80638
80664
|
*/
|
|
@@ -80643,6 +80669,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
|
|
|
80643
80669
|
30 * SECOND_MS;
|
|
80644
80670
|
60 * SECOND_MS;
|
|
80645
80671
|
20 * MINUTE_MS$1;
|
|
80672
|
+
90 * SECOND_MS;
|
|
80673
|
+
30 * MINUTE_MS$1;
|
|
80674
|
+
10 * MINUTE_MS$1;
|
|
80675
|
+
DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
|
|
80646
80676
|
10 * SECOND_MS;
|
|
80647
80677
|
10 * SECOND_MS;
|
|
80648
80678
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
@@ -85962,13 +85992,6 @@ Object.freeze({
|
|
|
85962
85992
|
access: "view",
|
|
85963
85993
|
role: "admin"
|
|
85964
85994
|
},
|
|
85965
|
-
"integrations.getSettings": {
|
|
85966
|
-
capName: "integrations",
|
|
85967
|
-
capScope: "system",
|
|
85968
|
-
addonId: null,
|
|
85969
|
-
access: "view",
|
|
85970
|
-
role: "admin"
|
|
85971
|
-
},
|
|
85972
85995
|
"integrations.list": {
|
|
85973
85996
|
capName: "integrations",
|
|
85974
85997
|
capScope: "system",
|
|
@@ -85976,13 +85999,6 @@ Object.freeze({
|
|
|
85976
85999
|
access: "view",
|
|
85977
86000
|
role: "admin"
|
|
85978
86001
|
},
|
|
85979
|
-
"integrations.setSettings": {
|
|
85980
|
-
capName: "integrations",
|
|
85981
|
-
capScope: "system",
|
|
85982
|
-
addonId: null,
|
|
85983
|
-
access: "create",
|
|
85984
|
-
role: "admin"
|
|
85985
|
-
},
|
|
85986
86002
|
"integrations.testConnection": {
|
|
85987
86003
|
capName: "integrations",
|
|
85988
86004
|
capScope: "system",
|
|
@@ -86977,6 +86993,13 @@ Object.freeze({
|
|
|
86977
86993
|
access: "view",
|
|
86978
86994
|
role: "admin"
|
|
86979
86995
|
},
|
|
86996
|
+
"migrations.clearLegacyIntegrationRows": {
|
|
86997
|
+
capName: "migrations",
|
|
86998
|
+
capScope: "system",
|
|
86999
|
+
addonId: null,
|
|
87000
|
+
access: "delete",
|
|
87001
|
+
role: "admin"
|
|
87002
|
+
},
|
|
86980
87003
|
"migrations.declareOwner": {
|
|
86981
87004
|
capName: "migrations",
|
|
86982
87005
|
capScope: "system",
|
|
@@ -86984,6 +87007,13 @@ Object.freeze({
|
|
|
86984
87007
|
access: "create",
|
|
86985
87008
|
role: "admin"
|
|
86986
87009
|
},
|
|
87010
|
+
"migrations.deleteLegacyIntegrationRowKeys": {
|
|
87011
|
+
capName: "migrations",
|
|
87012
|
+
capScope: "system",
|
|
87013
|
+
addonId: null,
|
|
87014
|
+
access: "delete",
|
|
87015
|
+
role: "admin"
|
|
87016
|
+
},
|
|
86987
87017
|
"migrations.dryRun": {
|
|
86988
87018
|
capName: "migrations",
|
|
86989
87019
|
capScope: "system",
|
|
@@ -87019,6 +87049,20 @@ Object.freeze({
|
|
|
87019
87049
|
access: "create",
|
|
87020
87050
|
role: "admin"
|
|
87021
87051
|
},
|
|
87052
|
+
"migrations.patchLegacyIntegrationRows": {
|
|
87053
|
+
capName: "migrations",
|
|
87054
|
+
capScope: "system",
|
|
87055
|
+
addonId: null,
|
|
87056
|
+
access: "create",
|
|
87057
|
+
role: "admin"
|
|
87058
|
+
},
|
|
87059
|
+
"migrations.readLegacyIntegrationRows": {
|
|
87060
|
+
capName: "migrations",
|
|
87061
|
+
capScope: "system",
|
|
87062
|
+
addonId: null,
|
|
87063
|
+
access: "view",
|
|
87064
|
+
role: "admin"
|
|
87065
|
+
},
|
|
87022
87066
|
"migrations.restorePreMigration": {
|
|
87023
87067
|
capName: "migrations",
|
|
87024
87068
|
capScope: "system",
|
|
@@ -141636,10 +141680,8 @@ var AlexaIntegrationManager = class {
|
|
|
141636
141680
|
this.#status = "connected";
|
|
141637
141681
|
this.#lastCheckedAt = Date.now();
|
|
141638
141682
|
this.#onConnected(this.#id);
|
|
141639
|
-
await this.refreshAppliances();
|
|
141640
|
-
await this.poll();
|
|
141641
|
-
this.#schedulePoll();
|
|
141642
141683
|
this.#logger.info("AlexaIntegrationManager: started", { tags: { brokerId: this.#id } });
|
|
141684
|
+
this.#firstSync(client);
|
|
141643
141685
|
} catch (err) {
|
|
141644
141686
|
if (err instanceof AlexaAuthError) {
|
|
141645
141687
|
this.#status = "auth-failed";
|
|
@@ -141660,6 +141702,24 @@ var AlexaIntegrationManager = class {
|
|
|
141660
141702
|
});
|
|
141661
141703
|
}
|
|
141662
141704
|
}
|
|
141705
|
+
/** Background first refresh + poll, then the poll loop. Does nothing once `client` is stale. */
|
|
141706
|
+
async #firstSync(client) {
|
|
141707
|
+
let refreshed = false;
|
|
141708
|
+
try {
|
|
141709
|
+
await this.refreshAppliances();
|
|
141710
|
+
refreshed = true;
|
|
141711
|
+
} catch (err) {
|
|
141712
|
+
this.#error = errMsg(err);
|
|
141713
|
+
this.#logger.warn("AlexaIntegrationManager: first appliance refresh failed", {
|
|
141714
|
+
tags: { brokerId: this.#id },
|
|
141715
|
+
meta: { error: errMsg(err) }
|
|
141716
|
+
});
|
|
141717
|
+
}
|
|
141718
|
+
if (this.#client !== client) return;
|
|
141719
|
+
if (refreshed) await this.poll();
|
|
141720
|
+
if (this.#client !== client) return;
|
|
141721
|
+
this.#schedulePoll();
|
|
141722
|
+
}
|
|
141663
141723
|
/** List the account's appliances + publish them to the resolver. */
|
|
141664
141724
|
async refreshAppliances() {
|
|
141665
141725
|
const client = this.#client;
|
|
@@ -145114,7 +145174,7 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
145114
145174
|
infoOf: (id) => this.registry?.get(id) ?? null,
|
|
145115
145175
|
onChanged: (id) => this.syncBrokerRuntime(id)
|
|
145116
145176
|
});
|
|
145117
|
-
await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS
|
|
145177
|
+
await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, this.migrationPorts(records)), { only: [FLATTEN_BROKER_RECORDS_ID] });
|
|
145118
145178
|
this.activeBrokers = await this.loadPersistedBrokers();
|
|
145119
145179
|
this.registry = new AlexaBrokerRegistry(this.ctx.logger, {
|
|
145120
145180
|
dataDir: this.ctx.dataDir,
|
|
@@ -145159,7 +145219,7 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
145159
145219
|
deviceCode: this.deviceCodeLogin,
|
|
145160
145220
|
commitCookie: (input) => this.commitCapturedCookie(input)
|
|
145161
145221
|
}), ...speakIconRoutes()];
|
|
145162
|
-
await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS.
|
|
145222
|
+
await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, this.migrationPorts(records)), { only: ADDON_MIGRATIONS.map((m) => m.meta.id).filter((id) => id !== FLATTEN_BROKER_RECORDS_ID) });
|
|
145163
145223
|
const speak = new AlexaSpeakController({
|
|
145164
145224
|
logger: this.ctx.logger.child("speak"),
|
|
145165
145225
|
readConfig: () => this.config,
|
|
@@ -145477,18 +145537,10 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
145477
145537
|
try {
|
|
145478
145538
|
const mine = (await this.ctx.api.integrations.list.query()).filter((i) => i.addonId === this.ctx.id);
|
|
145479
145539
|
const surviving = new Set(mine.map((i) => i.id));
|
|
145480
|
-
for (const
|
|
145481
|
-
const
|
|
145482
|
-
|
|
145483
|
-
|
|
145484
|
-
if (!this.activeBrokers.some((b) => b.id === brokerId)) {
|
|
145485
|
-
this.ctx.logger.warn("Alexa integration→broker: linked broker not found", { meta: {
|
|
145486
|
-
integrationId: integration.id,
|
|
145487
|
-
brokerId
|
|
145488
|
-
} });
|
|
145489
|
-
continue;
|
|
145490
|
-
}
|
|
145491
|
-
this.requireRegistry().linkIntegration(integration.id, brokerId);
|
|
145540
|
+
for (const broker of this.activeBrokers) {
|
|
145541
|
+
const integrationId = broker.integrationId;
|
|
145542
|
+
if (integrationId === void 0 || !surviving.has(integrationId)) continue;
|
|
145543
|
+
this.requireRegistry().linkIntegration(integrationId, broker.id);
|
|
145492
145544
|
}
|
|
145493
145545
|
const toRemove = this.activeBrokers.filter((b) => b.integrationId !== void 0 && !surviving.has(b.integrationId)).map((b) => b.id);
|
|
145494
145546
|
if (toRemove.length === 0) return;
|
|
@@ -145530,9 +145582,9 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
145530
145582
|
},
|
|
145531
145583
|
logger: this.ctx.logger,
|
|
145532
145584
|
getBrokerIdForIntegration: async (id) => {
|
|
145533
|
-
const
|
|
145534
|
-
if (
|
|
145535
|
-
return
|
|
145585
|
+
const broker = this.activeBrokers.find((entry) => entry.integrationId === id);
|
|
145586
|
+
if (broker === void 0) throw new Error(`integration ${id} has no linked brokerId`);
|
|
145587
|
+
return broker.id;
|
|
145536
145588
|
},
|
|
145537
145589
|
listAdoptedAlexa: async () => {
|
|
145538
145590
|
const reg = this.ctx.kernel.deviceRegistry;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-import-alexa",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.186",
|
|
4
4
|
"description": "Alexa device-import provider for CamStack — imports the smart-home devices in a user's Alexa account via the unofficial alexa-remote2 cookie/token client (the inverse of the Alexa exporter)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
}
|
|
73
73
|
],
|
|
74
74
|
"requires": {
|
|
75
|
-
"server": ">=1.2.
|
|
75
|
+
"server": ">=1.2.770"
|
|
76
76
|
}
|
|
77
77
|
},
|
|
78
78
|
"files": [
|