dsh-provider-proxy 0.6.0 → 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 CHANGED
@@ -77,6 +77,11 @@ Host catalog 是否列出某个 provider(`live`)不影响已保存路由的
77
77
  因为成功判据在响应头到达时就已成立(流式转发不能靠消费 body 来判断)。
78
78
  「最近调用」的时间取请求**发起**时刻;「最近成功 / 失败」的结果与时间来自最近一次**结束**的请求——
79
79
  两者分开记录,并发调用下不会把一次请求的发起时间配到另一次的结果上。
80
+ **这条记录只属于当前路由配置**:改代理端点、凭据、`useDefault` 的默认端点、目标域名规则或
81
+ `localDirect` 后,该 provider 的「最近调用」会变成「尚无代理记录」;`testUrl` / `testTarget`
82
+ 只影响测试,不会清掉它。仅关闭再打开代理开关也保留记录。修改配置前已经启动的请求不会把旧结果
83
+ 写回新配置;删除后重新添加同名 provider 同样视为一条新路由。`hits` 命中计数是历史累计,
84
+ 上述配置变化都不会清空它。
80
85
 
81
86
  页面顶部与顶层还有三项整页设置(写在 `cordis.patch.yml`,也可以直接编辑):
82
87
 
package/lib/index.js CHANGED
@@ -1203,6 +1203,28 @@ function deadRuleEntries(raw) {
1203
1203
  }
1204
1204
 
