@camstack/addon-import-alexa 0.2.163 → 0.2.165
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 +353 -412
- package/dist/addon.mjs +354 -411
- 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-CwP9H68T.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-Dy4CxmqQ.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-DarhMhK_.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",
|
|
@@ -43930,6 +43907,17 @@ var LlmProfileSchema = object({
|
|
|
43930
43907
|
* field in its editor only when its descriptor lists it in `profileFields`. */
|
|
43931
43908
|
topK: number().int().positive().optional(),
|
|
43932
43909
|
maxTokens: number().int().positive().optional(),
|
|
43910
|
+
/** How hard the model "thinks" before it answers. Omitted = the model's own
|
|
43911
|
+
* default. `off` matters for structured output: a thinking model can spend
|
|
43912
|
+
* the whole completion on reasoning and return an empty `content`. Carried
|
|
43913
|
+
* only by the wires that have it — a kind offers the field in its editor
|
|
43914
|
+
* only when its descriptor lists it in `profileFields`. */
|
|
43915
|
+
reasoningEffort: _enum([
|
|
43916
|
+
"off",
|
|
43917
|
+
"low",
|
|
43918
|
+
"medium",
|
|
43919
|
+
"high"
|
|
43920
|
+
]).optional(),
|
|
43933
43921
|
/** Prompt context window. Advisory for cloud kinds (they enforce their own). */
|
|
43934
43922
|
contextLength: number().int().positive().optional(),
|
|
43935
43923
|
/** Default system prompt. A caller's `system` REPLACES it (never appends —
|
|
@@ -43968,6 +43956,7 @@ var LlmProfileFieldSchema = _enum([
|
|
|
43968
43956
|
"topP",
|
|
43969
43957
|
"topK",
|
|
43970
43958
|
"maxTokens",
|
|
43959
|
+
"reasoningEffort",
|
|
43971
43960
|
"contextLength",
|
|
43972
43961
|
"systemPrompt",
|
|
43973
43962
|
"timeoutMs",
|
|
@@ -45218,6 +45207,43 @@ var NotificationActionSchema = object({
|
|
|
45218
45207
|
*/
|
|
45219
45208
|
mode: _enum(["navigate", "background"]).optional()
|
|
45220
45209
|
});
|
|
45210
|
+
/** A delivery parameter value: what a notifier declared it takes. */
|
|
45211
|
+
var DeliveryParamValueSchema = union([
|
|
45212
|
+
string(),
|
|
45213
|
+
number(),
|
|
45214
|
+
boolean()
|
|
45215
|
+
]);
|
|
45216
|
+
/**
|
|
45217
|
+
* One extra delivery parameter a notifier KIND declares (D938). A rule that
|
|
45218
|
+
* addresses a target of that kind may set it; a kind that declares nothing is
|
|
45219
|
+
* never handed one. The admin UI derives its control from this descriptor —
|
|
45220
|
+
* no editor knows a service by name.
|
|
45221
|
+
*
|
|
45222
|
+
* Optional fields are `.optional()`, never `.default()` (a Zod default does not
|
|
45223
|
+
* run on the addon cap path).
|
|
45224
|
+
*/
|
|
45225
|
+
var DeliveryParamSchema = object({
|
|
45226
|
+
key: string().min(1),
|
|
45227
|
+
label: string(),
|
|
45228
|
+
/** i18n key of the label, resolved by the declaring addon's namespace. */
|
|
45229
|
+
labelKey: string().optional(),
|
|
45230
|
+
type: _enum([
|
|
45231
|
+
"enum",
|
|
45232
|
+
"number",
|
|
45233
|
+
"boolean",
|
|
45234
|
+
"string"
|
|
45235
|
+
]),
|
|
45236
|
+
/** `enum` only: the closed set of accepted values. */
|
|
45237
|
+
options: array(object({
|
|
45238
|
+
value: string(),
|
|
45239
|
+
label: string()
|
|
45240
|
+
})).optional(),
|
|
45241
|
+
/** `number` only. */
|
|
45242
|
+
min: number().optional(),
|
|
45243
|
+
max: number().optional(),
|
|
45244
|
+
/** What the notifier does when the rule sets nothing; shown as the placeholder. */
|
|
45245
|
+
default: DeliveryParamValueSchema.optional()
|
|
45246
|
+
});
|
|
45221
45247
|
/**
|
|
45222
45248
|
* The canonical notification. `body` is the only hard field (Apprise model).
|
|
45223
45249
|
* `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
|
|
@@ -45240,7 +45266,13 @@ var NotificationSchema = object({
|
|
|
45240
45266
|
tag: string().optional(),
|
|
45241
45267
|
deviceId: number().optional(),
|
|
45242
45268
|
eventId: string().optional(),
|
|
45243
|
-
metadata: record(string(), unknown()).optional()
|
|
45269
|
+
metadata: record(string(), unknown()).optional(),
|
|
45270
|
+
/**
|
|
45271
|
+
* Values for the parameters the target's kind DECLARES in
|
|
45272
|
+
* {@link TargetKindSchema} `deliveryParams`. Already validated against the
|
|
45273
|
+
* declaration by the sender; a notifier reads only the keys it declared.
|
|
45274
|
+
*/
|
|
45275
|
+
delivery: record(string(), DeliveryParamValueSchema).optional()
|
|
45244
45276
|
});
|
|
45245
45277
|
/** One declared native severity/priority level for a kind. */
|
|
45246
45278
|
var TargetKindLevelSchema = object({
|
|
@@ -45346,6 +45378,11 @@ var TargetKindSchema = object({
|
|
|
45346
45378
|
*/
|
|
45347
45379
|
destinationKeys: array(string()).optional(),
|
|
45348
45380
|
supportsDiscovery: boolean(),
|
|
45381
|
+
/**
|
|
45382
|
+
* Extra delivery parameters a rule may set for a target of this kind (D938).
|
|
45383
|
+
* Absent = none; the rule editor then offers nothing.
|
|
45384
|
+
*/
|
|
45385
|
+
deliveryParams: array(DeliveryParamSchema).optional(),
|
|
45349
45386
|
caps: TargetKindCapsSchema
|
|
45350
45387
|
});
|
|
45351
45388
|
/**
|
|
@@ -57659,7 +57696,9 @@ var ManifestShapeSchema = object({
|
|
|
57659
57696
|
icon: string().optional()
|
|
57660
57697
|
}).optional(),
|
|
57661
57698
|
protected: boolean().optional(),
|
|
57662
|
-
removable: boolean().optional()
|
|
57699
|
+
removable: boolean().optional(),
|
|
57700
|
+
/** Declared Integrations-page block (mirror of `AddonDeclaration.integrationSection`). */
|
|
57701
|
+
integrationSection: _enum(["system", "providers"]).optional()
|
|
57663
57702
|
});
|
|
57664
57703
|
var AddonProcessSchema = object({
|
|
57665
57704
|
pid: number().optional(),
|
|
@@ -90791,6 +90830,29 @@ function rewriteSetCookie(cookie, route) {
|
|
|
90791
90830
|
return kept.join("; ");
|
|
90792
90831
|
}
|
|
90793
90832
|
/**
|
|
90833
|
+
* Every Set-Cookie of one vendor response, rewritten. Dropping `Domain` folds cookies
|
|
90834
|
+
* the vendor keeps apart (`session-id` on `.amazon.it` and on `.www.amazon.it`) into
|
|
90835
|
+
* one, so a past-dated set that only deleted the other domain's copy would delete the
|
|
90836
|
+
* live one the same response just set: that deletion is dropped.
|
|
90837
|
+
*/
|
|
90838
|
+
function rewriteSetCookies(cookies, route) {
|
|
90839
|
+
const now = Date.now();
|
|
90840
|
+
const liveNames = new Set(cookies.filter((c) => !isExpiredCookie(c, now)).map(cookieName));
|
|
90841
|
+
return cookies.filter((c) => !(isExpiredCookie(c, now) && liveNames.has(cookieName(c)))).map((c) => rewriteSetCookie(c, route));
|
|
90842
|
+
}
|
|
90843
|
+
function cookieName(cookie) {
|
|
90844
|
+
return cookie.split(";")[0]?.split("=")[0]?.trim() ?? "";
|
|
90845
|
+
}
|
|
90846
|
+
function isExpiredCookie(cookie, now) {
|
|
90847
|
+
const attrs = cookie.split(";").slice(1).map((part) => part.trim());
|
|
90848
|
+
const maxAge = attrs.find((a) => /^max-age=/i.test(a));
|
|
90849
|
+
if (maxAge !== void 0) return Number(maxAge.slice(8)) <= 0;
|
|
90850
|
+
const expires = attrs.find((a) => /^expires=/i.test(a));
|
|
90851
|
+
if (expires === void 0) return false;
|
|
90852
|
+
const at = Date.parse(expires.slice(8));
|
|
90853
|
+
return Number.isFinite(at) && at <= now;
|
|
90854
|
+
}
|
|
90855
|
+
/**
|
|
90794
90856
|
* A browser `Referer` that points into this proxy, turned back into the vendor URL it
|
|
90795
90857
|
* stands for. A Referer that is not ours is dropped (null): it names the hub.
|
|
90796
90858
|
*/
|
|
@@ -90809,6 +90871,29 @@ function rewriteReferer(referer, route) {
|
|
|
90809
90871
|
if (upstream === void 0) return null;
|
|
90810
90872
|
return `${upstream.scheme}://${upstream.host}${rest?.[2] ?? "/"}${url.search}`;
|
|
90811
90873
|
}
|
|
90874
|
+
function decodeFormComponent(raw) {
|
|
90875
|
+
try {
|
|
90876
|
+
return decodeURIComponent(raw.replace(/\+/g, " "));
|
|
90877
|
+
} catch {
|
|
90878
|
+
return null;
|
|
90879
|
+
}
|
|
90880
|
+
}
|
|
90881
|
+
/**
|
|
90882
|
+
* An `application/x-www-form-urlencoded` body whose field VALUES that are proxy URLs of
|
|
90883
|
+
* this attempt (absolute or root-relative) are turned back into the vendor URL they stand
|
|
90884
|
+
* for — the page's hidden inputs were rewritten on the way out (`rewriteBody`), and the
|
|
90885
|
+
* vendor must get its own URL back. Field order and every untouched pair stay byte-identical.
|
|
90886
|
+
*/
|
|
90887
|
+
function rewriteFormBody(body, route) {
|
|
90888
|
+
return body.split("&").map((pair) => {
|
|
90889
|
+
const eq = pair.indexOf("=");
|
|
90890
|
+
if (eq === -1) return pair;
|
|
90891
|
+
const value = decodeFormComponent(pair.slice(eq + 1));
|
|
90892
|
+
if (value === null || !/^(?:https?:\/\/|\/)/i.test(value)) return pair;
|
|
90893
|
+
const back = rewriteReferer(value, route);
|
|
90894
|
+
return back === null ? pair : `${pair.slice(0, eq + 1)}${encodeURIComponent(back)}`;
|
|
90895
|
+
}).join("&");
|
|
90896
|
+
}
|
|
90812
90897
|
/** Cookies the browser holds for the hub itself must never reach a vendor. */
|
|
90813
90898
|
function filterForwardedCookies(cookieHeader) {
|
|
90814
90899
|
return cookieHeader.split(";").map((part) => part.trim()).filter((part) => part.length > 0 && !part.toLowerCase().startsWith("camstack_")).join("; ");
|
|
@@ -90867,6 +90952,9 @@ function bodyKindOf(contentType) {
|
|
|
90867
90952
|
if (type.startsWith("text/") || type.includes("javascript") || type.includes("json") || type.includes("xml")) return "other";
|
|
90868
90953
|
return null;
|
|
90869
90954
|
}
|
|
90955
|
+
function isFormEncoded(contentType) {
|
|
90956
|
+
return (firstHeader(contentType) ?? "").toLowerCase().startsWith("application/x-www-form-urlencoded");
|
|
90957
|
+
}
|
|
90870
90958
|
function firstHeader(value) {
|
|
90871
90959
|
return Array.isArray(value) ? value[0] : value;
|
|
90872
90960
|
}
|
|
@@ -90881,6 +90969,26 @@ async function readRequestBody(req) {
|
|
|
90881
90969
|
}
|
|
90882
90970
|
return Buffer.concat(chunks);
|
|
90883
90971
|
}
|
|
90972
|
+
/** The path of the Referer sent upstream, or why none was sent — the query is never logged. */
|
|
90973
|
+
function forwardedRefererPath(forwarded, received) {
|
|
90974
|
+
if (forwarded !== void 0) return URL.canParse(forwarded) ? new URL(forwarded).pathname : "unparsable";
|
|
90975
|
+
return received === void 0 ? "absent" : "unmapped";
|
|
90976
|
+
}
|
|
90977
|
+
/** The form targets a vendor page posts to, path only (no query), so a lost path prefix is visible. */
|
|
90978
|
+
function formActionPaths(html) {
|
|
90979
|
+
const actions = [...html.matchAll(/<form\b[^>]*\baction\s*=\s*["']([^"']*)["']/gi)].map((m) => (m[1] ?? "").split("?")[0] ?? "").slice(0, 5);
|
|
90980
|
+
return actions.length === 0 ? "none" : actions.join(" | ");
|
|
90981
|
+
}
|
|
90982
|
+
/** Cookie NAMES only — values are session secrets and are never logged. */
|
|
90983
|
+
function cookieNames(header) {
|
|
90984
|
+
if (header === void 0 || header.length === 0) return "none";
|
|
90985
|
+
return header.split(";").map((part) => part.split("=")[0]?.trim() ?? "").join(",");
|
|
90986
|
+
}
|
|
90987
|
+
/** A Set-Cookie's name and attributes without its value, so a cookie the browser refuses is visible. */
|
|
90988
|
+
function setCookieShape(cookie) {
|
|
90989
|
+
const [first, ...attrs] = cookie.split(";").map((part) => part.trim());
|
|
90990
|
+
return [first?.split("=")[0] ?? "", ...attrs.filter((a) => !/^expires=/i.test(a))].join(";");
|
|
90991
|
+
}
|
|
90884
90992
|
function sendPlain(res, status, text) {
|
|
90885
90993
|
res.writeHead(status, {
|
|
90886
90994
|
"content-type": "text/plain; charset=utf-8",
|
|
@@ -90997,7 +91105,7 @@ var VendorLoginProxy = class {
|
|
|
90997
91105
|
sendPlain(res, 413, "Request too large");
|
|
90998
91106
|
return;
|
|
90999
91107
|
}
|
|
91000
|
-
body = read;
|
|
91108
|
+
body = isFormEncoded(req.headers["content-type"]) ? Buffer.from(rewriteFormBody(read.toString("utf8"), route), "utf8") : read;
|
|
91001
91109
|
}
|
|
91002
91110
|
const headers = this.#requestHeaders(req, route, upstream);
|
|
91003
91111
|
const upstreamRes = await this.#fetch(target, {
|
|
@@ -91011,11 +91119,16 @@ var VendorLoginProxy = class {
|
|
|
91011
91119
|
const kind = bodyKindOf(upstreamRes.headers.get("content-type") ?? "");
|
|
91012
91120
|
const declaredLength = Number(upstreamRes.headers.get("content-length") ?? "0");
|
|
91013
91121
|
const text = kind !== null && !(declaredLength > MAX_TEXT_BODY_BYTES) && method !== "HEAD" ? await upstreamRes.text() : null;
|
|
91014
|
-
this.#logger.
|
|
91122
|
+
this.#logger.info("vendor login proxy: upstream answered", { meta: {
|
|
91015
91123
|
addonId: this.#addonId,
|
|
91016
91124
|
host: upstream.host,
|
|
91125
|
+
path: target.pathname,
|
|
91017
91126
|
status: upstreamRes.status,
|
|
91018
|
-
method
|
|
91127
|
+
method,
|
|
91128
|
+
refererPath: forwardedRefererPath(headers["referer"], req.headers["referer"]),
|
|
91129
|
+
formActions: kind === "html" && text !== null ? formActionPaths(text) : void 0,
|
|
91130
|
+
sentNames: cookieNames(headers["cookie"]),
|
|
91131
|
+
vendorSets: setCookies.map(setCookieShape).join(" | ")
|
|
91019
91132
|
} });
|
|
91020
91133
|
const settled = await attempt.evaluate({
|
|
91021
91134
|
status: upstreamRes.status,
|
|
@@ -91037,7 +91150,7 @@ var VendorLoginProxy = class {
|
|
|
91037
91150
|
upstreamRes.headers.forEach((value, name) => {
|
|
91038
91151
|
if (!DROP_RESPONSE_HEADERS.has(name.toLowerCase())) out[name] = value;
|
|
91039
91152
|
});
|
|
91040
|
-
if (setCookies.length > 0) out["set-cookie"] = setCookies
|
|
91153
|
+
if (setCookies.length > 0) out["set-cookie"] = rewriteSetCookies(setCookies, route);
|
|
91041
91154
|
if (location !== null) out["location"] = rewriteLocation(location, upstream, route);
|
|
91042
91155
|
out["cache-control"] = "no-store";
|
|
91043
91156
|
if (text !== null && kind !== null) {
|
|
@@ -91146,59 +91259,37 @@ var AlexaBrokerEntrySchema = object({
|
|
|
91146
91259
|
*/
|
|
91147
91260
|
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
91261
|
/**
|
|
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([])
|
|
91262
|
+
* The Echo notification targets this addon owns. Speech signs in as an import
|
|
91263
|
+
* broker, so a target is only the Echo it speaks on and how.
|
|
91264
|
+
*/
|
|
91265
|
+
var alexaEchoTargetSchema = object({
|
|
91266
|
+
id: string(),
|
|
91267
|
+
name: string(),
|
|
91268
|
+
enabled: boolean(),
|
|
91269
|
+
/** The broker (Amazon account) the Echo belongs to; '' until one is chosen. */
|
|
91270
|
+
brokerId: string().default(""),
|
|
91271
|
+
echoId: string(),
|
|
91272
|
+
mode: _enum(["speak", "announce"]),
|
|
91273
|
+
volume: number(),
|
|
91274
|
+
respectDnd: boolean()
|
|
91174
91275
|
});
|
|
91175
91276
|
function isRecord$3(value) {
|
|
91176
91277
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
91177
91278
|
}
|
|
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 } : {}
|
|
91279
|
+
/** The stored Echo targets; a row that no longer parses is dropped, never guessed. */
|
|
91280
|
+
function readEchoTargets(config) {
|
|
91281
|
+
const raw = config.echoTargets;
|
|
91282
|
+
if (!Array.isArray(raw)) return [];
|
|
91283
|
+
return raw.flatMap((row) => {
|
|
91284
|
+
const parsed = alexaEchoTargetSchema.safeParse(isRecord$3(row) ? row : {});
|
|
91285
|
+
return parsed.success ? [parsed.data] : [];
|
|
91189
91286
|
});
|
|
91190
91287
|
}
|
|
91191
91288
|
/** Top-level addon config — broker entries + the proxy-login settings. */
|
|
91192
91289
|
var alexaAddonConfigSchema = object({
|
|
91193
91290
|
brokers: array(AlexaBrokerEntrySchema).default([]),
|
|
91194
91291
|
proxyLogin: alexaProxyLoginSchema.default({ proxyPort: 3456 }),
|
|
91195
|
-
|
|
91196
|
-
email: "",
|
|
91197
|
-
password: "",
|
|
91198
|
-
region: "EU",
|
|
91199
|
-
session: "",
|
|
91200
|
-
targets: []
|
|
91201
|
-
})
|
|
91292
|
+
echoTargets: array(alexaEchoTargetSchema).default([])
|
|
91202
91293
|
});
|
|
91203
91294
|
/** Narrow an `unknown` to a shallow plain-object record (cast-free boundary). */
|
|
91204
91295
|
function toRecord(value) {
|
|
@@ -132745,6 +132836,33 @@ var ALEXA_API_USER_AGENT_POSTFIX = `AlexaRemote/${{
|
|
|
132745
132836
|
readmeFilename: "readme.md"
|
|
132746
132837
|
}.version}`;
|
|
132747
132838
|
//#endregion
|
|
132839
|
+
//#region src/alexa-region-options.ts
|
|
132840
|
+
var DEFAULT_LOCALE = "en-US";
|
|
132841
|
+
var LOCALE_BY_DOMAIN = {
|
|
132842
|
+
"amazon.com": "en-US",
|
|
132843
|
+
"amazon.it": "it-IT",
|
|
132844
|
+
"amazon.de": "de-DE",
|
|
132845
|
+
"amazon.co.uk": "en-GB",
|
|
132846
|
+
"amazon.fr": "fr-FR",
|
|
132847
|
+
"amazon.es": "es-ES",
|
|
132848
|
+
"amazon.ca": "en-CA",
|
|
132849
|
+
"amazon.com.au": "en-AU",
|
|
132850
|
+
"amazon.co.jp": "ja-JP",
|
|
132851
|
+
"amazon.in": "en-IN",
|
|
132852
|
+
"amazon.com.br": "pt-BR",
|
|
132853
|
+
"amazon.com.mx": "es-MX"
|
|
132854
|
+
};
|
|
132855
|
+
function alexaRegionOptions(amazonDomain) {
|
|
132856
|
+
const domain = amazonDomain.length > 0 ? amazonDomain : "amazon.com";
|
|
132857
|
+
const locale = LOCALE_BY_DOMAIN[domain] ?? DEFAULT_LOCALE;
|
|
132858
|
+
return {
|
|
132859
|
+
baseAmazonPage: domain,
|
|
132860
|
+
amazonPage: domain,
|
|
132861
|
+
acceptLanguage: locale,
|
|
132862
|
+
amazonPageProxyLanguage: locale.replace("-", "_")
|
|
132863
|
+
};
|
|
132864
|
+
}
|
|
132865
|
+
//#endregion
|
|
132748
132866
|
//#region src/alexa-proxy-trace.ts
|
|
132749
132867
|
/**
|
|
132750
132868
|
* Sanitised one-line trace of the alexa-cookie2 proxy's own log lines.
|
|
@@ -132877,7 +132995,7 @@ var AlexaProxyLoginSession = class {
|
|
|
132877
132995
|
async start() {
|
|
132878
132996
|
const remote = new import_alexa_remote.default();
|
|
132879
132997
|
this.#remote = remote;
|
|
132880
|
-
const
|
|
132998
|
+
const region = alexaRegionOptions(this.#opts.amazonDomain);
|
|
132881
132999
|
remote.on("cookie", () => this.#onCookieEvent(remote));
|
|
132882
133000
|
remote.init({
|
|
132883
133001
|
proxyOnly: true,
|
|
@@ -132885,7 +133003,7 @@ var AlexaProxyLoginSession = class {
|
|
|
132885
133003
|
proxyListenBind: ALEXA_PROXY_LOOPBACK_HOST,
|
|
132886
133004
|
proxyPort: this.#opts.proxyPort,
|
|
132887
133005
|
proxyLogLevel: "warn",
|
|
132888
|
-
|
|
133006
|
+
...region,
|
|
132889
133007
|
apiUserAgentPostfix: ALEXA_API_USER_AGENT_POSTFIX,
|
|
132890
133008
|
logger: (...args) => {
|
|
132891
133009
|
const line = args.map(String).join(" ");
|
|
@@ -135151,126 +135269,6 @@ var AlexaBrokerStore = class AlexaBrokerStore {
|
|
|
135151
135269
|
}
|
|
135152
135270
|
};
|
|
135153
135271
|
//#endregion
|
|
135154
|
-
//#region src/speak/constants.ts
|
|
135155
|
-
/** Echo speech limits and the worker restart bound (same window as CrashSupervisor). */
|
|
135156
|
-
var ALEXA_SPEAK_ADDON_ID = "import-alexa";
|
|
135157
|
-
var ALEXA_SPEAK_KIND = "alexa-echo";
|
|
135158
|
-
var DEVICE_REFRESH_MS = 5 * 6e4;
|
|
135159
|
-
/** Where the addon serves the Alexa mark; `listTargetKinds` stamps it as `iconUrl`. */
|
|
135160
|
-
var ALEXA_SPEAK_ICON_URL = `/addon/${ALEXA_SPEAK_ADDON_ID}/icons/${ALEXA_SPEAK_KIND}`;
|
|
135161
|
-
//#endregion
|
|
135162
|
-
//#region src/speak/worker-protocol.ts
|
|
135163
|
-
/** Line-delimited JSON the long-lived Python worker speaks. One object per line. */
|
|
135164
|
-
var EchoDeviceSchema = object({
|
|
135165
|
-
id: string().min(1),
|
|
135166
|
-
name: string(),
|
|
135167
|
-
family: string(),
|
|
135168
|
-
online: boolean(),
|
|
135169
|
-
kind: _enum(["device", "group"]),
|
|
135170
|
-
dnd: boolean(),
|
|
135171
|
-
deviceType: string(),
|
|
135172
|
-
/** The room the account files the device under, when Amazon names one. */
|
|
135173
|
-
room: string().optional()
|
|
135174
|
-
});
|
|
135175
|
-
var LoginStatusSchema = _enum(["ok", "failed"]);
|
|
135176
|
-
var SessionStatusSchema = object({
|
|
135177
|
-
loggedIn: boolean(),
|
|
135178
|
-
ageSec: number().int().nonnegative(),
|
|
135179
|
-
lastSuccessAt: number().int().nullable()
|
|
135180
|
-
});
|
|
135181
|
-
var TargetDeliverySchema = object({
|
|
135182
|
-
targetId: string(),
|
|
135183
|
-
ok: boolean(),
|
|
135184
|
-
error: string().optional()
|
|
135185
|
-
});
|
|
135186
|
-
var WorkerLineSchema = union([object({
|
|
135187
|
-
id: string(),
|
|
135188
|
-
ok: literal(true),
|
|
135189
|
-
result: unknown().optional()
|
|
135190
|
-
}), object({
|
|
135191
|
-
id: string(),
|
|
135192
|
-
ok: literal(false),
|
|
135193
|
-
error: string()
|
|
135194
|
-
})]);
|
|
135195
|
-
function requestLine(id, request) {
|
|
135196
|
-
return `${JSON.stringify({
|
|
135197
|
-
id,
|
|
135198
|
-
...request
|
|
135199
|
-
})}\n`;
|
|
135200
|
-
}
|
|
135201
|
-
function parseLoginResult(result) {
|
|
135202
|
-
const parsed = object({
|
|
135203
|
-
status: LoginStatusSchema,
|
|
135204
|
-
error: string().optional()
|
|
135205
|
-
}).safeParse(result);
|
|
135206
|
-
if (!parsed.success) return {
|
|
135207
|
-
status: "failed",
|
|
135208
|
-
error: "worker returned an unexpected login result"
|
|
135209
|
-
};
|
|
135210
|
-
return parsed.data;
|
|
135211
|
-
}
|
|
135212
|
-
function parseDevices(result) {
|
|
135213
|
-
const parsed = object({ devices: array(EchoDeviceSchema) }).safeParse(result);
|
|
135214
|
-
if (!parsed.success) throw new Error("worker returned an unexpected device list");
|
|
135215
|
-
return parsed.data.devices;
|
|
135216
|
-
}
|
|
135217
|
-
function parseDeliveries(result) {
|
|
135218
|
-
const parsed = object({ outcomes: array(TargetDeliverySchema) }).safeParse(result);
|
|
135219
|
-
if (!parsed.success) throw new Error("worker returned an unexpected delivery list");
|
|
135220
|
-
return parsed.data.outcomes;
|
|
135221
|
-
}
|
|
135222
|
-
function parseSessionStatus(result) {
|
|
135223
|
-
const parsed = SessionStatusSchema.safeParse(result);
|
|
135224
|
-
if (!parsed.success) throw new Error("worker returned an unexpected session status");
|
|
135225
|
-
return parsed.data;
|
|
135226
|
-
}
|
|
135227
|
-
//#endregion
|
|
135228
|
-
//#region src/speak/actions.ts
|
|
135229
|
-
var BrokerEchoStateSchema = _enum([
|
|
135230
|
-
"starting",
|
|
135231
|
-
"ready",
|
|
135232
|
-
"broker-not-signed-in",
|
|
135233
|
-
"failed"
|
|
135234
|
-
]);
|
|
135235
|
-
var SpeakStatusSchema = object({
|
|
135236
|
-
brokers: array(object({
|
|
135237
|
-
id: string(),
|
|
135238
|
-
name: string(),
|
|
135239
|
-
state: BrokerEchoStateSchema,
|
|
135240
|
-
error: string().optional(),
|
|
135241
|
-
devices: array(EchoDeviceSchema),
|
|
135242
|
-
session: SessionStatusSchema
|
|
135243
|
-
})),
|
|
135244
|
-
/** Targets that name no Amazon account yet (they cannot speak until one is chosen). */
|
|
135245
|
-
unassignedTargets: number().int().nonnegative(),
|
|
135246
|
-
/** What the old standalone login left behind, named; '' when nothing is pending. */
|
|
135247
|
-
notice: _enum([
|
|
135248
|
-
"",
|
|
135249
|
-
"no-broker",
|
|
135250
|
-
"choose-broker"
|
|
135251
|
-
])
|
|
135252
|
-
});
|
|
135253
|
-
var alexaSpeakActions = defineCustomActions({
|
|
135254
|
-
speakStatus: customAction(object({}), SpeakStatusSchema, {
|
|
135255
|
-
kind: "mutation",
|
|
135256
|
-
auth: "admin"
|
|
135257
|
-
}),
|
|
135258
|
-
sendTest: customAction(object({
|
|
135259
|
-
brokerId: string().min(1),
|
|
135260
|
-
echoIds: array(string().min(1)).min(1),
|
|
135261
|
-
mode: _enum(["speak", "announce"]),
|
|
135262
|
-
volume: number().int().min(0).max(100),
|
|
135263
|
-
text: string().min(1).max(450)
|
|
135264
|
-
}), object({ outcomes: array(object({
|
|
135265
|
-
echoId: string(),
|
|
135266
|
-
ok: boolean(),
|
|
135267
|
-
error: string().optional()
|
|
135268
|
-
})) }), {
|
|
135269
|
-
kind: "mutation",
|
|
135270
|
-
auth: "admin"
|
|
135271
|
-
})
|
|
135272
|
-
});
|
|
135273
|
-
//#endregion
|
|
135274
135272
|
//#region src/speak/broker-seed.ts
|
|
135275
135273
|
/**
|
|
135276
135274
|
* What the Echo worker needs from an import broker to act as the same Amazon
|
|
@@ -135505,6 +135503,72 @@ function scrubSecrets(text, secrets) {
|
|
|
135505
135503
|
return out;
|
|
135506
135504
|
}
|
|
135507
135505
|
//#endregion
|
|
135506
|
+
//#region src/speak/worker-protocol.ts
|
|
135507
|
+
/** Line-delimited JSON the long-lived Python worker speaks. One object per line. */
|
|
135508
|
+
var EchoDeviceSchema = object({
|
|
135509
|
+
id: string().min(1),
|
|
135510
|
+
name: string(),
|
|
135511
|
+
family: string(),
|
|
135512
|
+
online: boolean(),
|
|
135513
|
+
kind: _enum(["device", "group"]),
|
|
135514
|
+
dnd: boolean(),
|
|
135515
|
+
deviceType: string(),
|
|
135516
|
+
/** The room the account files the device under, when Amazon names one. */
|
|
135517
|
+
room: string().optional()
|
|
135518
|
+
});
|
|
135519
|
+
var LoginStatusSchema = _enum(["ok", "failed"]);
|
|
135520
|
+
var SessionStatusSchema = object({
|
|
135521
|
+
loggedIn: boolean(),
|
|
135522
|
+
ageSec: number().int().nonnegative(),
|
|
135523
|
+
lastSuccessAt: number().int().nullable()
|
|
135524
|
+
});
|
|
135525
|
+
var TargetDeliverySchema = object({
|
|
135526
|
+
targetId: string(),
|
|
135527
|
+
ok: boolean(),
|
|
135528
|
+
error: string().optional()
|
|
135529
|
+
});
|
|
135530
|
+
var WorkerLineSchema = union([object({
|
|
135531
|
+
id: string(),
|
|
135532
|
+
ok: literal(true),
|
|
135533
|
+
result: unknown().optional()
|
|
135534
|
+
}), object({
|
|
135535
|
+
id: string(),
|
|
135536
|
+
ok: literal(false),
|
|
135537
|
+
error: string()
|
|
135538
|
+
})]);
|
|
135539
|
+
function requestLine(id, request) {
|
|
135540
|
+
return `${JSON.stringify({
|
|
135541
|
+
id,
|
|
135542
|
+
...request
|
|
135543
|
+
})}\n`;
|
|
135544
|
+
}
|
|
135545
|
+
function parseLoginResult(result) {
|
|
135546
|
+
const parsed = object({
|
|
135547
|
+
status: LoginStatusSchema,
|
|
135548
|
+
error: string().optional()
|
|
135549
|
+
}).safeParse(result);
|
|
135550
|
+
if (!parsed.success) return {
|
|
135551
|
+
status: "failed",
|
|
135552
|
+
error: "worker returned an unexpected login result"
|
|
135553
|
+
};
|
|
135554
|
+
return parsed.data;
|
|
135555
|
+
}
|
|
135556
|
+
function parseDevices(result) {
|
|
135557
|
+
const parsed = object({ devices: array(EchoDeviceSchema) }).safeParse(result);
|
|
135558
|
+
if (!parsed.success) throw new Error("worker returned an unexpected device list");
|
|
135559
|
+
return parsed.data.devices;
|
|
135560
|
+
}
|
|
135561
|
+
function parseDeliveries(result) {
|
|
135562
|
+
const parsed = object({ outcomes: array(TargetDeliverySchema) }).safeParse(result);
|
|
135563
|
+
if (!parsed.success) throw new Error("worker returned an unexpected delivery list");
|
|
135564
|
+
return parsed.data.outcomes;
|
|
135565
|
+
}
|
|
135566
|
+
function parseSessionStatus(result) {
|
|
135567
|
+
const parsed = SessionStatusSchema.safeParse(result);
|
|
135568
|
+
if (!parsed.success) throw new Error("worker returned an unexpected session status");
|
|
135569
|
+
return parsed.data;
|
|
135570
|
+
}
|
|
135571
|
+
//#endregion
|
|
135508
135572
|
//#region src/speak/broker-echo.ts
|
|
135509
135573
|
var BrokerEcho = class {
|
|
135510
135574
|
deps;
|
|
@@ -135633,40 +135697,13 @@ var BrokerEcho = class {
|
|
|
135633
135697
|
}
|
|
135634
135698
|
};
|
|
135635
135699
|
//#endregion
|
|
135636
|
-
//#region src/speak/
|
|
135637
|
-
|
|
135638
|
-
|
|
135639
|
-
|
|
135640
|
-
|
|
135641
|
-
|
|
135642
|
-
|
|
135643
|
-
}
|
|
135644
|
-
function planLegacyMigration(legacy, brokerIds, targets) {
|
|
135645
|
-
const present = legacy.email.length > 0 || legacy.password.length > 0 || legacy.session.length > 0;
|
|
135646
|
-
const only = brokerIds.length === 1 ? brokerIds[0] ?? null : null;
|
|
135647
|
-
if (!present || only === null) return {
|
|
135648
|
-
present,
|
|
135649
|
-
attachTo: null,
|
|
135650
|
-
targets
|
|
135651
|
-
};
|
|
135652
|
-
return {
|
|
135653
|
-
present,
|
|
135654
|
-
attachTo: legacy.session.length > 0 ? only : null,
|
|
135655
|
-
targets: targets.map((row) => row.brokerId.length > 0 ? row : {
|
|
135656
|
-
...row,
|
|
135657
|
-
brokerId: only
|
|
135658
|
-
})
|
|
135659
|
-
};
|
|
135660
|
-
}
|
|
135661
|
-
/** What the page must tell the operator about targets that name no account. */
|
|
135662
|
-
function noticeFor(targets, brokerIds) {
|
|
135663
|
-
if (!targets.some((row) => row.brokerId.length === 0)) return "";
|
|
135664
|
-
if (brokerIds.length === 0) return "no-broker";
|
|
135665
|
-
return brokerIds.length > 1 ? "choose-broker" : "";
|
|
135666
|
-
}
|
|
135667
|
-
function unassignedCount(targets) {
|
|
135668
|
-
return targets.filter((row) => row.brokerId.length === 0).length;
|
|
135669
|
-
}
|
|
135700
|
+
//#region src/speak/constants.ts
|
|
135701
|
+
/** Echo speech limits and the worker restart bound (same window as CrashSupervisor). */
|
|
135702
|
+
var ALEXA_SPEAK_ADDON_ID = "import-alexa";
|
|
135703
|
+
var ALEXA_SPEAK_KIND = "alexa-echo";
|
|
135704
|
+
var DEVICE_REFRESH_MS = 5 * 6e4;
|
|
135705
|
+
/** Where the addon serves the Alexa mark; `listTargetKinds` stamps it as `iconUrl`. */
|
|
135706
|
+
var ALEXA_SPEAK_ICON_URL = `/addon/${ALEXA_SPEAK_ADDON_ID}/icons/${ALEXA_SPEAK_KIND}`;
|
|
135670
135707
|
//#endregion
|
|
135671
135708
|
//#region src/speak/rate-limit.ts
|
|
135672
135709
|
/**
|
|
@@ -135771,14 +135808,42 @@ function readString(config, key) {
|
|
|
135771
135808
|
const value = config[key];
|
|
135772
135809
|
return typeof value === "string" ? value : "";
|
|
135773
135810
|
}
|
|
135774
|
-
|
|
135775
|
-
|
|
135811
|
+
/** The rule's value for this send wins; the target's saved config is the fallback. */
|
|
135812
|
+
function readMode(config, delivery) {
|
|
135813
|
+
return (delivery?.["mode"] ?? config["mode"]) === "announce" ? "announce" : "speak";
|
|
135776
135814
|
}
|
|
135777
|
-
function readVolume(config) {
|
|
135778
|
-
const
|
|
135815
|
+
function readVolume(config, delivery) {
|
|
135816
|
+
const ruled = delivery?.["volume"];
|
|
135817
|
+
const value = typeof ruled === "number" ? ruled : config["volume"];
|
|
135779
135818
|
if (typeof value !== "number" || !Number.isFinite(value)) return 40;
|
|
135780
135819
|
return Math.max(0, Math.min(100, Math.round(value)));
|
|
135781
135820
|
}
|
|
135821
|
+
/**
|
|
135822
|
+
* The delivery parameters a rule may set for an Echo target (D938). The
|
|
135823
|
+
* target's own config holds the default; these override it per notification.
|
|
135824
|
+
*/
|
|
135825
|
+
var DELIVERY_PARAMS = [{
|
|
135826
|
+
key: "mode",
|
|
135827
|
+
label: "Mode",
|
|
135828
|
+
labelKey: "import-alexa:echoTarget.s_echo.mode.label",
|
|
135829
|
+
type: "enum",
|
|
135830
|
+
options: [{
|
|
135831
|
+
value: "speak",
|
|
135832
|
+
label: "Speak (quiet hours apply)"
|
|
135833
|
+
}, {
|
|
135834
|
+
value: "announce",
|
|
135835
|
+
label: "Announce (chime, then speech)"
|
|
135836
|
+
}],
|
|
135837
|
+
default: "speak"
|
|
135838
|
+
}, {
|
|
135839
|
+
key: "volume",
|
|
135840
|
+
label: "Volume",
|
|
135841
|
+
labelKey: "import-alexa:echoTarget.s_echo.volume.label",
|
|
135842
|
+
type: "number",
|
|
135843
|
+
min: 0,
|
|
135844
|
+
max: 100,
|
|
135845
|
+
default: 40
|
|
135846
|
+
}];
|
|
135782
135847
|
function respectDnd(config) {
|
|
135783
135848
|
return config["respectDnd"] !== false;
|
|
135784
135849
|
}
|
|
@@ -135879,6 +135944,7 @@ function alexaTargetKind(brokers) {
|
|
|
135879
135944
|
supportsDiscovery: true,
|
|
135880
135945
|
destinationKeys: ["brokerId", "echoId"],
|
|
135881
135946
|
configSchema: configSchema(brokers),
|
|
135947
|
+
deliveryParams: DELIVERY_PARAMS,
|
|
135882
135948
|
caps: {
|
|
135883
135949
|
attachments: {
|
|
135884
135950
|
mediaTypes: [],
|
|
@@ -136061,14 +136127,14 @@ function createAlexaNotificationProvider(ports) {
|
|
|
136061
136127
|
renderedAs: speech.renderedAs
|
|
136062
136128
|
};
|
|
136063
136129
|
}
|
|
136064
|
-
const mode = readMode(target.config);
|
|
136130
|
+
const mode = readMode(target.config, notification.delivery);
|
|
136065
136131
|
try {
|
|
136066
136132
|
const outcomes = await ports.speak({
|
|
136067
136133
|
brokerId: broker.id,
|
|
136068
136134
|
mode,
|
|
136069
136135
|
targets: [echoId],
|
|
136070
136136
|
text: speech.text,
|
|
136071
|
-
volume: readVolume(target.config)
|
|
136137
|
+
volume: readVolume(target.config, notification.delivery)
|
|
136072
136138
|
});
|
|
136073
136139
|
const one = outcomes.find((row) => row.targetId === echoId) ?? outcomes[0];
|
|
136074
136140
|
if (one === void 0 || !one.ok) {
|
|
@@ -136468,8 +136534,8 @@ function startAlexaWorker(deps) {
|
|
|
136468
136534
|
}
|
|
136469
136535
|
//#endregion
|
|
136470
136536
|
//#region src/speak/controller.ts
|
|
136471
|
-
function targetsFrom(
|
|
136472
|
-
return
|
|
136537
|
+
function targetsFrom(rows) {
|
|
136538
|
+
return rows.map((row) => ({
|
|
136473
136539
|
id: row.id,
|
|
136474
136540
|
name: row.name,
|
|
136475
136541
|
kind: ALEXA_SPEAK_KIND,
|
|
@@ -136499,7 +136565,7 @@ function rowsFrom(targets) {
|
|
|
136499
136565
|
var AlexaSpeakController = class {
|
|
136500
136566
|
host;
|
|
136501
136567
|
provider;
|
|
136502
|
-
|
|
136568
|
+
rows;
|
|
136503
136569
|
pythonPath = null;
|
|
136504
136570
|
echoes = /* @__PURE__ */ new Map();
|
|
136505
136571
|
timer = null;
|
|
@@ -136508,17 +136574,14 @@ var AlexaSpeakController = class {
|
|
|
136508
136574
|
constructor(host) {
|
|
136509
136575
|
this.host = host;
|
|
136510
136576
|
this.now = host.now ?? (() => Date.now());
|
|
136511
|
-
this.
|
|
136577
|
+
this.rows = readEchoTargets(host.readConfig());
|
|
136512
136578
|
this.provider = createAlexaNotificationProvider({
|
|
136513
136579
|
brokers: () => this.brokerViews(),
|
|
136514
136580
|
now: this.now,
|
|
136515
136581
|
logger: host.logger,
|
|
136516
|
-
loadTargets: () => targetsFrom(this.
|
|
136582
|
+
loadTargets: () => targetsFrom(this.rows),
|
|
136517
136583
|
saveTargets: (targets) => {
|
|
136518
|
-
this.
|
|
136519
|
-
...this.settings,
|
|
136520
|
-
targets: [...rowsFrom(targets)]
|
|
136521
|
-
};
|
|
136584
|
+
this.rows = rowsFrom(targets);
|
|
136522
136585
|
this.persist();
|
|
136523
136586
|
},
|
|
136524
136587
|
speak: (command) => this.utter(command.brokerId, command.mode, command.targets, command.text, command.volume)
|
|
@@ -136530,7 +136593,6 @@ var AlexaSpeakController = class {
|
|
|
136530
136593
|
this.host.logger.warn("alexa speak: embedded Python unavailable; Echo speech will not run");
|
|
136531
136594
|
return;
|
|
136532
136595
|
}
|
|
136533
|
-
await this.migrateLegacyLogin();
|
|
136534
136596
|
await this.brokersChanged();
|
|
136535
136597
|
this.timer = setInterval(() => {
|
|
136536
136598
|
this.refreshAll();
|
|
@@ -136547,12 +136609,6 @@ var AlexaSpeakController = class {
|
|
|
136547
136609
|
this.queue = this.queue.then(() => this.reconcile()).catch(() => void 0);
|
|
136548
136610
|
return this.queue;
|
|
136549
136611
|
}
|
|
136550
|
-
handlers() {
|
|
136551
|
-
return {
|
|
136552
|
-
speakStatus: async () => this.status(),
|
|
136553
|
-
sendTest: async (input) => this.sendTest(input)
|
|
136554
|
-
};
|
|
136555
|
-
}
|
|
136556
136612
|
brokerViews() {
|
|
136557
136613
|
return this.host.listBrokers().map((ref) => {
|
|
136558
136614
|
const echo = this.echoes.get(ref.id);
|
|
@@ -136566,36 +136622,11 @@ var AlexaSpeakController = class {
|
|
|
136566
136622
|
}
|
|
136567
136623
|
async persist() {
|
|
136568
136624
|
try {
|
|
136569
|
-
await this.host.
|
|
136625
|
+
await this.host.writeTargets(this.rows);
|
|
136570
136626
|
} catch (err) {
|
|
136571
|
-
this.host.logger.warn("alexa speak: could not store
|
|
136627
|
+
this.host.logger.warn("alexa speak: could not store targets", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
136572
136628
|
}
|
|
136573
136629
|
}
|
|
136574
|
-
/**
|
|
136575
|
-
* Drain the old standalone login once. With exactly one broker its jar and
|
|
136576
|
-
* its targets move onto that broker; otherwise the jar is dropped and the
|
|
136577
|
-
* targets wait for the operator to choose (the status names that state).
|
|
136578
|
-
*/
|
|
136579
|
-
async migrateLegacyLogin() {
|
|
136580
|
-
const brokers = this.host.listBrokers();
|
|
136581
|
-
const plan = planLegacyMigration(legacyLoginOf(this.settings), brokers.map((ref) => ref.id), this.settings.targets);
|
|
136582
|
-
if (!plan.present) return;
|
|
136583
|
-
if (plan.attachTo !== null) await this.host.sessions.put(plan.attachTo, this.settings.session);
|
|
136584
|
-
this.settings = {
|
|
136585
|
-
...this.settings,
|
|
136586
|
-
email: "",
|
|
136587
|
-
password: "",
|
|
136588
|
-
session: "",
|
|
136589
|
-
targets: [...plan.targets]
|
|
136590
|
-
};
|
|
136591
|
-
await this.persist();
|
|
136592
|
-
await this.host.clearLegacySecrets();
|
|
136593
|
-
this.host.logger.info("alexa speak: the old standalone login was retired", { meta: {
|
|
136594
|
-
attachedTo: plan.attachTo ?? "none",
|
|
136595
|
-
brokers: brokers.length,
|
|
136596
|
-
targets: plan.targets.length
|
|
136597
|
-
} });
|
|
136598
|
-
}
|
|
136599
136630
|
makeWorker(brokerId, pythonPath) {
|
|
136600
136631
|
if (this.host.startWorker !== void 0) return this.host.startWorker(brokerId, pythonPath);
|
|
136601
136632
|
return startAlexaWorker({
|
|
@@ -136645,59 +136676,6 @@ var AlexaSpeakController = class {
|
|
|
136645
136676
|
if (echo === void 0) throw new Error("no Echo session for this Amazon account");
|
|
136646
136677
|
return echo.utter(mode === "announce" ? "announce" : "speak", targets, text, volume);
|
|
136647
136678
|
}
|
|
136648
|
-
async status() {
|
|
136649
|
-
const brokers = this.host.listBrokers();
|
|
136650
|
-
const rows = await Promise.all(brokers.map(async (ref) => {
|
|
136651
|
-
const echo = this.echoes.get(ref.id);
|
|
136652
|
-
return {
|
|
136653
|
-
id: ref.id,
|
|
136654
|
-
name: ref.name,
|
|
136655
|
-
state: echo?.state ?? "failed",
|
|
136656
|
-
...echo === void 0 ? { error: "embedded Python unavailable" } : echo.error === null ? {} : { error: echo.error },
|
|
136657
|
-
devices: [...echo?.devices ?? []],
|
|
136658
|
-
session: echo === void 0 ? {
|
|
136659
|
-
loggedIn: false,
|
|
136660
|
-
ageSec: 0,
|
|
136661
|
-
lastSuccessAt: null
|
|
136662
|
-
} : await echo.session()
|
|
136663
|
-
};
|
|
136664
|
-
}));
|
|
136665
|
-
const targets = this.settings.targets;
|
|
136666
|
-
const ids = brokers.map((ref) => ref.id);
|
|
136667
|
-
return {
|
|
136668
|
-
brokers: rows,
|
|
136669
|
-
unassignedTargets: unassignedCount(targets),
|
|
136670
|
-
notice: noticeFor(targets, ids)
|
|
136671
|
-
};
|
|
136672
|
-
}
|
|
136673
|
-
async sendTest(input) {
|
|
136674
|
-
const outcomes = await this.utter(input.brokerId, input.mode, input.echoIds, input.text, input.volume);
|
|
136675
|
-
const known = new Map((this.echoes.get(input.brokerId)?.devices ?? []).map((device) => [device.id, device]));
|
|
136676
|
-
const next = [...this.settings.targets];
|
|
136677
|
-
for (const echoId of input.echoIds) {
|
|
136678
|
-
if (next.some((row) => row.echoId === echoId && row.brokerId === input.brokerId)) continue;
|
|
136679
|
-
next.push({
|
|
136680
|
-
id: `echo-${input.brokerId}-${echoId}`,
|
|
136681
|
-
name: known.get(echoId)?.name ?? echoId,
|
|
136682
|
-
enabled: true,
|
|
136683
|
-
brokerId: input.brokerId,
|
|
136684
|
-
echoId,
|
|
136685
|
-
mode: input.mode,
|
|
136686
|
-
volume: input.volume,
|
|
136687
|
-
respectDnd: true
|
|
136688
|
-
});
|
|
136689
|
-
}
|
|
136690
|
-
this.settings = {
|
|
136691
|
-
...this.settings,
|
|
136692
|
-
targets: next
|
|
136693
|
-
};
|
|
136694
|
-
await this.persist();
|
|
136695
|
-
return { outcomes: outcomes.map((row) => ({
|
|
136696
|
-
echoId: row.targetId,
|
|
136697
|
-
ok: row.ok,
|
|
136698
|
-
...row.error !== void 0 ? { error: row.error } : {}
|
|
136699
|
-
})) };
|
|
136700
|
-
}
|
|
136701
136679
|
};
|
|
136702
136680
|
//#endregion
|
|
136703
136681
|
//#region src/speak/icon-routes.ts
|
|
@@ -136750,26 +136728,11 @@ var RecordsSpeakSessionStore = class RecordsSpeakSessionStore {
|
|
|
136750
136728
|
};
|
|
136751
136729
|
//#endregion
|
|
136752
136730
|
//#region src/addon.ts
|
|
136753
|
-
var SPEAK_PAGE = {
|
|
136754
|
-
id: "speak",
|
|
136755
|
-
label: "Speak on Echo",
|
|
136756
|
-
icon: "megaphone",
|
|
136757
|
-
path: "/addon/import-alexa",
|
|
136758
|
-
remoteName: "addon_import_alexa_page",
|
|
136759
|
-
bundle: "remoteEntry.js",
|
|
136760
|
-
section: "administration"
|
|
136761
|
-
};
|
|
136762
136731
|
/** Default config — a fresh install starts with no accounts. */
|
|
136763
136732
|
var DEFAULTS = {
|
|
136764
136733
|
brokers: [],
|
|
136765
136734
|
proxyLogin: { proxyPort: 3456 },
|
|
136766
|
-
|
|
136767
|
-
email: "",
|
|
136768
|
-
password: "",
|
|
136769
|
-
region: "EU",
|
|
136770
|
-
session: "",
|
|
136771
|
-
targets: []
|
|
136772
|
-
}
|
|
136735
|
+
echoTargets: []
|
|
136773
136736
|
};
|
|
136774
136737
|
/** Device types an imported Alexa appliance can materialise as. */
|
|
136775
136738
|
var ALEXA_DEVICE_TYPES = [
|
|
@@ -136813,7 +136776,6 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
136813
136776
|
brokerStore = null;
|
|
136814
136777
|
activeBrokers = [];
|
|
136815
136778
|
speak = null;
|
|
136816
|
-
secretStatePaths = ["speak.password", "speak.session"];
|
|
136817
136779
|
constructor() {
|
|
136818
136780
|
super({ ...DEFAULTS });
|
|
136819
136781
|
}
|
|
@@ -136868,8 +136830,8 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
136868
136830
|
const speak = new AlexaSpeakController({
|
|
136869
136831
|
logger: this.ctx.logger.child("speak"),
|
|
136870
136832
|
readConfig: () => this.config,
|
|
136871
|
-
|
|
136872
|
-
await this.updateGlobalSettings({
|
|
136833
|
+
writeTargets: async (next) => {
|
|
136834
|
+
await this.updateGlobalSettings({ echoTargets: [...next] });
|
|
136873
136835
|
},
|
|
136874
136836
|
listBrokers: () => this.activeBrokers.map((entry) => ({
|
|
136875
136837
|
id: entry.id,
|
|
@@ -136881,50 +136843,31 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
136881
136843
|
return entry === void 0 ? null : readBrokerSeed(this.ctx.dataDir, entry);
|
|
136882
136844
|
},
|
|
136883
136845
|
sessions: RecordsSpeakSessionStore.of(records),
|
|
136884
|
-
clearLegacySecrets: async () => {
|
|
136885
|
-
await this.updateGlobalSettingsAsOperator({ speak: {
|
|
136886
|
-
...this.config.speak,
|
|
136887
|
-
password: SECRET_CLEAR,
|
|
136888
|
-
session: SECRET_CLEAR
|
|
136889
|
-
} });
|
|
136890
|
-
},
|
|
136891
136846
|
ensurePython: () => this.ctx.deps.ensurePython()
|
|
136892
136847
|
});
|
|
136893
136848
|
this.speak = speak;
|
|
136894
136849
|
speak.start().catch((err) => {
|
|
136895
136850
|
this.ctx.logger.warn("alexa speak: worker did not start", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
136896
136851
|
});
|
|
136897
|
-
|
|
136898
|
-
|
|
136899
|
-
|
|
136900
|
-
|
|
136901
|
-
|
|
136902
|
-
|
|
136903
|
-
|
|
136904
|
-
|
|
136905
|
-
|
|
136906
|
-
|
|
136907
|
-
|
|
136908
|
-
|
|
136909
|
-
|
|
136910
|
-
|
|
136911
|
-
|
|
136912
|
-
|
|
136913
|
-
|
|
136914
|
-
|
|
136915
|
-
|
|
136916
|
-
{
|
|
136917
|
-
capability: notificationOutputCapability,
|
|
136918
|
-
provider: speak.provider
|
|
136919
|
-
},
|
|
136920
|
-
{
|
|
136921
|
-
capability: addonPagesSourceCapability,
|
|
136922
|
-
provider: pages
|
|
136923
|
-
}
|
|
136924
|
-
],
|
|
136925
|
-
customActions: alexaSpeakActions,
|
|
136926
|
-
actionHandlers: speak.handlers()
|
|
136927
|
-
};
|
|
136852
|
+
return { providers: [
|
|
136853
|
+
...regs,
|
|
136854
|
+
{
|
|
136855
|
+
capability: brokerCapability,
|
|
136856
|
+
provider: this.buildBrokerProvider()
|
|
136857
|
+
},
|
|
136858
|
+
{
|
|
136859
|
+
capability: deviceAdoptionCapability,
|
|
136860
|
+
provider: this.buildAdoptionProvider()
|
|
136861
|
+
},
|
|
136862
|
+
{
|
|
136863
|
+
capability: addonRoutesCapability,
|
|
136864
|
+
provider: buildAddonRouteProvider("import-alexa", loginRoutes)
|
|
136865
|
+
},
|
|
136866
|
+
{
|
|
136867
|
+
capability: notificationOutputCapability,
|
|
136868
|
+
provider: speak.provider
|
|
136869
|
+
}
|
|
136870
|
+
] };
|
|
136928
136871
|
}
|
|
136929
136872
|
async loadPersistedBrokers() {
|
|
136930
136873
|
const store = this.brokerStore;
|
|
@@ -137315,12 +137258,10 @@ exports.StubAlexaClient = StubAlexaClient;
|
|
|
137315
137258
|
exports.__toCommonJS = __toCommonJS;
|
|
137316
137259
|
exports.alexaAddonConfigSchema = alexaAddonConfigSchema;
|
|
137317
137260
|
exports.alexaConnectionSchema = alexaConnectionSchema;
|
|
137318
|
-
exports.alexaSpeakActions = alexaSpeakActions;
|
|
137319
137261
|
exports.buildAlexaApplianceCandidate = buildAlexaApplianceCandidate;
|
|
137320
137262
|
exports.buildConnectionFormSchema = buildConnectionFormSchema;
|
|
137321
137263
|
exports.connectionToCredentials = connectionToCredentials;
|
|
137322
137264
|
exports.createAlexaClient = createAlexaClient;
|
|
137323
|
-
exports.customActions = alexaSpeakActions;
|
|
137324
137265
|
exports.mapAlexaCapabilitiesToCamStack = mapAlexaCapabilitiesToCamStack;
|
|
137325
137266
|
exports.mappableAppliance = mappableAppliance;
|
|
137326
137267
|
exports.settingsToAlexaConnection = settingsToAlexaConnection;
|