dsh-multi-router 0.0.0-stage → 0.3.8

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/LICENSE +21 -0
  3. package/README.md +259 -2
  4. package/THIRD_PARTY_NOTICES.md +135 -0
  5. package/cordis.patch.yml +3 -0
  6. package/lib/accounts.js +17 -0
  7. package/lib/accounts.js.map +7 -0
  8. package/lib/chunk-3VGKH7MF.js +164 -0
  9. package/lib/chunk-3VGKH7MF.js.map +7 -0
  10. package/lib/chunk-DNQPZMPF.js +33 -0
  11. package/lib/chunk-DNQPZMPF.js.map +7 -0
  12. package/lib/chunk-FTM7RHQV.js +2017 -0
  13. package/lib/chunk-FTM7RHQV.js.map +7 -0
  14. package/lib/chunk-GGRCX2NR.js +449 -0
  15. package/lib/chunk-GGRCX2NR.js.map +7 -0
  16. package/lib/chunk-S5KMFAQM.js +50 -0
  17. package/lib/chunk-S5KMFAQM.js.map +7 -0
  18. package/lib/chunk-TEZ2FMEO.js +1091 -0
  19. package/lib/chunk-TEZ2FMEO.js.map +7 -0
  20. package/lib/chunk-UFS7ZJKY.js +5068 -0
  21. package/lib/chunk-UFS7ZJKY.js.map +7 -0
  22. package/lib/chunk-WIDXMBQ2.js +987 -0
  23. package/lib/chunk-WIDXMBQ2.js.map +7 -0
  24. package/lib/chunk-XMXFIV5G.js +209 -0
  25. package/lib/chunk-XMXFIV5G.js.map +7 -0
  26. package/lib/chunk-XY2UB22K.js +116 -0
  27. package/lib/chunk-XY2UB22K.js.map +7 -0
  28. package/lib/cli.js +102 -0
  29. package/lib/cli.js.map +7 -0
  30. package/lib/client.js +1402 -0
  31. package/lib/client.js.map +7 -0
  32. package/lib/clients.js +16 -0
  33. package/lib/clients.js.map +7 -0
  34. package/lib/host.js +364 -0
  35. package/lib/index.js +364 -0
  36. package/lib/index.js.map +7 -0
  37. package/lib/manager.js +23 -0
  38. package/lib/manager.js.map +7 -0
  39. package/lib/region-DO3KAV7V.js +15 -0
  40. package/lib/region-DO3KAV7V.js.map +7 -0
  41. package/lib/router.js +14 -0
  42. package/lib/router.js.map +7 -0
  43. package/lib/shim.js +10 -0
  44. package/lib/shim.js.map +7 -0
  45. package/lib/upstream-42RS7F4H.js +25 -0
  46. package/lib/upstream-42RS7F4H.js.map +7 -0
  47. package/package.json +76 -4
  48. package/router.example.json +289 -0
  49. package/upstream-lock.json +17 -0