1205
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
+ }
1206
1228
  function proxyUrl(route) {
1207
1229
  const credentials = route.username.length === 0 ? "" : route.password.length > 0 && route.protocol !== "socks5" ? `${encodeURIComponent(route.username)}:${encodeURIComponent(route.password)}@` : `${encodeURIComponent(route.username)}@`;
1208
1230
  const host = route.host.includes(":") && !route.host.startsWith("[") ? `[${route.host}]` : route.host;
@@ -1372,12 +1394,16 @@ function apply(ctx, config) {
1372
1394
  let closed = false;
1373
1395
  let webAvailable = false;
1374
1396
  const hits = /* @__PURE__ */ new Map();
1397
+ const routeStates = /* @__PURE__ */ new Map();
1398
+ let nextRouteToken = 1;
1375
1399
  const lastUse = /* @__PURE__ */ new Map();
1376
- const bump = (provider) => {
1400
+ const bump = (provider, token) => {
1377
1401
  hits.set(provider, (hits.get(provider) ?? 0) + 1);
1402
+ if (routeStates.get(provider)?.token !== token) return;
1378
1403
  lastUse.set(provider, { ...lastUse.get(provider), startedAt: Date.now() });
1379
1404
  };
1380
- const settle = (provider) => (ok, detail) => {
1405
+ const settle = (provider, token) => (ok, detail) => {
1406
+ if (routeStates.get(provider)?.token !== token) return;
1381
1407
  const previous = lastUse.get(provider);
1382
1408
  lastUse.set(provider, {
1383
1409
  startedAt: previous?.startedAt ?? Date.now(),
@@ -1393,7 +1419,60 @@ function apply(ctx, config) {
1393
1419
  const MAX_REQUEST_BYTES = 8 * 1024;
1394
1420
  const selfChecks = /* @__PURE__ */ new Map();
1395
1421
  const SELF_CHECK_COOLDOWN_MS = 3e3;
1422
+ const MAX_CHECK_ENTRIES_PER_PROVIDER = 8;
1423
+ const MAX_CHECK_PROVIDERS = 64;
1396
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
+ };
1397
1476
  const plainTestUrls = () => {
1398
1477
  const raw = config?.testUrl;
1399
1478
  const snapshot2 = raw?.get;
@@ -1651,17 +1730,56 @@ function apply(ctx, config) {
1651
1730
  ctx.logger.info("dsh-provider-proxy mounted (pid %d)", process.pid);
1652
1731
  } catch {
1653
1732
  }
1654
- const rebuild = async () => {
1655
- const next = createProxyTable(plainProviders(config), {
1656
- defaultProxy: plainDefaultProxy(config),
1657
- localDirect: plainLocalDirect(config)
1658
- });
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 });
1659
1738
  const previous = current;
1660
1739
  current = next.entries;
1661
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();
1662
1766
  void closeTable(previous);
1767
+ return Promise.resolve();
1663
1768
  };
1664
- const ready = rebuild();
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();
1665
1783
  const catalog = () => {
1666
1784
  const live = /* @__PURE__ */ new Set();
1667
1785
  const out = [];
@@ -1777,16 +1895,20 @@ function apply(ctx, config) {
1777
1895
  send(400, { ok: false, kind: "body", error: "\u7F3A\u5C11 provider" });
1778
1896
  return;
1779
1897
  }
1780
- const key = selfCheckKey(provider);
1898
+ const key = `${provider}\0${selfCheckKey(provider)}`;
1781
1899
  let pending = selfChecks.get(key);
1782
1900
  if (pending === void 0) {
1783
- const last = lastChecks.get(key);
1901
+ pruneCheckCache();
1902
+ const last = lastChecks.get(provider)?.get(checkKeyOf(provider, key));
1784
1903
  if (last !== void 0 && Date.now() - last.at < SELF_CHECK_COOLDOWN_MS) {
1785
1904
  send(200, { ...last.result, cached: true });
1786
1905
  return;
1787
1906
  }
1907
+ const checkToken = routeStates.get(provider)?.token;
1788
1908
  pending = runSelfCheck(provider).then((result) => {
1789
- lastChecks.set(key, { at: Date.now(), result });
1909
+ if (checkToken !== void 0 && routeStates.get(provider)?.token === checkToken) {
1910
+ rememberCheck(provider, key, result);
1911
+ }
1790
1912
  return result;
1791
1913
  });
1792
1914
  selfChecks.set(key, pending);
@@ -1813,11 +1935,13 @@ function apply(ctx, config) {
1813
1935
  const patched = function(request, signal) {
1814
1936
  const route = current.get(WEB_SEARCH_KEY);
1815
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);
1816
1940
  return router.run(
1817
1941
  {
1818
1942
  dispatcher: route.dispatcher,
1819
- onHit: () => bump(WEB_SEARCH_KEY),
1820
- onSettle: settle(WEB_SEARCH_KEY),
1943
+ onHit: () => bump(WEB_SEARCH_KEY, token),
1944
+ onSettle: settle(WEB_SEARCH_KEY, token),
1821
1945
  decide: route.decide
1822
1946
  },
1823
1947
  () => original.call(this, request, signal)
@@ -1837,11 +1961,13 @@ function apply(ctx, config) {
1837
1961
  if (typeof provider !== "string") return next();
1838
1962
  const route = current.get(provider);
1839
1963
  if (route === void 0) return next();
1964
+ const token = routeStates.get(provider)?.token;
1965
+ if (token === void 0) return next();
1840
1966
  return router.wrap(
1841
1967
  {
1842
1968
  dispatcher: route.dispatcher,
1843
- onHit: () => bump(provider),
1844
- onSettle: settle(provider),
1969
+ onHit: () => bump(provider, token),
1970
+ onSettle: settle(provider, token),
1845
1971
  decide: route.decide
1846
1972
  },
1847
1973
  next
@@ -1850,14 +1976,14 @@ function apply(ctx, config) {
1850
1976
  { global: true }
1851
1977
  );
1852
1978
  const onConfig = () => {
1853
- void rebuild();
1979
+ void enqueueRebuild();
1854
1980
  };
1855
1981
  ctx.on("loader/volatile-update", onConfig, { global: true });
1856
1982
  return async () => {
1857
1983
  stop();
1858
1984
  if (closed) return;
1859
1985
  closed = true;
1860
- await ready.catch(() => void 0);
1986
+ await rebuildQueue.catch(() => void 0);
1861
1987
  await closeTable(current);
1862
1988
  current = /* @__PURE__ */ new Map();
1863
1989
  router.release();
@@ -1872,7 +1998,26 @@ function apply(ctx, config) {
1872
1998
  })),
1873
1999
  problems: Object.fromEntries(problems),
1874
2000
  hits: Object.fromEntries(hits),
1875
- 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
+ })()
1876
2021
  })
1877
2022
  });
1878
2023
  }
@@ -1897,6 +2042,8 @@ export {
1897
2042
  parseHostList,
1898
2043
  proxyHostProblem,
1899
2044
  proxyUrl,
2045
+ routeFingerprint,
2046
+ routeStateDecision,
1900
2047
  splitHostRules
1901
2048
  };
1902
2049
  //# sourceMappingURL=index.js.map