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