dsh-provider-proxy 0.5.1 → 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
@@ -1167,6 +1167,41 @@ function normalizeRoute(provider, raw, options) {
1167
1167
  return { ...value, host, username: value.username.trim(), onlyHosts, bypassHosts, testTarget };
1168
1168
  }
1169
1169
 
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;
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";
1199
+ return void 0;
1200
+ }
1201
+ function deadRuleEntries(raw) {
1202
+ return splitHostRules(raw).filter((entry) => hostRuleProblem(entry) !== void 0);
1203
+ }
1204
+
1170
1205
  // src/proxy-table.ts
1171
1206
  function proxyUrl(route) {
1172
1207
  const credentials = route.username.length === 0 ? "" : route.password.length > 0 && route.protocol !== "socks5" ? `${encodeURIComponent(route.username)}:${encodeURIComponent(route.password)}@` : `${encodeURIComponent(route.username)}@`;
@@ -1187,16 +1222,6 @@ function createDispatcher(route, url, password) {
1187
1222
  }
1188
1223
  return new ProxyAgent({ uri: url });
1189
1224
  }
1190
- function parseHostList(raw) {
1191
- return raw.split(/[,\s]+/).map((entry) => entry.trim().replace(/^\*?\./, "")).filter((entry) => entry.length > 0);
1192
- }
1193
- function hostMatches(host, rules) {
1194
- for (const rule of rules) {
1195
- if (rule === "*") return true;
1196
- if (host === rule || host.endsWith(`.${rule}`)) return true;
1197
- }
1198
- return false;
1199
- }
1200
1225
  function isLocalHost(host) {
1201
1226
  const bare = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
1202
1227
  if (bare === "localhost" || bare.endsWith(".localhost")) return true;
@@ -1426,12 +1451,15 @@ function apply(ctx, config) {
1426
1451
  }
1427
1452
  return out;
1428
1453
  };
