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/README.md +17 -4
- package/lib/client.js +328 -19
- package/lib/client.js.map +3 -3
- package/lib/index.js +108 -26
- package/lib/index.js.map +3 -3
- package/package.json +1 -1
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 =
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|