dsh-provider-proxy 0.5.0 → 0.6.0

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/lib/index.js CHANGED
@@ -134,6 +134,42 @@ import { ProxyAgent, Socks5ProxyAgent } from "undici";
134
134
  // src/config.ts
135
135
  import { createRequire } from "node:module";
136
136
 
137
+ // src/proxy-address.ts
138
+ var PROXY_HOST_INVALID = "\u4EE3\u7406\u5730\u5740\u53EA\u80FD\u586B IPv4\u3001IPv6 \u6216\u57DF\u540D";
139
+ var IPV4 = /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/;
140
+ var DOMAIN = /^(?=.*[a-z])(?=.{1,253}$)(?!.*\.\d+$)[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?)*$/i;
141
+ var IPV6_CHARS = /^[0-9a-f:.]+$/i;
142
+ function proxyHostProblem(rawHost) {
143
+ const host = (rawHost ?? "").trim().replace(/\.$/, "");
144
+ if (host.length === 0) return "\u672A\u586B\u5199\u4EE3\u7406\u5730\u5740";
145
+ if (IPV4.test(host)) return void 0;
146
+ if (host.includes(":") || host.startsWith("[")) {
147
+ const literal = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
148
+ if (!IPV6_CHARS.test(literal)) return PROXY_HOST_INVALID;
149
+ try {
150
+ new URL(`http://[${literal}]:1`);
151
+ return void 0;
152
+ } catch {
153
+ return PROXY_HOST_INVALID;
154
+ }
155
+ }
156
+ return DOMAIN.test(host) ? void 0 : PROXY_HOST_INVALID;
157
+ }
158
+ function defaultProxyProblem(endpoint) {
159
+ const hostProblem = proxyHostProblem(endpoint.host);
160
+ if (hostProblem !== void 0) return hostProblem;
161
+ const port = endpoint.port ?? 0;
162
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
163
+ return "\u4EE3\u7406\u7AEF\u53E3\u65E0\u6548\uFF08\u9700\u4E3A 1-65535 \u7684\u6574\u6570\uFF09";
164
+ }
165
+ const hasUser = (endpoint.username ?? "").trim().length > 0;
166
+ const hasPassword = (endpoint.password ?? "").length > 0;
167
+ if (hasUser !== hasPassword) {
168
+ return hasUser ? "\u914D\u7F6E\u4E86\u4EE3\u7406\u7528\u6237\u540D\u4F46\u6CA1\u6709\u5BC6\u7801\uFF1B\u8BF7\u8865\u5168\u5BC6\u7801\u6216\u6E05\u7A7A\u7528\u6237\u540D" : "\u914D\u7F6E\u4E86\u4EE3\u7406\u5BC6\u7801\u4F46\u6CA1\u6709\u7528\u6237\u540D\uFF1B\u8BF7\u8865\u5168\u7528\u6237\u540D\u6216\u6E05\u7A7A\u5BC6\u7801";
169
+ }
170
+ return void 0;
171
+ }
172
+
137
173
  // node_modules/@deepseek-ai/schemastery/lib/index.mjs
138
174
  var lib_exports = {};