@@ -0,0 +1,164 @@
1
+ import {
2
+ TraeCredentialStore,
3
+ WORKBUDDY_VARIANTS,
4
+ WorkBuddyCredentialStore,
5
+ atRestKeyProviderFor,
6
+ readTraeIdentity,
7
+ traeStorageCandidates
8
+ } from "./chunk-FTM7RHQV.js";
9
+ import {
10
+ regionOfCredential
11
+ } from "./chunk-S5KMFAQM.js";
12
+ import {
13
+ PROVIDERS,
14
+ writePrivateJson
15
+ } from "./chunk-XMXFIV5G.js";
16
+
17
+ // src/accounts.mjs
18
+ import { readFile } from "node:fs/promises";
19
+ import { tmpdir } from "node:os";
20
+ import { join } from "node:path";
21
+ import { randomBytes } from "node:crypto";
22
+ var localKeyProviders = /* @__PURE__ */ new Map();
23
+ var localAccountKey = (provider, document) => JSON.stringify([
24
+ provider,
25
+ document.credential?.uid || document.credential?.userId,
26
+ document.credential?.enterpriseId || "",
27
+ document.credential?.edition || ""
28
+ ]);
29
+ async function discoverLocalAccounts(provider) {
30
+ const accounts = [], status = [];
31
+ for (const variant of WORKBUDDY_VARIANTS.filter((v) => !provider || v.id === provider)) {
32
+ try {
33
+ if (!localKeyProviders.has(variant.id)) localKeyProviders.set(variant.id, atRestKeyProviderFor(variant));
34
+ const store = new WorkBuddyCredentialStore({
35
+ variant,
36
+ ownPath: join(tmpdir(), `dsh-router-scan-${randomBytes(16).toString("hex")}.json`),
37
+ keyProvider: localKeyProviders.get(variant.id),
38
+ refresh: async () => {
39
+ throw new Error("Discovery cannot refresh");
40
+ }
41
+ });
42
+ const credential = await store.current();
43
+ if (credential) accounts.push({ provider: variant.id, document: { version: 1, credential } });
44
+ status.push({ provider: variant.id, state: credential ? "found" : "not-found" });
45
+ } catch (e) {
46
+ status.push({ provider: variant.id, state: "unreadable", code: e.code || "credential_unreadable" });
47
+ }
48
+ }
49
+ for (const id of ["trae", "trae-global"].filter((id2) => !provider || id2 === provider)) {
50
+ let found = false, unreadable = false;
51
+ for (const candidate of traeStorageCandidates().filter((c) => c.source === "desktop" && (id === "trae" ? ["cn", "solo"] : ["sg", "solo-sg"]).includes(c.edition))) {
52
+ try {
53
+ await readFile(candidate.path, "utf8");
54
+ const store = new TraeCredentialStore({
55
+ storagePath: candidate.path,
56
+ edition: candidate.edition,
57
+ region: id === "trae" ? "cn" : "ai",
58
+ ownPath: `${candidate.path}.dsh-router-no-owned-credential`,
59
+ legacyOwnPath: `${candidate.path}.dsh-router-no-legacy`,
60
+ refresh: async () => {
61
+ throw new Error("Discovery cannot refresh");
62
+ }
63
+ });
64
+ const credential = await store.current();
65
+ if (!credential) {
66
+ const diagnostic = await store.diagnose();
67
+ unreadable ||= diagnostic.failures.some((f) => f.reason !== "missing");
68
+ continue;
69
+ }
70
+ if (regionOfCredential(credential) === "ai" !== (id === "trae-global")) continue;
71
+ const identityDocument = await readTraeIdentity(candidate);
72
+ accounts.push({ provider: id, document: { version: 1, credential }, identityDocument });
73
+ found = true;
74
+ } catch (e) {
75
+ if (e.code !== "ENOENT") unreadable = true;
76
+ }
77
+ }
78
+ status.push({ provider: id, state: found ? "found" : unreadable ? "unreadable" : "not-found" });
79
+ }
80
+ return { accounts, status };
81
+ }
82
+ var pick = (value, keys) => Object.fromEntries(keys.filter((k) => Object.hasOwn(value ?? {}, k)).map((k) => [k, value[k]]));
83
+ async function readPortableCredential(account) {
84
+ const raw = JSON.parse(await readFile(account.credentialFile, "utf8"));
85
+ if (account.provider.startsWith("qoder")) return raw.pat ? { document: pick(raw, ["version", "pat", "region"]) } : null;
86
+ if (!raw.credential?.accessToken) return null;
87
+ const document = { version: 1, credential: pick(raw.credential, ["accessToken", "refreshToken", "expiresAtMs", "refreshExpiresAtMs", "uid", "nickname", "enterpriseId", "domain", "userId", "accountName", "edition", "userRegion", "host"]) };
88
+ const identity = account.provider.startsWith("trae") ? pick(JSON.parse(await readFile(account.identityFile, "utf8")), ["machineId", "deviceId", "edition"]) : void 0;
89
+ if (identity && (!identity.machineId || !identity.deviceId || identity.edition !== raw.credential.edition)) throw new Error("Trae identity is incomplete");
90
+ return { document, identity };
91
+ }
92
+ async function importAccount({ provider, source, credentialFile, identityFile, edition, identitySource, document, identityDocument }) {
93
+ if (!PROVIDERS.includes(provider)) throw new Error("Unknown provider");
94
+ const inline = document !== void 0;
95
+ const raw = inline ? JSON.stringify(document) : await readFile(source, "utf8");
96
+ if (!inline) try {
97
+ document = JSON.parse(raw);
98
+ } catch {
99
+ }
100
+ if (document?.format === "dsh-multi-router-account" && document.provider !== provider) throw new Error("Portable credential provider does not match selected provider");
101
+ const text = (value) => typeof value === "string" && value.trim().length > 0 && value.length <= 32768;
102
+ if (provider.startsWith("workbuddy")) {
103
+ let credential;
104
+ if (document?.version === 1 && document.credential) credential = document.credential;
105
+ else if (inline) credential = document;
106
+ else {
107
+ const variant = WORKBUDDY_VARIANTS.find((v) => v.id === provider);
108
+ const store = new WorkBuddyCredentialStore({
109
+ desktopPath: source,
110
+ ownPath: `${credentialFile}.import-empty`,
111
+ variant,
112
+ keyProvider: atRestKeyProviderFor(variant),
113
+ refresh: async () => {
114
+ throw new Error("Import cannot refresh");
115
+ }
116
+ });
117
+ credential = await store.current();
118
+ }
119
+ if (!text(credential?.accessToken) || !text(credential.uid) || !Number.isFinite(credential.expiresAtMs) || credential.expiresAtMs <= 0 || typeof credential.domain !== "string" || /[\r\n]/.test(credential.domain)) throw new Error("No valid WorkBuddy credential in source");
120
+ const { regionOf } = await import("./upstream-42RS7F4H.js");
121
+ if (regionOf(credential.domain) === "global" !== (provider === "workbuddy-ai")) throw new Error("WorkBuddy credential region does not match provider");
122
+ await writePrivateJson(credentialFile, { version: 1, credential: { ...credential, source: "dsh" } });
123
+ } else if (provider.startsWith("trae")) {
124
+ let credential;
125
+ if (document?.version === 1 && document.credential) credential = document.credential;
126
+ else if (inline) credential = document;
127
+ else {
128
+ if (!["cn", "sg", "solo", "solo-sg"].includes(edition)) throw new Error("Trae desktop import needs --edition cn|sg|solo|solo-sg");
129
+ const store = new TraeCredentialStore({
130
+ storagePath: source,
131
+ edition,
132
+ ownPath: `${credentialFile}.import-empty`,
133
+ legacyOwnPath: `${credentialFile}.no-legacy`,
134
+ region: provider === "trae" ? "cn" : "ai",
135
+ refresh: async () => {
136
+ throw new Error("Import cannot refresh");
137
+ }
138
+ });
139
+ credential = await store.current();
140
+ }
141
+ if (!text(credential?.accessToken) || !text(credential.userId) || !Number.isFinite(credential.expiresAtMs) || credential.expiresAtMs <= 0 || !["cn", "sg", "solo", "solo-sg"].includes(credential.edition)) throw new Error("No valid Trae credential in source");
142
+ const { regionOfCredential: regionOfCredential2, regionOfHost } = await import("./region-DO3KAV7V.js");
143
+ if (credential.host && !regionOfHost(credential.host)) throw new Error("Invalid Trae credential host");
144
+ if (regionOfCredential2(credential) === "ai" !== (provider === "trae-global")) throw new Error("Trae credential region does not match provider");
145
+ const identity = identityDocument ?? document?.identity ?? (identitySource ? JSON.parse(await readFile(identitySource, "utf8")) : source ? await readTraeIdentity({ edition: credential.edition, path: source, source: "desktop" }) : void 0);
146
+ if (!text(identity?.machineId) || !text(identity.deviceId) || identity.edition !== credential.edition) throw new Error("Identity must belong to this Trae edition");
147
+ await writePrivateJson(identityFile, identity);
148
+ await writePrivateJson(credentialFile, { version: 1, credential: { ...credential, source: "dsh" } });
149
+ } else {
150
+ const region = provider === "qoder" ? "china" : "global";
151
+ const pat = typeof document?.pat === "string" ? document.pat.trim() : raw.trim();
152
+ if (!pat || document && typeof document.pat !== "string" || /\s/.test(pat)) throw new Error("Source must be a Qoder PAT text file or credential JSON");
153
+ if (document?.region && document.region !== region) throw new Error("Qoder PAT region does not match provider");
154
+ await writePrivateJson(credentialFile, { version: 2, pat, region, savedAt: (/* @__PURE__ */ new Date()).toISOString() });
155
+ }
156
+ }
157
+
158
+ export {
159
+ localAccountKey,
160
+ discoverLocalAccounts,
161
+ readPortableCredential,
162
+ importAccount
163
+ };
164
+ //# sourceMappingURL=chunk-3VGKH7MF.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/accounts.mjs"],
4
+ "sourcesContent": ["import { readFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { randomBytes } from 'node:crypto';\nimport { WorkBuddyCredentialStore } from '../upstream/workbuddy/src/auth.ts';\nimport { WORKBUDDY_VARIANTS } from '../upstream/workbuddy/src/variants.ts';\nimport { atRestKeyProviderFor } from '../upstream/workbuddy/src/desktop-credential-protection.ts';\nimport { TraeCredentialStore } from '../upstream/trae/src/auth.ts';\nimport { readTraeIdentity } from '../upstream/trae/src/identity.ts';\nimport { PROVIDERS, writePrivateJson } from './config.mjs';\nimport { traeStorageCandidates } from '../upstream/trae/src/paths.ts';\nimport { regionOfCredential } from '../upstream/trae/src/region.ts';\n\nconst localKeyProviders = new Map();\nexport const localAccountKey = (provider, document) => JSON.stringify([provider,\n document.credential?.uid || document.credential?.userId,\n document.credential?.enterpriseId || '', document.credential?.edition || '']);\n\n// Reuse the upstream readers, including WorkBuddy's environment overrides,\n// candidate fallback and encrypted desktop format. Never refresh/write upstream files.\nexport async function discoverLocalAccounts(provider) {\n const accounts = [], status = [];\n for (const variant of WORKBUDDY_VARIANTS.filter(v => !provider || v.id === provider)) {\n try {\n if (!localKeyProviders.has(variant.id)) localKeyProviders.set(variant.id, atRestKeyProviderFor(variant));\n const store = new WorkBuddyCredentialStore({ variant, ownPath: join(tmpdir(), `dsh-router-scan-${randomBytes(16).toString('hex')}.json`),\n keyProvider: localKeyProviders.get(variant.id), refresh: async () => { throw new Error('Discovery cannot refresh'); } });\n const credential = await store.current();\n if (credential) accounts.push({ provider: variant.id, document: { version: 1, credential } });\n status.push({ provider: variant.id, state: credential ? 'found' : 'not-found' });\n } catch (e) { status.push({ provider: variant.id, state: 'unreadable', code: e.code || 'credential_unreadable' }); }\n }\n for (const id of ['trae', 'trae-global'].filter(id => !provider || id === provider)) {\n let found = false, unreadable = false;\n for (const candidate of traeStorageCandidates().filter(c => c.source === 'desktop'\n && (id === 'trae' ? ['cn', 'solo'] : ['sg', 'solo-sg']).includes(c.edition))) {\n try {\n await readFile(candidate.path, 'utf8');\n const store = new TraeCredentialStore({ storagePath: candidate.path, edition: candidate.edition,\n region: id === 'trae' ? 'cn' : 'ai', ownPath: `${candidate.path}.dsh-router-no-owned-credential`,\n legacyOwnPath: `${candidate.path}.dsh-router-no-legacy`, refresh: async () => { throw new Error('Discovery cannot refresh'); } });\n const credential = await store.current();\n if (!credential) { const diagnostic = await store.diagnose(); unreadable ||= diagnostic.failures.some(f => f.reason !== 'missing'); continue; }\n if ((regionOfCredential(credential) === 'ai') !== (id === 'trae-global')) continue;\n const identityDocument = await readTraeIdentity(candidate);\n accounts.push({ provider: id, document: { version: 1, credential }, identityDocument }); found = true;\n } catch (e) { if (e.code !== 'ENOENT') unreadable = true; }\n }\n status.push({ provider: id, state: found ? 'found' : unreadable ? 'unreadable' : 'not-found' });\n }\n return { accounts, status };\n}\n\nconst pick = (value, keys) => Object.fromEntries(keys.filter(k => Object.hasOwn(value ?? {}, k)).map(k => [k, value[k]]));\n// Share the same portable fields with full encrypted configuration migration.\nexport async function readPortableCredential(account) {\n const raw = JSON.parse(await readFile(account.credentialFile, 'utf8'));\n if (account.provider.startsWith('qoder')) return raw.pat ? { document: pick(raw, ['version', 'pat', 'region']) } : null;\n if (!raw.credential?.accessToken) return null;\n const document = { version: 1, credential: pick(raw.credential, ['accessToken', 'refreshToken', 'expiresAtMs', 'refreshExpiresAtMs', 'uid', 'nickname', 'enterpriseId', 'domain', 'userId', 'accountName', 'edition', 'userRegion', 'host']) };\n const identity = account.provider.startsWith('trae') ? pick(JSON.parse(await readFile(account.identityFile, 'utf8')), ['machineId', 'deviceId', 'edition']) : undefined;\n if (identity && (!identity.machineId || !identity.deviceId || identity.edition !== raw.credential.edition)) throw new Error('Trae identity is incomplete');\n return { document, identity };\n}\n\n// Imports never refresh tokens or write to the provider's own files.\nexport async function importAccount({ provider, source, credentialFile, identityFile, edition, identitySource, document, identityDocument }) {\n if (!PROVIDERS.includes(provider)) throw new Error('Unknown provider');\n const inline = document !== undefined;\n const raw = inline ? JSON.stringify(document) : await readFile(source, 'utf8');\n if (!inline) try { document = JSON.parse(raw); } catch { /* A Qoder PAT can be a plain text file. */ }\n if (document?.format === 'dsh-multi-router-account' && document.provider !== provider) throw new Error('Portable credential provider does not match selected provider');\n const text = value => typeof value === 'string' && value.trim().length > 0 && value.length <= 32768;\n if (provider.startsWith('workbuddy')) {\n let credential;\n if (document?.version === 1 && document.credential) credential = document.credential;\n else if (inline) credential = document;\n else {\n const variant = WORKBUDDY_VARIANTS.find(v => v.id === provider);\n const store = new WorkBuddyCredentialStore({ desktopPath: source, ownPath: `${credentialFile}.import-empty`,\n variant, keyProvider: atRestKeyProviderFor(variant), refresh: async () => { throw new Error('Import cannot refresh'); } });\n credential = await store.current();\n }\n if (!text(credential?.accessToken) || !text(credential.uid) || !Number.isFinite(credential.expiresAtMs) || credential.expiresAtMs <= 0 || typeof credential.domain !== 'string' || /[\\r\\n]/.test(credential.domain)) throw new Error('No valid WorkBuddy credential in source');\n // Use the upstream region resolver rather than guessing regional domains.\n const { regionOf } = await import('../upstream/workbuddy/src/upstream.ts');\n if ((regionOf(credential.domain) === 'global') !== (provider === 'workbuddy-ai')) throw new Error('WorkBuddy credential region does not match provider');\n await writePrivateJson(credentialFile, { version: 1, credential: { ...credential, source: 'dsh' } });\n } else if (provider.startsWith('trae')) {\n let credential;\n if (document?.version === 1 && document.credential) credential = document.credential;\n else if (inline) credential = document;\n else {\n if (!['cn', 'sg', 'solo', 'solo-sg'].includes(edition)) throw new Error('Trae desktop import needs --edition cn|sg|solo|solo-sg');\n const store = new TraeCredentialStore({ storagePath: source, edition, ownPath: `${credentialFile}.import-empty`,\n legacyOwnPath: `${credentialFile}.no-legacy`, region: provider === 'trae' ? 'cn' : 'ai',\n refresh: async () => { throw new Error('Import cannot refresh'); } });\n credential = await store.current();\n }\n if (!text(credential?.accessToken) || !text(credential.userId) || !Number.isFinite(credential.expiresAtMs) || credential.expiresAtMs <= 0 || !['cn', 'sg', 'solo', 'solo-sg'].includes(credential.edition)) throw new Error('No valid Trae credential in source');\n const { regionOfCredential, regionOfHost } = await import('../upstream/trae/src/region.ts');\n if (credential.host && !regionOfHost(credential.host)) throw new Error('Invalid Trae credential host');\n if ((regionOfCredential(credential) === 'ai') !== (provider === 'trae-global')) throw new Error('Trae credential region does not match provider');\n const identity = identityDocument ?? document?.identity ?? (identitySource ? JSON.parse(await readFile(identitySource, 'utf8'))\n : source ? await readTraeIdentity({ edition: credential.edition, path: source, source: 'desktop' }) : undefined);\n if (!text(identity?.machineId) || !text(identity.deviceId) || identity.edition !== credential.edition) throw new Error('Identity must belong to this Trae edition');\n await writePrivateJson(identityFile, identity);\n await writePrivateJson(credentialFile, { version: 1, credential: { ...credential, source: 'dsh' } });\n } else {\n const region = provider === 'qoder' ? 'china' : 'global';\n const pat = typeof document?.pat === 'string' ? document.pat.trim() : raw.trim();\n if (!pat || (document && typeof document.pat !== 'string') || /\\s/.test(pat)) throw new Error('Source must be a Qoder PAT text file or credential JSON');\n if (document?.region && document.region !== region) throw new Error('Qoder PAT region does not match provider');\n await writePrivateJson(credentialFile, { version: 2, pat, region, savedAt: new Date().toISOString() });\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["id", "regionOfCredential"]
7
+ }
@@ -0,0 +1,33 @@
1
+ // upstream/workbuddy/src/loopback.ts
2
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "[::1]"]);
3
+ function hostnameOfHost(host) {
4
+ let hostname = host.trim().toLowerCase();
5
+ if (hostname.startsWith("[")) {
6
+ const end = hostname.indexOf("]");
7
+ return end === -1 ? hostname : hostname.slice(0, end + 1);
8
+ }
9
+ const colon = hostname.lastIndexOf(":");
10
+ if (colon !== -1 && !hostname.slice(0, colon).includes(":") && /^\d+$/.test(hostname.slice(colon + 1))) {
11
+ hostname = hostname.slice(0, colon);
12
+ }
13
+ return hostname;
14
+ }
15
+ function hostIsLoopback(host) {
16
+ if (host === void 0 || host.trim() === "") return false;
17
+ return LOOPBACK_HOSTS.has(hostnameOfHost(host));
18
+ }
19
+ function originIsLoopback(origin) {
20
+ if (origin === void 0 || origin.trim() === "") return true;
21
+ try {
22
+ const { hostname } = new URL(origin);
23
+ return LOOPBACK_HOSTS.has(hostname) || hostname === "::1";
24
+ } catch {
25
+ return false;
26
+ }
27
+ }
28
+
29
+ export {
30
+ hostIsLoopback,
31
+ originIsLoopback
32
+ };
33
+ //# sourceMappingURL=chunk-DNQPZMPF.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../upstream/workbuddy/src/loopback.ts"],
4
+ "sourcesContent": ["/**\n * Shared loopback gates for the plugin's local HTTP surfaces: the loopback\n * shim and the same-origin web-status route. Both are only ever meant to be\n * addressed through the machine's loopback interface.\n *\n * @module dsh-workbuddy-connect/loopback\n */\n\n/** Loopback hostnames a local plugin surface may be addressed by. */\nexport const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '[::1]'])\n\n/** Strip the optional :port from a Host header value, IPv6-bracket aware. */\nexport function hostnameOfHost(host: string): string {\n let hostname = host.trim().toLowerCase()\n if (hostname.startsWith('[')) {\n const end = hostname.indexOf(']')\n return end === -1 ? hostname : hostname.slice(0, end + 1)\n }\n // Only `name:port` with a single colon is a port; anything with more colons\n // is an (unbracketed) IPv6 literal and must not be truncated.\n const colon = hostname.lastIndexOf(':')\n if (colon !== -1 && !hostname.slice(0, colon).includes(':') && /^\\d+$/.test(hostname.slice(colon + 1))) {\n hostname = hostname.slice(0, colon)\n }\n return hostname\n}\n\n/**\n * The request's Host header must name the loopback interface. A DNS-rebinding\n * page (attacker domain re-resolved to 127.0.0.1) sends its own domain in\n * Host, so this check drops those before any routing happens.\n */\nexport function hostIsLoopback(host: string | undefined): boolean {\n if (host === undefined || host.trim() === '') return false\n return LOOPBACK_HOSTS.has(hostnameOfHost(host))\n}\n\n/**\n * A browser-sent Origin (present header) must be loopback. Non-browser\n * clients (the plugin's own fetch calls) send no Origin at all and pass.\n */\nexport function originIsLoopback(origin: string | undefined): boolean {\n if (origin === undefined || origin.trim() === '') return true\n try {\n const { hostname } = new URL(origin)\n return LOOPBACK_HOSTS.has(hostname) || hostname === '::1'\n } catch {\n return false\n }\n}\n"],
5
+ "mappings": ";AASO,IAAM,iBAAiB,oBAAI,IAAI,CAAC,aAAa,aAAa,OAAO,CAAC;AAGlE,SAAS,eAAe,MAAsB;AACnD,MAAI,WAAW,KAAK,KAAK,EAAE,YAAY;AACvC,MAAI,SAAS,WAAW,GAAG,GAAG;AAC5B,UAAM,MAAM,SAAS,QAAQ,GAAG;AAChC,WAAO,QAAQ,KAAK,WAAW,SAAS,MAAM,GAAG,MAAM,CAAC;AAAA,EAC1D;AAGA,QAAM,QAAQ,SAAS,YAAY,GAAG;AACtC,MAAI,UAAU,MAAM,CAAC,SAAS,MAAM,GAAG,KAAK,EAAE,SAAS,GAAG,KAAK,QAAQ,KAAK,SAAS,MAAM,QAAQ,CAAC,CAAC,GAAG;AACtG,eAAW,SAAS,MAAM,GAAG,KAAK;AAAA,EACpC;AACA,SAAO;AACT;AAOO,SAAS,eAAe,MAAmC;AAChE,MAAI,SAAS,UAAa,KAAK,KAAK,MAAM,GAAI,QAAO;AACrD,SAAO,eAAe,IAAI,eAAe,IAAI,CAAC;AAChD;AAMO,SAAS,iBAAiB,QAAqC;AACpE,MAAI,WAAW,UAAa,OAAO,KAAK,MAAM,GAAI,QAAO;AACzD,MAAI;AACF,UAAM,EAAE,SAAS,IAAI,IAAI,IAAI,MAAM;AACnC,WAAO,eAAe,IAAI,QAAQ,KAAK,aAAa;AAAA,EACtD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;",
6
+ "names": []
7
+ }