dsh-provider-proxy 0.5.1 → 0.6.1
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 +22 -4
- package/lib/client.js +328 -19
- package/lib/client.js.map +3 -3
- package/lib/index.js +273 -44
- package/lib/index.js.map +3 -3
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1167,7 +1167,64 @@ 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
|
|
1206
|
+
function normalizedRuleSet(raw) {
|
|
1207
|
+
const rules = (raw ?? "").split(/[,\s]+/).map(normalizeHostRule).filter((entry) => entry.length > 0);
|
|
1208
|
+
return [...new Set(rules)].sort();
|
|
1209
|
+
}
|
|
1210
|
+
function routeFingerprint(input) {
|
|
1211
|
+
return JSON.stringify([
|
|
1212
|
+
input.protocol ?? "http",
|
|
1213
|
+
String(input.host ?? "").trim(),
|
|
1214
|
+
input.port ?? 0,
|
|
1215
|
+
String(input.username ?? "").trim(),
|
|
1216
|
+
input.password ?? "",
|
|
1217
|
+
normalizedRuleSet(input.onlyHosts),
|
|
1218
|
+
normalizedRuleSet(input.bypassHosts),
|
|
1219
|
+
input.localDirect !== false
|
|
1220
|
+
]);
|
|
1221
|
+
}
|
|
1222
|
+
function routeStateDecision(previous, next) {
|
|
1223
|
+
if (previous === void 0) return { token: "new", clearLastUse: false };
|
|
1224
|
+
if (previous.fingerprint !== next.fingerprint) return { token: "new", clearLastUse: true };
|
|
1225
|
+
if (previous.enabled !== next.enabled) return { token: "new", clearLastUse: false };
|
|
1226
|
+
return { token: "keep", clearLastUse: false };
|
|
1227
|
+
}
|
|
1171
1228
|
function proxyUrl(route) {
|
|
1172
1229
|
const credentials = route.username.length === 0 ? "" : route.password.length > 0 && route.protocol !== "socks5" ? `${encodeURIComponent(route.username)}:${encodeURIComponent(route.password)}@` : `${encodeURIComponent(route.username)}@`;
|
|
1173
1230
|
const host = route.host.includes(":") && !route.host.startsWith("[") ? `[${route.host}]` : route.host;
|
|
@@ -1187,16 +1244,6 @@ function createDispatcher(route, url, password) {
|
|
|
1187
1244
|
}
|
|
1188
1245
|
return new ProxyAgent({ uri: url });
|
|
1189
1246
|
}
|
|
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
1247
|
function isLocalHost(host) {
|
|
1201
1248
|
const bare = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
|
|
1202
1249
|
if (bare === "localhost" || bare.endsWith(".localhost")) return true;
|
|
@@ -1347,12 +1394,16 @@ function apply(ctx, config) {
|
|
|
1347
1394
|
let closed = false;
|
|
1348
1395
|
let webAvailable = false;
|
|
1349
1396
|
const hits = /* @__PURE__ */ new Map();
|
|
1397
|
+
const routeStates = /* @__PURE__ */ new Map();
|
|
1398
|
+
let nextRouteToken = 1;
|
|
1350
1399
|
const lastUse = /* @__PURE__ */ new Map();
|
|
1351
|
-
const bump = (provider) => {
|
|
1400
|
+
const bump = (provider, token) => {
|
|
1352
1401
|
hits.set(provider, (hits.get(provider) ?? 0) + 1);
|
|
1402
|
+
if (routeStates.get(provider)?.token !== token) return;
|
|
1353
1403
|
lastUse.set(provider, { ...lastUse.get(provider), startedAt: Date.now() });
|
|
1354
1404
|
};
|
|
1355
|
-
const settle = (provider) => (ok, detail) => {
|
|
1405
|
+
const settle = (provider, token) => (ok, detail) => {
|
|
1406
|
+
if (routeStates.get(provider)?.token !== token) return;
|
|
1356
1407
|
const previous = lastUse.get(provider);
|
|
1357
1408
|
lastUse.set(provider, {
|
|
1358
1409
|
startedAt: previous?.startedAt ?? Date.now(),
|
|
@@ -1368,7 +1419,60 @@ function apply(ctx, config) {
|
|
|
1368
1419
|
const MAX_REQUEST_BYTES = 8 * 1024;
|
|
1369
1420
|
const selfChecks = /* @__PURE__ */ new Map();
|
|
1370
1421
|
const SELF_CHECK_COOLDOWN_MS = 3e3;
|
|
1422
|
+
const MAX_CHECK_ENTRIES_PER_PROVIDER = 8;
|
|
1423
|
+
const MAX_CHECK_PROVIDERS = 64;
|
|
1371
1424
|
const lastChecks = /* @__PURE__ */ new Map();
|
|
1425
|
+
const checkKeyOf = (provider, key) => key.slice(provider.length);
|
|
1426
|
+
const pruneCheckCache = (now = Date.now()) => {
|
|
1427
|
+
for (const [provider, entries] of lastChecks) {
|
|
1428
|
+
for (const [key, entry] of entries) {
|
|
1429
|
+
if (now - entry.at >= SELF_CHECK_COOLDOWN_MS) entries.delete(key);
|
|
1430
|
+
}
|
|
1431
|
+
if (entries.size === 0) lastChecks.delete(provider);
|
|
1432
|
+
}
|
|
1433
|
+
while (lastChecks.size > MAX_CHECK_PROVIDERS) {
|
|
1434
|
+
let oldestProvider;
|
|
1435
|
+
let oldestAt = Infinity;
|
|
1436
|
+
for (const [provider, entries] of lastChecks) {
|
|
1437
|
+
let latest = -Infinity;
|
|
1438
|
+
for (const entry of entries.values()) latest = Math.max(latest, entry.at);
|
|
1439
|
+
if (latest < oldestAt) {
|
|
1440
|
+
oldestAt = latest;
|
|
1441
|
+
oldestProvider = provider;
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
if (oldestProvider === void 0) break;
|
|
1445
|
+
lastChecks.delete(oldestProvider);
|
|
1446
|
+
}
|
|
1447
|
+
};
|
|
1448
|
+
const rememberCheck = (provider, key, result) => {
|
|
1449
|
+
pruneCheckCache();
|
|
1450
|
+
let entries = lastChecks.get(provider);
|
|
1451
|
+
if (entries === void 0) {
|
|
1452
|
+
entries = /* @__PURE__ */ new Map();
|
|
1453
|
+
lastChecks.set(provider, entries);
|
|
1454
|
+
}
|
|
1455
|
+
entries.set(checkKeyOf(provider, key), { at: Date.now(), result });
|
|
1456
|
+
while (entries.size > MAX_CHECK_ENTRIES_PER_PROVIDER) {
|
|
1457
|
+
let oldestKey;
|
|
1458
|
+
let oldestAt = Infinity;
|
|
1459
|
+
for (const [candidate, entry] of entries) {
|
|
1460
|
+
if (entry.at < oldestAt) {
|
|
1461
|
+
oldestAt = entry.at;
|
|
1462
|
+
oldestKey = candidate;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
if (oldestKey === void 0) break;
|
|
1466
|
+
entries.delete(oldestKey);
|
|
1467
|
+
}
|
|
1468
|
+
pruneCheckCache();
|
|
1469
|
+
};
|
|
1470
|
+
const forgetChecks = (provider) => {
|
|
1471
|
+
lastChecks.delete(provider);
|
|
1472
|
+
for (const key of [...selfChecks.keys()]) {
|
|
1473
|
+
if (key.startsWith(`${provider}\0`)) selfChecks.delete(key);
|
|
1474
|
+
}
|
|
1475
|
+
};
|
|
1372
1476
|
const plainTestUrls = () => {
|
|
1373
1477
|
const raw = config?.testUrl;
|
|
1374
1478
|
const snapshot2 = raw?.get;
|
|
@@ -1426,12 +1530,15 @@ function apply(ctx, config) {
|
|
|
1426
1530
|
}
|
|
1427
1531
|
return out;
|
|
1428
1532
|
};
|
|
1429
|
-
const classifyTestError = (error, viaProxy) => {
|
|
1533
|
+
const classifyTestError = (error, viaProxy, rawTarget) => {
|
|
1430
1534
|
const chain = causesOf(error);
|
|
1431
1535
|
const names = chain.map((node) => node.name).filter((name2) => typeof name2 === "string");
|
|
1432
1536
|
const codes = chain.map((node) => node.code).filter((code2) => typeof code2 === "string");
|
|
1433
1537
|
const messages = chain.map((node) => node.message).filter((text) => typeof text === "string");
|
|
1434
|
-
const message =
|
|
1538
|
+
const message = redactTargetFromText(
|
|
1539
|
+
error instanceof Error ? error.message : String(error),
|
|
1540
|
+
rawTarget
|
|
1541
|
+
);
|
|
1435
1542
|
if (names.includes("TimeoutError") || names.includes("AbortError")) {
|
|
1436
1543
|
return { kind: "timeout", error: `\u8D85\u65F6\uFF08${TEST_TIMEOUT_MS / 1e3}s\uFF09\uFF1A\u4EE3\u7406\u6216\u76EE\u6807\u65E0\u54CD\u5E94` };
|
|
1437
1544
|
}
|
|
@@ -1445,6 +1552,28 @@ function apply(ctx, config) {
|
|
|
1445
1552
|
}
|
|
1446
1553
|
return { kind: "target", error: `\u8BF7\u6C42\u5931\u8D25\uFF1A${message}` };
|
|
1447
1554
|
};
|
|
1555
|
+
const sanitizeTargetForDisplay = (target) => {
|
|
1556
|
+
const raw = String(target ?? "");
|
|
1557
|
+
try {
|
|
1558
|
+
const url = new URL(raw);
|
|
1559
|
+
return `${url.protocol}//${url.host}${url.pathname}`;
|
|
1560
|
+
} catch {
|
|
1561
|
+
const withoutUserinfo = raw.replace(
|
|
1562
|
+
/^([a-z][a-z0-9+.-]*:\/\/)[^/@\s]+@/i,
|
|
1563
|
+
"$1"
|
|
1564
|
+
);
|
|
1565
|
+
const withoutQuery = withoutUserinfo.split(/[?#]/, 1)[0] ?? "";
|
|
1566
|
+
const bounded = withoutQuery.length > 160 ? `${withoutQuery.slice(0, 160)}\u2026` : withoutQuery;
|
|
1567
|
+
return bounded;
|
|
1568
|
+
}
|
|
1569
|
+
};
|
|
1570
|
+
const redactTargetFromText = (text, rawTarget) => {
|
|
1571
|
+
let safe = text;
|
|
1572
|
+
if (rawTarget.length > 0) safe = safe.split(rawTarget).join("[\u76EE\u6807\u5DF2\u8131\u654F]");
|
|
1573
|
+
safe = safe.replace(/\b([a-z][a-z0-9+.-]*:\/\/)[^/@\s]+@/gi, "$1");
|
|
1574
|
+
safe = safe.replace(/([?&#])[^\s"'<>]*/g, "$1");
|
|
1575
|
+
return safe.length > 500 ? `${safe.slice(0, 500)}\u2026` : safe;
|
|
1576
|
+
};
|
|
1448
1577
|
const failureCodeOf = (error) => {
|
|
1449
1578
|
const chain = causesOf(error);
|
|
1450
1579
|
const names = chain.map((node) => node.name).filter((name2) => typeof name2 === "string");
|
|
@@ -1457,24 +1586,45 @@ function apply(ctx, config) {
|
|
|
1457
1586
|
return "ERROR";
|
|
1458
1587
|
};
|
|
1459
1588
|
const probeTarget = async (target, route) => {
|
|
1589
|
+
const displayTarget = sanitizeTargetForDisplay(target);
|
|
1460
1590
|
let host;
|
|
1461
1591
|
try {
|
|
1462
1592
|
host = new URL(target).hostname;
|
|
1463
1593
|
} catch {
|
|
1464
|
-
return {
|
|
1594
|
+
return {
|
|
1595
|
+
ok: false,
|
|
1596
|
+
kind: "target",
|
|
1597
|
+
target: displayTarget,
|
|
1598
|
+
error: `\u81EA\u68C0\u76EE\u6807\u4E0D\u662F\u5408\u6CD5 URL\uFF1A${displayTarget}`
|
|
1599
|
+
};
|
|
1465
1600
|
}
|
|
1466
1601
|
const viaProxy = route.decide(host) === "proxy";
|
|
1467
1602
|
const started = performance.now();
|
|
1468
1603
|
try {
|
|
1469
|
-
const
|
|
1604
|
+
const requestTarget = (() => {
|
|
1605
|
+
const url = new URL(target);
|
|
1606
|
+
url.username = "";
|
|
1607
|
+
url.password = "";
|
|
1608
|
+
return url.toString();
|
|
1609
|
+
})();
|
|
1610
|
+
const response = await undiciFetch2(requestTarget, {
|
|
1470
1611
|
// A direct probe must NOT carry the dispatcher, or undici would force it
|
|
1471
1612
|
// through the proxy anyway.
|
|
1472
1613
|
...viaProxy ? { dispatcher: route.dispatcher } : {},
|
|
1473
1614
|
signal: AbortSignal.timeout(TEST_TIMEOUT_MS)
|
|
1474
1615
|
});
|
|
1475
1616
|
const ms = Math.round(performance.now() - started);
|
|
1617
|
+
const targetField = { target: displayTarget };
|
|
1476
1618
|
if (viaProxy && response.status === 407) {
|
|
1477
|
-
return {
|
|
1619
|
+
return {
|
|
1620
|
+
ok: false,
|
|
1621
|
+
ms,
|
|
1622
|
+
status: 407,
|
|
1623
|
+
kind: "proxy-auth",
|
|
1624
|
+
viaProxy,
|
|
1625
|
+
...targetField,
|
|
1626
|
+
error: `\u4EE3\u7406\u8981\u6C42\u8BA4\u8BC1\uFF08HTTP 407\uFF09\uFF1A${displayTarget}`
|
|
1627
|
+
};
|
|
1478
1628
|
}
|
|
1479
1629
|
let exitIp;
|
|
1480
1630
|
try {
|
|
@@ -1489,6 +1639,7 @@ function apply(ctx, config) {
|
|
|
1489
1639
|
ms,
|
|
1490
1640
|
status: response.status,
|
|
1491
1641
|
viaProxy,
|
|
1642
|
+
...targetField,
|
|
1492
1643
|
...exitIp === void 0 ? {} : { exitIp },
|
|
1493
1644
|
kind: "ok"
|
|
1494
1645
|
};
|
|
@@ -1498,24 +1649,28 @@ function apply(ctx, config) {
|
|
|
1498
1649
|
ms,
|
|
1499
1650
|
status: response.status,
|
|
1500
1651
|
viaProxy,
|
|
1652
|
+
...targetField,
|
|
1501
1653
|
kind: "target",
|
|
1502
|
-
|
|
1654
|
+
// The status travels in its own field; the reason text must not repeat
|
|
1655
|
+
// it, or the page shows the same code twice in different colours.
|
|
1656
|
+
error: `\u76EE\u6807\u8FD4\u56DE 5xx\uFF08\u4E0A\u6E38\u6216\u94FE\u8DEF\u5F02\u5E38\uFF09\uFF1A${displayTarget}`
|
|
1503
1657
|
};
|
|
1504
1658
|
} catch (error) {
|
|
1505
1659
|
return {
|
|
1506
1660
|
ok: false,
|
|
1507
1661
|
ms: Math.round(performance.now() - started),
|
|
1508
1662
|
viaProxy,
|
|
1509
|
-
...classifyTestError(error, viaProxy)
|
|
1663
|
+
...classifyTestError(error, viaProxy, target),
|
|
1664
|
+
target: displayTarget
|
|
1510
1665
|
};
|
|
1511
1666
|
}
|
|
1512
1667
|
};
|
|
1513
|
-
const
|
|
1668
|
+
const selfCheckTargetsOf = (provider, testTarget) => {
|
|
1514
1669
|
const own = (testTarget ?? "").trim();
|
|
1515
|
-
if (own.length > 0) return [own];
|
|
1670
|
+
if (own.length > 0) return { targets: [own], source: "route" };
|
|
1516
1671
|
const upstream = upstreamOf(provider);
|
|
1517
|
-
if (upstream !== void 0) return [upstream];
|
|
1518
|
-
return plainTestUrls();
|
|
1672
|
+
if (upstream !== void 0) return { targets: [upstream], source: "upstream" };
|
|
1673
|
+
return { targets: plainTestUrls(), source: "testUrl" };
|
|
1519
1674
|
};
|
|
1520
1675
|
const selfCheckKey = (provider) => {
|
|
1521
1676
|
const raw = plainProviders(config)[provider] ?? {};
|
|
@@ -1531,7 +1686,7 @@ function apply(ctx, config) {
|
|
|
1531
1686
|
raw.onlyHosts ?? "",
|
|
1532
1687
|
raw.bypassHosts ?? "",
|
|
1533
1688
|
plainLocalDirect(config),
|
|
1534
|
-
|
|
1689
|
+
selfCheckTargetsOf(provider, raw.testTarget).targets
|
|
1535
1690
|
]);
|
|
1536
1691
|
};
|
|
1537
1692
|
const runSelfCheck = async (provider) => {
|
|
@@ -1559,13 +1714,14 @@ function apply(ctx, config) {
|
|
|
1559
1714
|
}
|
|
1560
1715
|
try {
|
|
1561
1716
|
let last;
|
|
1562
|
-
|
|
1717
|
+
const { targets, source } = selfCheckTargetsOf(provider, route.testTarget);
|
|
1718
|
+
for (const target of targets) {
|
|
1563
1719
|
const outcome = await probeTarget(target, route);
|
|
1564
|
-
if (outcome.ok === true) return outcome;
|
|
1720
|
+
if (outcome.ok === true) return { ...outcome, targetSource: source };
|
|
1565
1721
|
last = outcome;
|
|
1566
1722
|
if (outcome.kind === "proxy-auth") break;
|
|
1567
1723
|
}
|
|
1568
|
-
return last
|
|
1724
|
+
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
1725
|
} finally {
|
|
1570
1726
|
if (temporary) await route.dispatcher.close().catch(() => void 0);
|
|
1571
1727
|
}
|
|
@@ -1574,17 +1730,56 @@ function apply(ctx, config) {
|
|
|
1574
1730
|
ctx.logger.info("dsh-provider-proxy mounted (pid %d)", process.pid);
|
|
1575
1731
|
} catch {
|
|
1576
1732
|
}
|
|
1577
|
-
const rebuild =
|
|
1578
|
-
const
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
});
|
|
1733
|
+
const rebuild = () => {
|
|
1734
|
+
const providers = plainProviders(config);
|
|
1735
|
+
const defaultProxy = plainDefaultProxy(config);
|
|
1736
|
+
const localDirect = plainLocalDirect(config);
|
|
1737
|
+
const next = createProxyTable(providers, { defaultProxy, localDirect });
|
|
1582
1738
|
const previous = current;
|
|
1583
1739
|
current = next.entries;
|
|
1584
1740
|
problems = next.problems;
|
|
1741
|
+
const configured = new Set(Object.keys(providers));
|
|
1742
|
+
for (const provider of [...routeStates.keys()]) {
|
|
1743
|
+
if (configured.has(provider)) continue;
|
|
1744
|
+
routeStates.delete(provider);
|
|
1745
|
+
lastUse.delete(provider);
|
|
1746
|
+
forgetChecks(provider);
|
|
1747
|
+
}
|
|
1748
|
+
for (const [provider, raw] of Object.entries(providers)) {
|
|
1749
|
+
const effective = raw?.useDefault === true ? { ...raw, ...defaultProxy } : raw;
|
|
1750
|
+
const fingerprint = routeFingerprint({
|
|
1751
|
+
...effective,
|
|
1752
|
+
localDirect
|
|
1753
|
+
});
|
|
1754
|
+
const decision = routeStateDecision(routeStates.get(provider), {
|
|
1755
|
+
fingerprint,
|
|
1756
|
+
enabled: raw?.enabled === true
|
|
1757
|
+
});
|
|
1758
|
+
const token = decision.token === "new" ? nextRouteToken++ : routeStates.get(provider).token;
|
|
1759
|
+
if (decision.clearLastUse) {
|
|
1760
|
+
lastUse.delete(provider);
|
|
1761
|
+
forgetChecks(provider);
|
|
1762
|
+
}
|
|
1763
|
+
routeStates.set(provider, { fingerprint, enabled: raw?.enabled === true, token });
|
|
1764
|
+
}
|
|
1765
|
+
pruneCheckCache();
|
|
1585
1766
|
void closeTable(previous);
|
|
1767
|
+
return Promise.resolve();
|
|
1586
1768
|
};
|
|
1587
|
-
|
|
1769
|
+
let rebuildQueue = Promise.resolve();
|
|
1770
|
+
const enqueueRebuild = () => {
|
|
1771
|
+
if (closed) return Promise.resolve();
|
|
1772
|
+
try {
|
|
1773
|
+
const done = rebuild();
|
|
1774
|
+
rebuildQueue = rebuildQueue.then(() => done).catch(() => void 0);
|
|
1775
|
+
return done;
|
|
1776
|
+
} catch (error) {
|
|
1777
|
+
const failed = Promise.reject(error);
|
|
1778
|
+
rebuildQueue = failed.catch(() => void 0);
|
|
1779
|
+
return failed;
|
|
1780
|
+
}
|
|
1781
|
+
};
|
|
1782
|
+
enqueueRebuild();
|
|
1588
1783
|
const catalog = () => {
|
|
1589
1784
|
const live = /* @__PURE__ */ new Set();
|
|
1590
1785
|
const out = [];
|
|
@@ -1700,16 +1895,20 @@ function apply(ctx, config) {
|
|
|
1700
1895
|
send(400, { ok: false, kind: "body", error: "\u7F3A\u5C11 provider" });
|
|
1701
1896
|
return;
|
|
1702
1897
|
}
|
|
1703
|
-
const key = selfCheckKey(provider)
|
|
1898
|
+
const key = `${provider}\0${selfCheckKey(provider)}`;
|
|
1704
1899
|
let pending = selfChecks.get(key);
|
|
1705
1900
|
if (pending === void 0) {
|
|
1706
|
-
|
|
1901
|
+
pruneCheckCache();
|
|
1902
|
+
const last = lastChecks.get(provider)?.get(checkKeyOf(provider, key));
|
|
1707
1903
|
if (last !== void 0 && Date.now() - last.at < SELF_CHECK_COOLDOWN_MS) {
|
|
1708
1904
|
send(200, { ...last.result, cached: true });
|
|
1709
1905
|
return;
|
|
1710
1906
|
}
|
|
1907
|
+
const checkToken = routeStates.get(provider)?.token;
|
|
1711
1908
|
pending = runSelfCheck(provider).then((result) => {
|
|
1712
|
-
|
|
1909
|
+
if (checkToken !== void 0 && routeStates.get(provider)?.token === checkToken) {
|
|
1910
|
+
rememberCheck(provider, key, result);
|
|
1911
|
+
}
|
|
1713
1912
|
return result;
|
|
1714
1913
|
});
|
|
1715
1914
|
selfChecks.set(key, pending);
|
|
@@ -1736,11 +1935,13 @@ function apply(ctx, config) {
|
|
|
1736
1935
|
const patched = function(request, signal) {
|
|
1737
1936
|
const route = current.get(WEB_SEARCH_KEY);
|
|
1738
1937
|
if (route === void 0) return original.call(this, request, signal);
|
|
1938
|
+
const token = routeStates.get(WEB_SEARCH_KEY)?.token;
|
|
1939
|
+
if (token === void 0) return original.call(this, request, signal);
|
|
1739
1940
|
return router.run(
|
|
1740
1941
|
{
|
|
1741
1942
|
dispatcher: route.dispatcher,
|
|
1742
|
-
onHit: () => bump(WEB_SEARCH_KEY),
|
|
1743
|
-
onSettle: settle(WEB_SEARCH_KEY),
|
|
1943
|
+
onHit: () => bump(WEB_SEARCH_KEY, token),
|
|
1944
|
+
onSettle: settle(WEB_SEARCH_KEY, token),
|
|
1744
1945
|
decide: route.decide
|
|
1745
1946
|
},
|
|
1746
1947
|
() => original.call(this, request, signal)
|
|
@@ -1760,11 +1961,13 @@ function apply(ctx, config) {
|
|
|
1760
1961
|
if (typeof provider !== "string") return next();
|
|
1761
1962
|
const route = current.get(provider);
|
|
1762
1963
|
if (route === void 0) return next();
|
|
1964
|
+
const token = routeStates.get(provider)?.token;
|
|
1965
|
+
if (token === void 0) return next();
|
|
1763
1966
|
return router.wrap(
|
|
1764
1967
|
{
|
|
1765
1968
|
dispatcher: route.dispatcher,
|
|
1766
|
-
onHit: () => bump(provider),
|
|
1767
|
-
onSettle: settle(provider),
|
|
1969
|
+
onHit: () => bump(provider, token),
|
|
1970
|
+
onSettle: settle(provider, token),
|
|
1768
1971
|
decide: route.decide
|
|
1769
1972
|
},
|
|
1770
1973
|
next
|
|
@@ -1773,14 +1976,14 @@ function apply(ctx, config) {
|
|
|
1773
1976
|
{ global: true }
|
|
1774
1977
|
);
|
|
1775
1978
|
const onConfig = () => {
|
|
1776
|
-
void
|
|
1979
|
+
void enqueueRebuild();
|
|
1777
1980
|
};
|
|
1778
1981
|
ctx.on("loader/volatile-update", onConfig, { global: true });
|
|
1779
1982
|
return async () => {
|
|
1780
1983
|
stop();
|
|
1781
1984
|
if (closed) return;
|
|
1782
1985
|
closed = true;
|
|
1783
|
-
await
|
|
1986
|
+
await rebuildQueue.catch(() => void 0);
|
|
1784
1987
|
await closeTable(current);
|
|
1785
1988
|
current = /* @__PURE__ */ new Map();
|
|
1786
1989
|
router.release();
|
|
@@ -1795,7 +1998,26 @@ function apply(ctx, config) {
|
|
|
1795
1998
|
})),
|
|
1796
1999
|
problems: Object.fromEntries(problems),
|
|
1797
2000
|
hits: Object.fromEntries(hits),
|
|
1798
|
-
lastUse: Object.fromEntries(lastUse)
|
|
2001
|
+
lastUse: Object.fromEntries(lastUse),
|
|
2002
|
+
selfCheckCache: (() => {
|
|
2003
|
+
let expiredEntries = 0;
|
|
2004
|
+
let maxPerProvider = 0;
|
|
2005
|
+
let totalEntries = 0;
|
|
2006
|
+
for (const entries of lastChecks.values()) {
|
|
2007
|
+
totalEntries += entries.size;
|
|
2008
|
+
maxPerProvider = Math.max(maxPerProvider, entries.size);
|
|
2009
|
+
for (const entry of entries.values()) {
|
|
2010
|
+
if (Date.now() - entry.at >= SELF_CHECK_COOLDOWN_MS) expiredEntries += 1;
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
return {
|
|
2014
|
+
providers: lastChecks.size,
|
|
2015
|
+
pending: selfChecks.size,
|
|
2016
|
+
expiredEntries,
|
|
2017
|
+
totalEntries,
|
|
2018
|
+
maxPerProvider
|
|
2019
|
+
};
|
|
2020
|
+
})()
|
|
1799
2021
|
})
|
|
1800
2022
|
});
|
|
1801
2023
|
}
|
|
@@ -1809,12 +2031,19 @@ export {
|
|
|
1809
2031
|
buildRouteEntry,
|
|
1810
2032
|
closeTable,
|
|
1811
2033
|
createProxyTable,
|
|
2034
|
+
deadRuleEntries,
|
|
1812
2035
|
defaultProxyProblem,
|
|
2036
|
+
hostMatches,
|
|
2037
|
+
hostRuleProblem,
|
|
1813
2038
|
inject,
|
|
1814
2039
|
name,
|
|
1815
2040
|
normalizeDefaultProxy,
|
|
1816
2041
|
normalizeRoute,
|
|
2042
|
+
parseHostList,
|
|
1817
2043
|
proxyHostProblem,
|
|
1818
|
-
proxyUrl
|
|
2044
|
+
proxyUrl,
|
|
2045
|
+
routeFingerprint,
|
|
2046
|
+
routeStateDecision,
|
|
2047
|
+
splitHostRules
|
|
1819
2048
|
};
|
|
1820
2049
|
//# sourceMappingURL=index.js.map
|