139
175
  __export(lib_exports, {
@@ -1117,11 +1153,11 @@ function normalizeRoute(provider, raw, options) {
1117
1153
  return { ...value, port: value.port || 0, onlyHosts, bypassHosts, testTarget };
1118
1154
  }
1119
1155
  const host = value.host.trim();
1120
- if (host.length === 0) {
1121
- throw new TypeError(`dsh-provider-proxy: provider "${provider}" \u542F\u7528\u4E86\u4EE3\u7406\uFF0C\u4F46\u672A\u586B\u5199\u4EE3\u7406\u5730\u5740`);
1122
- }
1123
- if (/^[a-z][a-z0-9+.-]*:\/\//i.test(host) || host.includes("/")) {
1124
- throw new TypeError(`dsh-provider-proxy: provider "${provider}" \u7684\u4EE3\u7406\u5730\u5740\u53EA\u586B\u4E3B\u673A\u540D\u6216 IP\uFF0C\u4E0D\u8981\u5E26\u534F\u8BAE\u524D\u7F00\u6216\u8DEF\u5F84`);
1156
+ const hostProblem = proxyHostProblem(host);
1157
+ if (hostProblem !== void 0) {
1158
+ throw new TypeError(
1159
+ hostProblem === "\u672A\u586B\u5199\u4EE3\u7406\u5730\u5740" ? `dsh-provider-proxy: provider "${provider}" \u542F\u7528\u4E86\u4EE3\u7406\uFF0C\u4F46\u672A\u586B\u5199\u4EE3\u7406\u5730\u5740` : `dsh-provider-proxy: provider "${provider}" \u7684${hostProblem}`
1160
+ );
1125
1161
  }
1126
1162
  if (!Number.isInteger(value.port) || value.port < 1 || value.port > 65535) {
1127
1163
  throw new TypeError(
@@ -1131,24 +1167,40 @@ function normalizeRoute(provider, raw, options) {
1131
1167
  return { ...value, host, username: value.username.trim(), onlyHosts, bypassHosts, testTarget };
1132
1168
  }
1133
1169
 
1134
- // src/proxy-address.ts
1135
- function defaultProxyProblem(endpoint) {
1136
- const host = (endpoint.host ?? "").trim();
1137
- if (host.length === 0) return "\u672A\u586B\u5199\u4EE3\u7406\u5730\u5740";
1138
- if (/^[a-z][a-z0-9+.-]*:\/\//i.test(host) || host.includes("/")) {
1139
- return "\u4EE3\u7406\u5730\u5740\u53EA\u586B\u4E3B\u673A\u540D\u6216 IP\uFF0C\u4E0D\u8981\u5E26\u534F\u8BAE\u524D\u7F00\u6216\u8DEF\u5F84";
1140
- }
1141
- const port = endpoint.port ?? 0;
1142
- if (!Number.isInteger(port) || port < 1 || port > 65535) {
1143
- return "\u4EE3\u7406\u7AEF\u53E3\u65E0\u6548\uFF08\u9700\u4E3A 1-65535 \u7684\u6574\u6570\uFF09";
1144
- }
1145
- const hasUser = (endpoint.username ?? "").length > 0;
1146
- const hasPassword = (endpoint.password ?? "").length > 0;
1147
- if (hasUser !== hasPassword) {
1148
- return hasUser ? "\u914D\u7F6E\u4E86\u4EE3\u7406\u7528\u6237\u540D\u4F46\u6CA1\u6709\u5BC6\u7801\uFF1B\u8BF7\u8865\u5168\u5BC6\u7801\u6216\u6E05\u7A7A\u7528\u6237\u540D" : "\u914D\u7F6E\u4E86\u4EE3\u7406\u5BC6\u7801\u4F46\u6CA1\u6709\u7528\u6237\u540D\uFF1B\u8BF7\u8865\u5168\u7528\u6237\u540D\u6216\u6E05\u7A7A\u5BC6\u7801";
1170
+ // src/host-rules.ts
1171
+ function splitHostRules(raw) {
1172
+ return raw.split(/[,\s]+/).map((entry) => entry.trim()).filter((entry) => entry.length > 0);
1173
+ }
1174
+ function normalizeHostRule(raw) {
1175
+ return raw.trim().replace(/^\*?\./, "").toLowerCase();
1176
+ }
1177
+ function parseHostList(raw) {
1178
+ return splitHostRules(raw).map(normalizeHostRule).filter((entry) => entry.length > 0);
1179
+ }
1180
+ function hostMatches(host, rules) {
1181
+ for (const rule of rules) {
1182
+ if (rule === "*") return true;
1183
+ if (host === rule || host.endsWith(`.${rule}`)) return true;
1149
1184
  }
1185
+ return false;
1186
+ }
1187
+ function isBracketedIpv6(token) {
1188
+ return /^\[[0-9a-f:.]+\]$/i.test(token);
1189
+ }
1190
+ function hostRuleProblem(raw) {
1191
+ const token = raw.trim();
1192
+ if (token.length === 0) return void 0;
1193
+ if (token === "*." || normalizeHostRule(token).length === 0) return "\u7A7A\u901A\u914D\u7B26";
1194
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(token)) return "\u5E26\u534F\u8BAE\u524D\u7F00";
1195
+ if (token.includes("/")) return "\u5E26\u8DEF\u5F84";
1196
+ if (token.includes(":") && !isBracketedIpv6(token)) return "\u5E26\u7AEF\u53E3\u6216\u975E\u6CD5\u5192\u53F7";
1197
+ const withoutLeadingWildcard = token.replace(/^\*\./, "");
1198
+ if (withoutLeadingWildcard.includes("*") && token !== "*") return "\u6742\u6563\u901A\u914D\u7B26";
1150
1199
  return void 0;
1151
1200
  }
1201
+ function deadRuleEntries(raw) {
1202
+ return splitHostRules(raw).filter((entry) => hostRuleProblem(entry) !== void 0);
1203
+ }
1152
1204
 
1153
1205
  // src/proxy-table.ts
1154
1206
  function proxyUrl(route) {
@@ -1170,16 +1222,6 @@ function createDispatcher(route, url, password) {
1170
1222
  }
1171
1223
  return new ProxyAgent({ uri: url });
1172
1224
  }
1173
- function parseHostList(raw) {
1174
- return raw.split(/[,\s]+/).map((entry) => entry.trim().replace(/^\*?\./, "")).filter((entry) => entry.length > 0);
1175
- }
1176
- function hostMatches(host, rules) {
1177
- for (const rule of rules) {
1178
- if (rule === "*") return true;
1179
- if (host === rule || host.endsWith(`.${rule}`)) return true;
1180
- }
1181
- return false;
1182
- }
1183
1225
  function isLocalHost(host) {
1184
1226
  const bare = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
1185
1227
  if (bare === "localhost" || bare.endsWith(".localhost")) return true;
@@ -1409,12 +1451,15 @@ function apply(ctx, config) {
1409
1451
  }
1410
1452
  return out;
1411
1453
  };
1412
- const classifyTestError = (error, viaProxy) => {
1454
+ const classifyTestError = (error, viaProxy, rawTarget) => {
1413
1455
  const chain = causesOf(error);
1414
1456
  const names = chain.map((node) => node.name).filter((name2) => typeof name2 === "string");
1415
1457
  const codes = chain.map((node) => node.code).filter((code2) => typeof code2 === "string");
1416
1458
  const messages = chain.map((node) => node.message).filter((text) => typeof text === "string");
1417
- const message = error instanceof Error ? error.message : String(error);
1459
+ const message = redactTargetFromText(
1460
+ error instanceof Error ? error.message : String(error),
1461
+ rawTarget
1462
+ );
1418
1463
  if (names.includes("TimeoutError") || names.includes("AbortError")) {
1419
1464
  return { kind: "timeout", error: `\u8D85\u65F6\uFF08${TEST_TIMEOUT_MS / 1e3}s\uFF09\uFF1A\u4EE3\u7406\u6216\u76EE\u6807\u65E0\u54CD\u5E94` };
1420
1465
  }
@@ -1428,6 +1473,28 @@ function apply(ctx, config) {
1428
1473
  }
1429
1474
  return { kind: "target", error: `\u8BF7\u6C42\u5931\u8D25\uFF1A${message}` };
1430
1475
  };
1476
+ const sanitizeTargetForDisplay = (target) => {
1477
+ const raw = String(target ?? "");
1478
+ try {
1479
+ const url = new URL(raw);
1480
+ return `${url.protocol}//${url.host}${url.pathname}`;
1481
+ } catch {
1482
+ const withoutUserinfo = raw.replace(
1483
+ /^([a-z][a-z0-9+.-]*:\/\/)[^/@\s]+@/i,
1484
+ "$1"
1485
+ );
1486
+ const withoutQuery = withoutUserinfo.split(/[?#]/, 1)[0] ?? "";
1487
+ const bounded = withoutQuery.length > 160 ? `${withoutQuery.slice(0, 160)}\u2026` : withoutQuery;
1488
+ return bounded;
1489
+ }
1490
+ };
1491
+ const redactTargetFromText = (text, rawTarget) => {
1492
+ let safe = text;
1493
+ if (rawTarget.length > 0) safe = safe.split(rawTarget).join("[\u76EE\u6807\u5DF2\u8131\u654F]");
1494
+ safe = safe.replace(/\b([a-z][a-z0-9+.-]*:\/\/)[^/@\s]+@/gi, "$1");
1495
+ safe = safe.replace(/([?&#])[^\s"'<>]*/g, "$1");
1496
+ return safe.length > 500 ? `${safe.slice(0, 500)}\u2026` : safe;
1497
+ };
1431
1498
  const failureCodeOf = (error) => {
1432
1499
  const chain = causesOf(error);
1433
1500
  const names = chain.map((node) => node.name).filter((name2) => typeof name2 === "string");
@@ -1440,24 +1507,45 @@ function apply(ctx, config) {
1440
1507
  return "ERROR";
1441
1508
  };
1442
1509
  const probeTarget = async (target, route) => {
1510
+ const displayTarget = sanitizeTargetForDisplay(target);
1443
1511
  let host;
1444
1512
  try {
1445
1513
  host = new URL(target).hostname;
1446
1514
  } catch {
1447
- return { ok: false, kind: "target", error: `\u81EA\u68C0\u76EE\u6807\u4E0D\u662F\u5408\u6CD5 URL\uFF1A${target}` };
1515
+ return {
1516
+ ok: false,
1517
+ kind: "target",
1518
+ target: displayTarget,
1519
+ error: `\u81EA\u68C0\u76EE\u6807\u4E0D\u662F\u5408\u6CD5 URL\uFF1A${displayTarget}`
1520
+ };
1448
1521
  }
1449
1522
  const viaProxy = route.decide(host) === "proxy";
1450
1523
  const started = performance.now();
1451
1524
  try {
1452
- const response = await undiciFetch2(target, {
1525
+ const requestTarget = (() => {
1526
+ const url = new URL(target);
1527
+ url.username = "";
1528
+ url.password = "";
1529
+ return url.toString();
1530
+ })();
1531
+ const response = await undiciFetch2(requestTarget, {
1453
1532
  // A direct probe must NOT carry the dispatcher, or undici would force it
1454
1533
  // through the proxy anyway.
1455
1534
  ...viaProxy ? { dispatcher: route.dispatcher } : {},
1456
1535
  signal: AbortSignal.timeout(TEST_TIMEOUT_MS)
1457
1536
  });
1458
1537
  const ms = Math.round(performance.now() - started);
1538
+ const targetField = { target: displayTarget };
1459
1539
  if (viaProxy && response.status === 407) {
1460
- return { ok: false, ms, status: 407, kind: "proxy-auth", viaProxy, error: "\u4EE3\u7406\u8981\u6C42\u8BA4\u8BC1\uFF08HTTP 407\uFF09" };
1540
+ return {
1541
+ ok: false,
1542
+ ms,
1543
+ status: 407,
1544
+ kind: "proxy-auth",
1545
+ viaProxy,
1546
+ ...targetField,
1547
+ error: `\u4EE3\u7406\u8981\u6C42\u8BA4\u8BC1\uFF08HTTP 407\uFF09\uFF1A${displayTarget}`
1548
+ };
1461
1549
  }
1462
1550
  let exitIp;
1463
1551
  try {
@@ -1472,6 +1560,7 @@ function apply(ctx, config) {
1472
1560
  ms,
1473
1561
  status: response.status,
1474
1562
  viaProxy,
1563
+ ...targetField,
1475
1564
  ...exitIp === void 0 ? {} : { exitIp },
1476
1565
  kind: "ok"
1477
1566
  };
@@ -1481,24 +1570,28 @@ function apply(ctx, config) {
1481
1570
  ms,
1482
1571
  status: response.status,
1483
1572
  viaProxy,
1573
+ ...targetField,
1484
1574
  kind: "target",
1485
- error: `\u76EE\u6807\u8FD4\u56DE HTTP ${response.status}\uFF085xx\uFF0C\u4E0A\u6E38\u6216\u94FE\u8DEF\u5F02\u5E38\uFF09`
1575
+ // The status travels in its own field; the reason text must not repeat
1576
+ // it, or the page shows the same code twice in different colours.
1577
+ error: `\u76EE\u6807\u8FD4\u56DE 5xx\uFF08\u4E0A\u6E38\u6216\u94FE\u8DEF\u5F02\u5E38\uFF09\uFF1A${displayTarget}`
1486
1578
  };
1487
1579
  } catch (error) {
1488
1580
  return {
1489
1581
  ok: false,
1490
1582
  ms: Math.round(performance.now() - started),
1491
1583
  viaProxy,
1492
- ...classifyTestError(error, viaProxy)
1584
+ ...classifyTestError(error, viaProxy, target),
1585
+ target: displayTarget
1493
1586
  };
1494
1587
  }
1495
1588
  };
1496
- const selfCheckTargets = (provider, testTarget) => {
1589
+ const selfCheckTargetsOf = (provider, testTarget) => {
1497
1590
  const own = (testTarget ?? "").trim();
1498
- if (own.length > 0) return [own];
1591
+ if (own.length > 0) return { targets: [own], source: "route" };
1499
1592
  const upstream = upstreamOf(provider);
1500
- if (upstream !== void 0) return [upstream];
1501
- return plainTestUrls();
1593
+ if (upstream !== void 0) return { targets: [upstream], source: "upstream" };
1594
+ return { targets: plainTestUrls(), source: "testUrl" };
1502
1595
  };
1503
1596
  const selfCheckKey = (provider) => {
1504
1597
  const raw = plainProviders(config)[provider] ?? {};
@@ -1514,7 +1607,7 @@ function apply(ctx, config) {
1514
1607
  raw.onlyHosts ?? "",
1515
1608
  raw.bypassHosts ?? "",
1516
1609
  plainLocalDirect(config),
1517
- selfCheckTargets(provider, raw.testTarget)
1610
+ selfCheckTargetsOf(provider, raw.testTarget).targets
1518
1611
  ]);
1519
1612
  };
1520
1613
  const runSelfCheck = async (provider) => {
@@ -1542,13 +1635,14 @@ function apply(ctx, config) {
1542
1635
  }
1543
1636
  try {
1544
1637
  let last;
1545
- for (const target of selfCheckTargets(provider, route.testTarget)) {
1638
+ const { targets, source } = selfCheckTargetsOf(provider, route.testTarget);
1639
+ for (const target of targets) {
1546
1640
  const outcome = await probeTarget(target, route);
1547
- if (outcome.ok === true) return outcome;
1641
+ if (outcome.ok === true) return { ...outcome, targetSource: source };
1548
1642
  last = outcome;
1549
1643
  if (outcome.kind === "proxy-auth") break;
1550
1644
  }
1551
- return last ?? { ok: false, kind: "target", error: "\u6CA1\u6709\u53EF\u7528\u7684\u81EA\u68C0\u76EE\u6807" };
1645
+ return last === void 0 ? { ok: false, kind: "target", target: "", targetSource: source, error: "\u6CA1\u6709\u53EF\u7528\u7684\u81EA\u68C0\u76EE\u6807" } : { ...last, targetSource: source };
1552
1646
  } finally {
1553
1647
  if (temporary) await route.dispatcher.close().catch(() => void 0);
1554
1648
  }
@@ -1792,11 +1886,17 @@ export {
1792
1886
  buildRouteEntry,
1793
1887
  closeTable,
1794
1888
  createProxyTable,
1889
+ deadRuleEntries,
1795
1890
  defaultProxyProblem,
1891
+ hostMatches,
1892
+ hostRuleProblem,
1796
1893
  inject,
1797
1894
  name,
1798
1895
  normalizeDefaultProxy,
1799
1896
  normalizeRoute,
1800
- proxyUrl
1897
+ parseHostList,
1898
+ proxyHostProblem,
1899
+ proxyUrl,
1900
+ splitHostRules
1801
1901
  };
1802
1902
  //# sourceMappingURL=index.js.map