1429
- const classifyTestError = (error, viaProxy) => {
1454
+ const classifyTestError = (error, viaProxy, rawTarget) => {
1430
1455
  const chain = causesOf(error);
1431
1456
  const names = chain.map((node) => node.name).filter((name2) => typeof name2 === "string");
1432
1457
  const codes = chain.map((node) => node.code).filter((code2) => typeof code2 === "string");
1433
1458
  const messages = chain.map((node) => node.message).filter((text) => typeof text === "string");
1434
- const message = error instanceof Error ? error.message : String(error);
1459
+ const message = redactTargetFromText(
1460
+ error instanceof Error ? error.message : String(error),
1461
+ rawTarget
1462
+ );
1435
1463
  if (names.includes("TimeoutError") || names.includes("AbortError")) {
1436
1464
  return { kind: "timeout", error: `\u8D85\u65F6\uFF08${TEST_TIMEOUT_MS / 1e3}s\uFF09\uFF1A\u4EE3\u7406\u6216\u76EE\u6807\u65E0\u54CD\u5E94` };
1437
1465
  }
@@ -1445,6 +1473,28 @@ function apply(ctx, config) {
1445
1473
  }
1446
1474
  return { kind: "target", error: `\u8BF7\u6C42\u5931\u8D25\uFF1A${message}` };
1447
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
+ };
1448
1498
  const failureCodeOf = (error) => {
1449
1499
  const chain = causesOf(error);
1450
1500
  const names = chain.map((node) => node.name).filter((name2) => typeof name2 === "string");
@@ -1457,24 +1507,45 @@ function apply(ctx, config) {
1457
1507
  return "ERROR";
1458
1508
  };
1459
1509
  const probeTarget = async (target, route) => {
1510
+ const displayTarget = sanitizeTargetForDisplay(target);
1460
1511
  let host;
1461
1512
  try {
1462
1513
  host = new URL(target).hostname;
1463
1514
  } catch {
1464
- 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
+ };
1465
1521
  }
1466
1522
  const viaProxy = route.decide(host) === "proxy";
1467
1523
  const started = performance.now();
1468
1524
  try {
1469
- 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, {
1470
1532
  // A direct probe must NOT carry the dispatcher, or undici would force it
1471
1533
  // through the proxy anyway.
1472
1534
  ...viaProxy ? { dispatcher: route.dispatcher } : {},
1473
1535
  signal: AbortSignal.timeout(TEST_TIMEOUT_MS)
1474
1536
  });
1475
1537
  const ms = Math.round(performance.now() - started);
1538
+ const targetField = { target: displayTarget };
1476
1539
  if (viaProxy && response.status === 407) {
1477
- 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
+ };
1478
1549
  }
1479
1550
  let exitIp;
1480
1551
  try {
@@ -1489,6 +1560,7 @@ function apply(ctx, config) {
1489
1560
  ms,
1490
1561
  status: response.status,
1491
1562
  viaProxy,
1563
+ ...targetField,
1492
1564
  ...exitIp === void 0 ? {} : { exitIp },
1493
1565
  kind: "ok"
1494
1566
  };
@@ -1498,24 +1570,28 @@ function apply(ctx, config) {
1498
1570
  ms,
1499
1571
  status: response.status,
1500
1572
  viaProxy,
1573
+ ...targetField,
1501
1574
  kind: "target",
1502
- 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}`
1503
1578
  };
1504
1579
  } catch (error) {
1505
1580
  return {
1506
1581
  ok: false,
1507
1582
  ms: Math.round(performance.now() - started),
1508
1583
  viaProxy,
1509
- ...classifyTestError(error, viaProxy)
1584
+ ...classifyTestError(error, viaProxy, target),
1585
+ target: displayTarget
1510
1586
  };
1511
1587
  }
1512
1588
  };
1513
- const selfCheckTargets = (provider, testTarget) => {
1589
+ const selfCheckTargetsOf = (provider, testTarget) => {
1514
1590
  const own = (testTarget ?? "").trim();
1515
- if (own.length > 0) return [own];
1591
+ if (own.length > 0) return { targets: [own], source: "route" };
1516
1592
  const upstream = upstreamOf(provider);
1517
- if (upstream !== void 0) return [upstream];
1518
- return plainTestUrls();
1593
+ if (upstream !== void 0) return { targets: [upstream], source: "upstream" };
1594
+ return { targets: plainTestUrls(), source: "testUrl" };
1519
1595
  };
1520
1596
  const selfCheckKey = (provider) => {
1521
1597
  const raw = plainProviders(config)[provider] ?? {};
@@ -1531,7 +1607,7 @@ function apply(ctx, config) {
1531
1607
  raw.onlyHosts ?? "",
1532
1608
  raw.bypassHosts ?? "",
1533
1609
  plainLocalDirect(config),
1534
- selfCheckTargets(provider, raw.testTarget)
1610
+ selfCheckTargetsOf(provider, raw.testTarget).targets
1535
1611
  ]);
1536
1612
  };
1537
1613
  const runSelfCheck = async (provider) => {
@@ -1559,13 +1635,14 @@ function apply(ctx, config) {
1559
1635
  }
1560
1636
  try {
1561
1637
  let last;
1562
- for (const target of selfCheckTargets(provider, route.testTarget)) {
1638
+ const { targets, source } = selfCheckTargetsOf(provider, route.testTarget);
1639
+ for (const target of targets) {
1563
1640
  const outcome = await probeTarget(target, route);
1564
- if (outcome.ok === true) return outcome;
1641
+ if (outcome.ok === true) return { ...outcome, targetSource: source };
1565
1642
  last = outcome;
1566
1643
  if (outcome.kind === "proxy-auth") break;
1567
1644
  }
1568
- 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 };
1569
1646
  } finally {
1570
1647
  if (temporary) await route.dispatcher.close().catch(() => void 0);
1571
1648
  }
@@ -1809,12 +1886,17 @@ export {
1809
1886
  buildRouteEntry,
1810
1887
  closeTable,
1811
1888
  createProxyTable,
1889
+ deadRuleEntries,
1812
1890
  defaultProxyProblem,
1891
+ hostMatches,
1892
+ hostRuleProblem,
1813
1893
  inject,
1814
1894
  name,
1815
1895
  normalizeDefaultProxy,
1816
1896
  normalizeRoute,
1897
+ parseHostList,
1817
1898
  proxyHostProblem,
1818
- proxyUrl
1899
+ proxyUrl,
1900
+ splitHostRules
1819
1901
  };
1820
1902
  //# sourceMappingURL=index.js.map