@camstack/addon-import-alexa 0.2.162 → 0.2.164
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 +335 -411
- package/dist/addon.mjs +336 -410
- package/dist/index.js +0 -1
- package/dist/index.mjs +2 -2
- package/package.json +2 -17
- package/dist/MaskShapeCanvas-B6q5RDLi.mjs +0 -13807
- package/dist/MotionZonesSettings-ByDKuk5o.mjs +0 -309
- package/dist/PrivacyMaskSettings-Cn-_9Syv.mjs +0 -368
- package/dist/SceneMonitorEditor-jDMtE1yg.mjs +0 -845
- package/dist/_stub.js +0 -39538
- package/dist/_virtual_mf-localSharedImportMap___mfe_internal__addon_import_alexa_page-Ck3EMdmv.mjs +0 -156
- package/dist/_virtual_mf___mfe_internal__addon_import_alexa_page__loadShare___mf_0_camstack_mf_1_sdk__loadShare__.js-BmoaT3cr.mjs +0 -25
- package/dist/_virtual_mf___mfe_internal__addon_import_alexa_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DE1dtKjc.mjs +0 -26
- package/dist/_virtual_mf___mfe_internal__addon_import_alexa_page__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-Bct5qZia.mjs +0 -25
- package/dist/_virtual_mf___mfe_internal__addon_import_alexa_page__loadShare___mf_0_trpc_mf_1_client__loadShare__.js-BHqOw8oh.mjs +0 -25
- package/dist/_virtual_mf___mfe_internal__addon_import_alexa_page__loadShare___mf_0_trpc_mf_1_react_mf_2_query__loadShare__.js-B3qxfxBT.mjs +0 -26
- package/dist/_virtual_mf___mfe_internal__addon_import_alexa_page__loadShare__react__loadShare__.js-DwXXYAlk.mjs +0 -70
- package/dist/_virtual_mf___mfe_internal__addon_import_alexa_page__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-CUiVYNMY.mjs +0 -26
- package/dist/_virtual_mf___mfe_internal__addon_import_alexa_page__loadShare__react_mf_2_dom__loadShare__.js-CIPmAyPi.mjs +0 -25
- package/dist/_virtual_mf___mfe_internal__addon_import_alexa_page__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-tXm1x5EZ.mjs +0 -25
- package/dist/addon-import-alexa.css +0 -3
- package/dist/hostInit-CoSVGWK6.mjs +0 -129
- package/dist/player-overlays-CcdvNfxc.mjs +0 -51
- package/dist/remoteEntry.js +0 -2
- package/dist/remoteEntry.ssr.js +0 -33
- package/dist/responsive-BmleF5y2.mjs +0 -125
- package/dist/rolldown-runtime-HEgqtunE.mjs +0 -20
- package/dist/scene-monitor-copy-BEZSY5WG.mjs +0 -240
- package/dist/square-DZHhUhzl.mjs +0 -45
- package/dist/trash-2-BVrrPiKB.mjs +0 -25
- package/dist/ui-text-B5YO4hoJ.mjs +0 -34
- package/dist/virtualExposes-lZT06jeg.mjs +0 -2
- package/dist/virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_import_alexa_page__remoteEntry_js-BA8fy0_J.mjs +0 -2857
- package/dist/virtual_mf-exposes-ssr___mfe_internal__addon_import_alexa_page__remoteEntry_js-CViESMdd.mjs +0 -10
- package/dist/virtual_mf-exposes___mfe_internal__addon_import_alexa_page__remoteEntry_js-C3qJCjtV.mjs +0 -27
package/dist/addon.js
CHANGED
|
@@ -8865,28 +8865,6 @@ function event(data) {
|
|
|
8865
8865
|
var StaticDirOutputSchema$1 = object({ staticDir: string() });
|
|
8866
8866
|
var VersionOutputSchema$1 = object({ version: string() });
|
|
8867
8867
|
method(_void(), StaticDirOutputSchema$1, { auth: "admin" }), method(_void(), VersionOutputSchema$1, { auth: "admin" });
|
|
8868
|
-
/**
|
|
8869
|
-
* Identity — preserves literal types for downstream inference.
|
|
8870
|
-
*
|
|
8871
|
-
* The constraint is `Record<string, unknown>` (not `CustomActionsSpec`) so
|
|
8872
|
-
* TypeScript does not widen each entry's literal `kind`/`auth` fields to
|
|
8873
|
-
* the broader unions declared on `CustomActionSpec`'s default generics.
|
|
8874
|
-
* Shape validity is enforced separately by the `customAction(...)` helper
|
|
8875
|
-
* whose return type is already a `CustomActionSpec<...>`.
|
|
8876
|
-
*/
|
|
8877
|
-
function defineCustomActions(spec) {
|
|
8878
|
-
return spec;
|
|
8879
|
-
}
|
|
8880
|
-
function customAction(input, output, options) {
|
|
8881
|
-
return {
|
|
8882
|
-
input,
|
|
8883
|
-
output,
|
|
8884
|
-
kind: options?.kind ?? "query",
|
|
8885
|
-
auth: options?.auth ?? "protected",
|
|
8886
|
-
scope: options?.scope ?? { kind: "system" },
|
|
8887
|
-
...options?.caller ? { caller: "required" } : {}
|
|
8888
|
-
};
|
|
8889
|
-
}
|
|
8890
8868
|
var StaticDirOutputSchema = object({ staticDir: string() });
|
|
8891
8869
|
var VersionOutputSchema = object({ version: string() });
|
|
8892
8870
|
method(_void(), StaticDirOutputSchema, { auth: "admin" }), method(_void(), VersionOutputSchema, { auth: "admin" });
|
|
@@ -11231,7 +11209,7 @@ function errMsg(err) {
|
|
|
11231
11209
|
return String(err);
|
|
11232
11210
|
}
|
|
11233
11211
|
//#endregion
|
|
11234
|
-
//#region ../types/dist/composition-
|
|
11212
|
+
//#region ../types/dist/composition-DwA0uVW5.mjs
|
|
11235
11213
|
/**
|
|
11236
11214
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11237
11215
|
*
|
|
@@ -16476,24 +16454,7 @@ var AddonPageDeclarationSchema = object({
|
|
|
16476
16454
|
/** Display label for a CUSTOM `section` id (ignored for well-known ids). */
|
|
16477
16455
|
sectionLabel: string().optional()
|
|
16478
16456
|
});
|
|
16479
|
-
|
|
16480
|
-
name: "addon-pages-source",
|
|
16481
|
-
scope: "system",
|
|
16482
|
-
mode: "collection",
|
|
16483
|
-
internal: true,
|
|
16484
|
-
methods: {
|
|
16485
|
-
/**
|
|
16486
|
-
* `internal: true` did not gate the mount (see the 2026-08-26 note on
|
|
16487
|
-
* `data-store-provider`) — this was reachable on the AppRouter by ANY
|
|
16488
|
-
* authenticated session at the default `auth: 'protected'`. The only
|
|
16489
|
-
* consumer, the `addon-pages-aggregator` builtin, reads it via
|
|
16490
|
-
* `ctx.capabilities.getCollection('addon-pages-source')` — LOCAL-PROCESS
|
|
16491
|
-
* only, never tRPC (per `ctx.capabilities` seeing only the local
|
|
16492
|
-
* process). The public, enriched listing admin-ui actually consumes is
|
|
16493
|
-
* the separate `addon-pages` cap, unaffected by this change.
|
|
16494
|
-
*/
|
|
16495
|
-
listPages: method(_void(), array(AddonPageDeclarationSchema).readonly(), { auth: "admin" }) }
|
|
16496
|
-
};
|
|
16457
|
+
method(_void(), array(AddonPageDeclarationSchema).readonly(), { auth: "admin" });
|
|
16497
16458
|
var AddonHttpRouteSchema = object({
|
|
16498
16459
|
method: _enum([
|
|
16499
16460
|
"GET",
|
|
@@ -19451,12 +19412,13 @@ var CHANGED = {
|
|
|
19451
19412
|
expr: "now"
|
|
19452
19413
|
};
|
|
19453
19414
|
function numeric(input) {
|
|
19415
|
+
const { alsoFrom, ...base } = input;
|
|
19454
19416
|
return {
|
|
19455
|
-
...
|
|
19417
|
+
...base,
|
|
19456
19418
|
family: "numeric",
|
|
19457
19419
|
deviceType: DeviceType.Sensor,
|
|
19458
19420
|
memberDeviceType: DeviceType.Sensor,
|
|
19459
|
-
memberDeviceTypes: [DeviceType.Sensor],
|
|
19421
|
+
memberDeviceTypes: [DeviceType.Sensor, ...alsoFrom?.deviceTypes ?? []],
|
|
19460
19422
|
activeWhen: void 0,
|
|
19461
19423
|
commandCaps: [],
|
|
19462
19424
|
baseUnit: baseUnitOf(input.quantity),
|
|
@@ -19464,9 +19426,10 @@ function numeric(input) {
|
|
|
19464
19426
|
accept: [{
|
|
19465
19427
|
cap: input.outputCap,
|
|
19466
19428
|
fieldPath: input.outputField
|
|
19467
|
-
}]
|
|
19429
|
+
}, ...alsoFrom === void 0 ? [] : [alsoFrom.accept]]
|
|
19468
19430
|
};
|
|
19469
19431
|
}
|
|
19432
|
+
var CLIMATE_DEVICES = [DeviceType.Thermostat, DeviceType.Climate];
|
|
19470
19433
|
function binary(input) {
|
|
19471
19434
|
return {
|
|
19472
19435
|
...input,
|
|
@@ -19492,7 +19455,14 @@ var GROUP_TEMPLATES = [
|
|
|
19492
19455
|
quantity: "temperature",
|
|
19493
19456
|
defaultAggregate: "mean",
|
|
19494
19457
|
empty: "null",
|
|
19495
|
-
companions: [NOW$2]
|
|
19458
|
+
companions: [NOW$2],
|
|
19459
|
+
alsoFrom: {
|
|
19460
|
+
deviceTypes: CLIMATE_DEVICES,
|
|
19461
|
+
accept: {
|
|
19462
|
+
cap: "climate-control",
|
|
19463
|
+
fieldPath: "currentTemp"
|
|
19464
|
+
}
|
|
19465
|
+
}
|
|
19496
19466
|
}),
|
|
19497
19467
|
numeric({
|
|
19498
19468
|
id: "humidity",
|
|
@@ -19503,7 +19473,14 @@ var GROUP_TEMPLATES = [
|
|
|
19503
19473
|
quantity: "humidity",
|
|
19504
19474
|
defaultAggregate: "mean",
|
|
19505
19475
|
empty: "null",
|
|
19506
|
-
companions: [NOW$2]
|
|
19476
|
+
companions: [NOW$2],
|
|
19477
|
+
alsoFrom: {
|
|
19478
|
+
deviceTypes: CLIMATE_DEVICES,
|
|
19479
|
+
accept: {
|
|
19480
|
+
cap: "climate-control",
|
|
19481
|
+
fieldPath: "currentHumidity"
|
|
19482
|
+
}
|
|
19483
|
+
}
|
|
19507
19484
|
}),
|
|
19508
19485
|
numeric({
|
|
19509
19486
|
id: "pressure",
|
|
@@ -45218,6 +45195,43 @@ var NotificationActionSchema = object({
|
|
|
45218
45195
|
*/
|
|
45219
45196
|
mode: _enum(["navigate", "background"]).optional()
|
|
45220
45197
|
});
|
|
45198
|
+
/** A delivery parameter value: what a notifier declared it takes. */
|
|
45199
|
+
var DeliveryParamValueSchema = union([
|
|
45200
|
+
string(),
|
|
45201
|
+
number(),
|
|
45202
|
+
boolean()
|
|
45203
|
+
]);
|
|
45204
|
+
/**
|
|
45205
|
+
* One extra delivery parameter a notifier KIND declares (D938). A rule that
|
|
45206
|
+
* addresses a target of that kind may set it; a kind that declares nothing is
|
|
45207
|
+
* never handed one. The admin UI derives its control from this descriptor —
|
|
45208
|
+
* no editor knows a service by name.
|
|
45209
|
+
*
|
|
45210
|
+
* Optional fields are `.optional()`, never `.default()` (a Zod default does not
|
|
45211
|
+
* run on the addon cap path).
|
|
45212
|
+
*/
|
|
45213
|
+
var DeliveryParamSchema = object({
|
|
45214
|
+
key: string().min(1),
|
|
45215
|
+
label: string(),
|
|
45216
|
+
/** i18n key of the label, resolved by the declaring addon's namespace. */
|
|
45217
|
+
labelKey: string().optional(),
|
|
45218
|
+
type: _enum([
|
|
45219
|
+
"enum",
|
|
45220
|
+
"number",
|
|
45221
|
+
"boolean",
|
|
45222
|
+
"string"
|
|
45223
|
+
]),
|
|
45224
|
+
/** `enum` only: the closed set of accepted values. */
|
|
45225
|
+
options: array(object({
|
|
45226
|
+
value: string(),
|
|
45227
|
+
label: string()
|
|
45228
|
+
})).optional(),
|
|
45229
|
+
/** `number` only. */
|
|
45230
|
+
min: number().optional(),
|
|
45231
|
+
max: number().optional(),
|
|
45232
|
+
/** What the notifier does when the rule sets nothing; shown as the placeholder. */
|
|
45233
|
+
default: DeliveryParamValueSchema.optional()
|
|
45234
|
+
});
|
|
45221
45235
|
/**
|
|
45222
45236
|
* The canonical notification. `body` is the only hard field (Apprise model).
|
|
45223
45237
|
* `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
|
|
@@ -45240,7 +45254,13 @@ var NotificationSchema = object({
|
|
|
45240
45254
|
tag: string().optional(),
|
|
45241
45255
|
deviceId: number().optional(),
|
|
45242
45256
|
eventId: string().optional(),
|
|
45243
|
-
metadata: record(string(), unknown()).optional()
|
|
45257
|
+
metadata: record(string(), unknown()).optional(),
|
|
45258
|
+
/**
|
|
45259
|
+
* Values for the parameters the target's kind DECLARES in
|
|
45260
|
+
* {@link TargetKindSchema} `deliveryParams`. Already validated against the
|
|
45261
|
+
* declaration by the sender; a notifier reads only the keys it declared.
|
|
45262
|
+
*/
|
|
45263
|
+
delivery: record(string(), DeliveryParamValueSchema).optional()
|
|
45244
45264
|
});
|
|
45245
45265
|
/** One declared native severity/priority level for a kind. */
|
|
45246
45266
|
var TargetKindLevelSchema = object({
|
|
@@ -45346,6 +45366,11 @@ var TargetKindSchema = object({
|
|
|
45346
45366
|
*/
|
|
45347
45367
|
destinationKeys: array(string()).optional(),
|
|
45348
45368
|
supportsDiscovery: boolean(),
|
|
45369
|
+
/**
|
|
45370
|
+
* Extra delivery parameters a rule may set for a target of this kind (D938).
|
|
45371
|
+
* Absent = none; the rule editor then offers nothing.
|
|
45372
|
+
*/
|
|
45373
|
+
deliveryParams: array(DeliveryParamSchema).optional(),
|
|
45349
45374
|
caps: TargetKindCapsSchema
|
|
45350
45375
|
});
|
|
45351
45376
|
/**
|
|
@@ -57659,7 +57684,9 @@ var ManifestShapeSchema = object({
|
|
|
57659
57684
|
icon: string().optional()
|
|
57660
57685
|
}).optional(),
|
|
57661
57686
|
protected: boolean().optional(),
|
|
57662
|
-
removable: boolean().optional()
|
|
57687
|
+
removable: boolean().optional(),
|
|
57688
|
+
/** Declared Integrations-page block (mirror of `AddonDeclaration.integrationSection`). */
|
|
57689
|
+
integrationSection: _enum(["system", "providers"]).optional()
|
|
57663
57690
|
});
|
|
57664
57691
|
var AddonProcessSchema = object({
|
|
57665
57692
|
pid: number().optional(),
|
|
@@ -90809,6 +90836,29 @@ function rewriteReferer(referer, route) {
|
|
|
90809
90836
|
if (upstream === void 0) return null;
|
|
90810
90837
|
return `${upstream.scheme}://${upstream.host}${rest?.[2] ?? "/"}${url.search}`;
|
|
90811
90838
|
}
|
|
90839
|
+
function decodeFormComponent(raw) {
|
|
90840
|
+
try {
|
|
90841
|
+
return decodeURIComponent(raw.replace(/\+/g, " "));
|
|
90842
|
+
} catch {
|
|
90843
|
+
return null;
|
|
90844
|
+
}
|
|
90845
|
+
}
|
|
90846
|
+
/**
|
|
90847
|
+
* An `application/x-www-form-urlencoded` body whose field VALUES that are proxy URLs of
|
|
90848
|
+
* this attempt (absolute or root-relative) are turned back into the vendor URL they stand
|
|
90849
|
+
* for — the page's hidden inputs were rewritten on the way out (`rewriteBody`), and the
|
|
90850
|
+
* vendor must get its own URL back. Field order and every untouched pair stay byte-identical.
|
|
90851
|
+
*/
|
|
90852
|
+
function rewriteFormBody(body, route) {
|
|
90853
|
+
return body.split("&").map((pair) => {
|
|
90854
|
+
const eq = pair.indexOf("=");
|
|
90855
|
+
if (eq === -1) return pair;
|
|
90856
|
+
const value = decodeFormComponent(pair.slice(eq + 1));
|
|
90857
|
+
if (value === null || !/^(?:https?:\/\/|\/)/i.test(value)) return pair;
|
|
90858
|
+
const back = rewriteReferer(value, route);
|
|
90859
|
+
return back === null ? pair : `${pair.slice(0, eq + 1)}${encodeURIComponent(back)}`;
|
|
90860
|
+
}).join("&");
|
|
90861
|
+
}
|
|
90812
90862
|
/** Cookies the browser holds for the hub itself must never reach a vendor. */
|
|
90813
90863
|
function filterForwardedCookies(cookieHeader) {
|
|
90814
90864
|
return cookieHeader.split(";").map((part) => part.trim()).filter((part) => part.length > 0 && !part.toLowerCase().startsWith("camstack_")).join("; ");
|
|
@@ -90867,6 +90917,9 @@ function bodyKindOf(contentType) {
|
|
|
90867
90917
|
if (type.startsWith("text/") || type.includes("javascript") || type.includes("json") || type.includes("xml")) return "other";
|
|
90868
90918
|
return null;
|
|
90869
90919
|
}
|
|
90920
|
+
function isFormEncoded(contentType) {
|
|
90921
|
+
return (firstHeader(contentType) ?? "").toLowerCase().startsWith("application/x-www-form-urlencoded");
|
|
90922
|
+
}
|
|
90870
90923
|
function firstHeader(value) {
|
|
90871
90924
|
return Array.isArray(value) ? value[0] : value;
|
|
90872
90925
|
}
|
|
@@ -90997,7 +91050,7 @@ var VendorLoginProxy = class {
|
|
|
90997
91050
|
sendPlain(res, 413, "Request too large");
|
|
90998
91051
|
return;
|
|
90999
91052
|
}
|
|
91000
|
-
body = read;
|
|
91053
|
+
body = isFormEncoded(req.headers["content-type"]) ? Buffer.from(rewriteFormBody(read.toString("utf8"), route), "utf8") : read;
|
|
91001
91054
|
}
|
|
91002
91055
|
const headers = this.#requestHeaders(req, route, upstream);
|
|
91003
91056
|
const upstreamRes = await this.#fetch(target, {
|
|
@@ -91011,9 +91064,10 @@ var VendorLoginProxy = class {
|
|
|
91011
91064
|
const kind = bodyKindOf(upstreamRes.headers.get("content-type") ?? "");
|
|
91012
91065
|
const declaredLength = Number(upstreamRes.headers.get("content-length") ?? "0");
|
|
91013
91066
|
const text = kind !== null && !(declaredLength > MAX_TEXT_BODY_BYTES) && method !== "HEAD" ? await upstreamRes.text() : null;
|
|
91014
|
-
this.#logger.
|
|
91067
|
+
this.#logger.info("vendor login proxy: upstream answered", { meta: {
|
|
91015
91068
|
addonId: this.#addonId,
|
|
91016
91069
|
host: upstream.host,
|
|
91070
|
+
path: target.pathname,
|
|
91017
91071
|
status: upstreamRes.status,
|
|
91018
91072
|
method
|
|
91019
91073
|
} });
|
|
@@ -91146,59 +91200,37 @@ var AlexaBrokerEntrySchema = object({
|
|
|
91146
91200
|
*/
|
|
91147
91201
|
var alexaProxyLoginSchema = object({ proxyPort: preprocess((v) => v === "" || v === null ? void 0 : v, number().int().min(1).max(65535).default(3456)).describe("Loopback port the local Amazon-login proxy listens on (never exposed)") });
|
|
91148
91202
|
/**
|
|
91149
|
-
* Echo
|
|
91150
|
-
*
|
|
91151
|
-
|
|
91152
|
-
|
|
91153
|
-
|
|
91154
|
-
|
|
91155
|
-
|
|
91156
|
-
|
|
91157
|
-
|
|
91158
|
-
|
|
91159
|
-
|
|
91160
|
-
|
|
91161
|
-
|
|
91162
|
-
session: string().default("").describe("Legacy Alexa session blob").meta({ secret: true }),
|
|
91163
|
-
targets: array(object({
|
|
91164
|
-
id: string(),
|
|
91165
|
-
name: string(),
|
|
91166
|
-
enabled: boolean(),
|
|
91167
|
-
/** The broker (Amazon account) the Echo belongs to; '' until one is chosen. */
|
|
91168
|
-
brokerId: string().default(""),
|
|
91169
|
-
echoId: string(),
|
|
91170
|
-
mode: _enum(["speak", "announce"]),
|
|
91171
|
-
volume: number(),
|
|
91172
|
-
respectDnd: boolean()
|
|
91173
|
-
})).default([])
|
|
91203
|
+
* The Echo notification targets this addon owns. Speech signs in as an import
|
|
91204
|
+
* broker, so a target is only the Echo it speaks on and how.
|
|
91205
|
+
*/
|
|
91206
|
+
var alexaEchoTargetSchema = object({
|
|
91207
|
+
id: string(),
|
|
91208
|
+
name: string(),
|
|
91209
|
+
enabled: boolean(),
|
|
91210
|
+
/** The broker (Amazon account) the Echo belongs to; '' until one is chosen. */
|
|
91211
|
+
brokerId: string().default(""),
|
|
91212
|
+
echoId: string(),
|
|
91213
|
+
mode: _enum(["speak", "announce"]),
|
|
91214
|
+
volume: number(),
|
|
91215
|
+
respectDnd: boolean()
|
|
91174
91216
|
});
|
|
91175
91217
|
function isRecord$3(value) {
|
|
91176
91218
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
91177
91219
|
}
|
|
91178
|
-
/**
|
|
91179
|
-
function
|
|
91180
|
-
const raw =
|
|
91181
|
-
|
|
91182
|
-
|
|
91183
|
-
|
|
91184
|
-
|
|
91185
|
-
password: typeof raw["password"] === "string" ? raw["password"] : "",
|
|
91186
|
-
session: typeof raw["session"] === "string" ? raw["session"] : "",
|
|
91187
|
-
...region === "NA" || region === "EU" || region === "FE" ? { region } : {},
|
|
91188
|
-
...Array.isArray(targets) ? { targets } : {}
|
|
91220
|
+
/** The stored Echo targets; a row that no longer parses is dropped, never guessed. */
|
|
91221
|
+
function readEchoTargets(config) {
|
|
91222
|
+
const raw = config.echoTargets;
|
|
91223
|
+
if (!Array.isArray(raw)) return [];
|
|
91224
|
+
return raw.flatMap((row) => {
|
|
91225
|
+
const parsed = alexaEchoTargetSchema.safeParse(isRecord$3(row) ? row : {});
|
|
91226
|
+
return parsed.success ? [parsed.data] : [];
|
|
91189
91227
|
});
|
|
91190
91228
|
}
|
|
91191
91229
|
/** Top-level addon config — broker entries + the proxy-login settings. */
|
|
91192
91230
|
var alexaAddonConfigSchema = object({
|
|
91193
91231
|
brokers: array(AlexaBrokerEntrySchema).default([]),
|
|
91194
91232
|
proxyLogin: alexaProxyLoginSchema.default({ proxyPort: 3456 }),
|
|
91195
|
-
|
|
91196
|
-
email: "",
|
|
91197
|
-
password: "",
|
|
91198
|
-
region: "EU",
|
|
91199
|
-
session: "",
|
|
91200
|
-
targets: []
|
|
91201
|
-
})
|
|
91233
|
+
echoTargets: array(alexaEchoTargetSchema).default([])
|
|
91202
91234
|
});
|
|
91203
91235
|
/** Narrow an `unknown` to a shallow plain-object record (cast-free boundary). */
|
|
91204
91236
|
function toRecord(value) {
|
|
@@ -132745,6 +132777,67 @@ var ALEXA_API_USER_AGENT_POSTFIX = `AlexaRemote/${{
|
|
|
132745
132777
|
readmeFilename: "readme.md"
|
|
132746
132778
|
}.version}`;
|
|
132747
132779
|
//#endregion
|
|
132780
|
+
//#region src/alexa-region-options.ts
|
|
132781
|
+
var DEFAULT_LOCALE = "en-US";
|
|
132782
|
+
var LOCALE_BY_DOMAIN = {
|
|
132783
|
+
"amazon.com": "en-US",
|
|
132784
|
+
"amazon.it": "it-IT",
|
|
132785
|
+
"amazon.de": "de-DE",
|
|
132786
|
+
"amazon.co.uk": "en-GB",
|
|
132787
|
+
"amazon.fr": "fr-FR",
|
|
132788
|
+
"amazon.es": "es-ES",
|
|
132789
|
+
"amazon.ca": "en-CA",
|
|
132790
|
+
"amazon.com.au": "en-AU",
|
|
132791
|
+
"amazon.co.jp": "ja-JP",
|
|
132792
|
+
"amazon.in": "en-IN",
|
|
132793
|
+
"amazon.com.br": "pt-BR",
|
|
132794
|
+
"amazon.com.mx": "es-MX"
|
|
132795
|
+
};
|
|
132796
|
+
function alexaRegionOptions(amazonDomain) {
|
|
132797
|
+
const domain = amazonDomain.length > 0 ? amazonDomain : "amazon.com";
|
|
132798
|
+
const locale = LOCALE_BY_DOMAIN[domain] ?? DEFAULT_LOCALE;
|
|
132799
|
+
return {
|
|
132800
|
+
baseAmazonPage: domain,
|
|
132801
|
+
amazonPage: domain,
|
|
132802
|
+
acceptLanguage: locale,
|
|
132803
|
+
amazonPageProxyLanguage: locale.replace("-", "_")
|
|
132804
|
+
};
|
|
132805
|
+
}
|
|
132806
|
+
//#endregion
|
|
132807
|
+
//#region src/alexa-proxy-trace.ts
|
|
132808
|
+
/**
|
|
132809
|
+
* Sanitised one-line trace of the alexa-cookie2 proxy's own log lines.
|
|
132810
|
+
*
|
|
132811
|
+
* alexa-cookie2 logs every step it takes (routing, upstream request, redirect),
|
|
132812
|
+
* but its lines also carry headers, cookies and query values. This keeps only
|
|
132813
|
+
* what diagnoses a failed login: the method, the path or upstream host, and the
|
|
132814
|
+
* redirect target — always WITHOUT a query string or fragment. Any line that is
|
|
132815
|
+
* not one of the known step lines yields `null` and is never traced.
|
|
132816
|
+
*/
|
|
132817
|
+
var ROUTER_RE = /^Router: (\S+) \/ ([A-Z]+) \//;
|
|
132818
|
+
var PROXY_REQUEST_RE = /^Alexa-Cookie: Proxy-Request: ([A-Z]+) (\S+)$/;
|
|
132819
|
+
var PROXY_RESPONSE_RE = /^Alexa-Cookie: Proxy Response from Host: (\S+)$/;
|
|
132820
|
+
var REDIRECT_ORIGINAL_RE = /^Redirect: Original Location ----> (\S+)$/;
|
|
132821
|
+
var REDIRECT_FINAL_RE = /^Redirect: Final Location ----> (\S+)$/;
|
|
132822
|
+
/** Cut a URL or path at its query string or fragment. */
|
|
132823
|
+
function stripQuery(target) {
|
|
132824
|
+
const cut = target.search(/[?#]/);
|
|
132825
|
+
return cut === -1 ? target : target.slice(0, cut);
|
|
132826
|
+
}
|
|
132827
|
+
function sanitizeProxyTraceLine(line) {
|
|
132828
|
+
const router = ROUTER_RE.exec(line);
|
|
132829
|
+
if (router !== null) return `router ${router[2]} ${stripQuery(router[1] ?? "")}`;
|
|
132830
|
+
const request = PROXY_REQUEST_RE.exec(line);
|
|
132831
|
+
if (request !== null) return `proxy-request ${request[1]} ${stripQuery(request[2] ?? "")}`;
|
|
132832
|
+
const response = PROXY_RESPONSE_RE.exec(line);
|
|
132833
|
+
if (response !== null) return `proxy-response host=${response[1]}`;
|
|
132834
|
+
const original = REDIRECT_ORIGINAL_RE.exec(line);
|
|
132835
|
+
if (original !== null) return `redirect-original ${stripQuery(original[1] ?? "")}`;
|
|
132836
|
+
const final = REDIRECT_FINAL_RE.exec(line);
|
|
132837
|
+
if (final !== null) return `redirect-final ${stripQuery(final[1] ?? "")}`;
|
|
132838
|
+
return null;
|
|
132839
|
+
}
|
|
132840
|
+
//#endregion
|
|
132748
132841
|
//#region src/alexa-proxy-login.ts
|
|
132749
132842
|
/**
|
|
132750
132843
|
* INTERACTIVE Amazon login via `alexa-remote2`'s built-in proxy auth mode, behind the
|
|
@@ -132843,7 +132936,7 @@ var AlexaProxyLoginSession = class {
|
|
|
132843
132936
|
async start() {
|
|
132844
132937
|
const remote = new import_alexa_remote.default();
|
|
132845
132938
|
this.#remote = remote;
|
|
132846
|
-
const
|
|
132939
|
+
const region = alexaRegionOptions(this.#opts.amazonDomain);
|
|
132847
132940
|
remote.on("cookie", () => this.#onCookieEvent(remote));
|
|
132848
132941
|
remote.init({
|
|
132849
132942
|
proxyOnly: true,
|
|
@@ -132851,10 +132944,15 @@ var AlexaProxyLoginSession = class {
|
|
|
132851
132944
|
proxyListenBind: ALEXA_PROXY_LOOPBACK_HOST,
|
|
132852
132945
|
proxyPort: this.#opts.proxyPort,
|
|
132853
132946
|
proxyLogLevel: "warn",
|
|
132854
|
-
|
|
132947
|
+
...region,
|
|
132855
132948
|
apiUserAgentPostfix: ALEXA_API_USER_AGENT_POSTFIX,
|
|
132856
132949
|
logger: (...args) => {
|
|
132857
132950
|
const line = args.map(String).join(" ");
|
|
132951
|
+
const trace = sanitizeProxyTraceLine(line);
|
|
132952
|
+
if (trace !== null) {
|
|
132953
|
+
this.#logger.info(`alexa proxy-login: ${trace}`, { meta: { sessionId: this.#opts.sessionId } });
|
|
132954
|
+
return;
|
|
132955
|
+
}
|
|
132858
132956
|
if (SECRET_LINE_RE.test(line)) return;
|
|
132859
132957
|
this.#logger.debug("alexa proxy-login", { meta: {
|
|
132860
132958
|
sessionId: this.#opts.sessionId,
|
|
@@ -132916,7 +133014,10 @@ var AlexaProxyLoginSession = class {
|
|
|
132916
133014
|
if (isProxyReadyError(err)) {
|
|
132917
133015
|
if (this.#phase === "starting") {
|
|
132918
133016
|
this.#phase = "awaiting-login";
|
|
132919
|
-
this.#logger.info("alexa proxy-login: proxy ready — awaiting Amazon login", { meta: {
|
|
133017
|
+
this.#logger.info("alexa proxy-login: proxy ready — awaiting Amazon login", { meta: {
|
|
133018
|
+
sessionId: this.#opts.sessionId,
|
|
133019
|
+
amazonDomain: this.#opts.amazonDomain
|
|
133020
|
+
} });
|
|
132920
133021
|
}
|
|
132921
133022
|
return;
|
|
132922
133023
|
}
|
|
@@ -135109,126 +135210,6 @@ var AlexaBrokerStore = class AlexaBrokerStore {
|
|
|
135109
135210
|
}
|
|
135110
135211
|
};
|
|
135111
135212
|
//#endregion
|
|
135112
|
-
//#region src/speak/constants.ts
|
|
135113
|
-
/** Echo speech limits and the worker restart bound (same window as CrashSupervisor). */
|
|
135114
|
-
var ALEXA_SPEAK_ADDON_ID = "import-alexa";
|
|
135115
|
-
var ALEXA_SPEAK_KIND = "alexa-echo";
|
|
135116
|
-
var DEVICE_REFRESH_MS = 5 * 6e4;
|
|
135117
|
-
/** Where the addon serves the Alexa mark; `listTargetKinds` stamps it as `iconUrl`. */
|
|
135118
|
-
var ALEXA_SPEAK_ICON_URL = `/addon/${ALEXA_SPEAK_ADDON_ID}/icons/${ALEXA_SPEAK_KIND}`;
|
|
135119
|
-
//#endregion
|
|
135120
|
-
//#region src/speak/worker-protocol.ts
|
|
135121
|
-
/** Line-delimited JSON the long-lived Python worker speaks. One object per line. */
|
|
135122
|
-
var EchoDeviceSchema = object({
|
|
135123
|
-
id: string().min(1),
|
|
135124
|
-
name: string(),
|
|
135125
|
-
family: string(),
|
|
135126
|
-
online: boolean(),
|
|
135127
|
-
kind: _enum(["device", "group"]),
|
|
135128
|
-
dnd: boolean(),
|
|
135129
|
-
deviceType: string(),
|
|
135130
|
-
/** The room the account files the device under, when Amazon names one. */
|
|
135131
|
-
room: string().optional()
|
|
135132
|
-
});
|
|
135133
|
-
var LoginStatusSchema = _enum(["ok", "failed"]);
|
|
135134
|
-
var SessionStatusSchema = object({
|
|
135135
|
-
loggedIn: boolean(),
|
|
135136
|
-
ageSec: number().int().nonnegative(),
|
|
135137
|
-
lastSuccessAt: number().int().nullable()
|
|
135138
|
-
});
|
|
135139
|
-
var TargetDeliverySchema = object({
|
|
135140
|
-
targetId: string(),
|
|
135141
|
-
ok: boolean(),
|
|
135142
|
-
error: string().optional()
|
|
135143
|
-
});
|
|
135144
|
-
var WorkerLineSchema = union([object({
|
|
135145
|
-
id: string(),
|
|
135146
|
-
ok: literal(true),
|
|
135147
|
-
result: unknown().optional()
|
|
135148
|
-
}), object({
|
|
135149
|
-
id: string(),
|
|
135150
|
-
ok: literal(false),
|
|
135151
|
-
error: string()
|
|
135152
|
-
})]);
|
|
135153
|
-
function requestLine(id, request) {
|
|
135154
|
-
return `${JSON.stringify({
|
|
135155
|
-
id,
|
|
135156
|
-
...request
|
|
135157
|
-
})}\n`;
|
|
135158
|
-
}
|
|
135159
|
-
function parseLoginResult(result) {
|
|
135160
|
-
const parsed = object({
|
|
135161
|
-
status: LoginStatusSchema,
|
|
135162
|
-
error: string().optional()
|
|
135163
|
-
}).safeParse(result);
|
|
135164
|
-
if (!parsed.success) return {
|
|
135165
|
-
status: "failed",
|
|
135166
|
-
error: "worker returned an unexpected login result"
|
|
135167
|
-
};
|
|
135168
|
-
return parsed.data;
|
|
135169
|
-
}
|
|
135170
|
-
function parseDevices(result) {
|
|
135171
|
-
const parsed = object({ devices: array(EchoDeviceSchema) }).safeParse(result);
|
|
135172
|
-
if (!parsed.success) throw new Error("worker returned an unexpected device list");
|
|
135173
|
-
return parsed.data.devices;
|
|
135174
|
-
}
|
|
135175
|
-
function parseDeliveries(result) {
|
|
135176
|
-
const parsed = object({ outcomes: array(TargetDeliverySchema) }).safeParse(result);
|
|
135177
|
-
if (!parsed.success) throw new Error("worker returned an unexpected delivery list");
|
|
135178
|
-
return parsed.data.outcomes;
|
|
135179
|
-
}
|
|
135180
|
-
function parseSessionStatus(result) {
|
|
135181
|
-
const parsed = SessionStatusSchema.safeParse(result);
|
|
135182
|
-
if (!parsed.success) throw new Error("worker returned an unexpected session status");
|
|
135183
|
-
return parsed.data;
|
|
135184
|
-
}
|
|
135185
|
-
//#endregion
|
|
135186
|
-
//#region src/speak/actions.ts
|
|
135187
|
-
var BrokerEchoStateSchema = _enum([
|
|
135188
|
-
"starting",
|
|
135189
|
-
"ready",
|
|
135190
|
-
"broker-not-signed-in",
|
|
135191
|
-
"failed"
|
|
135192
|
-
]);
|
|
135193
|
-
var SpeakStatusSchema = object({
|
|
135194
|
-
brokers: array(object({
|
|
135195
|
-
id: string(),
|
|
135196
|
-
name: string(),
|
|
135197
|
-
state: BrokerEchoStateSchema,
|
|
135198
|
-
error: string().optional(),
|
|
135199
|
-
devices: array(EchoDeviceSchema),
|
|
135200
|
-
session: SessionStatusSchema
|
|
135201
|
-
})),
|
|
135202
|
-
/** Targets that name no Amazon account yet (they cannot speak until one is chosen). */
|
|
135203
|
-
unassignedTargets: number().int().nonnegative(),
|
|
135204
|
-
/** What the old standalone login left behind, named; '' when nothing is pending. */
|
|
135205
|
-
notice: _enum([
|
|
135206
|
-
"",
|
|
135207
|
-
"no-broker",
|
|
135208
|
-
"choose-broker"
|
|
135209
|
-
])
|
|
135210
|
-
});
|
|
135211
|
-
var alexaSpeakActions = defineCustomActions({
|
|
135212
|
-
speakStatus: customAction(object({}), SpeakStatusSchema, {
|
|
135213
|
-
kind: "mutation",
|
|
135214
|
-
auth: "admin"
|
|
135215
|
-
}),
|
|
135216
|
-
sendTest: customAction(object({
|
|
135217
|
-
brokerId: string().min(1),
|
|
135218
|
-
echoIds: array(string().min(1)).min(1),
|
|
135219
|
-
mode: _enum(["speak", "announce"]),
|
|
135220
|
-
volume: number().int().min(0).max(100),
|
|
135221
|
-
text: string().min(1).max(450)
|
|
135222
|
-
}), object({ outcomes: array(object({
|
|
135223
|
-
echoId: string(),
|
|
135224
|
-
ok: boolean(),
|
|
135225
|
-
error: string().optional()
|
|
135226
|
-
})) }), {
|
|
135227
|
-
kind: "mutation",
|
|
135228
|
-
auth: "admin"
|
|
135229
|
-
})
|
|
135230
|
-
});
|
|
135231
|
-
//#endregion
|
|
135232
135213
|
//#region src/speak/broker-seed.ts
|
|
135233
135214
|
/**
|
|
135234
135215
|
* What the Echo worker needs from an import broker to act as the same Amazon
|
|
@@ -135463,6 +135444,72 @@ function scrubSecrets(text, secrets) {
|
|
|
135463
135444
|
return out;
|
|
135464
135445
|
}
|
|
135465
135446
|
//#endregion
|
|
135447
|
+
//#region src/speak/worker-protocol.ts
|
|
135448
|
+
/** Line-delimited JSON the long-lived Python worker speaks. One object per line. */
|
|
135449
|
+
var EchoDeviceSchema = object({
|
|
135450
|
+
id: string().min(1),
|
|
135451
|
+
name: string(),
|
|
135452
|
+
family: string(),
|
|
135453
|
+
online: boolean(),
|
|
135454
|
+
kind: _enum(["device", "group"]),
|
|
135455
|
+
dnd: boolean(),
|
|
135456
|
+
deviceType: string(),
|
|
135457
|
+
/** The room the account files the device under, when Amazon names one. */
|
|
135458
|
+
room: string().optional()
|
|
135459
|
+
});
|
|
135460
|
+
var LoginStatusSchema = _enum(["ok", "failed"]);
|
|
135461
|
+
var SessionStatusSchema = object({
|
|
135462
|
+
loggedIn: boolean(),
|
|
135463
|
+
ageSec: number().int().nonnegative(),
|
|
135464
|
+
lastSuccessAt: number().int().nullable()
|
|
135465
|
+
});
|
|
135466
|
+
var TargetDeliverySchema = object({
|
|
135467
|
+
targetId: string(),
|
|
135468
|
+
ok: boolean(),
|
|
135469
|
+
error: string().optional()
|
|
135470
|
+
});
|
|
135471
|
+
var WorkerLineSchema = union([object({
|
|
135472
|
+
id: string(),
|
|
135473
|
+
ok: literal(true),
|
|
135474
|
+
result: unknown().optional()
|
|
135475
|
+
}), object({
|
|
135476
|
+
id: string(),
|
|
135477
|
+
ok: literal(false),
|
|
135478
|
+
error: string()
|
|
135479
|
+
})]);
|
|
135480
|
+
function requestLine(id, request) {
|
|
135481
|
+
return `${JSON.stringify({
|
|
135482
|
+
id,
|
|
135483
|
+
...request
|
|
135484
|
+
})}\n`;
|
|
135485
|
+
}
|
|
135486
|
+
function parseLoginResult(result) {
|
|
135487
|
+
const parsed = object({
|
|
135488
|
+
status: LoginStatusSchema,
|
|
135489
|
+
error: string().optional()
|
|
135490
|
+
}).safeParse(result);
|
|
135491
|
+
if (!parsed.success) return {
|
|
135492
|
+
status: "failed",
|
|
135493
|
+
error: "worker returned an unexpected login result"
|
|
135494
|
+
};
|
|
135495
|
+
return parsed.data;
|
|
135496
|
+
}
|
|
135497
|
+
function parseDevices(result) {
|
|
135498
|
+
const parsed = object({ devices: array(EchoDeviceSchema) }).safeParse(result);
|
|
135499
|
+
if (!parsed.success) throw new Error("worker returned an unexpected device list");
|
|
135500
|
+
return parsed.data.devices;
|
|
135501
|
+
}
|
|
135502
|
+
function parseDeliveries(result) {
|
|
135503
|
+
const parsed = object({ outcomes: array(TargetDeliverySchema) }).safeParse(result);
|
|
135504
|
+
if (!parsed.success) throw new Error("worker returned an unexpected delivery list");
|
|
135505
|
+
return parsed.data.outcomes;
|
|
135506
|
+
}
|
|
135507
|
+
function parseSessionStatus(result) {
|
|
135508
|
+
const parsed = SessionStatusSchema.safeParse(result);
|
|
135509
|
+
if (!parsed.success) throw new Error("worker returned an unexpected session status");
|
|
135510
|
+
return parsed.data;
|
|
135511
|
+
}
|
|
135512
|
+
//#endregion
|
|
135466
135513
|
//#region src/speak/broker-echo.ts
|
|
135467
135514
|
var BrokerEcho = class {
|
|
135468
135515
|
deps;
|
|
@@ -135591,40 +135638,13 @@ var BrokerEcho = class {
|
|
|
135591
135638
|
}
|
|
135592
135639
|
};
|
|
135593
135640
|
//#endregion
|
|
135594
|
-
//#region src/speak/
|
|
135595
|
-
|
|
135596
|
-
|
|
135597
|
-
|
|
135598
|
-
|
|
135599
|
-
|
|
135600
|
-
|
|
135601
|
-
}
|
|
135602
|
-
function planLegacyMigration(legacy, brokerIds, targets) {
|
|
135603
|
-
const present = legacy.email.length > 0 || legacy.password.length > 0 || legacy.session.length > 0;
|
|
135604
|
-
const only = brokerIds.length === 1 ? brokerIds[0] ?? null : null;
|
|
135605
|
-
if (!present || only === null) return {
|
|
135606
|
-
present,
|
|
135607
|
-
attachTo: null,
|
|
135608
|
-
targets
|
|
135609
|
-
};
|
|
135610
|
-
return {
|
|
135611
|
-
present,
|
|
135612
|
-
attachTo: legacy.session.length > 0 ? only : null,
|
|
135613
|
-
targets: targets.map((row) => row.brokerId.length > 0 ? row : {
|
|
135614
|
-
...row,
|
|
135615
|
-
brokerId: only
|
|
135616
|
-
})
|
|
135617
|
-
};
|
|
135618
|
-
}
|
|
135619
|
-
/** What the page must tell the operator about targets that name no account. */
|
|
135620
|
-
function noticeFor(targets, brokerIds) {
|
|
135621
|
-
if (!targets.some((row) => row.brokerId.length === 0)) return "";
|
|
135622
|
-
if (brokerIds.length === 0) return "no-broker";
|
|
135623
|
-
return brokerIds.length > 1 ? "choose-broker" : "";
|
|
135624
|
-
}
|
|
135625
|
-
function unassignedCount(targets) {
|
|
135626
|
-
return targets.filter((row) => row.brokerId.length === 0).length;
|
|
135627
|
-
}
|
|
135641
|
+
//#region src/speak/constants.ts
|
|
135642
|
+
/** Echo speech limits and the worker restart bound (same window as CrashSupervisor). */
|
|
135643
|
+
var ALEXA_SPEAK_ADDON_ID = "import-alexa";
|
|
135644
|
+
var ALEXA_SPEAK_KIND = "alexa-echo";
|
|
135645
|
+
var DEVICE_REFRESH_MS = 5 * 6e4;
|
|
135646
|
+
/** Where the addon serves the Alexa mark; `listTargetKinds` stamps it as `iconUrl`. */
|
|
135647
|
+
var ALEXA_SPEAK_ICON_URL = `/addon/${ALEXA_SPEAK_ADDON_ID}/icons/${ALEXA_SPEAK_KIND}`;
|
|
135628
135648
|
//#endregion
|
|
135629
135649
|
//#region src/speak/rate-limit.ts
|
|
135630
135650
|
/**
|
|
@@ -135729,14 +135749,42 @@ function readString(config, key) {
|
|
|
135729
135749
|
const value = config[key];
|
|
135730
135750
|
return typeof value === "string" ? value : "";
|
|
135731
135751
|
}
|
|
135732
|
-
|
|
135733
|
-
|
|
135752
|
+
/** The rule's value for this send wins; the target's saved config is the fallback. */
|
|
135753
|
+
function readMode(config, delivery) {
|
|
135754
|
+
return (delivery?.["mode"] ?? config["mode"]) === "announce" ? "announce" : "speak";
|
|
135734
135755
|
}
|
|
135735
|
-
function readVolume(config) {
|
|
135736
|
-
const
|
|
135756
|
+
function readVolume(config, delivery) {
|
|
135757
|
+
const ruled = delivery?.["volume"];
|
|
135758
|
+
const value = typeof ruled === "number" ? ruled : config["volume"];
|
|
135737
135759
|
if (typeof value !== "number" || !Number.isFinite(value)) return 40;
|
|
135738
135760
|
return Math.max(0, Math.min(100, Math.round(value)));
|
|
135739
135761
|
}
|
|
135762
|
+
/**
|
|
135763
|
+
* The delivery parameters a rule may set for an Echo target (D938). The
|
|
135764
|
+
* target's own config holds the default; these override it per notification.
|
|
135765
|
+
*/
|
|
135766
|
+
var DELIVERY_PARAMS = [{
|
|
135767
|
+
key: "mode",
|
|
135768
|
+
label: "Mode",
|
|
135769
|
+
labelKey: "import-alexa:echoTarget.s_echo.mode.label",
|
|
135770
|
+
type: "enum",
|
|
135771
|
+
options: [{
|
|
135772
|
+
value: "speak",
|
|
135773
|
+
label: "Speak (quiet hours apply)"
|
|
135774
|
+
}, {
|
|
135775
|
+
value: "announce",
|
|
135776
|
+
label: "Announce (chime, then speech)"
|
|
135777
|
+
}],
|
|
135778
|
+
default: "speak"
|
|
135779
|
+
}, {
|
|
135780
|
+
key: "volume",
|
|
135781
|
+
label: "Volume",
|
|
135782
|
+
labelKey: "import-alexa:echoTarget.s_echo.volume.label",
|
|
135783
|
+
type: "number",
|
|
135784
|
+
min: 0,
|
|
135785
|
+
max: 100,
|
|
135786
|
+
default: 40
|
|
135787
|
+
}];
|
|
135740
135788
|
function respectDnd(config) {
|
|
135741
135789
|
return config["respectDnd"] !== false;
|
|
135742
135790
|
}
|
|
@@ -135837,6 +135885,7 @@ function alexaTargetKind(brokers) {
|
|
|
135837
135885
|
supportsDiscovery: true,
|
|
135838
135886
|
destinationKeys: ["brokerId", "echoId"],
|
|
135839
135887
|
configSchema: configSchema(brokers),
|
|
135888
|
+
deliveryParams: DELIVERY_PARAMS,
|
|
135840
135889
|
caps: {
|
|
135841
135890
|
attachments: {
|
|
135842
135891
|
mediaTypes: [],
|
|
@@ -136019,14 +136068,14 @@ function createAlexaNotificationProvider(ports) {
|
|
|
136019
136068
|
renderedAs: speech.renderedAs
|
|
136020
136069
|
};
|
|
136021
136070
|
}
|
|
136022
|
-
const mode = readMode(target.config);
|
|
136071
|
+
const mode = readMode(target.config, notification.delivery);
|
|
136023
136072
|
try {
|
|
136024
136073
|
const outcomes = await ports.speak({
|
|
136025
136074
|
brokerId: broker.id,
|
|
136026
136075
|
mode,
|
|
136027
136076
|
targets: [echoId],
|
|
136028
136077
|
text: speech.text,
|
|
136029
|
-
volume: readVolume(target.config)
|
|
136078
|
+
volume: readVolume(target.config, notification.delivery)
|
|
136030
136079
|
});
|
|
136031
136080
|
const one = outcomes.find((row) => row.targetId === echoId) ?? outcomes[0];
|
|
136032
136081
|
if (one === void 0 || !one.ok) {
|
|
@@ -136426,8 +136475,8 @@ function startAlexaWorker(deps) {
|
|
|
136426
136475
|
}
|
|
136427
136476
|
//#endregion
|
|
136428
136477
|
//#region src/speak/controller.ts
|
|
136429
|
-
function targetsFrom(
|
|
136430
|
-
return
|
|
136478
|
+
function targetsFrom(rows) {
|
|
136479
|
+
return rows.map((row) => ({
|
|
136431
136480
|
id: row.id,
|
|
136432
136481
|
name: row.name,
|
|
136433
136482
|
kind: ALEXA_SPEAK_KIND,
|
|
@@ -136457,7 +136506,7 @@ function rowsFrom(targets) {
|
|
|
136457
136506
|
var AlexaSpeakController = class {
|
|
136458
136507
|
host;
|
|
136459
136508
|
provider;
|
|
136460
|
-
|
|
136509
|
+
rows;
|
|
136461
136510
|
pythonPath = null;
|
|
136462
136511
|
echoes = /* @__PURE__ */ new Map();
|
|
136463
136512
|
timer = null;
|
|
@@ -136466,17 +136515,14 @@ var AlexaSpeakController = class {
|
|
|
136466
136515
|
constructor(host) {
|
|
136467
136516
|
this.host = host;
|
|
136468
136517
|
this.now = host.now ?? (() => Date.now());
|
|
136469
|
-
this.
|
|
136518
|
+
this.rows = readEchoTargets(host.readConfig());
|
|
136470
136519
|
this.provider = createAlexaNotificationProvider({
|
|
136471
136520
|
brokers: () => this.brokerViews(),
|
|
136472
136521
|
now: this.now,
|
|
136473
136522
|
logger: host.logger,
|
|
136474
|
-
loadTargets: () => targetsFrom(this.
|
|
136523
|
+
loadTargets: () => targetsFrom(this.rows),
|
|
136475
136524
|
saveTargets: (targets) => {
|
|
136476
|
-
this.
|
|
136477
|
-
...this.settings,
|
|
136478
|
-
targets: [...rowsFrom(targets)]
|
|
136479
|
-
};
|
|
136525
|
+
this.rows = rowsFrom(targets);
|
|
136480
136526
|
this.persist();
|
|
136481
136527
|
},
|
|
136482
136528
|
speak: (command) => this.utter(command.brokerId, command.mode, command.targets, command.text, command.volume)
|
|
@@ -136488,7 +136534,6 @@ var AlexaSpeakController = class {
|
|
|
136488
136534
|
this.host.logger.warn("alexa speak: embedded Python unavailable; Echo speech will not run");
|
|
136489
136535
|
return;
|
|
136490
136536
|
}
|
|
136491
|
-
await this.migrateLegacyLogin();
|
|
136492
136537
|
await this.brokersChanged();
|
|
136493
136538
|
this.timer = setInterval(() => {
|
|
136494
136539
|
this.refreshAll();
|
|
@@ -136505,12 +136550,6 @@ var AlexaSpeakController = class {
|
|
|
136505
136550
|
this.queue = this.queue.then(() => this.reconcile()).catch(() => void 0);
|
|
136506
136551
|
return this.queue;
|
|
136507
136552
|
}
|
|
136508
|
-
handlers() {
|
|
136509
|
-
return {
|
|
136510
|
-
speakStatus: async () => this.status(),
|
|
136511
|
-
sendTest: async (input) => this.sendTest(input)
|
|
136512
|
-
};
|
|
136513
|
-
}
|
|
136514
136553
|
brokerViews() {
|
|
136515
136554
|
return this.host.listBrokers().map((ref) => {
|
|
136516
136555
|
const echo = this.echoes.get(ref.id);
|
|
@@ -136524,36 +136563,11 @@ var AlexaSpeakController = class {
|
|
|
136524
136563
|
}
|
|
136525
136564
|
async persist() {
|
|
136526
136565
|
try {
|
|
136527
|
-
await this.host.
|
|
136566
|
+
await this.host.writeTargets(this.rows);
|
|
136528
136567
|
} catch (err) {
|
|
136529
|
-
this.host.logger.warn("alexa speak: could not store
|
|
136568
|
+
this.host.logger.warn("alexa speak: could not store targets", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
136530
136569
|
}
|
|
136531
136570
|
}
|
|
136532
|
-
/**
|
|
136533
|
-
* Drain the old standalone login once. With exactly one broker its jar and
|
|
136534
|
-
* its targets move onto that broker; otherwise the jar is dropped and the
|
|
136535
|
-
* targets wait for the operator to choose (the status names that state).
|
|
136536
|
-
*/
|
|
136537
|
-
async migrateLegacyLogin() {
|
|
136538
|
-
const brokers = this.host.listBrokers();
|
|
136539
|
-
const plan = planLegacyMigration(legacyLoginOf(this.settings), brokers.map((ref) => ref.id), this.settings.targets);
|
|
136540
|
-
if (!plan.present) return;
|
|
136541
|
-
if (plan.attachTo !== null) await this.host.sessions.put(plan.attachTo, this.settings.session);
|
|
136542
|
-
this.settings = {
|
|
136543
|
-
...this.settings,
|
|
136544
|
-
email: "",
|
|
136545
|
-
password: "",
|
|
136546
|
-
session: "",
|
|
136547
|
-
targets: [...plan.targets]
|
|
136548
|
-
};
|
|
136549
|
-
await this.persist();
|
|
136550
|
-
await this.host.clearLegacySecrets();
|
|
136551
|
-
this.host.logger.info("alexa speak: the old standalone login was retired", { meta: {
|
|
136552
|
-
attachedTo: plan.attachTo ?? "none",
|
|
136553
|
-
brokers: brokers.length,
|
|
136554
|
-
targets: plan.targets.length
|
|
136555
|
-
} });
|
|
136556
|
-
}
|
|
136557
136571
|
makeWorker(brokerId, pythonPath) {
|
|
136558
136572
|
if (this.host.startWorker !== void 0) return this.host.startWorker(brokerId, pythonPath);
|
|
136559
136573
|
return startAlexaWorker({
|
|
@@ -136603,59 +136617,6 @@ var AlexaSpeakController = class {
|
|
|
136603
136617
|
if (echo === void 0) throw new Error("no Echo session for this Amazon account");
|
|
136604
136618
|
return echo.utter(mode === "announce" ? "announce" : "speak", targets, text, volume);
|
|
136605
136619
|
}
|
|
136606
|
-
async status() {
|
|
136607
|
-
const brokers = this.host.listBrokers();
|
|
136608
|
-
const rows = await Promise.all(brokers.map(async (ref) => {
|
|
136609
|
-
const echo = this.echoes.get(ref.id);
|
|
136610
|
-
return {
|
|
136611
|
-
id: ref.id,
|
|
136612
|
-
name: ref.name,
|
|
136613
|
-
state: echo?.state ?? "failed",
|
|
136614
|
-
...echo === void 0 ? { error: "embedded Python unavailable" } : echo.error === null ? {} : { error: echo.error },
|
|
136615
|
-
devices: [...echo?.devices ?? []],
|
|
136616
|
-
session: echo === void 0 ? {
|
|
136617
|
-
loggedIn: false,
|
|
136618
|
-
ageSec: 0,
|
|
136619
|
-
lastSuccessAt: null
|
|
136620
|
-
} : await echo.session()
|
|
136621
|
-
};
|
|
136622
|
-
}));
|
|
136623
|
-
const targets = this.settings.targets;
|
|
136624
|
-
const ids = brokers.map((ref) => ref.id);
|
|
136625
|
-
return {
|
|
136626
|
-
brokers: rows,
|
|
136627
|
-
unassignedTargets: unassignedCount(targets),
|
|
136628
|
-
notice: noticeFor(targets, ids)
|
|
136629
|
-
};
|
|
136630
|
-
}
|
|
136631
|
-
async sendTest(input) {
|
|
136632
|
-
const outcomes = await this.utter(input.brokerId, input.mode, input.echoIds, input.text, input.volume);
|
|
136633
|
-
const known = new Map((this.echoes.get(input.brokerId)?.devices ?? []).map((device) => [device.id, device]));
|
|
136634
|
-
const next = [...this.settings.targets];
|
|
136635
|
-
for (const echoId of input.echoIds) {
|
|
136636
|
-
if (next.some((row) => row.echoId === echoId && row.brokerId === input.brokerId)) continue;
|
|
136637
|
-
next.push({
|
|
136638
|
-
id: `echo-${input.brokerId}-${echoId}`,
|
|
136639
|
-
name: known.get(echoId)?.name ?? echoId,
|
|
136640
|
-
enabled: true,
|
|
136641
|
-
brokerId: input.brokerId,
|
|
136642
|
-
echoId,
|
|
136643
|
-
mode: input.mode,
|
|
136644
|
-
volume: input.volume,
|
|
136645
|
-
respectDnd: true
|
|
136646
|
-
});
|
|
136647
|
-
}
|
|
136648
|
-
this.settings = {
|
|
136649
|
-
...this.settings,
|
|
136650
|
-
targets: next
|
|
136651
|
-
};
|
|
136652
|
-
await this.persist();
|
|
136653
|
-
return { outcomes: outcomes.map((row) => ({
|
|
136654
|
-
echoId: row.targetId,
|
|
136655
|
-
ok: row.ok,
|
|
136656
|
-
...row.error !== void 0 ? { error: row.error } : {}
|
|
136657
|
-
})) };
|
|
136658
|
-
}
|
|
136659
136620
|
};
|
|
136660
136621
|
//#endregion
|
|
136661
136622
|
//#region src/speak/icon-routes.ts
|
|
@@ -136708,26 +136669,11 @@ var RecordsSpeakSessionStore = class RecordsSpeakSessionStore {
|
|
|
136708
136669
|
};
|
|
136709
136670
|
//#endregion
|
|
136710
136671
|
//#region src/addon.ts
|
|
136711
|
-
var SPEAK_PAGE = {
|
|
136712
|
-
id: "speak",
|
|
136713
|
-
label: "Speak on Echo",
|
|
136714
|
-
icon: "megaphone",
|
|
136715
|
-
path: "/addon/import-alexa",
|
|
136716
|
-
remoteName: "addon_import_alexa_page",
|
|
136717
|
-
bundle: "remoteEntry.js",
|
|
136718
|
-
section: "administration"
|
|
136719
|
-
};
|
|
136720
136672
|
/** Default config — a fresh install starts with no accounts. */
|
|
136721
136673
|
var DEFAULTS = {
|
|
136722
136674
|
brokers: [],
|
|
136723
136675
|
proxyLogin: { proxyPort: 3456 },
|
|
136724
|
-
|
|
136725
|
-
email: "",
|
|
136726
|
-
password: "",
|
|
136727
|
-
region: "EU",
|
|
136728
|
-
session: "",
|
|
136729
|
-
targets: []
|
|
136730
|
-
}
|
|
136676
|
+
echoTargets: []
|
|
136731
136677
|
};
|
|
136732
136678
|
/** Device types an imported Alexa appliance can materialise as. */
|
|
136733
136679
|
var ALEXA_DEVICE_TYPES = [
|
|
@@ -136771,7 +136717,6 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
136771
136717
|
brokerStore = null;
|
|
136772
136718
|
activeBrokers = [];
|
|
136773
136719
|
speak = null;
|
|
136774
|
-
secretStatePaths = ["speak.password", "speak.session"];
|
|
136775
136720
|
constructor() {
|
|
136776
136721
|
super({ ...DEFAULTS });
|
|
136777
136722
|
}
|
|
@@ -136826,8 +136771,8 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
136826
136771
|
const speak = new AlexaSpeakController({
|
|
136827
136772
|
logger: this.ctx.logger.child("speak"),
|
|
136828
136773
|
readConfig: () => this.config,
|
|
136829
|
-
|
|
136830
|
-
await this.updateGlobalSettings({
|
|
136774
|
+
writeTargets: async (next) => {
|
|
136775
|
+
await this.updateGlobalSettings({ echoTargets: [...next] });
|
|
136831
136776
|
},
|
|
136832
136777
|
listBrokers: () => this.activeBrokers.map((entry) => ({
|
|
136833
136778
|
id: entry.id,
|
|
@@ -136839,50 +136784,31 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
136839
136784
|
return entry === void 0 ? null : readBrokerSeed(this.ctx.dataDir, entry);
|
|
136840
136785
|
},
|
|
136841
136786
|
sessions: RecordsSpeakSessionStore.of(records),
|
|
136842
|
-
clearLegacySecrets: async () => {
|
|
136843
|
-
await this.updateGlobalSettingsAsOperator({ speak: {
|
|
136844
|
-
...this.config.speak,
|
|
136845
|
-
password: SECRET_CLEAR,
|
|
136846
|
-
session: SECRET_CLEAR
|
|
136847
|
-
} });
|
|
136848
|
-
},
|
|
136849
136787
|
ensurePython: () => this.ctx.deps.ensurePython()
|
|
136850
136788
|
});
|
|
136851
136789
|
this.speak = speak;
|
|
136852
136790
|
speak.start().catch((err) => {
|
|
136853
136791
|
this.ctx.logger.warn("alexa speak: worker did not start", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
136854
136792
|
});
|
|
136855
|
-
|
|
136856
|
-
|
|
136857
|
-
|
|
136858
|
-
|
|
136859
|
-
|
|
136860
|
-
|
|
136861
|
-
|
|
136862
|
-
|
|
136863
|
-
|
|
136864
|
-
|
|
136865
|
-
|
|
136866
|
-
|
|
136867
|
-
|
|
136868
|
-
|
|
136869
|
-
|
|
136870
|
-
|
|
136871
|
-
|
|
136872
|
-
|
|
136873
|
-
|
|
136874
|
-
{
|
|
136875
|
-
capability: notificationOutputCapability,
|
|
136876
|
-
provider: speak.provider
|
|
136877
|
-
},
|
|
136878
|
-
{
|
|
136879
|
-
capability: addonPagesSourceCapability,
|
|
136880
|
-
provider: pages
|
|
136881
|
-
}
|
|
136882
|
-
],
|
|
136883
|
-
customActions: alexaSpeakActions,
|
|
136884
|
-
actionHandlers: speak.handlers()
|
|
136885
|
-
};
|
|
136793
|
+
return { providers: [
|
|
136794
|
+
...regs,
|
|
136795
|
+
{
|
|
136796
|
+
capability: brokerCapability,
|
|
136797
|
+
provider: this.buildBrokerProvider()
|
|
136798
|
+
},
|
|
136799
|
+
{
|
|
136800
|
+
capability: deviceAdoptionCapability,
|
|
136801
|
+
provider: this.buildAdoptionProvider()
|
|
136802
|
+
},
|
|
136803
|
+
{
|
|
136804
|
+
capability: addonRoutesCapability,
|
|
136805
|
+
provider: buildAddonRouteProvider("import-alexa", loginRoutes)
|
|
136806
|
+
},
|
|
136807
|
+
{
|
|
136808
|
+
capability: notificationOutputCapability,
|
|
136809
|
+
provider: speak.provider
|
|
136810
|
+
}
|
|
136811
|
+
] };
|
|
136886
136812
|
}
|
|
136887
136813
|
async loadPersistedBrokers() {
|
|
136888
136814
|
const store = this.brokerStore;
|
|
@@ -137273,12 +137199,10 @@ exports.StubAlexaClient = StubAlexaClient;
|
|
|
137273
137199
|
exports.__toCommonJS = __toCommonJS;
|
|
137274
137200
|
exports.alexaAddonConfigSchema = alexaAddonConfigSchema;
|
|
137275
137201
|
exports.alexaConnectionSchema = alexaConnectionSchema;
|
|
137276
|
-
exports.alexaSpeakActions = alexaSpeakActions;
|
|
137277
137202
|
exports.buildAlexaApplianceCandidate = buildAlexaApplianceCandidate;
|
|
137278
137203
|
exports.buildConnectionFormSchema = buildConnectionFormSchema;
|
|
137279
137204
|
exports.connectionToCredentials = connectionToCredentials;
|
|
137280
137205
|
exports.createAlexaClient = createAlexaClient;
|
|
137281
|
-
exports.customActions = alexaSpeakActions;
|
|
137282
137206
|
exports.mapAlexaCapabilitiesToCamStack = mapAlexaCapabilitiesToCamStack;
|
|
137283
137207
|
exports.mappableAppliance = mappableAppliance;
|
|
137284
137208
|
exports.settingsToAlexaConnection = settingsToAlexaConnection;
|