@camstack/addon-import-alexa 0.2.157 → 0.2.159
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/{MotionZonesSettings-Z-NRG681.mjs → MotionZonesSettings-DGPV-8dv.mjs} +2 -2
- package/dist/{PrivacyMaskSettings-DiS2hPpi.mjs → PrivacyMaskSettings-Cn85uMbr.mjs} +4 -4
- package/dist/{SceneMonitorEditor-BYYK_XwY.mjs → SceneMonitorEditor-gIlEt9fl.mjs} +3 -3
- package/dist/_stub.js +9 -9
- package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_import_alexa_page-B4G6E4Vs.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_import_alexa_page-n3jAYuIE.mjs} +3 -3
- package/dist/{_virtual_mf___mfe_internal__addon_import_alexa_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DCV5s91A.mjs → _virtual_mf___mfe_internal__addon_import_alexa_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BwgFX4MI.mjs} +1 -1
- package/dist/addon.js +844 -160
- package/dist/addon.mjs +844 -160
- package/dist/{hostInit-DWBMKrvf.mjs → hostInit-CGhFmOcB.mjs} +2 -2
- package/dist/{player-overlays-5MrLqX2i.mjs → player-overlays-BHCfJpas.mjs} +1 -1
- package/dist/remoteEntry.js +1 -1
- package/dist/{responsive-CsfyZVWS.mjs → responsive-Dclc7CfC.mjs} +1 -1
- package/dist/{scene-monitor-copy-DOuMmSwJ.mjs → scene-monitor-copy-BcAIeCGo.mjs} +1 -1
- package/dist/{square-BGn7yRva.mjs → square-LLOXavH6.mjs} +1 -1
- package/dist/{trash-2-BiBTPv8Y.mjs → trash-2-P7HFzsfr.mjs} +1 -1
- package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_import_alexa_page__remoteEntry_js-BniKFfAU.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_import_alexa_page__remoteEntry_js-C0zVjr31.mjs} +1 -1
- package/i18n/en.json +9 -2
- package/i18n/it.json +9 -2
- package/package.json +2 -2
package/dist/addon.mjs
CHANGED
|
@@ -989,7 +989,7 @@ var propertyKeyTypes = /* @__PURE__*/ new Set([
|
|
|
989
989
|
"number",
|
|
990
990
|
"symbol"
|
|
991
991
|
]);
|
|
992
|
-
function escapeRegex(str) {
|
|
992
|
+
function escapeRegex$1(str) {
|
|
993
993
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
994
994
|
}
|
|
995
995
|
function clone(inst, def, params) {
|
|
@@ -1719,7 +1719,7 @@ var $ZodCheckUpperCase = /*@__PURE__*/ $constructor("$ZodCheckUpperCase", (inst,
|
|
|
1719
1719
|
});
|
|
1720
1720
|
var $ZodCheckIncludes = /*@__PURE__*/ $constructor("$ZodCheckIncludes", (inst, def) => {
|
|
1721
1721
|
$ZodCheck.init(inst, def);
|
|
1722
|
-
const escapedRegex = escapeRegex(def.includes);
|
|
1722
|
+
const escapedRegex = escapeRegex$1(def.includes);
|
|
1723
1723
|
const pattern = new RegExp(typeof def.position === "number" ? `^.{${def.position}}${escapedRegex}` : escapedRegex);
|
|
1724
1724
|
def.pattern = pattern;
|
|
1725
1725
|
inst._zod.onattach.push((inst) => {
|
|
@@ -1742,7 +1742,7 @@ var $ZodCheckIncludes = /*@__PURE__*/ $constructor("$ZodCheckIncludes", (inst, d
|
|
|
1742
1742
|
});
|
|
1743
1743
|
var $ZodCheckStartsWith = /*@__PURE__*/ $constructor("$ZodCheckStartsWith", (inst, def) => {
|
|
1744
1744
|
$ZodCheck.init(inst, def);
|
|
1745
|
-
const pattern = new RegExp(`^${escapeRegex(def.prefix)}.*`);
|
|
1745
|
+
const pattern = new RegExp(`^${escapeRegex$1(def.prefix)}.*`);
|
|
1746
1746
|
def.pattern ?? (def.pattern = pattern);
|
|
1747
1747
|
inst._zod.onattach.push((inst) => {
|
|
1748
1748
|
const bag = inst._zod.bag;
|
|
@@ -1764,7 +1764,7 @@ var $ZodCheckStartsWith = /*@__PURE__*/ $constructor("$ZodCheckStartsWith", (ins
|
|
|
1764
1764
|
});
|
|
1765
1765
|
var $ZodCheckEndsWith = /*@__PURE__*/ $constructor("$ZodCheckEndsWith", (inst, def) => {
|
|
1766
1766
|
$ZodCheck.init(inst, def);
|
|
1767
|
-
const pattern = new RegExp(`.*${escapeRegex(def.suffix)}$`);
|
|
1767
|
+
const pattern = new RegExp(`.*${escapeRegex$1(def.suffix)}$`);
|
|
1768
1768
|
def.pattern ?? (def.pattern = pattern);
|
|
1769
1769
|
inst._zod.onattach.push((inst) => {
|
|
1770
1770
|
const bag = inst._zod.bag;
|
|
@@ -3000,7 +3000,7 @@ var $ZodEnum = /*@__PURE__*/ $constructor("$ZodEnum", (inst, def) => {
|
|
|
3000
3000
|
const values = getEnumValues(def.entries);
|
|
3001
3001
|
const valuesSet = new Set(values);
|
|
3002
3002
|
inst._zod.values = valuesSet;
|
|
3003
|
-
inst._zod.pattern = new RegExp(`^(${values.filter((k) => propertyKeyTypes.has(typeof k)).map((o) => typeof o === "string" ? escapeRegex(o) : o.toString()).join("|")})$`);
|
|
3003
|
+
inst._zod.pattern = new RegExp(`^(${values.filter((k) => propertyKeyTypes.has(typeof k)).map((o) => typeof o === "string" ? escapeRegex$1(o) : o.toString()).join("|")})$`);
|
|
3004
3004
|
inst._zod.parse = (payload, _ctx) => {
|
|
3005
3005
|
const input = payload.value;
|
|
3006
3006
|
if (valuesSet.has(input)) return payload;
|
|
@@ -3018,7 +3018,7 @@ var $ZodLiteral = /*@__PURE__*/ $constructor("$ZodLiteral", (inst, def) => {
|
|
|
3018
3018
|
if (def.values.length === 0) throw new Error("Cannot create literal schema with no valid values");
|
|
3019
3019
|
const values = new Set(def.values);
|
|
3020
3020
|
inst._zod.values = values;
|
|
3021
|
-
inst._zod.pattern = new RegExp(`^(${def.values.map((o) => typeof o === "string" ? escapeRegex(o) : o ? escapeRegex(o.toString()) : String(o)).join("|")})$`);
|
|
3021
|
+
inst._zod.pattern = new RegExp(`^(${def.values.map((o) => typeof o === "string" ? escapeRegex$1(o) : o ? escapeRegex$1(o.toString()) : String(o)).join("|")})$`);
|
|
3022
3022
|
inst._zod.parse = (payload, _ctx) => {
|
|
3023
3023
|
const input = payload.value;
|
|
3024
3024
|
if (values.has(input)) return payload;
|
|
@@ -90636,6 +90636,448 @@ DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
|
|
|
90636
90636
|
DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
|
|
90637
90637
|
DEFAULT_NATIVE_LEASE_SETTINGS.sceneBudgetMb;
|
|
90638
90638
|
DEFAULT_NATIVE_LEASE_SETTINGS.admission;
|
|
90639
|
+
/**
|
|
90640
|
+
* Where an OAuth round-trip ends: the admin-ui completion route
|
|
90641
|
+
* (`/sso-complete`, the page every popup sign-in already lands on), with the
|
|
90642
|
+
* OUTCOME in the URL fragment.
|
|
90643
|
+
*
|
|
90644
|
+
* The outcome is a status plus a reason CODE from a closed list. It never
|
|
90645
|
+
* carries a token, a code, a client secret or a free-form error message: the
|
|
90646
|
+
* page words each code itself, and an unknown code reads as `unknown` rather
|
|
90647
|
+
* than being echoed. Fragment only, so nothing here reaches a server log.
|
|
90648
|
+
*
|
|
90649
|
+
* Pure and browser-safe.
|
|
90650
|
+
*/
|
|
90651
|
+
/** The admin-ui route the hub's OAuth completions land on (relative to its mount). */
|
|
90652
|
+
var OAUTH_LANDING_PATH = "/sso-complete";
|
|
90653
|
+
/**
|
|
90654
|
+
* The landing URL for `outcome`. `origin` is the hub address the flow already
|
|
90655
|
+
* runs on (from the `local-network` palette role, never a literal); `null`
|
|
90656
|
+
* gives a hub-relative URL.
|
|
90657
|
+
*/
|
|
90658
|
+
function oauthLandingUrl(origin, outcome) {
|
|
90659
|
+
const params = new URLSearchParams({
|
|
90660
|
+
return: "oauth",
|
|
90661
|
+
status: outcome.status
|
|
90662
|
+
});
|
|
90663
|
+
if (outcome.addonId !== void 0) params.set("addon", outcome.addonId);
|
|
90664
|
+
if (outcome.brokerId !== void 0) params.set("broker", outcome.brokerId);
|
|
90665
|
+
if (outcome.integration !== void 0) params.set("integration", outcome.integration);
|
|
90666
|
+
if (outcome.status === "error") params.set("reason", outcome.reason ?? "unknown");
|
|
90667
|
+
if (outcome.action === "disconnect") params.set("action", "disconnect");
|
|
90668
|
+
return `${origin === null ? "" : origin.replace(/\/+$/, "")}${OAUTH_LANDING_PATH}#${params.toString()}`;
|
|
90669
|
+
}
|
|
90670
|
+
var LOOPBACK_HOSTS = new Set([
|
|
90671
|
+
"127.0.0.1",
|
|
90672
|
+
"localhost",
|
|
90673
|
+
"[::1]"
|
|
90674
|
+
]);
|
|
90675
|
+
function hostWithoutPort(host) {
|
|
90676
|
+
if (host.startsWith("[")) return host.slice(0, host.indexOf("]") + 1);
|
|
90677
|
+
const colon = host.indexOf(":");
|
|
90678
|
+
return colon === -1 ? host : host.slice(0, colon);
|
|
90679
|
+
}
|
|
90680
|
+
function isLoopbackHost(host) {
|
|
90681
|
+
return LOOPBACK_HOSTS.has(hostWithoutPort(host.toLowerCase()));
|
|
90682
|
+
}
|
|
90683
|
+
/** An allow-list entry is valid when it is a bare host[:port] and `http` only targets loopback. */
|
|
90684
|
+
function isValidUpstream(upstream) {
|
|
90685
|
+
if (upstream.host.length === 0 || !/^[a-z0-9.[\]:-]+$/.test(upstream.host)) return false;
|
|
90686
|
+
return upstream.scheme === "https" || isLoopbackHost(upstream.host);
|
|
90687
|
+
}
|
|
90688
|
+
function findUpstream(route, host) {
|
|
90689
|
+
const wanted = host.toLowerCase();
|
|
90690
|
+
return route.upstreams.find((upstream) => upstream.host === wanted);
|
|
90691
|
+
}
|
|
90692
|
+
/** `<basePath>/<host><pathname><search>` — what the browser is sent to. */
|
|
90693
|
+
function proxyPathFor(route, host, pathname, search = "") {
|
|
90694
|
+
return `${route.basePath}/${host}${pathname.startsWith("/") ? pathname : `/${pathname}`}${search}`;
|
|
90695
|
+
}
|
|
90696
|
+
/** Split `/<attemptId>/<host>/<rest…>[?query]` (the data-plane view of a request). */
|
|
90697
|
+
function parseProxyPath(rawUrl) {
|
|
90698
|
+
const queryAt = rawUrl.indexOf("?");
|
|
90699
|
+
const path = queryAt === -1 ? rawUrl : rawUrl.slice(0, queryAt);
|
|
90700
|
+
const search = queryAt === -1 ? "" : rawUrl.slice(queryAt);
|
|
90701
|
+
const match = /^\/([A-Za-z0-9_-]{8,128})\/([^/]+)(\/.*)?$/.exec(path);
|
|
90702
|
+
if (match === null) return null;
|
|
90703
|
+
const attemptId = match[1];
|
|
90704
|
+
const host = match[2];
|
|
90705
|
+
if (attemptId === void 0 || host === void 0) return null;
|
|
90706
|
+
return {
|
|
90707
|
+
attemptId,
|
|
90708
|
+
host: host.toLowerCase(),
|
|
90709
|
+
pathname: match[3] ?? "/",
|
|
90710
|
+
search
|
|
90711
|
+
};
|
|
90712
|
+
}
|
|
90713
|
+
/** The vendor URL a proxied request targets, or null when the host would not stay `host`. */
|
|
90714
|
+
function upstreamUrlFor(upstream, parsed) {
|
|
90715
|
+
try {
|
|
90716
|
+
const url = new URL(`${upstream.scheme}://${upstream.host}${parsed.pathname}${parsed.search}`);
|
|
90717
|
+
return url.host.toLowerCase() === upstream.host ? url : null;
|
|
90718
|
+
} catch {
|
|
90719
|
+
return null;
|
|
90720
|
+
}
|
|
90721
|
+
}
|
|
90722
|
+
function escapeRegex(value) {
|
|
90723
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
90724
|
+
}
|
|
90725
|
+
/** A Location (or any URL value) to a vendor host becomes a proxy path; anything else is unchanged. */
|
|
90726
|
+
function rewriteLocation(location, currentUpstream, route) {
|
|
90727
|
+
let resolved;
|
|
90728
|
+
try {
|
|
90729
|
+
resolved = new URL(location, `${currentUpstream.scheme}://${currentUpstream.host}/`);
|
|
90730
|
+
} catch {
|
|
90731
|
+
return location;
|
|
90732
|
+
}
|
|
90733
|
+
if (resolved.protocol !== "https:" && resolved.protocol !== "http:") return location;
|
|
90734
|
+
const target = findUpstream(route, resolved.host);
|
|
90735
|
+
if (target === void 0) return location;
|
|
90736
|
+
return `${proxyPathFor(route, target.host, resolved.pathname, resolved.search)}${resolved.hash}`;
|
|
90737
|
+
}
|
|
90738
|
+
/** Characters that may precede a URL that STARTS a value (never `=`, `?`, `&`, `%3D`). */
|
|
90739
|
+
var VALUE_START = String.raw`(?<=^|["'(\s>,\[{])`;
|
|
90740
|
+
/**
|
|
90741
|
+
* Rewrite vendor URLs in a text body: absolute (`https://host/…`), protocol-relative
|
|
90742
|
+
* (`//host/…`) and JSON-escaped (`https:\/\/host\/…`) when they start a value; and, in
|
|
90743
|
+
* HTML/CSS, root-relative references (`href="/x"`, `url(/x)`) which would otherwise
|
|
90744
|
+
* escape the proxy mount and land on the hub.
|
|
90745
|
+
*/
|
|
90746
|
+
function rewriteBody(body, kind, currentUpstream, route) {
|
|
90747
|
+
let out = body;
|
|
90748
|
+
for (const upstream of route.upstreams) {
|
|
90749
|
+
const host = escapeRegex(upstream.host);
|
|
90750
|
+
const plain = new RegExp(`${VALUE_START}(?:https?:)?//${host}(?=[/"'?#\\s)\\]},<]|$)`, "g");
|
|
90751
|
+
out = out.replace(plain, `${route.basePath}/${upstream.host}`);
|
|
90752
|
+
const escapedBase = `${route.basePath}/${upstream.host}`.replace(/\//g, "\\/");
|
|
90753
|
+
const escaped = new RegExp(`${VALUE_START}(?:https?:)?\\\\/\\\\/${host}(?=\\\\/|["'?#\\s)\\]},<]|$)`, "g");
|
|
90754
|
+
out = out.replace(escaped, escapedBase);
|
|
90755
|
+
}
|
|
90756
|
+
const current = `${route.basePath}/${currentUpstream.host}/`;
|
|
90757
|
+
const notMounted = `(?!/)(?!${escapeRegex(route.basePath.slice(1))}/)`;
|
|
90758
|
+
if (kind === "html") {
|
|
90759
|
+
const attribute = String.raw`(\b(?:href|src|action|formaction|poster|data-href|data-src)\s*=\s*["'])/`;
|
|
90760
|
+
out = out.replace(new RegExp(`${attribute}${notMounted}`, "gi"), `$1${current}`);
|
|
90761
|
+
}
|
|
90762
|
+
if (kind === "html" || kind === "css") {
|
|
90763
|
+
const cssUrl = String.raw`(url\(\s*["']?)/`;
|
|
90764
|
+
out = out.replace(new RegExp(`${cssUrl}${notMounted}`, "gi"), `$1${current}`);
|
|
90765
|
+
}
|
|
90766
|
+
return out;
|
|
90767
|
+
}
|
|
90768
|
+
/**
|
|
90769
|
+
* A vendor `Set-Cookie` for the browser: no `Domain` (the cookie belongs to the hub
|
|
90770
|
+
* origin), `Path` narrowed to this attempt's prefix, `Secure` kept (the hub is HTTPS).
|
|
90771
|
+
*/
|
|
90772
|
+
function rewriteSetCookie(cookie, route) {
|
|
90773
|
+
const kept = cookie.split(";").map((part) => part.trim()).filter((part, index) => {
|
|
90774
|
+
if (index === 0) return true;
|
|
90775
|
+
const name = part.split("=")[0]?.trim().toLowerCase() ?? "";
|
|
90776
|
+
return name !== "domain" && name !== "path";
|
|
90777
|
+
});
|
|
90778
|
+
kept.push(`Path=${route.basePath}/`);
|
|
90779
|
+
return kept.join("; ");
|
|
90780
|
+
}
|
|
90781
|
+
/**
|
|
90782
|
+
* A browser `Referer` that points into this proxy, turned back into the vendor URL it
|
|
90783
|
+
* stands for. A Referer that is not ours is dropped (null): it names the hub.
|
|
90784
|
+
*/
|
|
90785
|
+
function rewriteReferer(referer, route) {
|
|
90786
|
+
let url;
|
|
90787
|
+
try {
|
|
90788
|
+
url = new URL(referer, "http://hub.invalid");
|
|
90789
|
+
} catch {
|
|
90790
|
+
return null;
|
|
90791
|
+
}
|
|
90792
|
+
if (!url.pathname.startsWith(`${route.basePath}/`)) return null;
|
|
90793
|
+
const rest = /^\/([^/]+)(\/.*)?$/.exec(url.pathname.slice(route.basePath.length));
|
|
90794
|
+
const host = rest?.[1]?.toLowerCase();
|
|
90795
|
+
if (host === void 0) return null;
|
|
90796
|
+
const upstream = findUpstream(route, host);
|
|
90797
|
+
if (upstream === void 0) return null;
|
|
90798
|
+
return `${upstream.scheme}://${upstream.host}${rest?.[2] ?? "/"}${url.search}`;
|
|
90799
|
+
}
|
|
90800
|
+
/** Cookies the browser holds for the hub itself must never reach a vendor. */
|
|
90801
|
+
function filterForwardedCookies(cookieHeader) {
|
|
90802
|
+
return cookieHeader.split(";").map((part) => part.trim()).filter((part) => part.length > 0 && !part.toLowerCase().startsWith("camstack_")).join("; ");
|
|
90803
|
+
}
|
|
90804
|
+
var MAX_REQUEST_BODY_BYTES = 2 * 1024 * 1024;
|
|
90805
|
+
var MAX_TEXT_BODY_BYTES = 4 * 1024 * 1024;
|
|
90806
|
+
/** Request headers that are never forwarded to the vendor. */
|
|
90807
|
+
var DROP_REQUEST_HEADERS = new Set([
|
|
90808
|
+
"host",
|
|
90809
|
+
"connection",
|
|
90810
|
+
"keep-alive",
|
|
90811
|
+
"proxy-authorization",
|
|
90812
|
+
"proxy-connection",
|
|
90813
|
+
"te",
|
|
90814
|
+
"trailer",
|
|
90815
|
+
"transfer-encoding",
|
|
90816
|
+
"upgrade",
|
|
90817
|
+
"content-length",
|
|
90818
|
+
"accept-encoding",
|
|
90819
|
+
"authorization",
|
|
90820
|
+
"cookie",
|
|
90821
|
+
"origin",
|
|
90822
|
+
"referer",
|
|
90823
|
+
"x-camstack-dataplane-secret",
|
|
90824
|
+
"x-forwarded-for",
|
|
90825
|
+
"x-forwarded-host",
|
|
90826
|
+
"x-forwarded-proto",
|
|
90827
|
+
"x-forwarded-prefix",
|
|
90828
|
+
"x-real-ip",
|
|
90829
|
+
"forwarded"
|
|
90830
|
+
]);
|
|
90831
|
+
/**
|
|
90832
|
+
* Response headers dropped: framing/CSP/HSTS that break a page under another origin, and
|
|
90833
|
+
* the ones that no longer describe the (decoded, possibly rewritten) body.
|
|
90834
|
+
*/
|
|
90835
|
+
var DROP_RESPONSE_HEADERS = new Set([
|
|
90836
|
+
"content-security-policy",
|
|
90837
|
+
"content-security-policy-report-only",
|
|
90838
|
+
"x-frame-options",
|
|
90839
|
+
"strict-transport-security",
|
|
90840
|
+
"public-key-pins",
|
|
90841
|
+
"report-to",
|
|
90842
|
+
"nel",
|
|
90843
|
+
"content-encoding",
|
|
90844
|
+
"content-length",
|
|
90845
|
+
"transfer-encoding",
|
|
90846
|
+
"connection",
|
|
90847
|
+
"keep-alive",
|
|
90848
|
+
"set-cookie",
|
|
90849
|
+
"location"
|
|
90850
|
+
]);
|
|
90851
|
+
function bodyKindOf(contentType) {
|
|
90852
|
+
const type = contentType.toLowerCase();
|
|
90853
|
+
if (type.includes("text/html") || type.includes("application/xhtml")) return "html";
|
|
90854
|
+
if (type.includes("text/css")) return "css";
|
|
90855
|
+
if (type.startsWith("text/") || type.includes("javascript") || type.includes("json") || type.includes("xml")) return "other";
|
|
90856
|
+
return null;
|
|
90857
|
+
}
|
|
90858
|
+
function firstHeader(value) {
|
|
90859
|
+
return Array.isArray(value) ? value[0] : value;
|
|
90860
|
+
}
|
|
90861
|
+
async function readRequestBody(req) {
|
|
90862
|
+
const chunks = [];
|
|
90863
|
+
let size = 0;
|
|
90864
|
+
for await (const chunk of req) {
|
|
90865
|
+
const buf = typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk);
|
|
90866
|
+
size += buf.length;
|
|
90867
|
+
if (size > MAX_REQUEST_BODY_BYTES) return "too-large";
|
|
90868
|
+
chunks.push(buf);
|
|
90869
|
+
}
|
|
90870
|
+
return Buffer.concat(chunks);
|
|
90871
|
+
}
|
|
90872
|
+
function sendPlain(res, status, text) {
|
|
90873
|
+
res.writeHead(status, {
|
|
90874
|
+
"content-type": "text/plain; charset=utf-8",
|
|
90875
|
+
"cache-control": "no-store"
|
|
90876
|
+
});
|
|
90877
|
+
res.end(text);
|
|
90878
|
+
}
|
|
90879
|
+
var VendorLoginProxy = class {
|
|
90880
|
+
#addonId;
|
|
90881
|
+
#prefix;
|
|
90882
|
+
#logger;
|
|
90883
|
+
#now;
|
|
90884
|
+
#fetch;
|
|
90885
|
+
#attempts = /* @__PURE__ */ new Map();
|
|
90886
|
+
constructor(options) {
|
|
90887
|
+
this.#addonId = options.addonId;
|
|
90888
|
+
this.#prefix = options.prefix;
|
|
90889
|
+
this.#logger = options.logger;
|
|
90890
|
+
this.#now = options.now ?? (() => Date.now());
|
|
90891
|
+
this.#fetch = options.fetchFn ?? globalThis.fetch.bind(globalThis);
|
|
90892
|
+
}
|
|
90893
|
+
/** The `DataPlaneHandler` to pass to `ctx.dataPlane.serve({ prefix, access: 'admin', … })`. */
|
|
90894
|
+
handler = (req, res) => this.#handle(req, res);
|
|
90895
|
+
/** Register an attempt. A one-shot capability: refused hosts are rejected up front. */
|
|
90896
|
+
open(spec) {
|
|
90897
|
+
for (const upstream of spec.upstreams) if (!isValidUpstream(upstream)) throw new Error(`vendor login proxy: upstream ${upstream.host} is not allowed`);
|
|
90898
|
+
this.#attempts.set(spec.attemptId, {
|
|
90899
|
+
expiresAtMs: spec.expiresAtMs,
|
|
90900
|
+
upstreams: spec.upstreams,
|
|
90901
|
+
evaluate: async (view) => {
|
|
90902
|
+
const verdict = await spec.terminal(view);
|
|
90903
|
+
if (verdict.kind === "continue") return verdict;
|
|
90904
|
+
return {
|
|
90905
|
+
kind: "done",
|
|
90906
|
+
landing: await spec.onDone(verdict.payload)
|
|
90907
|
+
};
|
|
90908
|
+
}
|
|
90909
|
+
});
|
|
90910
|
+
}
|
|
90911
|
+
close(attemptId) {
|
|
90912
|
+
this.#attempts.delete(attemptId);
|
|
90913
|
+
}
|
|
90914
|
+
isOpen(attemptId) {
|
|
90915
|
+
const attempt = this.#attempts.get(attemptId);
|
|
90916
|
+
return attempt !== void 0 && this.#now() <= attempt.expiresAtMs;
|
|
90917
|
+
}
|
|
90918
|
+
/**
|
|
90919
|
+
* The hub-relative path that opens `vendorUrl` through the proxy for this attempt:
|
|
90920
|
+
* the URL the browser is sent to. Null when the host is not in the attempt's list.
|
|
90921
|
+
*/
|
|
90922
|
+
entryPath(attemptId, vendorUrl) {
|
|
90923
|
+
const attempt = this.#attempts.get(attemptId);
|
|
90924
|
+
if (attempt === void 0) return null;
|
|
90925
|
+
const url = new URL(vendorUrl);
|
|
90926
|
+
if (findUpstream({
|
|
90927
|
+
basePath: "",
|
|
90928
|
+
upstreams: attempt.upstreams
|
|
90929
|
+
}, url.host) === void 0) return null;
|
|
90930
|
+
return `/addon/${this.#addonId}/${this.#prefix}/${attemptId}/${url.host}${url.pathname}${url.search}`;
|
|
90931
|
+
}
|
|
90932
|
+
#routeFor(attemptId, attempt, forwardedPrefix) {
|
|
90933
|
+
return {
|
|
90934
|
+
basePath: `${forwardedPrefix}/addon/${this.#addonId}/${this.#prefix}/${attemptId}`,
|
|
90935
|
+
upstreams: attempt.upstreams
|
|
90936
|
+
};
|
|
90937
|
+
}
|
|
90938
|
+
async #handle(req, res) {
|
|
90939
|
+
try {
|
|
90940
|
+
await this.#serve(req, res);
|
|
90941
|
+
} catch (err) {
|
|
90942
|
+
this.#logger.warn("vendor login proxy: request failed", { meta: {
|
|
90943
|
+
addonId: this.#addonId,
|
|
90944
|
+
error: err instanceof Error ? err.name : "unknown"
|
|
90945
|
+
} });
|
|
90946
|
+
if (!res.headersSent) sendPlain(res, 502, "The sign-in page could not be reached.");
|
|
90947
|
+
else res.end();
|
|
90948
|
+
}
|
|
90949
|
+
}
|
|
90950
|
+
async #serve(req, res) {
|
|
90951
|
+
const parsed = parseProxyPath(req.url ?? "/");
|
|
90952
|
+
if (parsed === null) {
|
|
90953
|
+
sendPlain(res, 404, "Not found");
|
|
90954
|
+
return;
|
|
90955
|
+
}
|
|
90956
|
+
const forwardedPrefix = (firstHeader(req.headers["x-forwarded-prefix"]) ?? "").replace(/\/+$/, "");
|
|
90957
|
+
const attempt = this.#attempts.get(parsed.attemptId);
|
|
90958
|
+
if (attempt === void 0 || this.#now() > attempt.expiresAtMs) {
|
|
90959
|
+
if (attempt !== void 0) this.#attempts.delete(parsed.attemptId);
|
|
90960
|
+
this.#logger.info("vendor login proxy: attempt refused", { meta: {
|
|
90961
|
+
addonId: this.#addonId,
|
|
90962
|
+
reason: attempt === void 0 ? "missing" : "expired"
|
|
90963
|
+
} });
|
|
90964
|
+
this.#redirect(res, forwardedPrefix, {
|
|
90965
|
+
status: "error",
|
|
90966
|
+
addonId: this.#addonId,
|
|
90967
|
+
reason: "invalid_state"
|
|
90968
|
+
});
|
|
90969
|
+
return;
|
|
90970
|
+
}
|
|
90971
|
+
const route = this.#routeFor(parsed.attemptId, attempt, forwardedPrefix);
|
|
90972
|
+
const upstream = findUpstream(route, parsed.host);
|
|
90973
|
+
const target = upstream === void 0 ? null : upstreamUrlFor(upstream, parsed);
|
|
90974
|
+
if (upstream === void 0 || target === null) {
|
|
90975
|
+
this.#logger.warn("vendor login proxy: host refused", { meta: {
|
|
90976
|
+
addonId: this.#addonId,
|
|
90977
|
+
host: parsed.host
|
|
90978
|
+
} });
|
|
90979
|
+
sendPlain(res, 404, "Not found");
|
|
90980
|
+
return;
|
|
90981
|
+
}
|
|
90982
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
90983
|
+
let body;
|
|
90984
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
90985
|
+
const read = await readRequestBody(req);
|
|
90986
|
+
if (read === "too-large") {
|
|
90987
|
+
sendPlain(res, 413, "Request too large");
|
|
90988
|
+
return;
|
|
90989
|
+
}
|
|
90990
|
+
body = read;
|
|
90991
|
+
}
|
|
90992
|
+
const headers = this.#requestHeaders(req, route, upstream);
|
|
90993
|
+
const upstreamRes = await this.#fetch(target, {
|
|
90994
|
+
method,
|
|
90995
|
+
headers,
|
|
90996
|
+
redirect: "manual",
|
|
90997
|
+
...body !== void 0 && body.length > 0 ? { body: new Uint8Array(body) } : {}
|
|
90998
|
+
});
|
|
90999
|
+
const location = upstreamRes.headers.get("location");
|
|
91000
|
+
const setCookies = upstreamRes.headers.getSetCookie();
|
|
91001
|
+
const kind = bodyKindOf(upstreamRes.headers.get("content-type") ?? "");
|
|
91002
|
+
const declaredLength = Number(upstreamRes.headers.get("content-length") ?? "0");
|
|
91003
|
+
const text = kind !== null && !(declaredLength > MAX_TEXT_BODY_BYTES) && method !== "HEAD" ? await upstreamRes.text() : null;
|
|
91004
|
+
this.#logger.debug("vendor login proxy: upstream answered", { meta: {
|
|
91005
|
+
addonId: this.#addonId,
|
|
91006
|
+
host: upstream.host,
|
|
91007
|
+
status: upstreamRes.status,
|
|
91008
|
+
method
|
|
91009
|
+
} });
|
|
91010
|
+
const settled = await attempt.evaluate({
|
|
91011
|
+
status: upstreamRes.status,
|
|
91012
|
+
url: target.toString(),
|
|
91013
|
+
location,
|
|
91014
|
+
setCookies,
|
|
91015
|
+
bodyText: text
|
|
91016
|
+
});
|
|
91017
|
+
if (settled.kind === "done") {
|
|
91018
|
+
this.#attempts.delete(parsed.attemptId);
|
|
91019
|
+
this.#logger.info("vendor login proxy: sign-in finished", { meta: {
|
|
91020
|
+
addonId: this.#addonId,
|
|
91021
|
+
status: settled.landing.status
|
|
91022
|
+
} });
|
|
91023
|
+
this.#redirect(res, forwardedPrefix, settled.landing);
|
|
91024
|
+
return;
|
|
91025
|
+
}
|
|
91026
|
+
const out = {};
|
|
91027
|
+
upstreamRes.headers.forEach((value, name) => {
|
|
91028
|
+
if (!DROP_RESPONSE_HEADERS.has(name.toLowerCase())) out[name] = value;
|
|
91029
|
+
});
|
|
91030
|
+
if (setCookies.length > 0) out["set-cookie"] = setCookies.map((c) => rewriteSetCookie(c, route));
|
|
91031
|
+
if (location !== null) out["location"] = rewriteLocation(location, upstream, route);
|
|
91032
|
+
out["cache-control"] = "no-store";
|
|
91033
|
+
if (text !== null && kind !== null) {
|
|
91034
|
+
const rewritten = rewriteBody(text, kind, upstream, route);
|
|
91035
|
+
res.writeHead(upstreamRes.status, out);
|
|
91036
|
+
res.end(rewritten);
|
|
91037
|
+
return;
|
|
91038
|
+
}
|
|
91039
|
+
res.writeHead(upstreamRes.status, out);
|
|
91040
|
+
await this.#pipeBody(upstreamRes, res);
|
|
91041
|
+
}
|
|
91042
|
+
#requestHeaders(req, route, upstream) {
|
|
91043
|
+
const headers = {};
|
|
91044
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
91045
|
+
const lower = name.toLowerCase();
|
|
91046
|
+
if (DROP_REQUEST_HEADERS.has(lower) || value === void 0) continue;
|
|
91047
|
+
headers[lower] = Array.isArray(value) ? value.join(", ") : value;
|
|
91048
|
+
}
|
|
91049
|
+
const cookie = filterForwardedCookies(firstHeader(req.headers["cookie"]) ?? "");
|
|
91050
|
+
if (cookie.length > 0) headers["cookie"] = cookie;
|
|
91051
|
+
if (req.headers["origin"] !== void 0) headers["origin"] = `${upstream.scheme}://${upstream.host}`;
|
|
91052
|
+
const referer = firstHeader(req.headers["referer"]);
|
|
91053
|
+
if (referer !== void 0) {
|
|
91054
|
+
const back = rewriteReferer(referer, route);
|
|
91055
|
+
if (back !== null) headers["referer"] = back;
|
|
91056
|
+
}
|
|
91057
|
+
return headers;
|
|
91058
|
+
}
|
|
91059
|
+
async #pipeBody(upstreamRes, res) {
|
|
91060
|
+
if (upstreamRes.body === null) {
|
|
91061
|
+
res.end();
|
|
91062
|
+
return;
|
|
91063
|
+
}
|
|
91064
|
+
for await (const chunk of upstreamRes.body) {
|
|
91065
|
+
if (!res.write(chunk)) await new Promise((resolve) => {
|
|
91066
|
+
res.once("drain", resolve);
|
|
91067
|
+
res.once("close", resolve);
|
|
91068
|
+
});
|
|
91069
|
+
if (res.destroyed) return;
|
|
91070
|
+
}
|
|
91071
|
+
res.end();
|
|
91072
|
+
}
|
|
91073
|
+
#redirect(res, forwardedPrefix, outcome) {
|
|
91074
|
+
res.writeHead(302, {
|
|
91075
|
+
location: `${forwardedPrefix}${oauthLandingUrl(null, outcome)}`,
|
|
91076
|
+
"cache-control": "no-store"
|
|
91077
|
+
});
|
|
91078
|
+
res.end();
|
|
91079
|
+
}
|
|
91080
|
+
};
|
|
90639
91081
|
var MB = 1024 * 1024;
|
|
90640
91082
|
1024 * MB, 3072 * MB;
|
|
90641
91083
|
//#endregion
|
|
@@ -90687,18 +91129,12 @@ var AlexaBrokerEntrySchema = object({
|
|
|
90687
91129
|
});
|
|
90688
91130
|
/**
|
|
90689
91131
|
* Interactive proxy-login settings (addon-global, not per-broker). alexa-remote2
|
|
90690
|
-
* spins a local reverse-proxy to Amazon's login page on `
|
|
90691
|
-
*
|
|
90692
|
-
*
|
|
90693
|
-
*
|
|
90694
|
-
* the hub's LAN IP/hostname — NOT behind the hub's HTTPS data-plane mount.
|
|
90695
|
-
* Empty `proxyHost` ⇒ the first LAN address of the hub's `appConnections`
|
|
90696
|
-
* role (local-network palette) at login-start time.
|
|
91132
|
+
* spins a local reverse-proxy to Amazon's login page on `127.0.0.1:proxyPort`; the
|
|
91133
|
+
* operator never reaches it directly — the hub's login proxy (D955) opens it at
|
|
91134
|
+
* `/addon/import-alexa/login/…` under the hub's own HTTPS origin. The port only has to
|
|
91135
|
+
* be free on the hub's loopback.
|
|
90697
91136
|
*/
|
|
90698
|
-
var alexaProxyLoginSchema = object({
|
|
90699
|
-
proxyHost: string().default("").describe("Host/IP the browser uses to reach the login proxy (blank = the hub LAN address)"),
|
|
90700
|
-
proxyPort: preprocess((v) => v === "" || v === null ? void 0 : v, number().int().min(1).max(65535).default(3456)).describe("Port the local Amazon-login proxy listens on (must be reachable)")
|
|
90701
|
-
});
|
|
91137
|
+
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)") });
|
|
90702
91138
|
/**
|
|
90703
91139
|
* Echo speech settings. Speech signs in as an import broker, so the targets
|
|
90704
91140
|
* are what lives here. `email`, `password`, `region` and `session` are the OLD
|
|
@@ -90745,10 +91181,7 @@ function readSpeakSettings(config) {
|
|
|
90745
91181
|
/** Top-level addon config — broker entries + the proxy-login settings. */
|
|
90746
91182
|
var alexaAddonConfigSchema = object({
|
|
90747
91183
|
brokers: array(AlexaBrokerEntrySchema).default([]),
|
|
90748
|
-
proxyLogin: alexaProxyLoginSchema.default({
|
|
90749
|
-
proxyHost: "",
|
|
90750
|
-
proxyPort: 3456
|
|
90751
|
-
}),
|
|
91184
|
+
proxyLogin: alexaProxyLoginSchema.default({ proxyPort: 3456 }),
|
|
90752
91185
|
speak: alexaSpeakSettingsSchema.default({
|
|
90753
91186
|
email: "",
|
|
90754
91187
|
password: "",
|
|
@@ -128425,7 +128858,7 @@ var require_fix_request_body = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
128425
128858
|
* Fix proxied body if bodyParser is involved.
|
|
128426
128859
|
*/
|
|
128427
128860
|
function fixRequestBody(proxyReq, req) {
|
|
128428
|
-
if (req.
|
|
128861
|
+
if (!req.readableEnded) return;
|
|
128429
128862
|
const requestBody = req.body;
|
|
128430
128863
|
if (!requestBody) return;
|
|
128431
128864
|
const contentType = proxyReq.getHeader("Content-Type");
|
|
@@ -128520,6 +128953,12 @@ var require_proxy = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
128520
128953
|
var path$2 = __require("path");
|
|
128521
128954
|
var crypto$1 = __require("crypto");
|
|
128522
128955
|
var FORMERDATA_STORE_VERSION = 4;
|
|
128956
|
+
var ALEXA_APP_VERSION = "2.2.651540.0";
|
|
128957
|
+
var EXTRA_AMAZON_PROXY_HOSTS = [
|
|
128958
|
+
"amazon.com",
|
|
128959
|
+
"www.amazon.com",
|
|
128960
|
+
"alexa.amazon.com"
|
|
128961
|
+
];
|
|
128523
128962
|
function addCookies(Cookie, headers) {
|
|
128524
128963
|
if (!headers || !headers["set-cookie"]) return Cookie;
|
|
128525
128964
|
const cookies = cookieTools.parse(Cookie);
|
|
@@ -128535,6 +128974,15 @@ var require_proxy = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
128535
128974
|
Cookie = Cookie.replace(/[; ]*$/, "");
|
|
128536
128975
|
return Cookie;
|
|
128537
128976
|
}
|
|
128977
|
+
function isAmazonCookieName(name) {
|
|
128978
|
+
return /^(session-id|session-id-time|session-token|ubid-.+|lc-.+|x-.+|at-.+|sess-at-.+|frc|map-md|csrf|sid|csm-hit|i18n-prefs|sp-cdn|skin)$/.test(name);
|
|
128979
|
+
}
|
|
128980
|
+
function sanitizeAmazonCookie(cookie) {
|
|
128981
|
+
const cookies = cookieTools.parse(cookie || "");
|
|
128982
|
+
let sanitizedCookie = "";
|
|
128983
|
+
for (const name of Object.keys(cookies)) if (isAmazonCookieName(name)) sanitizedCookie += `${name}=${cookies[name]}; `;
|
|
128984
|
+
return sanitizedCookie.replace(/[; ]*$/, "");
|
|
128985
|
+
}
|
|
128538
128986
|
function customStringify(v, func, intent) {
|
|
128539
128987
|
const cache = /* @__PURE__ */ new Map();
|
|
128540
128988
|
return JSON.stringify(v, function(key, value) {
|
|
@@ -128548,6 +128996,29 @@ var require_proxy = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
128548
128996
|
}
|
|
128549
128997
|
function initAmazonProxy(_options, callbackCookie, callbackListening) {
|
|
128550
128998
|
const initialCookies = {};
|
|
128999
|
+
const proxyBase = () => `http://${_options.proxyOwnIp}:${_options.proxyPort}/`;
|
|
129000
|
+
const defaultAmazonHost = `www.${_options.baseAmazonPage}`;
|
|
129001
|
+
const amazonProxyHosts = [];
|
|
129002
|
+
function escapeRegex(data) {
|
|
129003
|
+
return data.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
129004
|
+
}
|
|
129005
|
+
function addAmazonProxyHost(host) {
|
|
129006
|
+
if (host && !amazonProxyHosts.includes(host)) amazonProxyHosts.push(host);
|
|
129007
|
+
}
|
|
129008
|
+
addAmazonProxyHost(defaultAmazonHost);
|
|
129009
|
+
addAmazonProxyHost(`alexa.${_options.baseAmazonPage}`);
|
|
129010
|
+
addAmazonProxyHost(_options.baseAmazonPage);
|
|
129011
|
+
EXTRA_AMAZON_PROXY_HOSTS.forEach(addAmazonProxyHost);
|
|
129012
|
+
function amazonHostFromProxyPath(url) {
|
|
129013
|
+
const match = (url || "").match(/^\/([^/?#]+)(?:[/?#]|$)/);
|
|
129014
|
+
if (match && amazonProxyHosts.includes(match[1])) return match[1];
|
|
129015
|
+
return null;
|
|
129016
|
+
}
|
|
129017
|
+
function amazonHostFromProxyUrl(url) {
|
|
129018
|
+
if (!url) return null;
|
|
129019
|
+
for (const host of amazonProxyHosts) if (url === `${proxyBase()}${host}` || url.startsWith(`${proxyBase()}${host}/`)) return host;
|
|
129020
|
+
return null;
|
|
129021
|
+
}
|
|
128551
129022
|
const formerDataStorePath = _options.formerDataStorePath || path$2.join(__dirname, "formerDataStore.json");
|
|
128552
129023
|
let formerDataStoreValid = false;
|
|
128553
129024
|
if (!_options.formerRegistrationData) try {
|
|
@@ -128571,7 +129042,7 @@ var require_proxy = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
128571
129042
|
_options.logger && _options.logger("Proxy Init: reuse frc from former data");
|
|
128572
129043
|
initialCookies.frc = _options.formerRegistrationData.frc;
|
|
128573
129044
|
}
|
|
128574
|
-
if (!_options.formerRegistrationData || !_options.formerRegistrationData["map-md"]) initialCookies["map-md"] = Buffer.from(
|
|
129045
|
+
if (!_options.formerRegistrationData || !_options.formerRegistrationData["map-md"]) initialCookies["map-md"] = Buffer.from(`{"device_user_dictionary":[],"device_registration_data":{"software_version":"1"},"app_identifier":{"app_version":"${ALEXA_APP_VERSION}","bundle_id":"com.amazon.echo"}}`).toString("base64");
|
|
128575
129046
|
else {
|
|
128576
129047
|
_options.logger && _options.logger("Proxy Init: reuse map-md from former data");
|
|
128577
129048
|
initialCookies["map-md"] = _options.formerRegistrationData["map-md"];
|
|
@@ -128604,11 +129075,49 @@ var require_proxy = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
128604
129075
|
const code_verifier = base64URLEncode(crypto$1.randomBytes(32));
|
|
128605
129076
|
const code_challenge = base64URLEncode(sha256(code_verifier));
|
|
128606
129077
|
let proxyCookies = "";
|
|
129078
|
+
let returnedInitUrl;
|
|
129079
|
+
function buildInitialUrl() {
|
|
129080
|
+
return `https://www.${_options.baseAmazonPage}/ap/signin?openid.return_to=https%3A%2F%2Fwww.${_options.baseAmazonPage}%2Fap%2Fmaplanding&openid.assoc_handle=amzn_dp_project_dee_ios${_options.baseAmazonPageHandle}&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&pageId=amzn_dp_project_dee_ios${_options.baseAmazonPageHandle}&accountStatusPolicy=P1&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns.oa2=http%3A%2F%2Fwww.${_options.baseAmazonPage}%2Fap%2Fext%2Foauth%2F2&openid.oa2.client_id=device%3A${deviceId}&openid.ns.pape=http%3A%2F%2Fspecs.openid.net%2Fextensions%2Fpape%2F1.0&openid.oa2.response_type=code&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.pape.max_auth_age=0&openid.oa2.scope=device_auth_access&openid.oa2.code_challenge_method=S256&openid.oa2.code_challenge=${code_challenge}&language=${_options.amazonPageProxyLanguage}`;
|
|
129081
|
+
}
|
|
129082
|
+
function fixEmbeddedProxyUrls(pathAndQuery) {
|
|
129083
|
+
const qIndex = pathAndQuery.indexOf("?");
|
|
129084
|
+
if (qIndex === -1) return pathAndQuery;
|
|
129085
|
+
const p = pathAndQuery.slice(0, qIndex);
|
|
129086
|
+
const qs = pathAndQuery.slice(qIndex + 1);
|
|
129087
|
+
let params;
|
|
129088
|
+
try {
|
|
129089
|
+
params = new URLSearchParams(qs);
|
|
129090
|
+
} catch (_e) {
|
|
129091
|
+
return pathAndQuery;
|
|
129092
|
+
}
|
|
129093
|
+
let changed = false;
|
|
129094
|
+
for (const key of params.keys()) {
|
|
129095
|
+
const value = params.get(key);
|
|
129096
|
+
const fixed = replaceHostsBack(value);
|
|
129097
|
+
if (fixed !== value) {
|
|
129098
|
+
params.set(key, fixed);
|
|
129099
|
+
changed = true;
|
|
129100
|
+
}
|
|
129101
|
+
}
|
|
129102
|
+
if (changed) _options.logger && _options.logger(`Alexa-Cookie: Fixed embedded proxy URL(s) in outgoing request query`);
|
|
129103
|
+
return changed ? `${p}?${params.toString()}` : pathAndQuery;
|
|
129104
|
+
}
|
|
129105
|
+
function rewriteProxyPath(url, req) {
|
|
129106
|
+
let result;
|
|
129107
|
+
const urlHost = amazonHostFromProxyPath(url);
|
|
129108
|
+
if (urlHost) result = url.replace(new RegExp(`^/${escapeRegex(urlHost)}`), "") || "/";
|
|
129109
|
+
else if (req && req.headers && req.headers.host === `${_options.proxyOwnIp}:${_options.proxyPort}` && url === "/") {
|
|
129110
|
+
const initialUrl = returnedInitUrl || buildInitialUrl();
|
|
129111
|
+
const parsed = new URL(initialUrl);
|
|
129112
|
+
result = `${parsed.pathname}${parsed.search}`;
|
|
129113
|
+
} else result = url;
|
|
129114
|
+
return fixEmbeddedProxyUrls(result);
|
|
129115
|
+
}
|
|
128607
129116
|
const optionsAlexa = {
|
|
128608
129117
|
target: `https://alexa.${_options.baseAmazonPage}`,
|
|
128609
129118
|
changeOrigin: true,
|
|
128610
129119
|
ws: false,
|
|
128611
|
-
pathRewrite:
|
|
129120
|
+
pathRewrite: rewriteProxyPath,
|
|
128612
129121
|
router,
|
|
128613
129122
|
hostRewrite: true,
|
|
128614
129123
|
followRedirects: false,
|
|
@@ -128616,17 +129125,10 @@ var require_proxy = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
128616
129125
|
onError,
|
|
128617
129126
|
onProxyRes,
|
|
128618
129127
|
onProxyReq,
|
|
128619
|
-
headers: {
|
|
128620
|
-
|
|
128621
|
-
|
|
128622
|
-
"authority": `www.${_options.baseAmazonPage}`
|
|
128623
|
-
},
|
|
128624
|
-
cookieDomainRewrite: { "*": "" }
|
|
129128
|
+
headers: { "accept-language": _options.acceptLanguage },
|
|
129129
|
+
cookieDomainRewrite: { "*": "" },
|
|
129130
|
+
cookiePathRewrite: { "*": "/" }
|
|
128625
129131
|
};
|
|
128626
|
-
optionsAlexa.pathRewrite[`^/www.${_options.baseAmazonPage}`] = "";
|
|
128627
|
-
optionsAlexa.pathRewrite[`^/alexa.${_options.baseAmazonPage}`] = "";
|
|
128628
|
-
optionsAlexa.cookieDomainRewrite[`.${_options.baseAmazonPage}`] = _options.proxyOwnIp;
|
|
128629
|
-
optionsAlexa.cookieDomainRewrite[_options.baseAmazonPage] = _options.proxyOwnIp;
|
|
128630
129132
|
if (_options.logger) optionsAlexa.logProvider = function logProvider() {
|
|
128631
129133
|
return {
|
|
128632
129134
|
log: _options.logger.log || _options.logger,
|
|
@@ -128636,22 +129138,20 @@ var require_proxy = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
128636
129138
|
error: _options.logger.error || _options.logger
|
|
128637
129139
|
};
|
|
128638
129140
|
};
|
|
128639
|
-
let returnedInitUrl;
|
|
128640
129141
|
function router(req) {
|
|
128641
129142
|
const url = req.originalUrl || req.url;
|
|
128642
129143
|
_options.logger && _options.logger(`Router: ${url} / ${req.method} / ${JSON.stringify(req.headers)}`);
|
|
128643
129144
|
if (req.headers.host === `${_options.proxyOwnIp}:${_options.proxyPort}`) {
|
|
128644
|
-
|
|
128645
|
-
|
|
128646
|
-
|
|
128647
|
-
|
|
128648
|
-
else if (req.headers.referer.startsWith(`http://${_options.proxyOwnIp}:${_options.proxyPort}/alexa.${_options.baseAmazonPage}/`)) return `https://alexa.${_options.baseAmazonPage}`;
|
|
128649
|
-
}
|
|
129145
|
+
const urlHost = amazonHostFromProxyPath(url);
|
|
129146
|
+
if (urlHost) return `https://${urlHost}`;
|
|
129147
|
+
const refererHost = amazonHostFromProxyUrl(req.headers.referer);
|
|
129148
|
+
if (refererHost) return `https://${refererHost}`;
|
|
128650
129149
|
if (url === "/") {
|
|
128651
|
-
returnedInitUrl =
|
|
129150
|
+
returnedInitUrl = buildInitialUrl();
|
|
128652
129151
|
_options.logger && _options.logger(`Alexa-Cookie: Initial Page Request: ${returnedInitUrl}`);
|
|
128653
|
-
|
|
128654
|
-
|
|
129152
|
+
const parsedInitUrl = new URL(returnedInitUrl);
|
|
129153
|
+
return `${parsedInitUrl.protocol}//${parsedInitUrl.host}`;
|
|
129154
|
+
} else return `https://${defaultAmazonHost}`;
|
|
128655
129155
|
}
|
|
128656
129156
|
return `https://alexa.${_options.baseAmazonPage}`;
|
|
128657
129157
|
}
|
|
@@ -128663,21 +129163,34 @@ var require_proxy = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
128663
129163
|
} catch (err) {}
|
|
128664
129164
|
}
|
|
128665
129165
|
function replaceHosts(data) {
|
|
128666
|
-
const amazonRegex = new RegExp(`https?://www.${_options.baseAmazonPage}:?[0-9]*/`.replace(/\./g, "\\."), "g");
|
|
128667
|
-
const alexaRegex = new RegExp(`https?://alexa.${_options.baseAmazonPage}:?[0-9]*/`.replace(/\./g, "\\."), "g");
|
|
128668
129166
|
data = data.replace(///g, "/");
|
|
128669
|
-
|
|
128670
|
-
|
|
129167
|
+
for (const host of amazonProxyHosts) {
|
|
129168
|
+
const hostRegex = new RegExp(`https?://${escapeRegex(host)}:?[0-9]*/`, "g");
|
|
129169
|
+
data = data.replace(hostRegex, `${proxyBase()}${host}/`);
|
|
129170
|
+
}
|
|
128671
129171
|
return data;
|
|
128672
129172
|
}
|
|
128673
129173
|
function replaceHostsBack(data) {
|
|
128674
|
-
const
|
|
128675
|
-
const
|
|
128676
|
-
|
|
128677
|
-
|
|
128678
|
-
|
|
129174
|
+
const base = proxyBase();
|
|
129175
|
+
for (const host of amazonProxyHosts) {
|
|
129176
|
+
const hostRegex = new RegExp(`${escapeRegex(base)}${escapeRegex(host)}/`, "g");
|
|
129177
|
+
data = data.replace(hostRegex, `https://${host}/`);
|
|
129178
|
+
}
|
|
129179
|
+
if (data === base) data = returnedInitUrl;
|
|
129180
|
+
else if (data.startsWith(base)) data = `https://${defaultAmazonHost}/${data.slice(base.length)}`;
|
|
128679
129181
|
return data;
|
|
128680
129182
|
}
|
|
129183
|
+
function parseQueryParams(data) {
|
|
129184
|
+
const paramStart = data && data.indexOf("?");
|
|
129185
|
+
if (paramStart === -1 || paramStart === void 0) return {};
|
|
129186
|
+
return querystring$2.parse(data.substr(paramStart + 1));
|
|
129187
|
+
}
|
|
129188
|
+
function isProxySuccessUrl(data) {
|
|
129189
|
+
if (!data) return false;
|
|
129190
|
+
if (data.includes("/spa/index.html")) return true;
|
|
129191
|
+
if (!data.includes("/ap/maplanding?")) return false;
|
|
129192
|
+
return !!parseQueryParams(data)["openid.oa2.authorization_code"];
|
|
129193
|
+
}
|
|
128681
129194
|
function onProxyReq(proxyReq, req) {
|
|
128682
129195
|
const url = req.originalUrl || req.url;
|
|
128683
129196
|
if (url.endsWith(".ico") || url.endsWith(".js") || url.endsWith(".ttf") || url.endsWith(".svg") || url.endsWith(".png") || url.endsWith(".appcache")) return;
|
|
@@ -128704,14 +129217,26 @@ var require_proxy = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
128704
129217
|
}
|
|
128705
129218
|
}
|
|
128706
129219
|
if (typeof proxyReq.getHeader === "function" && proxyReq.getHeader("origin") !== `https://${proxyReq.getHeader("host")}`) {
|
|
128707
|
-
proxyReq.setHeader("origin", `https
|
|
128708
|
-
_options.logger && _options.logger("Alexa-Cookie: Modify headers:
|
|
129220
|
+
proxyReq.setHeader("origin", `https://${proxyReq.getHeader("host") || defaultAmazonHost}`);
|
|
129221
|
+
_options.logger && _options.logger("Alexa-Cookie: Modify headers: Changed Origin");
|
|
128709
129222
|
modified = true;
|
|
128710
129223
|
}
|
|
128711
|
-
|
|
128712
|
-
|
|
128713
|
-
|
|
128714
|
-
|
|
129224
|
+
if (req.body && typeof req.body === "object") {
|
|
129225
|
+
let bodyChanged = false;
|
|
129226
|
+
for (const key of Object.keys(req.body)) {
|
|
129227
|
+
const value = req.body[key];
|
|
129228
|
+
if (typeof value !== "string") continue;
|
|
129229
|
+
const fixed = replaceHostsBack(value);
|
|
129230
|
+
if (fixed !== value) {
|
|
129231
|
+
req.body[key] = fixed;
|
|
129232
|
+
bodyChanged = true;
|
|
129233
|
+
}
|
|
129234
|
+
}
|
|
129235
|
+
if (bodyChanged) _options.logger && _options.logger("Alexa-Cookie: Fixed embedded proxy URL(s) in outgoing POST body");
|
|
129236
|
+
const bodyData = querystring$2.stringify(req.body);
|
|
129237
|
+
proxyReq.setHeader("Content-Length", Buffer.byteLength(bodyData));
|
|
129238
|
+
proxyReq.write(bodyData);
|
|
129239
|
+
}
|
|
128715
129240
|
}
|
|
128716
129241
|
_options.proxyLogLevel === "debug" && _options.logger && _options.logger(`Alexa-Cookie: Proxy-Request: (modified:${modified})${customStringify(proxyReq, null, 2)}`);
|
|
128717
129242
|
}
|
|
@@ -128729,17 +129254,22 @@ var require_proxy = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
128729
129254
|
proxyCookies = addCookies(proxyCookies, proxyRes.headers);
|
|
128730
129255
|
}
|
|
128731
129256
|
_options.logger && _options.logger(`Alexa-Cookie: Cookies handled: ${JSON.stringify(proxyCookies)}`);
|
|
128732
|
-
|
|
129257
|
+
const outgoing = proxyRes.socket && proxyRes.socket.parser && proxyRes.socket.parser.outgoing;
|
|
129258
|
+
const successUrl = [
|
|
129259
|
+
proxyRes.headers.location,
|
|
129260
|
+
outgoing && outgoing.method === "GET" && outgoing.path,
|
|
129261
|
+
outgoing && outgoing.getHeader && outgoing.getHeader("location")
|
|
129262
|
+
].find(isProxySuccessUrl);
|
|
129263
|
+
if (successUrl) {
|
|
128733
129264
|
_options.logger && _options.logger("Alexa-Cookie: Proxy detected SUCCESS!!");
|
|
128734
|
-
const
|
|
128735
|
-
const queryParams = querystring$2.parse(proxyRes.headers.location.substr(paramStart + 1));
|
|
129265
|
+
const queryParams = parseQueryParams(successUrl);
|
|
128736
129266
|
proxyRes.statusCode = 302;
|
|
128737
129267
|
proxyRes.headers.location = `http://${_options.proxyOwnIp}:${_options.proxyPort}/cookie-success`;
|
|
128738
129268
|
delete proxyRes.headers.referer;
|
|
128739
129269
|
_options.logger && _options.logger(`Alexa-Cookie: Proxy catched cookie: ${proxyCookies}`);
|
|
128740
129270
|
_options.logger && _options.logger(`Alexa-Cookie: Proxy catched parameters: ${JSON.stringify(queryParams)}`);
|
|
128741
129271
|
callbackCookie && callbackCookie(null, {
|
|
128742
|
-
"loginCookie": proxyCookies,
|
|
129272
|
+
"loginCookie": sanitizeAmazonCookie(proxyCookies),
|
|
128743
129273
|
"authorization_code": queryParams["openid.oa2.authorization_code"],
|
|
128744
129274
|
"frc": initialCookies.frc,
|
|
128745
129275
|
"map-md": initialCookies["map-md"],
|
|
@@ -128751,7 +129281,7 @@ var require_proxy = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
128751
129281
|
if (proxyRes.headers.location) {
|
|
128752
129282
|
_options.logger && _options.logger(`Redirect: Original Location ----> ${proxyRes.headers.location}`);
|
|
128753
129283
|
proxyRes.headers.location = replaceHosts(proxyRes.headers.location);
|
|
128754
|
-
if (reqestHost && proxyRes.headers.location.startsWith("/")) proxyRes.headers.location = `http://${_options.proxyOwnIp}:${_options.proxyPort}/${reqestHost}${proxyRes.headers.location}`;
|
|
129284
|
+
if (reqestHost && amazonProxyHosts.includes(reqestHost) && proxyRes.headers.location.startsWith("/")) proxyRes.headers.location = `http://${_options.proxyOwnIp}:${_options.proxyPort}/${reqestHost}${proxyRes.headers.location}`;
|
|
128755
129285
|
_options.logger && _options.logger(`Redirect: Final Location ----> ${proxyRes.headers.location}`);
|
|
128756
129286
|
return;
|
|
128757
129287
|
}
|
|
@@ -128769,8 +129299,21 @@ var require_proxy = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
128769
129299
|
return body;
|
|
128770
129300
|
});
|
|
128771
129301
|
}
|
|
129302
|
+
function bufferUrlencodedBody(req, res, next) {
|
|
129303
|
+
const contentType = req.headers["content-type"] || "";
|
|
129304
|
+
if (req.method !== "POST" || contentType.indexOf("application/x-www-form-urlencoded") === -1) return next();
|
|
129305
|
+
let raw = "";
|
|
129306
|
+
req.on("data", (chunk) => {
|
|
129307
|
+
raw += chunk;
|
|
129308
|
+
});
|
|
129309
|
+
req.on("end", () => {
|
|
129310
|
+
req.body = querystring$2.parse(raw);
|
|
129311
|
+
next();
|
|
129312
|
+
});
|
|
129313
|
+
}
|
|
128772
129314
|
const myProxy = proxy("!/cookie-success", optionsAlexa);
|
|
128773
129315
|
const app = express();
|
|
129316
|
+
app.use(bufferUrlencodedBody);
|
|
128774
129317
|
app.use(myProxy);
|
|
128775
129318
|
app.get("/cookie-success", function(req, res) {
|
|
128776
129319
|
res.send(_options.proxyCloseWindowHTML);
|
|
@@ -128780,7 +129323,8 @@ var require_proxy = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
128780
129323
|
_options.proxyPort = void 0;
|
|
128781
129324
|
}
|
|
128782
129325
|
const server = app.listen(_options.proxyPort, _options.proxyListenBind, function() {
|
|
128783
|
-
_options.
|
|
129326
|
+
_options.proxyPort = this.address().port;
|
|
129327
|
+
_options.logger && _options.logger(`Alexa-Cookie: Proxy-Server listening on port ${_options.proxyPort}`);
|
|
128784
129328
|
callbackListening && callbackListening(server);
|
|
128785
129329
|
callbackListening = null;
|
|
128786
129330
|
}).on("error", (err) => {
|
|
@@ -128826,6 +129370,13 @@ var require_alexa_cookie = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
128826
129370
|
let proxyServer;
|
|
128827
129371
|
let _options;
|
|
128828
129372
|
let Cookie = "";
|
|
129373
|
+
const isAmazonCookieName = (name) => /^(session-id|session-id-time|session-token|ubid-.+|lc-.+|x-.+|at-.+|sess-at-.+|frc|map-md|csrf|sid|csm-hit|i18n-prefs|sp-cdn|skin)$/.test(name);
|
|
129374
|
+
const sanitizeAmazonCookie = (cookie) => {
|
|
129375
|
+
const cookies = cookieTools.parse(cookie || "");
|
|
129376
|
+
let sanitizedCookie = "";
|
|
129377
|
+
for (const name of Object.keys(cookies)) if (isAmazonCookieName(name)) sanitizedCookie += `${name}=${cookies[name]}; `;
|
|
129378
|
+
return sanitizedCookie.replace(/[; ]*$/, "");
|
|
129379
|
+
};
|
|
128829
129380
|
const addCookies = (Cookie, headers) => {
|
|
128830
129381
|
if (!headers || !headers["set-cookie"]) return Cookie;
|
|
128831
129382
|
const cookies = cookieTools.parse(Cookie || "");
|
|
@@ -128913,7 +129464,7 @@ var require_alexa_cookie = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
128913
129464
|
if (!_options.baseAmazonPageHandle && _options.baseAmazonPageHandle !== "") {
|
|
128914
129465
|
const amazonDomain = _options.baseAmazonPage.substr(_options.baseAmazonPage.lastIndexOf(".") + 1);
|
|
128915
129466
|
if (amazonDomain === "jp") _options.baseAmazonPageHandle = `_${amazonDomain}`;
|
|
128916
|
-
else if (amazonDomain !== "com") _options.baseAmazonPageHandle =
|
|
129467
|
+
else if (amazonDomain !== "com") _options.baseAmazonPageHandle = `_${amazonDomain}`;
|
|
128917
129468
|
else _options.baseAmazonPageHandle = "";
|
|
128918
129469
|
}
|
|
128919
129470
|
if (!_options.userAgent) if (os$2.platform() === "win32") _options.userAgent = defaultUserAgent;
|
|
@@ -128940,7 +129491,7 @@ var require_alexa_cookie = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
128940
129491
|
if (_options.formerRegistrationData) _options.proxyOnly = true;
|
|
128941
129492
|
};
|
|
128942
129493
|
const getCSRFFromCookies = (cookie, _options, callback) => {
|
|
128943
|
-
const csrfUrls = csrfOptions;
|
|
129494
|
+
const csrfUrls = [...csrfOptions];
|
|
128944
129495
|
function csrfTry() {
|
|
128945
129496
|
const path = csrfUrls.shift();
|
|
128946
129497
|
const options = {
|
|
@@ -129103,6 +129654,7 @@ var require_alexa_cookie = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
129103
129654
|
const handleTokenRegistration = (_options, loginData, callback) => {
|
|
129104
129655
|
_options.logger && _options.logger(`Handle token registration Start: ${JSON.stringify(loginData)}`);
|
|
129105
129656
|
loginData.deviceAppName = _options.deviceAppName;
|
|
129657
|
+
loginData.loginCookie = sanitizeAmazonCookie(loginData.loginCookie);
|
|
129106
129658
|
let deviceSerial;
|
|
129107
129659
|
if (!_options.formerRegistrationData || !_options.formerRegistrationData.deviceSerial) {
|
|
129108
129660
|
const deviceSerialBuffer = Buffer.alloc(16);
|
|
@@ -129190,6 +129742,7 @@ var require_alexa_cookie = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
129190
129742
|
Cookie = addCookies(Cookie, response.headers);
|
|
129191
129743
|
loginData.refreshToken = body.response.success.tokens.bearer.refresh_token;
|
|
129192
129744
|
const accessToken = body.response.success.tokens.bearer.access_token;
|
|
129745
|
+
loginData.accessToken = accessToken;
|
|
129193
129746
|
loginData.tokenDate = Date.now();
|
|
129194
129747
|
loginData.macDms = body.response.success.tokens.mac_dms;
|
|
129195
129748
|
if (body.response.success.tokens.website_cookies && Array.isArray(body.response.success.tokens.website_cookies)) {
|
|
@@ -129216,29 +129769,43 @@ var require_alexa_cookie = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
129216
129769
|
_options.logger && _options.logger("Alexa-Cookie: Get User data");
|
|
129217
129770
|
_options.logger && _options.logger(JSON.stringify(options));
|
|
129218
129771
|
request(options, (error, response, body) => {
|
|
129219
|
-
if (!error) {
|
|
129772
|
+
if (!error) if (!response || response.statusCode < 200 || response.statusCode >= 300 || !body) {
|
|
129773
|
+
_options.logger && _options.logger(`Get User data Response: status=${response && response.statusCode}, body=${JSON.stringify(body)}`);
|
|
129774
|
+
if (!_options.amazonPage) {
|
|
129775
|
+
callback && callback(/* @__PURE__ */ new Error(`Could not get user data from Amazon: HTTP ${response && response.statusCode}`), null);
|
|
129776
|
+
return;
|
|
129777
|
+
}
|
|
129778
|
+
loginData.amazonPage = _options.amazonPage;
|
|
129779
|
+
} else {
|
|
129220
129780
|
try {
|
|
129221
129781
|
if (typeof body !== "object") body = JSON.parse(body);
|
|
129222
129782
|
} catch (err) {
|
|
129223
129783
|
_options.logger && _options.logger(`Get User data Response: ${JSON.stringify(body)}`);
|
|
129224
|
-
|
|
129225
|
-
|
|
129784
|
+
if (!_options.amazonPage) {
|
|
129785
|
+
callback && callback(err, null);
|
|
129786
|
+
return;
|
|
129787
|
+
}
|
|
129788
|
+
loginData.amazonPage = _options.amazonPage;
|
|
129226
129789
|
}
|
|
129227
129790
|
_options.logger && _options.logger(`Get User data Response: ${JSON.stringify(body)}`);
|
|
129228
129791
|
Cookie = addCookies(Cookie, response.headers);
|
|
129229
|
-
if (body.marketPlaceDomainName) {
|
|
129792
|
+
if (body && body.marketPlaceDomainName) {
|
|
129230
129793
|
const pos = body.marketPlaceDomainName.indexOf(".");
|
|
129231
129794
|
if (pos !== -1) _options.amazonPage = body.marketPlaceDomainName.substr(pos + 1);
|
|
129232
129795
|
}
|
|
129233
129796
|
loginData.amazonPage = _options.amazonPage;
|
|
129234
|
-
}
|
|
129797
|
+
}
|
|
129798
|
+
else if (error && (!_options || !_options.amazonPage)) {
|
|
129235
129799
|
callback && callback(error, null);
|
|
129236
129800
|
return;
|
|
129237
129801
|
} else if (error && (!_options.formerRegistrationData || !_options.formerRegistrationData.amazonPage) && _options.amazonPage) _options.logger && _options.logger(`Continue with externally set amazonPage: ${_options.amazonPage}`);
|
|
129238
129802
|
else if (error) _options.logger && _options.logger("Ignore error while getting user data and amazonPage because previously set amazonPage is available");
|
|
129239
129803
|
loginData.loginCookie = Cookie;
|
|
129240
129804
|
getLocalCookies(loginData.amazonPage, loginData.refreshToken, (err, localCookie) => {
|
|
129241
|
-
if (err)
|
|
129805
|
+
if (err) {
|
|
129806
|
+
callback && callback(err, null);
|
|
129807
|
+
return;
|
|
129808
|
+
}
|
|
129242
129809
|
loginData.localCookie = localCookie;
|
|
129243
129810
|
getCSRFFromCookies(loginData.localCookie, _options, (err, resData) => {
|
|
129244
129811
|
if (err) {
|
|
@@ -129247,7 +129814,6 @@ var require_alexa_cookie = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
129247
129814
|
}
|
|
129248
129815
|
loginData.localCookie = resData.cookie;
|
|
129249
129816
|
loginData.csrf = resData.csrf;
|
|
129250
|
-
delete loginData.accessToken;
|
|
129251
129817
|
delete loginData.authorization_code;
|
|
129252
129818
|
delete loginData.verifier;
|
|
129253
129819
|
loginData.dataVersion = 2;
|
|
@@ -129350,6 +129916,39 @@ var require_alexa_cookie = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
129350
129916
|
callback && callback(null, localCookie);
|
|
129351
129917
|
});
|
|
129352
129918
|
};
|
|
129919
|
+
const finishCookieRefresh = (loginData, callback) => {
|
|
129920
|
+
_options.logger && _options.logger("Alexa-Cookie: Skip App registration during refresh and update local cookies");
|
|
129921
|
+
const updateLocalCookies = () => {
|
|
129922
|
+
const amazonPage = loginData.amazonPage || _options.amazonPage;
|
|
129923
|
+
getLocalCookies(amazonPage, loginData.refreshToken, (err, localCookie) => {
|
|
129924
|
+
if (err) {
|
|
129925
|
+
callback && callback(err, null);
|
|
129926
|
+
return;
|
|
129927
|
+
}
|
|
129928
|
+
loginData.localCookie = localCookie;
|
|
129929
|
+
getCSRFFromCookies(loginData.localCookie, _options, (err, resData) => {
|
|
129930
|
+
if (err) {
|
|
129931
|
+
callback && callback(/* @__PURE__ */ new Error(`Error getting csrf for ${amazonPage}`), null);
|
|
129932
|
+
return;
|
|
129933
|
+
}
|
|
129934
|
+
loginData.localCookie = resData.cookie;
|
|
129935
|
+
loginData.csrf = resData.csrf;
|
|
129936
|
+
loginData.amazonPage = amazonPage;
|
|
129937
|
+
loginData.tokenDate = Date.now();
|
|
129938
|
+
delete loginData.authorization_code;
|
|
129939
|
+
delete loginData.verifier;
|
|
129940
|
+
loginData.dataVersion = 2;
|
|
129941
|
+
_options.logger && _options.logger("Alexa-Cookie: Refresh finished with updated cookies and csrf");
|
|
129942
|
+
callback && callback(null, loginData);
|
|
129943
|
+
});
|
|
129944
|
+
});
|
|
129945
|
+
};
|
|
129946
|
+
if (loginData.accessToken) {
|
|
129947
|
+
registerTokenCapabilities(loginData.accessToken, updateLocalCookies);
|
|
129948
|
+
return;
|
|
129949
|
+
}
|
|
129950
|
+
updateLocalCookies();
|
|
129951
|
+
};
|
|
129353
129952
|
this.refreshAlexaCookie = (__options, callback) => {
|
|
129354
129953
|
if (!__options || !__options.formerRegistrationData || !__options.formerRegistrationData.loginCookie || !__options.formerRegistrationData.refreshToken) {
|
|
129355
129954
|
callback && callback(/* @__PURE__ */ new Error("No former registration data provided for Cookie Refresh"), null);
|
|
@@ -129416,13 +130015,16 @@ var require_alexa_cookie = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
129416
130015
|
_options.formerRegistrationData.loginCookie = addCookies(Cookie, response.headers);
|
|
129417
130016
|
_options.formerRegistrationData.accessToken = body.access_token;
|
|
129418
130017
|
getLocalCookies(_options.baseAmazonPage, _options.formerRegistrationData.refreshToken, (err, comCookie) => {
|
|
129419
|
-
if (err)
|
|
130018
|
+
if (err) {
|
|
130019
|
+
callback && callback(err, null);
|
|
130020
|
+
return;
|
|
130021
|
+
}
|
|
129420
130022
|
const initCookies = cookieTools.parse(_options.formerRegistrationData.loginCookie);
|
|
129421
130023
|
let newCookie = `frc=${initCookies.frc}; `;
|
|
129422
130024
|
newCookie += `map-md=${initCookies["map-md"]}; `;
|
|
129423
130025
|
newCookie += comCookie;
|
|
129424
|
-
_options.formerRegistrationData.loginCookie = newCookie;
|
|
129425
|
-
|
|
130026
|
+
_options.formerRegistrationData.loginCookie = sanitizeAmazonCookie(newCookie);
|
|
130027
|
+
finishCookieRefresh(_options.formerRegistrationData, callback);
|
|
129426
130028
|
});
|
|
129427
130029
|
});
|
|
129428
130030
|
};
|
|
@@ -130017,10 +130619,20 @@ var import_alexa_remote = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin
|
|
|
130017
130619
|
listItemId: payload.listItemId
|
|
130018
130620
|
});
|
|
130019
130621
|
return;
|
|
130622
|
+
case "NotifyNowPlayingUpdated":
|
|
130623
|
+
const nowPlaying = payload?.update?.update?.nowPlayingData ?? null;
|
|
130624
|
+
this.emit("ws-now-playing-updated", {
|
|
130625
|
+
customerId: payload?.customerId ?? null,
|
|
130626
|
+
taskSessionId: payload?.update?.taskSessionId ?? null,
|
|
130627
|
+
cause: payload?.update?.update?.cause ?? null,
|
|
130628
|
+
mediaId: nowPlaying?.mediaId ?? null,
|
|
130629
|
+
playerState: nowPlaying?.playerState ?? null,
|
|
130630
|
+
nowPlayingData: nowPlaying
|
|
130631
|
+
});
|
|
130632
|
+
return;
|
|
130020
130633
|
case "PUSH_MICROPHONE_STATE":
|
|
130021
130634
|
case "PUSH_DELETE_DOPPLER_ACTIVITIES":
|
|
130022
130635
|
case "PUSH_DEVICE_SETUP_STATE_CHANGE":
|
|
130023
|
-
case "NotifyNowPlayingUpdated":
|
|
130024
130636
|
case "NotifyMediaSessionsUpdated": return;
|
|
130025
130637
|
}
|
|
130026
130638
|
this.emit("ws-unknown-command", command, payload);
|
|
@@ -132067,7 +132679,7 @@ var import_alexa_remote = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin
|
|
|
132067
132679
|
*/
|
|
132068
132680
|
var ALEXA_API_USER_AGENT_POSTFIX = `AlexaRemote/${{
|
|
132069
132681
|
name: "alexa-remote2",
|
|
132070
|
-
version: "8.
|
|
132682
|
+
version: "8.1.1",
|
|
132071
132683
|
description: "Remote Control for amazon echo devices",
|
|
132072
132684
|
author: {
|
|
132073
132685
|
"name": "Apollon77",
|
|
@@ -132096,16 +132708,16 @@ var ALEXA_API_USER_AGENT_POSTFIX = `AlexaRemote/${{
|
|
|
132096
132708
|
"layla.amazon.de"
|
|
132097
132709
|
],
|
|
132098
132710
|
dependencies: {
|
|
132099
|
-
"alexa-cookie2": "^5.0.
|
|
132711
|
+
"alexa-cookie2": "^5.0.6",
|
|
132100
132712
|
"https": "^1.0.0",
|
|
132101
132713
|
"querystring": "^0.2.1",
|
|
132102
|
-
"ws": "^8.
|
|
132714
|
+
"ws": "^8.21.0",
|
|
132103
132715
|
"extend": "^3.0.2",
|
|
132104
|
-
"uuid": "^11.1.
|
|
132716
|
+
"uuid": "^11.1.1"
|
|
132105
132717
|
},
|
|
132106
132718
|
devDependencies: {
|
|
132107
|
-
"@alcalzone/release-script": "^5.
|
|
132108
|
-
"@alcalzone/release-script-plugin-license": "^
|
|
132719
|
+
"@alcalzone/release-script": "^5.2.1",
|
|
132720
|
+
"@alcalzone/release-script-plugin-license": "^5.2.0",
|
|
132109
132721
|
"eslint": "^8.57.1"
|
|
132110
132722
|
},
|
|
132111
132723
|
scripts: {
|
|
@@ -132125,6 +132737,31 @@ var ALEXA_API_USER_AGENT_POSTFIX = `AlexaRemote/${{
|
|
|
132125
132737
|
//#endregion
|
|
132126
132738
|
//#region src/alexa-proxy-login.ts
|
|
132127
132739
|
/**
|
|
132740
|
+
* INTERACTIVE Amazon login via `alexa-remote2`'s built-in proxy auth mode, behind the
|
|
132741
|
+
* hub's vendor login proxy (D955).
|
|
132742
|
+
*
|
|
132743
|
+
* alexa-remote2 (through `alexa-cookie2`) spins a local Express reverse-proxy to
|
|
132744
|
+
* Amazon's real login page and captures the resulting cookie + the richer
|
|
132745
|
+
* `cookieData` registration blob (it embeds the refresh token, so the session can
|
|
132746
|
+
* auto-refresh forever). That listener is bound to LOOPBACK only
|
|
132747
|
+
* (`proxyOwnIp` = `proxyListenBind` = 127.0.0.1): nothing outside this process's host can
|
|
132748
|
+
* reach it. The operator's browser reaches it through the hub's data plane instead
|
|
132749
|
+
* (`VendorLoginProxy`, `/addon/<id>/login/<attempt>/127.0.0.1:<port>/…`), which is
|
|
132750
|
+
* HTTPS under the same origin as the admin page. alexa-cookie2 keeps doing the Amazon
|
|
132751
|
+
* specific work (login cookies, device registration, the `/ax/claim` and CSRF steps)
|
|
132752
|
+
* and the hub proxy only rewrites the loopback-origin URLs it emits into the mount.
|
|
132753
|
+
*
|
|
132754
|
+
* Why not the second proxy replacing alexa-cookie2's: the capture needs the PKCE
|
|
132755
|
+
* verifier, the device id, the initial cookies and the app registration alexa-cookie2
|
|
132756
|
+
* already performs; re-implementing them would add JS-built-URL failure modes.
|
|
132757
|
+
*
|
|
132758
|
+
* The captured `cookieData` is handed to {@link AlexaProxyLoginSessionOptions.onCookie}
|
|
132759
|
+
* — the addon persists it on the broker entry, exactly where a pasted cookie
|
|
132760
|
+
* goes, so the rest of the addon (broker / poll / adoption) is unchanged.
|
|
132761
|
+
*/
|
|
132762
|
+
/** The only address the alexa-cookie2 listener binds and advertises. */
|
|
132763
|
+
var ALEXA_PROXY_LOOPBACK_HOST = "127.0.0.1";
|
|
132764
|
+
/**
|
|
132128
132765
|
* A single interactive proxy-login attempt. One instance = one Amazon login.
|
|
132129
132766
|
* Construct → {@link start} → poll {@link getStatus} → on `captured` the
|
|
132130
132767
|
* `onCookie` callback has fired with the blob → {@link dispose}.
|
|
@@ -132138,13 +132775,37 @@ var AlexaProxyLoginSession = class {
|
|
|
132138
132775
|
#startedAt = Date.now();
|
|
132139
132776
|
#capturedAt = null;
|
|
132140
132777
|
#captured = false;
|
|
132778
|
+
#waiters = /* @__PURE__ */ new Set();
|
|
132141
132779
|
constructor(opts) {
|
|
132142
132780
|
this.#opts = opts;
|
|
132143
132781
|
this.#logger = opts.logger;
|
|
132144
132782
|
}
|
|
132145
|
-
/**
|
|
132783
|
+
/** What the operator opens: the hub-relative entry of the login proxy. */
|
|
132146
132784
|
get loginUrl() {
|
|
132147
|
-
return
|
|
132785
|
+
return this.#opts.loginUrl;
|
|
132786
|
+
}
|
|
132787
|
+
/** `host:port` of the loopback listener — the one upstream the hub proxy may reach. */
|
|
132788
|
+
get upstreamHost() {
|
|
132789
|
+
return `${ALEXA_PROXY_LOOPBACK_HOST}:${this.#opts.proxyPort}`;
|
|
132790
|
+
}
|
|
132791
|
+
/**
|
|
132792
|
+
* Resolves true once the cookie material is in hand, false after `timeoutMs`.
|
|
132793
|
+
* The Amazon page ends on `/cookie-success` BEFORE alexa-cookie2 finished registering
|
|
132794
|
+
* the app, so the hub waits here before it sends the browser to the result page.
|
|
132795
|
+
*/
|
|
132796
|
+
whenCaptured(timeoutMs) {
|
|
132797
|
+
if (this.#captured) return Promise.resolve(true);
|
|
132798
|
+
return new Promise((resolve) => {
|
|
132799
|
+
const timer = setTimeout(() => {
|
|
132800
|
+
this.#waiters.delete(settle);
|
|
132801
|
+
resolve(false);
|
|
132802
|
+
}, timeoutMs);
|
|
132803
|
+
const settle = () => {
|
|
132804
|
+
clearTimeout(timer);
|
|
132805
|
+
resolve(true);
|
|
132806
|
+
};
|
|
132807
|
+
this.#waiters.add(settle);
|
|
132808
|
+
});
|
|
132148
132809
|
}
|
|
132149
132810
|
getStatus() {
|
|
132150
132811
|
return {
|
|
@@ -132176,7 +132837,8 @@ var AlexaProxyLoginSession = class {
|
|
|
132176
132837
|
remote.on("cookie", () => this.#onCookieEvent(remote));
|
|
132177
132838
|
remote.init({
|
|
132178
132839
|
proxyOnly: true,
|
|
132179
|
-
proxyOwnIp:
|
|
132840
|
+
proxyOwnIp: ALEXA_PROXY_LOOPBACK_HOST,
|
|
132841
|
+
proxyListenBind: ALEXA_PROXY_LOOPBACK_HOST,
|
|
132180
132842
|
proxyPort: this.#opts.proxyPort,
|
|
132181
132843
|
proxyLogLevel: "warn",
|
|
132182
132844
|
amazonPage,
|
|
@@ -132222,6 +132884,8 @@ var AlexaProxyLoginSession = class {
|
|
|
132222
132884
|
this.#captured = true;
|
|
132223
132885
|
this.#capturedAt = Date.now();
|
|
132224
132886
|
this.#phase = "captured";
|
|
132887
|
+
for (const settle of this.#waiters) settle();
|
|
132888
|
+
this.#waiters.clear();
|
|
132225
132889
|
this.#logger.info("alexa proxy-login: cookie captured", { meta: { sessionId: this.#opts.sessionId } });
|
|
132226
132890
|
try {
|
|
132227
132891
|
this.#opts.onCookie(this.#opts.sessionId, {
|
|
@@ -132242,10 +132906,7 @@ var AlexaProxyLoginSession = class {
|
|
|
132242
132906
|
if (isProxyReadyError(err)) {
|
|
132243
132907
|
if (this.#phase === "starting") {
|
|
132244
132908
|
this.#phase = "awaiting-login";
|
|
132245
|
-
this.#logger.info("alexa proxy-login: proxy ready — awaiting Amazon login", { meta: {
|
|
132246
|
-
sessionId: this.#opts.sessionId,
|
|
132247
|
-
loginUrl: this.loginUrl
|
|
132248
|
-
} });
|
|
132909
|
+
this.#logger.info("alexa proxy-login: proxy ready — awaiting Amazon login", { meta: { sessionId: this.#opts.sessionId } });
|
|
132249
132910
|
}
|
|
132250
132911
|
return;
|
|
132251
132912
|
}
|
|
@@ -132303,62 +132964,44 @@ function isStringOrObject(value) {
|
|
|
132303
132964
|
return typeof value === "string" || typeof value === "object" && value !== null;
|
|
132304
132965
|
}
|
|
132305
132966
|
//#endregion
|
|
132306
|
-
//#region src/alexa-proxy-
|
|
132307
|
-
/**
|
|
132308
|
-
var
|
|
132309
|
-
|
|
132310
|
-
|
|
132311
|
-
|
|
132312
|
-
|
|
132313
|
-
|
|
132314
|
-
|
|
132315
|
-
|
|
132316
|
-
|
|
132317
|
-
|
|
132318
|
-
|
|
132319
|
-
|
|
132320
|
-
|
|
132321
|
-
if (
|
|
132322
|
-
if (host.includes(":")) return false;
|
|
132323
|
-
return host.endsWith(".local") || !host.includes(".");
|
|
132324
|
-
}
|
|
132325
|
-
function lanHostOf(address) {
|
|
132967
|
+
//#region src/alexa-proxy-login-manager.ts
|
|
132968
|
+
/** The data-plane prefix the hub login proxy is served on. */
|
|
132969
|
+
var ALEXA_LOGIN_PROXY_PREFIX = "login";
|
|
132970
|
+
/** The addon id the result page names to return to. */
|
|
132971
|
+
var ADDON_ID = "import-alexa";
|
|
132972
|
+
/** An attempt lives as long as a sign-in attempt does elsewhere (10 min). */
|
|
132973
|
+
var ATTEMPT_TTL_MS = 600 * 1e3;
|
|
132974
|
+
/** How long the browser waits on the Amazon page for alexa-cookie2 to finish registering. */
|
|
132975
|
+
var CAPTURE_WAIT_MS = 6e4;
|
|
132976
|
+
/**
|
|
132977
|
+
* Terminal condition: alexa-cookie2 turns Amazon's final redirect into a 302 to its own
|
|
132978
|
+
* `/cookie-success` page once it holds the login cookie. Matching on the path of the raw
|
|
132979
|
+
* `Location` keeps this independent of the page's HTML.
|
|
132980
|
+
*/
|
|
132981
|
+
function cookieSuccessTerminal(view) {
|
|
132982
|
+
if (view.location === null) return { kind: "continue" };
|
|
132326
132983
|
try {
|
|
132327
|
-
|
|
132328
|
-
|
|
132984
|
+
return new URL(view.location, `http://127.0.0.1`).pathname === "/cookie-success" ? {
|
|
132985
|
+
kind: "done",
|
|
132986
|
+
payload: true
|
|
132987
|
+
} : { kind: "continue" };
|
|
132329
132988
|
} catch {
|
|
132330
|
-
return
|
|
132989
|
+
return { kind: "continue" };
|
|
132331
132990
|
}
|
|
132332
132991
|
}
|
|
132333
|
-
async function resolveProxyHost(input) {
|
|
132334
|
-
const override = input.override.trim();
|
|
132335
|
-
if (override.length > 0) return override;
|
|
132336
|
-
let addresses;
|
|
132337
|
-
try {
|
|
132338
|
-
addresses = await input.readAppConnections();
|
|
132339
|
-
} catch (err) {
|
|
132340
|
-
input.logger.warn("alexa proxy-login: hub addresses unavailable from local-network", { meta: { error: errMsg(err) } });
|
|
132341
|
-
throw new AlexaProxyHostError("palette-unavailable", "alexa proxy-login: the hub address list is unavailable — set proxyHost in settings", { cause: err });
|
|
132342
|
-
}
|
|
132343
|
-
for (const address of addresses) {
|
|
132344
|
-
const host = lanHostOf(address);
|
|
132345
|
-
if (host !== null) return host;
|
|
132346
|
-
}
|
|
132347
|
-
input.logger.warn("alexa proxy-login: no LAN address among the hub appConnections", { meta: { addresses: [...addresses] } });
|
|
132348
|
-
throw new AlexaProxyHostError("no-lan-address", "alexa proxy-login: the hub has no LAN address the browser can open directly — set proxyHost in settings");
|
|
132349
|
-
}
|
|
132350
|
-
//#endregion
|
|
132351
|
-
//#region src/alexa-proxy-login-manager.ts
|
|
132352
132992
|
var AlexaProxyLoginManager = class {
|
|
132353
132993
|
#logger;
|
|
132354
132994
|
#getProxyConfig;
|
|
132355
|
-
#
|
|
132995
|
+
#getLoginProxy;
|
|
132996
|
+
#captureWaitMs;
|
|
132356
132997
|
#session = null;
|
|
132357
132998
|
#captured = /* @__PURE__ */ new Map();
|
|
132999
|
+
#attemptCloser = null;
|
|
132358
133000
|
constructor(deps) {
|
|
132359
133001
|
this.#logger = deps.logger;
|
|
132360
133002
|
this.#getProxyConfig = deps.getProxyConfig;
|
|
132361
|
-
this.#
|
|
133003
|
+
this.#getLoginProxy = deps.getLoginProxy;
|
|
133004
|
+
this.#captureWaitMs = deps.captureWaitMs ?? CAPTURE_WAIT_MS;
|
|
132362
133005
|
}
|
|
132363
133006
|
/**
|
|
132364
133007
|
* Start a fresh proxy-login. Cancels any in-flight session first (single
|
|
@@ -132366,18 +133009,41 @@ var AlexaProxyLoginManager = class {
|
|
|
132366
133009
|
*/
|
|
132367
133010
|
async start(amazonDomain) {
|
|
132368
133011
|
await this.cancelActive();
|
|
132369
|
-
const
|
|
132370
|
-
|
|
132371
|
-
|
|
132372
|
-
|
|
132373
|
-
logger: this.#logger
|
|
132374
|
-
});
|
|
133012
|
+
const loginProxy = this.#getLoginProxy();
|
|
133013
|
+
if (loginProxy === null) throw new Error("alexa proxy-login: the hub login proxy is not available on this hub");
|
|
133014
|
+
const { proxyPort } = this.#getProxyConfig();
|
|
133015
|
+
const upstreamHost = `${ALEXA_PROXY_LOOPBACK_HOST}:${proxyPort}`;
|
|
132375
133016
|
const sessionId = randomBytes(9).toString("base64url");
|
|
133017
|
+
loginProxy.open({
|
|
133018
|
+
attemptId: sessionId,
|
|
133019
|
+
expiresAtMs: Date.now() + ATTEMPT_TTL_MS,
|
|
133020
|
+
upstreams: [{
|
|
133021
|
+
host: upstreamHost,
|
|
133022
|
+
scheme: "http"
|
|
133023
|
+
}],
|
|
133024
|
+
terminal: cookieSuccessTerminal,
|
|
133025
|
+
onDone: async () => {
|
|
133026
|
+
return await this.#session?.whenCaptured(this.#captureWaitMs) === true ? {
|
|
133027
|
+
status: "success",
|
|
133028
|
+
addonId: ADDON_ID
|
|
133029
|
+
} : {
|
|
133030
|
+
status: "error",
|
|
133031
|
+
addonId: ADDON_ID,
|
|
133032
|
+
reason: "exchange_failed"
|
|
133033
|
+
};
|
|
133034
|
+
}
|
|
133035
|
+
});
|
|
133036
|
+
const loginUrl = loginProxy.entryPath(sessionId, `http://${upstreamHost}/`);
|
|
133037
|
+
if (loginUrl === null) {
|
|
133038
|
+
loginProxy.close(sessionId);
|
|
133039
|
+
throw new Error("alexa proxy-login: the hub login proxy refused the login address");
|
|
133040
|
+
}
|
|
133041
|
+
this.#attemptCloser = () => loginProxy.close(sessionId);
|
|
132376
133042
|
const session = new AlexaProxyLoginSession({
|
|
132377
133043
|
sessionId,
|
|
132378
133044
|
amazonDomain,
|
|
132379
|
-
proxyOwnIp: host,
|
|
132380
133045
|
proxyPort,
|
|
133046
|
+
loginUrl,
|
|
132381
133047
|
logger: this.#logger,
|
|
132382
133048
|
onCookie: (id, captured) => {
|
|
132383
133049
|
this.#captured.set(id, captured);
|
|
@@ -132388,6 +133054,7 @@ var AlexaProxyLoginManager = class {
|
|
|
132388
133054
|
await session.start();
|
|
132389
133055
|
} catch (err) {
|
|
132390
133056
|
this.#session = null;
|
|
133057
|
+
this.#closeAttempt();
|
|
132391
133058
|
await session.dispose();
|
|
132392
133059
|
throw new Error(`alexa proxy-login: failed to start proxy — ${errMsg(err)}`, { cause: err });
|
|
132393
133060
|
}
|
|
@@ -132421,10 +133088,16 @@ var AlexaProxyLoginManager = class {
|
|
|
132421
133088
|
await this.cancelActive();
|
|
132422
133089
|
this.#captured.delete(sessionId);
|
|
132423
133090
|
}
|
|
133091
|
+
#closeAttempt() {
|
|
133092
|
+
const close = this.#attemptCloser;
|
|
133093
|
+
this.#attemptCloser = null;
|
|
133094
|
+
close?.();
|
|
133095
|
+
}
|
|
132424
133096
|
/** Cancel + dispose whatever session is currently active. */
|
|
132425
133097
|
async cancelActive() {
|
|
132426
133098
|
const session = this.#session;
|
|
132427
133099
|
this.#session = null;
|
|
133100
|
+
this.#closeAttempt();
|
|
132428
133101
|
if (session === null) return;
|
|
132429
133102
|
try {
|
|
132430
133103
|
await session.cancel();
|
|
@@ -136039,10 +136712,7 @@ var SPEAK_PAGE = {
|
|
|
136039
136712
|
/** Default config — a fresh install starts with no accounts. */
|
|
136040
136713
|
var DEFAULTS = {
|
|
136041
136714
|
brokers: [],
|
|
136042
|
-
proxyLogin: {
|
|
136043
|
-
proxyHost: "",
|
|
136044
|
-
proxyPort: 3456
|
|
136045
|
-
},
|
|
136715
|
+
proxyLogin: { proxyPort: 3456 },
|
|
136046
136716
|
speak: {
|
|
136047
136717
|
email: "",
|
|
136048
136718
|
password: "",
|
|
@@ -136088,6 +136758,8 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
136088
136758
|
deviceClasses = Object.fromEntries(ALEXA_DEVICE_TYPES.map((t) => [t, AlexaDevice]));
|
|
136089
136759
|
registry = null;
|
|
136090
136760
|
proxyLogin = null;
|
|
136761
|
+
loginPlane = null;
|
|
136762
|
+
loginProxy = null;
|
|
136091
136763
|
brokerStore = null;
|
|
136092
136764
|
activeBrokers = [];
|
|
136093
136765
|
speak = null;
|
|
@@ -136127,16 +136799,13 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
136127
136799
|
});
|
|
136128
136800
|
alexaCommands.setDispatcher((brokerId, command) => this.requireRegistry().sendCommand(brokerId, command));
|
|
136129
136801
|
await this.registry.restore(this.activeBrokers);
|
|
136802
|
+
await this.serveLoginProxy();
|
|
136130
136803
|
this.proxyLogin = new AlexaProxyLoginManager({
|
|
136131
136804
|
logger: this.ctx.logger,
|
|
136132
136805
|
getProxyConfig: () => {
|
|
136133
|
-
|
|
136134
|
-
return {
|
|
136135
|
-
proxyHost: cfg.proxyHost,
|
|
136136
|
-
proxyPort: cfg.proxyPort
|
|
136137
|
-
};
|
|
136806
|
+
return { proxyPort: alexaProxyLoginSchema.parse(this.config.proxyLogin ?? {}).proxyPort };
|
|
136138
136807
|
},
|
|
136139
|
-
|
|
136808
|
+
getLoginProxy: () => this.loginProxy
|
|
136140
136809
|
});
|
|
136141
136810
|
this.ctx.logger.info("Alexa: import addon initialised", { meta: { brokerCount: this.activeBrokers.length } });
|
|
136142
136811
|
await this.reconcileIntegrationsToBrokers();
|
|
@@ -136268,6 +136937,24 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
136268
136937
|
}
|
|
136269
136938
|
this.speak?.brokersChanged();
|
|
136270
136939
|
}
|
|
136940
|
+
/** The hub's HTTPS face for the Amazon login (D955): admin session only. */
|
|
136941
|
+
async serveLoginProxy() {
|
|
136942
|
+
const proxy = new VendorLoginProxy({
|
|
136943
|
+
addonId: this.ctx.id,
|
|
136944
|
+
prefix: ALEXA_LOGIN_PROXY_PREFIX,
|
|
136945
|
+
logger: this.ctx.logger
|
|
136946
|
+
});
|
|
136947
|
+
try {
|
|
136948
|
+
this.loginPlane = await this.ctx.dataPlane?.serve({
|
|
136949
|
+
prefix: "login",
|
|
136950
|
+
access: "admin",
|
|
136951
|
+
handler: proxy.handler
|
|
136952
|
+
}) ?? null;
|
|
136953
|
+
if (this.loginPlane !== null) this.loginProxy = proxy;
|
|
136954
|
+
} catch (err) {
|
|
136955
|
+
this.ctx.logger.warn("Alexa: login proxy failed to serve — interactive sign-in is unavailable", { meta: { error: errMsg(err) } });
|
|
136956
|
+
}
|
|
136957
|
+
}
|
|
136271
136958
|
async onShutdown() {
|
|
136272
136959
|
this.speak?.stop();
|
|
136273
136960
|
this.speak = null;
|
|
@@ -136278,6 +136965,9 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
136278
136965
|
this.ctx.logger.warn("Alexa: proxy-login dispose error", { meta: { error: errMsg(err) } });
|
|
136279
136966
|
}
|
|
136280
136967
|
this.proxyLogin = null;
|
|
136968
|
+
await this.loginPlane?.dispose().catch(() => void 0);
|
|
136969
|
+
this.loginPlane = null;
|
|
136970
|
+
this.loginProxy = null;
|
|
136281
136971
|
try {
|
|
136282
136972
|
await this.registry?.shutdown();
|
|
136283
136973
|
} catch (err) {
|
|
@@ -136523,19 +137213,13 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
136523
137213
|
id: "alexa-proxy-login",
|
|
136524
137214
|
title: "Interactive login (proxy)",
|
|
136525
137215
|
titleKey: "import-alexa:globalSettings.s_alexa-proxy-login.title",
|
|
136526
|
-
description: "The \"
|
|
137216
|
+
description: "The \"Sign in\" button in onboarding opens the real Amazon login page through this hub (same address and HTTPS as this page) and captures the cookie after you complete it (incl. 2FA). The port below is only used on the hub itself and is never exposed.",
|
|
136527
137217
|
descriptionKey: "import-alexa:globalSettings.s_alexa-proxy-login.description",
|
|
136528
137218
|
columns: 2,
|
|
136529
137219
|
fields: [{
|
|
136530
|
-
type: "text",
|
|
136531
|
-
key: "proxyLogin.proxyHost",
|
|
136532
|
-
label: "Login proxy host/IP (blank = auto-detect)",
|
|
136533
|
-
labelKey: "import-alexa:globalSettings.s_alexa-proxy-login.proxyLogin_proxyHost.label",
|
|
136534
|
-
placeholder: "auto-detect"
|
|
136535
|
-
}, {
|
|
136536
137220
|
type: "number",
|
|
136537
137221
|
key: "proxyLogin.proxyPort",
|
|
136538
|
-
label: "
|
|
137222
|
+
label: "Internal login port (loopback)",
|
|
136539
137223
|
labelKey: "import-alexa:globalSettings.s_alexa-proxy-login.proxyLogin_proxyPort.label",
|
|
136540
137224
|
min: 1,
|
|
136541
137225
|
max: 65535,
|