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/README.md +38 -12
- package/lib/client.js +516 -89
- package/lib/client.js.map +3 -3
- package/lib/index.js +146 -46
- package/lib/index.js.map +3 -3
- package/package.json +1 -1
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
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
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/
|
|
1135
|
-
function
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
const
|
|
1146
|
-
|
|
1147
|
-
|
|
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 =
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1897
|
+
parseHostList,
|
|
1898
|
+
proxyHostProblem,
|
|
1899
|
+
proxyUrl,
|
|
1900
|
+
splitHostRules
|
|
1801
1901
|
};
|
|
1802
1902
|
//# sourceMappingURL=index.js.map
|