@camstack/addon-import-alexa 0.2.190 → 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 +6 -0
- package/dist/addon.js +162 -61
- package/dist/addon.mjs +162 -61
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.191] - 2026-11-06
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Echo notification targets pick their Amazon account through the shared instance picker
|
|
15
|
+
|
|
10
16
|
## [0.2.190] - 2026-11-06
|
|
11
17
|
|
|
12
18
|
### Changed
|
package/dist/addon.js
CHANGED
|
@@ -13577,6 +13577,39 @@ function buildCapturingReply(envelope) {
|
|
|
13577
13577
|
return wrapper;
|
|
13578
13578
|
}
|
|
13579
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
|
+
/**
|
|
13580
13613
|
* `RecordIntegrationInstances` - the `integration-instances` provider over ONE
|
|
13581
13614
|
* entry kind, so an addon that holds a connection serves it in ~30 lines.
|
|
13582
13615
|
*
|
|
@@ -13697,9 +13730,18 @@ var RecordIntegrationInstances = class {
|
|
|
13697
13730
|
...health?.lastCheckedAt !== void 0 ? { lastCheckedAt: health.lastCheckedAt } : {}
|
|
13698
13731
|
};
|
|
13699
13732
|
}
|
|
13700
|
-
describe = async () =>
|
|
13701
|
-
|
|
13702
|
-
|
|
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
|
+
};
|
|
13703
13745
|
};
|
|
13704
13746
|
instanceFormSchema = async ({ id }) => this.hooks.formSchema(id);
|
|
13705
13747
|
listInstances = async () => {
|
|
@@ -13820,10 +13862,14 @@ function composeInstanceKinds(parts, base) {
|
|
|
13820
13862
|
kind: part.kind.id
|
|
13821
13863
|
});
|
|
13822
13864
|
return {
|
|
13823
|
-
describe: async () =>
|
|
13824
|
-
|
|
13825
|
-
|
|
13826
|
-
|
|
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
|
+
},
|
|
13827
13873
|
instanceFormSchema: async ({ id, kind }) => {
|
|
13828
13874
|
return (id === null ? byKind(kind) : await holderOf(id)).provider.instanceFormSchema({ id });
|
|
13829
13875
|
},
|
|
@@ -56524,18 +56570,38 @@ var IntegrationInstanceKindSchema = object({
|
|
|
56524
56570
|
/** Relative hub path to disconnect that account. */
|
|
56525
56571
|
disconnectPath: string().min(1).optional()
|
|
56526
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
|
+
});
|
|
56527
56600
|
var IntegrationInstanceDescriptionSchema = object({
|
|
56528
56601
|
/** What the instances are for the Integrations page; see {@link IntegrationInstanceRoleSchema}. */
|
|
56529
56602
|
role: IntegrationInstanceRoleSchema,
|
|
56530
56603
|
/** How the devices of an instance are found; absent means `{ mode: 'none' }`. */
|
|
56531
|
-
discovery:
|
|
56532
|
-
mode: _enum([
|
|
56533
|
-
"none",
|
|
56534
|
-
"lan",
|
|
56535
|
-
"account"
|
|
56536
|
-
]),
|
|
56537
|
-
scopedToInstance: boolean()
|
|
56538
|
-
}).optional(),
|
|
56604
|
+
discovery: IntegrationInstanceDiscoverySchema.optional(),
|
|
56539
56605
|
/** `single`: at most one instance can exist (Loki). `multiple`: an account each. */
|
|
56540
56606
|
instanceMode: IntegrationInstanceModeSchema,
|
|
56541
56607
|
/** What the instance is, for the UI (`destination`, `relay`, `account`). */
|
|
@@ -56556,7 +56622,9 @@ var IntegrationInstanceDescriptionSchema = object({
|
|
|
56556
56622
|
* `createInstance` without an `id` works: the provider mints it. Absent means the caller must
|
|
56557
56623
|
* send an id (the UI then mints an opaque one).
|
|
56558
56624
|
*/
|
|
56559
|
-
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()
|
|
56560
56628
|
});
|
|
56561
56629
|
var IntegrationInstanceStatusSchema = object({
|
|
56562
56630
|
state: _enum([
|
|
@@ -98442,27 +98510,51 @@ object({
|
|
|
98442
98510
|
delay: number$1().nonnegative().optional(),
|
|
98443
98511
|
commands: record(string(), unknown())
|
|
98444
98512
|
});
|
|
98445
|
-
var
|
|
98446
|
-
|
|
98513
|
+
var InstanceInUseError = class extends Error {
|
|
98514
|
+
instanceId;
|
|
98447
98515
|
users;
|
|
98448
|
-
name = "
|
|
98449
|
-
constructor(
|
|
98450
|
-
super(`
|
|
98451
|
-
this.
|
|
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;
|
|
98452
98520
|
this.users = users;
|
|
98453
98521
|
}
|
|
98454
98522
|
};
|
|
98455
|
-
/** Throw {@link
|
|
98456
|
-
async function
|
|
98523
|
+
/** Throw {@link InstanceInUseError} when any finder names a user of `instanceId`. */
|
|
98524
|
+
async function assertInstanceUnused(instanceId, finders) {
|
|
98457
98525
|
const users = [];
|
|
98458
|
-
for (const finder of finders) users.push(...await finder.find(
|
|
98459
|
-
if (users.length > 0) throw new
|
|
98526
|
+
for (const finder of finders) users.push(...await finder.find(instanceId));
|
|
98527
|
+
if (users.length > 0) throw new InstanceInUseError(instanceId, users);
|
|
98460
98528
|
}
|
|
98461
|
-
/**
|
|
98462
|
-
|
|
98463
|
-
|
|
98464
|
-
|
|
98465
|
-
|
|
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`. */
|
|
98466
98558
|
function guardBrokerDelete(base, users) {
|
|
98467
98559
|
return {
|
|
98468
98560
|
...base,
|
|
@@ -98616,6 +98708,35 @@ function brokerLinkPortsOver(deps) {
|
|
|
98616
98708
|
};
|
|
98617
98709
|
}
|
|
98618
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;
|
|
98739
|
+
//#endregion
|
|
98619
98740
|
//#region src/config.ts
|
|
98620
98741
|
/**
|
|
98621
98742
|
* Alexa account connection settings for ONE broker (= one Amazon account).
|
|
@@ -98672,7 +98793,7 @@ var alexaEchoTargetSchema = object({
|
|
|
98672
98793
|
name: string(),
|
|
98673
98794
|
enabled: boolean(),
|
|
98674
98795
|
/** The broker (Amazon account) the Echo belongs to; '' until one is chosen. */
|
|
98675
|
-
brokerId:
|
|
98796
|
+
brokerId: linkField(ALEXA_ACCOUNT_LINK).default(""),
|
|
98676
98797
|
echoId: string(),
|
|
98677
98798
|
respectDnd: boolean()
|
|
98678
98799
|
});
|
|
@@ -144037,30 +144158,6 @@ function withListedVolume(status, listed) {
|
|
|
144037
144158
|
};
|
|
144038
144159
|
}
|
|
144039
144160
|
//#endregion
|
|
144040
|
-
//#region src/speak/constants.ts
|
|
144041
|
-
/** Echo speech limits and the worker restart bound (same window as CrashSupervisor). */
|
|
144042
|
-
var ALEXA_SPEAK_ADDON_ID = "import-alexa";
|
|
144043
|
-
var ALEXA_SPEAK_KIND = "alexa-echo";
|
|
144044
|
-
/** The kind of the Amazon-account broker the Echoes are read from. */
|
|
144045
|
-
var ALEXA_BROKER_KIND = "alexa";
|
|
144046
|
-
var DEVICE_REFRESH_MS = 5 * 6e4;
|
|
144047
|
-
/** Where the addon serves the Alexa mark; `listTargetKinds` stamps it as `iconUrl`. */
|
|
144048
|
-
var ALEXA_SPEAK_ICON_URL = `/addon/${ALEXA_SPEAK_ADDON_ID}/icons/${ALEXA_SPEAK_KIND}`;
|
|
144049
|
-
/** How often an adopted Echo's player state is read (a push channel is a later phase). */
|
|
144050
|
-
var ECHO_POLL_MS = 3e4;
|
|
144051
|
-
/** One more read after a command, once Amazon has had time to apply it. */
|
|
144052
|
-
var ECHO_REREAD_MS = 1500;
|
|
144053
|
-
/** After a 429 the account is left alone for this long, doubling up to the cap. */
|
|
144054
|
-
var ECHO_RATE_BACKOFF_BASE_MS = 3e4;
|
|
144055
|
-
var ECHO_RATE_BACKOFF_MAX_MS = 5 * 6e4;
|
|
144056
|
-
/**
|
|
144057
|
-
* A command (volume, play, Do Not Disturb) that has not started within this, or not
|
|
144058
|
-
* finished, is answered with an error instead of running minutes late.
|
|
144059
|
-
*/
|
|
144060
|
-
var ECHO_COMMAND_DEADLINE_MS = 12e3;
|
|
144061
|
-
/** A poll read is stale after this; it is dropped from the queue rather than sent late. */
|
|
144062
|
-
var ECHO_READ_DEADLINE_MS = 25e3;
|
|
144063
|
-
//#endregion
|
|
144064
144161
|
//#region src/speak/worker-protocol.ts
|
|
144065
144162
|
/** Line-delimited JSON the long-lived Python worker speaks. One object per line. */
|
|
144066
144163
|
var EchoDeviceSchema = object({
|
|
@@ -145138,6 +145235,13 @@ function speechFromNotification(notification) {
|
|
|
145138
145235
|
}
|
|
145139
145236
|
//#endregion
|
|
145140
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
|
+
*/
|
|
145141
145245
|
function quietLogger$1() {
|
|
145142
145246
|
const logger = {
|
|
145143
145247
|
debug() {},
|
|
@@ -145193,11 +145297,8 @@ function configSchema() {
|
|
|
145193
145297
|
title: "Echo",
|
|
145194
145298
|
titleKey: "import-alexa:echoTarget.s_echo.title",
|
|
145195
145299
|
fields: [...[{
|
|
145196
|
-
|
|
145197
|
-
|
|
145198
|
-
label: "Amazon account",
|
|
145199
|
-
labelKey: "import-alexa:echoTarget.s_echo.brokerId.label",
|
|
145200
|
-
brokerKind: ALEXA_BROKER_KIND
|
|
145300
|
+
...linkFieldDescriptor("brokerId", ALEXA_ACCOUNT_LINK, { label: "Amazon account" }),
|
|
145301
|
+
labelKey: "import-alexa:echoTarget.s_echo.brokerId.label"
|
|
145201
145302
|
}, {
|
|
145202
145303
|
type: "select",
|
|
145203
145304
|
key: "echoId",
|
package/dist/addon.mjs
CHANGED
|
@@ -13575,6 +13575,39 @@ function buildCapturingReply(envelope) {
|
|
|
13575
13575
|
return wrapper;
|
|
13576
13576
|
}
|
|
13577
13577
|
/**
|
|
13578
|
+
* The link primitive's declaration half (D1013 rule 5): what a link field names and how a kind
|
|
13579
|
+
* records it. Split from `instance-link.ts` so `RecordIntegrationInstances` can read the links of
|
|
13580
|
+
* its kind without importing the builders that import it.
|
|
13581
|
+
*/
|
|
13582
|
+
/** The body field of a link: the id of the instance it names; the target rides in the field meta. */
|
|
13583
|
+
function linkField(target) {
|
|
13584
|
+
return string().meta({ link: target });
|
|
13585
|
+
}
|
|
13586
|
+
function linkTargetOf(meta) {
|
|
13587
|
+
if (typeof meta !== "object" || meta === null) return void 0;
|
|
13588
|
+
const link = Reflect.get(meta, "link");
|
|
13589
|
+
const parsed = LinkTargetSchema.safeParse(link);
|
|
13590
|
+
return parsed.success ? parsed.data : void 0;
|
|
13591
|
+
}
|
|
13592
|
+
var LinkTargetSchema = object({
|
|
13593
|
+
cap: string().min(1),
|
|
13594
|
+
kind: string().min(1).optional()
|
|
13595
|
+
});
|
|
13596
|
+
/** The links a kind declares, read from the meta of its body fields. */
|
|
13597
|
+
function linksOfKind(kind) {
|
|
13598
|
+
const schema = kind.schema;
|
|
13599
|
+
if (!(schema instanceof ZodObject)) return [];
|
|
13600
|
+
const links = [];
|
|
13601
|
+
for (const [field, fieldSchema] of Object.entries(schema.shape)) {
|
|
13602
|
+
const to = linkTargetOf(globalRegistry.get(fieldSchema));
|
|
13603
|
+
if (to !== void 0) links.push({
|
|
13604
|
+
field,
|
|
13605
|
+
to
|
|
13606
|
+
});
|
|
13607
|
+
}
|
|
13608
|
+
return links;
|
|
13609
|
+
}
|
|
13610
|
+
/**
|
|
13578
13611
|
* `RecordIntegrationInstances` - the `integration-instances` provider over ONE
|
|
13579
13612
|
* entry kind, so an addon that holds a connection serves it in ~30 lines.
|
|
13580
13613
|
*
|
|
@@ -13695,9 +13728,18 @@ var RecordIntegrationInstances = class {
|
|
|
13695
13728
|
...health?.lastCheckedAt !== void 0 ? { lastCheckedAt: health.lastCheckedAt } : {}
|
|
13696
13729
|
};
|
|
13697
13730
|
}
|
|
13698
|
-
describe = async () =>
|
|
13699
|
-
|
|
13700
|
-
|
|
13731
|
+
describe = async () => {
|
|
13732
|
+
const declared = linksOfKind(this.kind);
|
|
13733
|
+
const links = this.hooks.description.links !== void 0 || declared.length === 0 ? {} : { links: declared.map((link) => ({
|
|
13734
|
+
kind: this.kind.name,
|
|
13735
|
+
field: link.field,
|
|
13736
|
+
to: link.to
|
|
13737
|
+
})) };
|
|
13738
|
+
return {
|
|
13739
|
+
...this.hooks.description,
|
|
13740
|
+
...links,
|
|
13741
|
+
...this.hooks.mintId === void 0 ? {} : { mintsId: true }
|
|
13742
|
+
};
|
|
13701
13743
|
};
|
|
13702
13744
|
instanceFormSchema = async ({ id }) => this.hooks.formSchema(id);
|
|
13703
13745
|
listInstances = async () => {
|
|
@@ -13818,10 +13860,14 @@ function composeInstanceKinds(parts, base) {
|
|
|
13818
13860
|
kind: part.kind.id
|
|
13819
13861
|
});
|
|
13820
13862
|
return {
|
|
13821
|
-
describe: async () =>
|
|
13822
|
-
|
|
13823
|
-
|
|
13824
|
-
|
|
13863
|
+
describe: async () => {
|
|
13864
|
+
const links = (await Promise.all(parts.map(async (part) => (await part.provider.describe()).links ?? []))).flat();
|
|
13865
|
+
return {
|
|
13866
|
+
...base,
|
|
13867
|
+
kinds: parts.map((part) => part.kind),
|
|
13868
|
+
...links.length > 0 ? { links } : {}
|
|
13869
|
+
};
|
|
13870
|
+
},
|
|
13825
13871
|
instanceFormSchema: async ({ id, kind }) => {
|
|
13826
13872
|
return (id === null ? byKind(kind) : await holderOf(id)).provider.instanceFormSchema({ id });
|
|
13827
13873
|
},
|
|
@@ -56522,18 +56568,38 @@ var IntegrationInstanceKindSchema = object({
|
|
|
56522
56568
|
/** Relative hub path to disconnect that account. */
|
|
56523
56569
|
disconnectPath: string().min(1).optional()
|
|
56524
56570
|
});
|
|
56571
|
+
/**
|
|
56572
|
+
* How devices of an instance are found. `none`: the operator adds them by hand; `lan`: the addon
|
|
56573
|
+
* scans the network before any instance exists (the wizard offers the candidates); `account`: the
|
|
56574
|
+
* devices appear after the instance signs in. `scopedToInstance`: a discovery run belongs to ONE
|
|
56575
|
+
* instance and its candidates are claimed by it.
|
|
56576
|
+
*/
|
|
56577
|
+
var IntegrationInstanceDiscoverySchema = object({
|
|
56578
|
+
mode: _enum([
|
|
56579
|
+
"none",
|
|
56580
|
+
"lan",
|
|
56581
|
+
"account"
|
|
56582
|
+
]),
|
|
56583
|
+
scopedToInstance: boolean()
|
|
56584
|
+
});
|
|
56585
|
+
/**
|
|
56586
|
+
* One link a kind of this provider declares (D1013 rule 5): the body `field` of an instance of
|
|
56587
|
+
* `kind` holds the id of an instance of the function cap `to.cap` (of kind `to.kind`, when set).
|
|
56588
|
+
* The hub indexes these so the target's delete is refused while a link names it.
|
|
56589
|
+
*/
|
|
56590
|
+
var IntegrationInstanceLinkSchema = object({
|
|
56591
|
+
kind: string().min(1),
|
|
56592
|
+
field: string().min(1),
|
|
56593
|
+
to: object({
|
|
56594
|
+
cap: string().min(1),
|
|
56595
|
+
kind: string().min(1).optional()
|
|
56596
|
+
})
|
|
56597
|
+
});
|
|
56525
56598
|
var IntegrationInstanceDescriptionSchema = object({
|
|
56526
56599
|
/** What the instances are for the Integrations page; see {@link IntegrationInstanceRoleSchema}. */
|
|
56527
56600
|
role: IntegrationInstanceRoleSchema,
|
|
56528
56601
|
/** How the devices of an instance are found; absent means `{ mode: 'none' }`. */
|
|
56529
|
-
discovery:
|
|
56530
|
-
mode: _enum([
|
|
56531
|
-
"none",
|
|
56532
|
-
"lan",
|
|
56533
|
-
"account"
|
|
56534
|
-
]),
|
|
56535
|
-
scopedToInstance: boolean()
|
|
56536
|
-
}).optional(),
|
|
56602
|
+
discovery: IntegrationInstanceDiscoverySchema.optional(),
|
|
56537
56603
|
/** `single`: at most one instance can exist (Loki). `multiple`: an account each. */
|
|
56538
56604
|
instanceMode: IntegrationInstanceModeSchema,
|
|
56539
56605
|
/** What the instance is, for the UI (`destination`, `relay`, `account`). */
|
|
@@ -56554,7 +56620,9 @@ var IntegrationInstanceDescriptionSchema = object({
|
|
|
56554
56620
|
* `createInstance` without an `id` works: the provider mints it. Absent means the caller must
|
|
56555
56621
|
* send an id (the UI then mints an opaque one).
|
|
56556
56622
|
*/
|
|
56557
|
-
mintsId: boolean().optional()
|
|
56623
|
+
mintsId: boolean().optional(),
|
|
56624
|
+
/** The links to other instances the provider's kinds declare; absent means none. */
|
|
56625
|
+
links: array(IntegrationInstanceLinkSchema).optional()
|
|
56558
56626
|
});
|
|
56559
56627
|
var IntegrationInstanceStatusSchema = object({
|
|
56560
56628
|
state: _enum([
|
|
@@ -98440,27 +98508,51 @@ object({
|
|
|
98440
98508
|
delay: number$1().nonnegative().optional(),
|
|
98441
98509
|
commands: record(string(), unknown())
|
|
98442
98510
|
});
|
|
98443
|
-
var
|
|
98444
|
-
|
|
98511
|
+
var InstanceInUseError = class extends Error {
|
|
98512
|
+
instanceId;
|
|
98445
98513
|
users;
|
|
98446
|
-
name = "
|
|
98447
|
-
constructor(
|
|
98448
|
-
super(`
|
|
98449
|
-
this.
|
|
98514
|
+
name = "InstanceInUseError";
|
|
98515
|
+
constructor(instanceId, users) {
|
|
98516
|
+
super(`instance '${instanceId}' is still in use by ${users.map((u) => `${u.kind} '${u.name ?? u.id}' (${u.id})`).join(", ")}; unlink them first`);
|
|
98517
|
+
this.instanceId = instanceId;
|
|
98450
98518
|
this.users = users;
|
|
98451
98519
|
}
|
|
98452
98520
|
};
|
|
98453
|
-
/** Throw {@link
|
|
98454
|
-
async function
|
|
98521
|
+
/** Throw {@link InstanceInUseError} when any finder names a user of `instanceId`. */
|
|
98522
|
+
async function assertInstanceUnused(instanceId, finders) {
|
|
98455
98523
|
const users = [];
|
|
98456
|
-
for (const finder of finders) users.push(...await finder.find(
|
|
98457
|
-
if (users.length > 0) throw new
|
|
98524
|
+
for (const finder of finders) users.push(...await finder.find(instanceId));
|
|
98525
|
+
if (users.length > 0) throw new InstanceInUseError(instanceId, users);
|
|
98458
98526
|
}
|
|
98459
|
-
/**
|
|
98460
|
-
|
|
98461
|
-
|
|
98462
|
-
|
|
98463
|
-
|
|
98527
|
+
/** The `instance-select` field of a link, keyed like the body field it fills. */
|
|
98528
|
+
function linkFieldDescriptor(key, target, labels) {
|
|
98529
|
+
return {
|
|
98530
|
+
type: "instance-select",
|
|
98531
|
+
key,
|
|
98532
|
+
label: labels.label,
|
|
98533
|
+
...labels.description !== void 0 ? { description: labels.description } : {},
|
|
98534
|
+
required: labels.required ?? true,
|
|
98535
|
+
...labels.noneLabel !== void 0 ? { noneLabel: labels.noneLabel } : {},
|
|
98536
|
+
functionCap: target.cap,
|
|
98537
|
+
...target.kind !== void 0 ? { kind: target.kind } : {}
|
|
98538
|
+
};
|
|
98539
|
+
}
|
|
98540
|
+
/** @deprecated use {@link InstanceInUseError}; this one only adds the old `brokerId` name. */
|
|
98541
|
+
var BrokerInUseError = class extends InstanceInUseError {
|
|
98542
|
+
get brokerId() {
|
|
98543
|
+
return this.instanceId;
|
|
98544
|
+
}
|
|
98545
|
+
};
|
|
98546
|
+
/** @deprecated use {@link assertInstanceUnused}. */
|
|
98547
|
+
async function assertBrokerUnused(brokerId, finders) {
|
|
98548
|
+
try {
|
|
98549
|
+
await assertInstanceUnused(brokerId, finders);
|
|
98550
|
+
} catch (err) {
|
|
98551
|
+
if (err instanceof InstanceInUseError) throw new BrokerInUseError(err.instanceId, err.users);
|
|
98552
|
+
throw err;
|
|
98553
|
+
}
|
|
98554
|
+
}
|
|
98555
|
+
/** @deprecated use `guardInstanceDelete`. */
|
|
98464
98556
|
function guardBrokerDelete(base, users) {
|
|
98465
98557
|
return {
|
|
98466
98558
|
...base,
|
|
@@ -98614,6 +98706,35 @@ function brokerLinkPortsOver(deps) {
|
|
|
98614
98706
|
};
|
|
98615
98707
|
}
|
|
98616
98708
|
//#endregion
|
|
98709
|
+
//#region src/speak/constants.ts
|
|
98710
|
+
/** Echo speech limits and the worker restart bound (same window as CrashSupervisor). */
|
|
98711
|
+
var ALEXA_SPEAK_ADDON_ID = "import-alexa";
|
|
98712
|
+
var ALEXA_SPEAK_KIND = "alexa-echo";
|
|
98713
|
+
/** The kind of the Amazon-account broker the Echoes are read from. */
|
|
98714
|
+
var ALEXA_BROKER_KIND = "alexa";
|
|
98715
|
+
/** The Amazon account an Echo target speaks through. */
|
|
98716
|
+
var ALEXA_ACCOUNT_LINK = {
|
|
98717
|
+
cap: "broker",
|
|
98718
|
+
kind: ALEXA_BROKER_KIND
|
|
98719
|
+
};
|
|
98720
|
+
var DEVICE_REFRESH_MS = 5 * 6e4;
|
|
98721
|
+
/** Where the addon serves the Alexa mark; `listTargetKinds` stamps it as `iconUrl`. */
|
|
98722
|
+
var ALEXA_SPEAK_ICON_URL = `/addon/${ALEXA_SPEAK_ADDON_ID}/icons/${ALEXA_SPEAK_KIND}`;
|
|
98723
|
+
/** How often an adopted Echo's player state is read (a push channel is a later phase). */
|
|
98724
|
+
var ECHO_POLL_MS = 3e4;
|
|
98725
|
+
/** One more read after a command, once Amazon has had time to apply it. */
|
|
98726
|
+
var ECHO_REREAD_MS = 1500;
|
|
98727
|
+
/** After a 429 the account is left alone for this long, doubling up to the cap. */
|
|
98728
|
+
var ECHO_RATE_BACKOFF_BASE_MS = 3e4;
|
|
98729
|
+
var ECHO_RATE_BACKOFF_MAX_MS = 5 * 6e4;
|
|
98730
|
+
/**
|
|
98731
|
+
* A command (volume, play, Do Not Disturb) that has not started within this, or not
|
|
98732
|
+
* finished, is answered with an error instead of running minutes late.
|
|
98733
|
+
*/
|
|
98734
|
+
var ECHO_COMMAND_DEADLINE_MS = 12e3;
|
|
98735
|
+
/** A poll read is stale after this; it is dropped from the queue rather than sent late. */
|
|
98736
|
+
var ECHO_READ_DEADLINE_MS = 25e3;
|
|
98737
|
+
//#endregion
|
|
98617
98738
|
//#region src/config.ts
|
|
98618
98739
|
/**
|
|
98619
98740
|
* Alexa account connection settings for ONE broker (= one Amazon account).
|
|
@@ -98670,7 +98791,7 @@ var alexaEchoTargetSchema = object({
|
|
|
98670
98791
|
name: string(),
|
|
98671
98792
|
enabled: boolean(),
|
|
98672
98793
|
/** The broker (Amazon account) the Echo belongs to; '' until one is chosen. */
|
|
98673
|
-
brokerId:
|
|
98794
|
+
brokerId: linkField(ALEXA_ACCOUNT_LINK).default(""),
|
|
98674
98795
|
echoId: string(),
|
|
98675
98796
|
respectDnd: boolean()
|
|
98676
98797
|
});
|
|
@@ -144035,30 +144156,6 @@ function withListedVolume(status, listed) {
|
|
|
144035
144156
|
};
|
|
144036
144157
|
}
|
|
144037
144158
|
//#endregion
|
|
144038
|
-
//#region src/speak/constants.ts
|
|
144039
|
-
/** Echo speech limits and the worker restart bound (same window as CrashSupervisor). */
|
|
144040
|
-
var ALEXA_SPEAK_ADDON_ID = "import-alexa";
|
|
144041
|
-
var ALEXA_SPEAK_KIND = "alexa-echo";
|
|
144042
|
-
/** The kind of the Amazon-account broker the Echoes are read from. */
|
|
144043
|
-
var ALEXA_BROKER_KIND = "alexa";
|
|
144044
|
-
var DEVICE_REFRESH_MS = 5 * 6e4;
|
|
144045
|
-
/** Where the addon serves the Alexa mark; `listTargetKinds` stamps it as `iconUrl`. */
|
|
144046
|
-
var ALEXA_SPEAK_ICON_URL = `/addon/${ALEXA_SPEAK_ADDON_ID}/icons/${ALEXA_SPEAK_KIND}`;
|
|
144047
|
-
/** How often an adopted Echo's player state is read (a push channel is a later phase). */
|
|
144048
|
-
var ECHO_POLL_MS = 3e4;
|
|
144049
|
-
/** One more read after a command, once Amazon has had time to apply it. */
|
|
144050
|
-
var ECHO_REREAD_MS = 1500;
|
|
144051
|
-
/** After a 429 the account is left alone for this long, doubling up to the cap. */
|
|
144052
|
-
var ECHO_RATE_BACKOFF_BASE_MS = 3e4;
|
|
144053
|
-
var ECHO_RATE_BACKOFF_MAX_MS = 5 * 6e4;
|
|
144054
|
-
/**
|
|
144055
|
-
* A command (volume, play, Do Not Disturb) that has not started within this, or not
|
|
144056
|
-
* finished, is answered with an error instead of running minutes late.
|
|
144057
|
-
*/
|
|
144058
|
-
var ECHO_COMMAND_DEADLINE_MS = 12e3;
|
|
144059
|
-
/** A poll read is stale after this; it is dropped from the queue rather than sent late. */
|
|
144060
|
-
var ECHO_READ_DEADLINE_MS = 25e3;
|
|
144061
|
-
//#endregion
|
|
144062
144159
|
//#region src/speak/worker-protocol.ts
|
|
144063
144160
|
/** Line-delimited JSON the long-lived Python worker speaks. One object per line. */
|
|
144064
144161
|
var EchoDeviceSchema = object({
|
|
@@ -145136,6 +145233,13 @@ function speechFromNotification(notification) {
|
|
|
145136
145233
|
}
|
|
145137
145234
|
//#endregion
|
|
145138
145235
|
//#region src/speak/provider.ts
|
|
145236
|
+
/**
|
|
145237
|
+
* `notification-output` provider for Echo devices. Text is the rule template
|
|
145238
|
+
* as plain speech. A burst collapses or waits; it is not queued. A transport
|
|
145239
|
+
* failure is `success: false` so the notification outbox retries. A collapse,
|
|
145240
|
+
* a rate limit, or Do Not Disturb is a recorded skip (`success: true`) so the
|
|
145241
|
+
* outbox does not speak the same sentence again later.
|
|
145242
|
+
*/
|
|
145139
145243
|
function quietLogger$1() {
|
|
145140
145244
|
const logger = {
|
|
145141
145245
|
debug() {},
|
|
@@ -145191,11 +145295,8 @@ function configSchema() {
|
|
|
145191
145295
|
title: "Echo",
|
|
145192
145296
|
titleKey: "import-alexa:echoTarget.s_echo.title",
|
|
145193
145297
|
fields: [...[{
|
|
145194
|
-
|
|
145195
|
-
|
|
145196
|
-
label: "Amazon account",
|
|
145197
|
-
labelKey: "import-alexa:echoTarget.s_echo.brokerId.label",
|
|
145198
|
-
brokerKind: ALEXA_BROKER_KIND
|
|
145298
|
+
...linkFieldDescriptor("brokerId", ALEXA_ACCOUNT_LINK, { label: "Amazon account" }),
|
|
145299
|
+
labelKey: "import-alexa:echoTarget.s_echo.brokerId.label"
|
|
145199
145300
|
}, {
|
|
145200
145301
|
type: "select",
|
|
145201
145302
|
key: "echoId",
|
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.191",
|
|
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.784"
|
|
76
76
|
}
|
|
77
77
|
},
|
|
78
78
|
"files": [
|