dsh-host-router 0.1.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 +67 -0
- package/cordis.patch.yml +3 -0
- package/dist/client.cjs +203 -0
- package/dist/client.cjs.map +1 -0
- package/lib/config.js +51 -0
- package/lib/config.js.map +1 -0
- package/lib/index.js +367 -0
- package/lib/index.js.map +1 -0
- package/lib/routing-dispatcher.js +38 -0
- package/lib/routing-dispatcher.js.map +1 -0
- package/lib/seen.js +104 -0
- package/lib/seen.js.map +1 -0
- package/lib/types/config.d.ts +13 -0
- package/lib/types/index.d.ts +2 -0
- package/lib/types/routing-dispatcher.d.ts +14 -0
- package/lib/types/seen.d.ts +22 -0
- package/package.json +81 -0
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# dsh-host-router
|
|
2
|
+
|
|
3
|
+
dsh 外挂式网络路由插件:按 **hostname** 给 dsh 的请求分流——勾选的域名走本地代理(Clash 等),其余直连。内置嗅探,自动记录 dsh 访问过的 hostname,设置页勾选即生效。
|
|
4
|
+
|
|
5
|
+
## 跟 dsh-llm-proxy 的区别
|
|
6
|
+
|
|
7
|
+
| | dsh-llm-proxy | dsh-host-router |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| 路由维度 | 按模型(解出 baseURL host) | 按 hostname 通用 |
|
|
10
|
+
| 覆盖范围 | 仅 LLM 请求 | LLM + 市场目录 + 截图源 + 任意 dsh 网络请求 |
|
|
11
|
+
| 配置 | dsh Config(live) | 独立 config.json(重启生效) |
|
|
12
|
+
| 扫描清单 | 无 | 嗅探 + 主动扫(读 settings.yaml provider) |
|
|
13
|
+
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
dsh plugin --profile web add D:/ai/dsh/dsh-host-router
|
|
18
|
+
```
|
|
19
|
+
重启 `dsh web`(托盘退出 → 启动)。
|
|
20
|
+
|
|
21
|
+
## 使用
|
|
22
|
+
|
|
23
|
+
1. 设置页 → 「网络路由」(settings.section 独立页)。
|
|
24
|
+
2. 点「扫描配置」:读 dsh 已装 provider(`~/.dsh/settings.yaml` 的 baseURL)+ 常见境外兜底,批量直连探测,标出"直连不通→建议代理"。
|
|
25
|
+
3. 勾选要走代理的域名 → 「保存勾选」写回 `~/.dsh/host-router/config.json`。
|
|
26
|
+
4. 「保存勾选」即**即时生效**(live reload,免重启;只有装/卸插件才需要重启 dsh)。
|
|
27
|
+
5. 「刷新」看嗅探实时清单(dsh 用一会儿后,实际访问过的 host 带 状态/耗时/次数)。
|
|
28
|
+
|
|
29
|
+
## 配置
|
|
30
|
+
|
|
31
|
+
`~/.dsh/host-router/config.json`:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"proxyHost": "127.0.0.1",
|
|
36
|
+
"proxyPort": 7897,
|
|
37
|
+
"proxiedHosts": ["api.openai.com", "awesome-dsh-plugin.com"],
|
|
38
|
+
"sniff": true
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`proxiedHosts` 命中(含子域后缀 `xxx.example.com` 命中 `example.com`)走代理;loopback 永远直连。
|
|
43
|
+
|
|
44
|
+
## 构建
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
node scripts/build.mjs
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
跨平台(Windows/macOS/Linux)。工具链定位:优先本机 `node_modules`(标准 `npm install` 流程);本开发机故意不装 devDeps,自动回退复用 `D:/ai/dsh/dsh/dsh-capture/node_modules` 借来的 tsc/tsdown(host tsc + client tsdown factory bundle)。可用 `DSH_CAPTURE_NODE_MODULES` 指定工具链目录。`npm publish` 前会自动执行本脚本(prepublishOnly)。
|
|
51
|
+
|
|
52
|
+
## 机制
|
|
53
|
+
|
|
54
|
+
- `globalThis.fetch` 替换(保留原引用,dispose 时还原):白名单 host → undici fetch + ProxyAgent(Clash 等);其余 → 原内置 fetch 原样直连;loopback 永远直连。
|
|
55
|
+
- 嗅探在 myFetch 内联:每次请求按 host 聚合(状态/耗时/次数,同步落 `~/.dsh/host-router/log.txt`),喂设置页清单。
|
|
56
|
+
- ProxyAgent `clientFactory pipelining:0`:避 Clash 关 idle 隧道复用死连接。
|
|
57
|
+
- 保存即热生效:`/save` 写完 config.json 立刻重建 myFetch(毫秒级,免重启)。
|
|
58
|
+
- 设置页:`settings.section` slot + React(纯 createElement),host `webServer.register` 前缀路由 `/api/dsh-host-router/*`(loopback-only 鉴权)。
|
|
59
|
+
- 纯外挂:`dependencies: {}`,运行时零 require,宿主 ctx 注入;不碰 settings.yaml provider 配置。
|
|
60
|
+
|
|
61
|
+
## 为什么用独立 config.json(B 方案)
|
|
62
|
+
|
|
63
|
+
配置走独立 `~/.dsh/host-router/config.json`,而非 dsh 的 schemastery Config(live)。收益:省掉 settings binder(~460 行,绑 dsh 内部 API),稳、轻、跟 dsh 版本解耦。代价只是「live 重载为整体重建 myFetch」——而 `/save` 已经做到保存即生效,免重启。
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
MIT
|
package/cordis.patch.yml
ADDED
package/dist/client.cjs
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "dsh-host-router",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
//#region rolldown:runtime
|
|
8
|
+
var __create = Object.create;
|
|
9
|
+
var __defProp = Object.defineProperty;
|
|
10
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
11
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
12
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
16
|
+
key = keys[i];
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
18
|
+
get: ((k) => from[k]).bind(null, key),
|
|
19
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
25
|
+
value: mod,
|
|
26
|
+
enumerable: true
|
|
27
|
+
}) : target, mod));
|
|
28
|
+
|
|
29
|
+
//#endregion
|
|
30
|
+
const react = __toESM(require("react"));
|
|
31
|
+
|
|
32
|
+
//#region src/client/index.ts
|
|
33
|
+
const inject = ["slots"];
|
|
34
|
+
const API = "/api/dsh-host-router";
|
|
35
|
+
const STYLES = `
|
|
36
|
+
.hr-page{font-family:ui-monospace,monospace;font-size:13px;line-height:1.55;padding:10px 14px;max-width:900px}
|
|
37
|
+
.hr-page h3{margin:0 0 4px;font-size:14px}
|
|
38
|
+
.hr-ep{color:var(--theme-text-secondary,#999);margin:0 0 6px;font-size:12px}
|
|
39
|
+
.hr-hint{color:var(--theme-text-tertiary,#777);font-size:12px;margin:0 0 10px}
|
|
40
|
+
.hr-bar{display:flex;gap:6px;align-items:center;margin-bottom:8px;flex-wrap:wrap}
|
|
41
|
+
.hr-btn{background:var(--theme-accent,#4a9eff);color:#fff;border:none;border-radius:6px;padding:4px 10px;cursor:pointer;font-size:12px;white-space:nowrap}
|
|
42
|
+
.hr-btn.ghost{background:transparent;border:1px solid var(--theme-border,#444);color:var(--theme-text,#ccc)}
|
|
43
|
+
.hr-btn:disabled{opacity:.45;cursor:not-allowed}
|
|
44
|
+
.hr-msg{color:var(--theme-text-secondary,#999);font-size:12px;padding:5px 8px;border-radius:6px;background:var(--theme-input-bg,#111);border:1px solid var(--theme-border,#333);white-space:pre-wrap;max-height:90px;overflow:auto;margin:6px 0}
|
|
45
|
+
.table-wrap{overflow:auto;max-height:55vh;margin-bottom:10px}
|
|
46
|
+
table{border-collapse:separate;border-spacing:0;width:auto;table-layout:fixed}
|
|
47
|
+
th,td{border-bottom:1px solid var(--theme-border,#333);padding:3px 6px;text-align:left;font-size:12px;white-space:nowrap}
|
|
48
|
+
td{overflow:hidden;text-overflow:ellipsis}
|
|
49
|
+
th{font-size:11px;color:var(--theme-text-secondary,#999);font-weight:600}
|
|
50
|
+
thead th{position:sticky;top:0;background:var(--theme-bg,#1b1b1c);z-index:1}
|
|
51
|
+
td.c,th.c{text-align:center}
|
|
52
|
+
tr.stale-row{opacity:.5}
|
|
53
|
+
.st-ok{color:#2ecc71}.st-fail{color:#e74c3c}.st-gray{color:#888}
|
|
54
|
+
.empty{padding:12px;text-align:center;color:var(--theme-text-secondary,#888)}
|
|
55
|
+
.hr-tip{position:fixed;z-index:1000;pointer-events:none;background:var(--theme-input-bg,#111);border:1px solid var(--theme-border,#444);color:var(--theme-text,#ccc);padding:4px 8px;border-radius:5px;font-size:12px;max-width:440px;white-space:normal;word-break:break-all;box-shadow:0 2px 8px rgba(0,0,0,.35)}
|
|
56
|
+
.h4{margin:14px 0 4px;font-size:12px;color:var(--theme-text-secondary,#aaa)}
|
|
57
|
+
`;
|
|
58
|
+
async function jget(path) {
|
|
59
|
+
return fetch(API + path).then((r) => r.json()).catch(() => null);
|
|
60
|
+
}
|
|
61
|
+
async function jpost(path, body) {
|
|
62
|
+
return fetch(API + path, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: { "content-type": "application/json" },
|
|
65
|
+
body: body ? JSON.stringify(body) : void 0
|
|
66
|
+
}).then((r) => r.json()).catch(() => null);
|
|
67
|
+
}
|
|
68
|
+
function HostRouterPage() {
|
|
69
|
+
const [cfg, setCfg] = react.default.useState({});
|
|
70
|
+
const [draft, setDraft] = react.default.useState(/* @__PURE__ */ new Set());
|
|
71
|
+
const [seen, setSeen] = react.default.useState([]);
|
|
72
|
+
const [msg, setMsg] = react.default.useState("");
|
|
73
|
+
const [busy, setBusy] = react.default.useState(false);
|
|
74
|
+
const [tip, setTip] = react.default.useState(null);
|
|
75
|
+
const showTip = (ev, text) => setTip({
|
|
76
|
+
x: Math.min(ev.clientX + 12, window.innerWidth - 452),
|
|
77
|
+
y: ev.clientY + 16,
|
|
78
|
+
text
|
|
79
|
+
});
|
|
80
|
+
const hideTip = () => setTip(null);
|
|
81
|
+
const refreshData = react.default.useCallback(async () => {
|
|
82
|
+
const [seenR, cfgR] = await Promise.all([jget("/seen"), jget("/config")]);
|
|
83
|
+
if (cfgR?.ok?.value) setCfg(cfgR.value);
|
|
84
|
+
setSeen(seenR?.ok ? seenR.hosts : []);
|
|
85
|
+
}, []);
|
|
86
|
+
const refreshAll = react.default.useCallback(async () => {
|
|
87
|
+
const [seenR, cfgR] = await Promise.all([jget("/seen"), jget("/config")]);
|
|
88
|
+
if (cfgR?.ok?.value) {
|
|
89
|
+
setCfg(cfgR.value);
|
|
90
|
+
setDraft(new Set(cfgR.value.proxiedHosts ?? []));
|
|
91
|
+
}
|
|
92
|
+
setSeen(seenR?.ok ? seenR.hosts : []);
|
|
93
|
+
}, []);
|
|
94
|
+
react.default.useEffect(() => {
|
|
95
|
+
refreshAll();
|
|
96
|
+
const t = window.setInterval(refreshData, 5e3);
|
|
97
|
+
return () => window.clearInterval(t);
|
|
98
|
+
}, [refreshAll, refreshData]);
|
|
99
|
+
const toggle = (h) => setDraft((d) => {
|
|
100
|
+
const n = new Set(d);
|
|
101
|
+
n.has(h) ? n.delete(h) : n.add(h);
|
|
102
|
+
return n;
|
|
103
|
+
});
|
|
104
|
+
const doScan = async () => {
|
|
105
|
+
setMsg("扫描中…");
|
|
106
|
+
setBusy(true);
|
|
107
|
+
const r = await jpost("/scan");
|
|
108
|
+
setBusy(false);
|
|
109
|
+
if (!r?.ok) {
|
|
110
|
+
setMsg("扫描失败");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
setMsg(`扫描完成,${r.hosts.filter((h) => h.needsProxy).length} 个直连不通(建议代理)`);
|
|
114
|
+
await refreshData();
|
|
115
|
+
};
|
|
116
|
+
const doSave = async () => {
|
|
117
|
+
setBusy(true);
|
|
118
|
+
setMsg("保存中…");
|
|
119
|
+
const r = await jpost("/save", { proxiedHosts: [...draft] });
|
|
120
|
+
setBusy(false);
|
|
121
|
+
setMsg(r?.ok ? "已保存并即时生效" : "保存失败");
|
|
122
|
+
if (r?.ok) refreshData();
|
|
123
|
+
};
|
|
124
|
+
const doTest = async (h) => {
|
|
125
|
+
const r = await jpost("/test", { host: h });
|
|
126
|
+
setMsg(`${h}: ${r.status} · ${r.ms}ms · ${r.proxy ? "代理" : "直连"}` + (r.message ? ` · ${r.message}` : ""));
|
|
127
|
+
};
|
|
128
|
+
const th = (txt, c = true) => react.default.createElement("th", {
|
|
129
|
+
className: c ? "c" : "",
|
|
130
|
+
key: txt
|
|
131
|
+
}, txt);
|
|
132
|
+
const endpoint = `${cfg.proxyHost ?? "127.0.0.1"}:${cfg.proxyPort ?? 7897}`;
|
|
133
|
+
const doCleanStale = async () => {
|
|
134
|
+
setBusy(true);
|
|
135
|
+
setMsg("清理过期…");
|
|
136
|
+
const r = await jpost("/clean-stale");
|
|
137
|
+
setBusy(false);
|
|
138
|
+
setMsg(r?.ok ? `已清理 ${r.removed?.length ?? 0} 个过期 host:${(r.removed ?? []).join(", ") || "无"}` : "清理失败");
|
|
139
|
+
if (r?.ok) refreshAll();
|
|
140
|
+
};
|
|
141
|
+
return react.default.createElement("div", { className: "hr-page" }, react.default.createElement("style", null, STYLES), react.default.createElement("h3", null, "网络路由(dsh-host-router)"), react.default.createElement("p", { className: "hr-ep" }, "代理: ", react.default.createElement("code", null, endpoint)), react.default.createElement("p", { className: "hr-hint" }, "勾选走代理,保存即生效。绿=本次扫到在用,灰=本次没扫到(几次重启都灰可删),过期=白名单里但久未用。"), react.default.createElement("div", { className: "hr-bar" }, react.default.createElement("button", {
|
|
142
|
+
className: "hr-btn",
|
|
143
|
+
onClick: doScan,
|
|
144
|
+
disabled: busy
|
|
145
|
+
}, "扫描配置"), react.default.createElement("button", {
|
|
146
|
+
className: "hr-btn ghost",
|
|
147
|
+
onClick: doSave,
|
|
148
|
+
disabled: busy
|
|
149
|
+
}, "保存勾选"), react.default.createElement("button", {
|
|
150
|
+
className: "hr-btn ghost",
|
|
151
|
+
onClick: doCleanStale,
|
|
152
|
+
disabled: busy
|
|
153
|
+
}, "清理过期"), react.default.createElement("button", {
|
|
154
|
+
className: "hr-btn ghost",
|
|
155
|
+
onClick: refreshData
|
|
156
|
+
}, "刷新")), msg && react.default.createElement("div", { className: "hr-msg" }, msg), react.default.createElement("table", null, react.default.createElement("colgroup", null, react.default.createElement("col", { style: { width: "200px" } }), react.default.createElement("col", { style: { width: "44px" } }), react.default.createElement("col", { style: { width: "64px" } }), react.default.createElement("col", { style: { width: "68px" } }), react.default.createElement("col", { style: { width: "56px" } }), react.default.createElement("col", { style: { width: "76px" } }), react.default.createElement("col", { style: { width: "44px" } })), react.default.createElement("thead", null, react.default.createElement("tr", null, th("hostname", false), th("代理"), th("归属"), th("本次命中"), th("状态"), th("耗时"), th("测"))), react.default.createElement("tbody", null, seen.length === 0 ? react.default.createElement("tr", null, react.default.createElement("td", {
|
|
157
|
+
className: "empty",
|
|
158
|
+
colSpan: 5
|
|
159
|
+
}, "暂无数据,先用 dsh 一会儿或点「扫描配置」")) : seen.map((h) => {
|
|
160
|
+
const alive = h.alive !== false && (h.count ?? 0) > 0;
|
|
161
|
+
return react.default.createElement("tr", {
|
|
162
|
+
key: h.host,
|
|
163
|
+
className: h.stale ? "stale-row" : void 0
|
|
164
|
+
}, react.default.createElement("td", {
|
|
165
|
+
onMouseEnter: (e) => showTip(e, h.host),
|
|
166
|
+
onMouseLeave: hideTip
|
|
167
|
+
}, h.host), react.default.createElement("td", { className: "c" }, react.default.createElement("input", {
|
|
168
|
+
type: "checkbox",
|
|
169
|
+
checked: draft.has(h.host),
|
|
170
|
+
onChange: () => toggle(h.host)
|
|
171
|
+
})), react.default.createElement("td", {
|
|
172
|
+
className: "c",
|
|
173
|
+
onMouseEnter: (e) => showTip(e, `${h.owner ?? "未知"} · ${h.source ?? "-"}`),
|
|
174
|
+
onMouseLeave: hideTip
|
|
175
|
+
}, h.owner ?? "未知"), react.default.createElement("td", { className: "c" }, react.default.createElement("span", { className: alive ? "st-ok" : "st-gray" }, alive ? `●${h.count}` : "○")), react.default.createElement("td", { className: "c" }, react.default.createElement("span", { className: h.lastStatus === "ERR" ? "st-fail" : "st-ok" }, String(h.lastStatus ?? "—"))), react.default.createElement("td", { className: "c" }, h.lastMs != null ? `${h.lastMs}ms` : ""), react.default.createElement("td", { className: "c" }, react.default.createElement("button", {
|
|
176
|
+
className: "hr-btn ghost",
|
|
177
|
+
onClick: () => doTest(h.host),
|
|
178
|
+
style: { padding: "0 6px" }
|
|
179
|
+
}, "测")));
|
|
180
|
+
}))), tip && react.default.createElement("div", {
|
|
181
|
+
className: "hr-tip",
|
|
182
|
+
style: {
|
|
183
|
+
left: tip.x,
|
|
184
|
+
top: tip.y
|
|
185
|
+
}
|
|
186
|
+
}, tip.text));
|
|
187
|
+
}
|
|
188
|
+
function apply(ctx) {
|
|
189
|
+
ctx.effect(() => ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
190
|
+
name: "settings.section",
|
|
191
|
+
id: "dsh-host-router",
|
|
192
|
+
order: 60,
|
|
193
|
+
label: "网络路由"
|
|
194
|
+
}, (props) => react.default.createElement(HostRouterPage, props))), "dsh-host-router: settings page");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
//#endregion
|
|
198
|
+
exports.apply = apply;
|
|
199
|
+
exports.inject = inject;
|
|
200
|
+
return module.exports;
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
//# sourceMappingURL=client.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.cjs","names":["path: string","body?: any","ev: any","text: string","h: string","h: any","txt: string","e: any","ctx: ClientContext","props: any"],"sources":["../src/client/index.ts"],"sourcesContent":["/**\n * dsh-host-router · client 设置页(纯 React)。\n *\n * 挂 settings.section slot:设置页里独立一页「网络路由」。\n * 通信:同源 fetch → host webServer API(/api/dsh-host-router/{seen,scan,save,test,config})。\n *\n * 渲染:纯 React(createElement + hooks),React 管 mount/unmount,切走回来自动重渲染不丢。\n * (之前 useEffect+ref 塞原生 DOM,切走回来组件未 remount 时 DOM 丢失 → 改纯 React。)\n */\nimport React from 'react'\n\ntype SlotsService = any\ntype ClientContext = { slots: SlotsService }\n\nexport const inject = ['slots']\n\nconst API = '/api/dsh-host-router'\n\nconst STYLES = `\n.hr-page{font-family:ui-monospace,monospace;font-size:13px;line-height:1.55;padding:10px 14px;max-width:900px}\n.hr-page h3{margin:0 0 4px;font-size:14px}\n.hr-ep{color:var(--theme-text-secondary,#999);margin:0 0 6px;font-size:12px}\n.hr-hint{color:var(--theme-text-tertiary,#777);font-size:12px;margin:0 0 10px}\n.hr-bar{display:flex;gap:6px;align-items:center;margin-bottom:8px;flex-wrap:wrap}\n.hr-btn{background:var(--theme-accent,#4a9eff);color:#fff;border:none;border-radius:6px;padding:4px 10px;cursor:pointer;font-size:12px;white-space:nowrap}\n.hr-btn.ghost{background:transparent;border:1px solid var(--theme-border,#444);color:var(--theme-text,#ccc)}\n.hr-btn:disabled{opacity:.45;cursor:not-allowed}\n.hr-msg{color:var(--theme-text-secondary,#999);font-size:12px;padding:5px 8px;border-radius:6px;background:var(--theme-input-bg,#111);border:1px solid var(--theme-border,#333);white-space:pre-wrap;max-height:90px;overflow:auto;margin:6px 0}\n.table-wrap{overflow:auto;max-height:55vh;margin-bottom:10px}\ntable{border-collapse:separate;border-spacing:0;width:auto;table-layout:fixed}\nth,td{border-bottom:1px solid var(--theme-border,#333);padding:3px 6px;text-align:left;font-size:12px;white-space:nowrap}\ntd{overflow:hidden;text-overflow:ellipsis}\nth{font-size:11px;color:var(--theme-text-secondary,#999);font-weight:600}\nthead th{position:sticky;top:0;background:var(--theme-bg,#1b1b1c);z-index:1}\ntd.c,th.c{text-align:center}\ntr.stale-row{opacity:.5}\n.st-ok{color:#2ecc71}.st-fail{color:#e74c3c}.st-gray{color:#888}\n.empty{padding:12px;text-align:center;color:var(--theme-text-secondary,#888)}\n.hr-tip{position:fixed;z-index:1000;pointer-events:none;background:var(--theme-input-bg,#111);border:1px solid var(--theme-border,#444);color:var(--theme-text,#ccc);padding:4px 8px;border-radius:5px;font-size:12px;max-width:440px;white-space:normal;word-break:break-all;box-shadow:0 2px 8px rgba(0,0,0,.35)}\n.h4{margin:14px 0 4px;font-size:12px;color:var(--theme-text-secondary,#aaa)}\n`\n\nasync function jget(path: string): Promise<any> { return fetch(API + path).then((r) => r.json()).catch(() => null) }\nasync function jpost(path: string, body?: any): Promise<any> {\n return fetch(API + path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: body ? JSON.stringify(body) : undefined })\n .then((r) => r.json()).catch(() => null)\n}\n\ninterface SeenHost {\n host: string; lastStatus?: number | string; lastMs?: number; count?: number\n owner?: string; source?: string; proxied?: boolean; hits?: number; stale?: boolean; alive?: boolean\n}\ninterface Cfg { proxyHost?: string; proxyPort?: number; proxiedHosts?: string[] }\n\nfunction HostRouterPage() {\n const [cfg, setCfg] = React.useState<Cfg>({})\n const [draft, setDraft] = React.useState<Set<string>>(new Set())\n const [seen, setSeen] = React.useState<SeenHost[]>([])\n const [msg, setMsg] = React.useState('')\n const [busy, setBusy] = React.useState(false)\n const [tip, setTip] = React.useState<{ x: number; y: number; text: string } | null>(null)\n\n // 超长内容浮窗(fixed 定位,不被表格 overflow 裁剪;右缘 clamp 防出屏)。\n const showTip = (ev: any, text: string) => setTip({ x: Math.min(ev.clientX + 12, window.innerWidth - 452), y: ev.clientY + 16, text })\n const hideTip = () => setTip(null)\n\n // 只刷数据(seen/cfg),不重置草稿:轮询与手动刷新不覆盖用户编辑到一半的勾选(2026-08-29)。\n const refreshData = React.useCallback(async () => {\n const [seenR, cfgR] = await Promise.all([jget('/seen'), jget('/config')])\n if (cfgR?.ok?.value) setCfg(cfgR.value)\n setSeen(seenR?.ok ? seenR.hosts : [])\n }, [])\n // 全量刷新:数据 + 草稿同步磁盘白名单(首次加载/清理过期后用)。\n const refreshAll = React.useCallback(async () => {\n const [seenR, cfgR] = await Promise.all([jget('/seen'), jget('/config')])\n if (cfgR?.ok?.value) { setCfg(cfgR.value); setDraft(new Set(cfgR.value.proxiedHosts ?? [])) }\n setSeen(seenR?.ok ? seenR.hosts : [])\n }, [])\n\n React.useEffect(() => {\n refreshAll()\n const t = window.setInterval(refreshData, 5000)\n return () => window.clearInterval(t)\n }, [refreshAll, refreshData])\n\n const toggle = (h: string) => setDraft((d) => { const n = new Set(d); n.has(h) ? n.delete(h) : n.add(h); return n })\n\n const doScan = async () => {\n setMsg('扫描中…'); setBusy(true)\n const r = await jpost('/scan')\n setBusy(false)\n if (!r?.ok) { setMsg('扫描失败'); return }\n setMsg(`扫描完成,${r.hosts.filter((h: any) => h.needsProxy).length} 个直连不通(建议代理)`)\n await refreshData() // 扫描结果已 upsert 进 seen,刷新拉合并表(不动草稿)\n }\n\n const doSave = async () => {\n setBusy(true); setMsg('保存中…')\n const r = await jpost('/save', { proxiedHosts: [...draft] })\n setBusy(false)\n setMsg(r?.ok ? '已保存并即时生效' : '保存失败')\n if (r?.ok) refreshData()\n }\n\n const doTest = async (h: string) => {\n const r = await jpost('/test', { host: h })\n setMsg(`${h}: ${r.status} · ${r.ms}ms · ${r.proxy ? '代理' : '直连'}` + (r.message ? ` · ${r.message}` : ''))\n }\n\n const th = (txt: string, c = true) => React.createElement('th', { className: c ? 'c' : '', key: txt }, txt)\n const endpoint = `${cfg.proxyHost ?? '127.0.0.1'}:${cfg.proxyPort ?? 7897}`\n\n const doCleanStale = async () => {\n setBusy(true); setMsg('清理过期…')\n const r = await jpost('/clean-stale')\n setBusy(false)\n setMsg(r?.ok ? `已清理 ${r.removed?.length ?? 0} 个过期 host:${(r.removed ?? []).join(', ') || '无'}` : '清理失败')\n if (r?.ok) refreshAll() // 磁盘白名单被清理,草稿需同步\n }\n\n return React.createElement('div', { className: 'hr-page' },\n React.createElement('style', null, STYLES),\n React.createElement('h3', null, '网络路由(dsh-host-router)'),\n React.createElement('p', { className: 'hr-ep' }, '代理: ', React.createElement('code', null, endpoint)),\n React.createElement('p', { className: 'hr-hint' }, '勾选走代理,保存即生效。绿=本次扫到在用,灰=本次没扫到(几次重启都灰可删),过期=白名单里但久未用。'),\n React.createElement('div', { className: 'hr-bar' },\n React.createElement('button', { className: 'hr-btn', onClick: doScan, disabled: busy }, '扫描配置'),\n React.createElement('button', { className: 'hr-btn ghost', onClick: doSave, disabled: busy }, '保存勾选'),\n React.createElement('button', { className: 'hr-btn ghost', onClick: doCleanStale, disabled: busy }, '清理过期'),\n React.createElement('button', { className: 'hr-btn ghost', onClick: refreshData }, '刷新'),\n ),\n msg && React.createElement('div', { className: 'hr-msg' }, msg),\n // 合一表:嗅探 + 白名单(没扫到也显示);精简 5 列,tip 浮窗展示超长内容\n React.createElement('table', null,\n React.createElement('colgroup', null,\n React.createElement('col', { style: { width: '200px' } }),\n React.createElement('col', { style: { width: '44px' } }),\n React.createElement('col', { style: { width: '64px' } }),\n React.createElement('col', { style: { width: '68px' } }),\n React.createElement('col', { style: { width: '56px' } }),\n React.createElement('col', { style: { width: '76px' } }),\n React.createElement('col', { style: { width: '44px' } }),\n ),\n React.createElement('thead', null, React.createElement('tr', null,\n th('hostname', false), th('代理'), th('归属'), th('本次命中'), th('状态'), th('耗时'), th('测'))),\n React.createElement('tbody', null,\n seen.length === 0\n ? React.createElement('tr', null, React.createElement('td', { className: 'empty', colSpan: 5 }, '暂无数据,先用 dsh 一会儿或点「扫描配置」'))\n : seen.map((h: any) => {\n const alive = h.alive !== false && (h.count ?? 0) > 0\n return React.createElement('tr', { key: h.host, className: h.stale ? 'stale-row' : undefined },\n React.createElement('td', { onMouseEnter: (e: any) => showTip(e, h.host), onMouseLeave: hideTip }, h.host),\n React.createElement('td', { className: 'c' }, React.createElement('input', { type: 'checkbox', checked: draft.has(h.host), onChange: () => toggle(h.host) })),\n React.createElement('td', { className: 'c', onMouseEnter: (e: any) => showTip(e, `${h.owner ?? '未知'} · ${h.source ?? '-'}`), onMouseLeave: hideTip }, h.owner ?? '未知'),\n React.createElement('td', { className: 'c' }, React.createElement('span', { className: alive ? 'st-ok' : 'st-gray' }, alive ? `●${h.count}` : '○')),\n React.createElement('td', { className: 'c' }, React.createElement('span', { className: h.lastStatus === 'ERR' ? 'st-fail' : 'st-ok' }, String(h.lastStatus ?? '—'))),\n React.createElement('td', { className: 'c' }, h.lastMs != null ? `${h.lastMs}ms` : ''),\n React.createElement('td', { className: 'c' }, React.createElement('button', { className: 'hr-btn ghost', onClick: () => doTest(h.host), style: { padding: '0 6px' } }, '测')),\n )\n })\n ),\n ),\n tip && React.createElement('div', { className: 'hr-tip', style: { left: tip.x, top: tip.y } }, tip.text),\n )\n}\n\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.slots.inject('settings.section', () =>\n ctx.slots.register(\n { name: 'settings.section', id: 'dsh-host-router', order: 60, label: '网络路由' },\n (props: any) => React.createElement(HostRouterPage, props),\n ),\n ), 'dsh-host-router: settings page')\n}\n"],"mappings":"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"}
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-host-router · 配置(独立 config.json;/save 写完 live reload 即生效)。
|
|
3
|
+
*
|
|
4
|
+
* 存 ~/.dsh/host-router/config.json。优先 DSH_HOME(实测:部署 web 进程 homedir
|
|
5
|
+
* 可能与 DSH_HOME 指向不同用户,homedir() 推导路径错位——DSH_HOME 才是权威)。
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync, writeFileSync, mkdirSync, renameSync } from 'node:fs';
|
|
8
|
+
import { join, dirname } from 'node:path';
|
|
9
|
+
import { homedir } from 'node:os';
|
|
10
|
+
const DEFAULT = {
|
|
11
|
+
proxyHost: '127.0.0.1',
|
|
12
|
+
proxyPort: 7897,
|
|
13
|
+
proxiedHosts: ['api.openai.com', 'awesome-dsh-plugin.com'],
|
|
14
|
+
sniff: true,
|
|
15
|
+
};
|
|
16
|
+
export function configDir() {
|
|
17
|
+
const dshHome = process.env.DSH_HOME || join(homedir(), '.dsh');
|
|
18
|
+
return join(dshHome, 'host-router');
|
|
19
|
+
}
|
|
20
|
+
export function configPath() {
|
|
21
|
+
return join(configDir(), 'config.json');
|
|
22
|
+
}
|
|
23
|
+
export function logPath() {
|
|
24
|
+
return join(configDir(), 'log.txt');
|
|
25
|
+
}
|
|
26
|
+
/** 读配置(缺字段用默认;文件不存在用默认并落盘)。 */
|
|
27
|
+
export function readConfig() {
|
|
28
|
+
const p = configPath();
|
|
29
|
+
try {
|
|
30
|
+
const raw = JSON.parse(readFileSync(p, 'utf8'));
|
|
31
|
+
return { ...DEFAULT, ...raw };
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return { ...DEFAULT };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** 原子写配置(先 tmp 再 rename,防中途崩溃留半截 JSON)。 */
|
|
38
|
+
export function writeConfig(cfg) {
|
|
39
|
+
const p = configPath();
|
|
40
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
41
|
+
const tmp = p + '.tmp';
|
|
42
|
+
writeFileSync(tmp, JSON.stringify(cfg, null, 2) + '\n', 'utf8');
|
|
43
|
+
// rename 跨设备可能失败,fallback 直接 write
|
|
44
|
+
try {
|
|
45
|
+
renameSync(tmp, p);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
writeFileSync(p, JSON.stringify(cfg, null, 2) + '\n', 'utf8');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/host/config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAC5E,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AASjC,MAAM,OAAO,GAAqB;IAChC,SAAS,EAAE,WAAW;IACtB,SAAS,EAAE,IAAI;IACf,YAAY,EAAE,CAAC,gBAAgB,EAAE,wBAAwB,CAAC;IAC1D,KAAK,EAAE,IAAI;CACZ,CAAA;AAED,MAAM,UAAU,SAAS;IACvB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,CAAA;IAC/D,OAAO,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAA;AACrC,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,OAAO,IAAI,CAAC,SAAS,EAAE,EAAE,aAAa,CAAC,CAAA;AACzC,CAAC;AAED,MAAM,UAAU,OAAO;IACrB,OAAO,IAAI,CAAC,SAAS,EAAE,EAAE,SAAS,CAAC,CAAA;AACrC,CAAC;AAED,+BAA+B;AAC/B,MAAM,UAAU,UAAU;IACxB,MAAM,CAAC,GAAG,UAAU,EAAE,CAAA;IACtB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAA8B,CAAA;QAC5E,OAAO,EAAE,GAAG,OAAO,EAAE,GAAG,GAAG,EAAE,CAAA;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,GAAG,OAAO,EAAE,CAAA;IACvB,CAAC;AACH,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,WAAW,CAAC,GAAqB;IAC/C,MAAM,CAAC,GAAG,UAAU,EAAE,CAAA;IACtB,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC1C,MAAM,GAAG,GAAG,CAAC,GAAG,MAAM,CAAA;IACtB,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAA;IAC/D,mCAAmC;IACnC,IAAI,CAAC;QACH,UAAU,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;IACpB,CAAC;IAAC,MAAM,CAAC;QACP,aAAa,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAA;IAC/D,CAAC;AACH,CAAC"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-host-router · host 入口。
|
|
3
|
+
*
|
|
4
|
+
* 外挂式 host 路由:按 hostname 给 dsh 请求分流(勾选走代理,其余直连)+ 嗅探
|
|
5
|
+
* + 设置页 API(/api/dsh-host-router/{seen,scan,clean-stale,save,test,config})。
|
|
6
|
+
*
|
|
7
|
+
* 全程在 globalThis.fetch 层替换(白名单走 undici+ProxyAgent,其余原样直连),
|
|
8
|
+
* 位于 LLM 适配器之下,不碰 settings.yaml
|
|
9
|
+
* provider 配置。纯外挂:dependencies 空,运行时零 require,宿主 ctx 注入。
|
|
10
|
+
* 配置存 ~/.dsh/host-router/config.json;/save 写完 live reload 即生效(免重启)。
|
|
11
|
+
*/
|
|
12
|
+
import { normalizeHost, isLoopback, matchesProxied } from './routing-dispatcher.js';
|
|
13
|
+
import { makeSeenStore, scanCandidates } from './seen.js';
|
|
14
|
+
import { readConfig, writeConfig, logPath } from './config.js';
|
|
15
|
+
import { appendFileSync } from 'node:fs';
|
|
16
|
+
import { createRequire } from 'node:module';
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
import { homedir } from 'node:os';
|
|
19
|
+
import { pathToFileURL } from 'node:url';
|
|
20
|
+
let undiciMod = null;
|
|
21
|
+
let installed = false;
|
|
22
|
+
/** 7 天没嗅探到 = stale(清理过期用)。 */
|
|
23
|
+
const STALE_MS = 7 * 24 * 3600 * 1000;
|
|
24
|
+
/** 解析 undici 绝对路径:插件目录可能无 node_modules,从 profile node_modules 找。
|
|
25
|
+
* 优先 DSH_HOME/profiles/web/node_modules,退 homedir/.dsh/...
|
|
26
|
+
* 返回 file:// URL(Windows 绝对路径不能直接 import,需转 URL)。 */
|
|
27
|
+
function resolveUndiciPath() {
|
|
28
|
+
const candidates = [
|
|
29
|
+
process.env.DSH_HOME && join(process.env.DSH_HOME, 'profiles', 'web', 'node_modules'),
|
|
30
|
+
join(homedir(), '.dsh', 'profiles', 'web', 'node_modules'),
|
|
31
|
+
].filter(Boolean);
|
|
32
|
+
for (const base of candidates) {
|
|
33
|
+
try {
|
|
34
|
+
const req = createRequire(join(base, 'package.json'));
|
|
35
|
+
const p = req.resolve('undici');
|
|
36
|
+
return pathToFileURL(p).href;
|
|
37
|
+
}
|
|
38
|
+
catch { /* try next */ }
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
let originalFetch = null;
|
|
43
|
+
let patched = false;
|
|
44
|
+
const seen = makeSeenStore();
|
|
45
|
+
/** 造 myFetch:替换 globalThis.fetch。白名单 host → 包 undici fetch + ProxyAgent;其余 → 原内置 fetch。
|
|
46
|
+
* 嗅探也在 myFetch 里记每个请求(按 host 聚合 + log.txt)。 */
|
|
47
|
+
function buildMyFetch(undici, config) {
|
|
48
|
+
const proxied = new Set((config.proxiedHosts ?? []).map(String));
|
|
49
|
+
const proxyUrl = `http://${config.proxyHost}:${config.proxyPort}`;
|
|
50
|
+
// ProxyAgent:Clash 关 idle 隧道复用死连接 → 超时;clientFactory 每请求新连接避坑。
|
|
51
|
+
let proxyAgent = null;
|
|
52
|
+
try {
|
|
53
|
+
proxyAgent = new undici.ProxyAgent({
|
|
54
|
+
uri: proxyUrl,
|
|
55
|
+
clientFactory: (origin, o) => new undici.Pool(origin, { ...o, pipelining: 0 }),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
catch (e) { /* proxy 不可用 → 白名单也走直连 */ }
|
|
59
|
+
const sniff = config.sniff !== false;
|
|
60
|
+
const doSniff = (host, path, status, ms, proxy, error) => {
|
|
61
|
+
if (!sniff)
|
|
62
|
+
return;
|
|
63
|
+
seen.record({ host, path, status, ms, proxy, ts: Date.now(), error });
|
|
64
|
+
};
|
|
65
|
+
return async function myFetch(input, init) {
|
|
66
|
+
let host = '';
|
|
67
|
+
let path = '';
|
|
68
|
+
try {
|
|
69
|
+
const u = new URL(typeof input === 'string' ? input : (input?.url ?? input));
|
|
70
|
+
host = u.hostname;
|
|
71
|
+
path = u.pathname;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
host = String(input ?? '');
|
|
75
|
+
}
|
|
76
|
+
const useProxy = proxyAgent && matchesProxied(host, proxied);
|
|
77
|
+
const t = Date.now();
|
|
78
|
+
try {
|
|
79
|
+
let res;
|
|
80
|
+
if (useProxy) {
|
|
81
|
+
// 白名单 → 包 undici fetch + ProxyAgent(走代理)
|
|
82
|
+
res = await undici.fetch(input, { ...init, dispatcher: proxyAgent });
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// 其余 → 原内置 fetch(行为不变)
|
|
86
|
+
res = await originalFetch.call(globalThis, input, init);
|
|
87
|
+
}
|
|
88
|
+
doSniff(host, path, res.status, Date.now() - t, useProxy);
|
|
89
|
+
return res;
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
doSniff(host, path, 'ERR', Date.now() - t, useProxy, String(e.message).slice(0, 140));
|
|
93
|
+
throw e;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/** monkeypatch globalThis.fetch(保留原引用,可还原)。 */
|
|
98
|
+
function patchFetch(undici, config) {
|
|
99
|
+
if (!patched)
|
|
100
|
+
originalFetch = globalThis.fetch;
|
|
101
|
+
globalThis.fetch = buildMyFetch(undici, config);
|
|
102
|
+
patched = true;
|
|
103
|
+
installed = true;
|
|
104
|
+
try {
|
|
105
|
+
appendFileSync(logPath(), `# fetch patched at ${new Date().toISOString()} proxied=[${config.proxiedHosts.join(',') || '(none)'}]\n`);
|
|
106
|
+
}
|
|
107
|
+
catch { }
|
|
108
|
+
}
|
|
109
|
+
/** 还原 globalThis.fetch。 */
|
|
110
|
+
function unpatchFetch() {
|
|
111
|
+
if (!patched)
|
|
112
|
+
return;
|
|
113
|
+
try {
|
|
114
|
+
if (originalFetch)
|
|
115
|
+
globalThis.fetch = originalFetch;
|
|
116
|
+
}
|
|
117
|
+
catch { }
|
|
118
|
+
patched = false;
|
|
119
|
+
}
|
|
120
|
+
/** live 重载:用新 config 重建 myFetch(保存即生效,免重启)。
|
|
121
|
+
* fetch 全局引用换一下即可,毫秒级,无重连开销。 */
|
|
122
|
+
function reloadFetch(undici, config) {
|
|
123
|
+
if (!undici || !patched) {
|
|
124
|
+
patchFetch(undici, config);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
;
|
|
128
|
+
globalThis.fetch = buildMyFetch(undici, config);
|
|
129
|
+
try {
|
|
130
|
+
appendFileSync(logPath(), `# fetch reloaded at ${new Date().toISOString()} proxied=[${config.proxiedHosts.join(',') || '(none)'}]\n`);
|
|
131
|
+
}
|
|
132
|
+
catch { }
|
|
133
|
+
}
|
|
134
|
+
/** 同源 loopback guard(照 dsh-llm-proxy bridge)。 */
|
|
135
|
+
function isTrusted(req) {
|
|
136
|
+
const addr = req.socket?.remoteAddress;
|
|
137
|
+
if (addr !== '127.0.0.1' && addr !== '::1' && addr !== '::ffff:127.0.0.1')
|
|
138
|
+
return false;
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
function json(res, status, body) {
|
|
142
|
+
res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' });
|
|
143
|
+
res.end(JSON.stringify(body));
|
|
144
|
+
}
|
|
145
|
+
async function readBody(req) {
|
|
146
|
+
return new Promise((resolve) => {
|
|
147
|
+
const ch = [];
|
|
148
|
+
req.on('data', (c) => ch.push(c));
|
|
149
|
+
req.on('end', () => { try {
|
|
150
|
+
resolve(JSON.parse(Buffer.concat(ch).toString('utf8')));
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
resolve(null);
|
|
154
|
+
} });
|
|
155
|
+
req.on('error', () => resolve(null));
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
/** 对某 host 探测(走当前全局 fetch,真实路径)。 */
|
|
159
|
+
async function testHost(host) {
|
|
160
|
+
const h = normalizeHost(host);
|
|
161
|
+
if (!h || isLoopback(h))
|
|
162
|
+
return { host: h, status: 'ERR', ms: 0, proxy: false, message: 'loopback/empty' };
|
|
163
|
+
const cfg = readConfig();
|
|
164
|
+
const useProxy = matchesProxied(h, new Set((cfg.proxiedHosts ?? []).map(String)));
|
|
165
|
+
const t = Date.now();
|
|
166
|
+
try {
|
|
167
|
+
const r = await fetch(`https://${h}/`, { method: 'HEAD', signal: AbortSignal.timeout(8000) });
|
|
168
|
+
return { host: h, status: r.status, ms: Date.now() - t, proxy: useProxy };
|
|
169
|
+
}
|
|
170
|
+
catch (e) {
|
|
171
|
+
return { host: h, status: 'ERR', ms: Date.now() - t, proxy: useProxy, message: String(e.message).slice(0, 100) };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
export const inject = ['webServer'];
|
|
175
|
+
export function apply(ctx) {
|
|
176
|
+
let diag = 'apply-start';
|
|
177
|
+
try {
|
|
178
|
+
appendFileSync(logPath(), `# apply at ${new Date().toISOString()}\n`);
|
|
179
|
+
diag = 'logged';
|
|
180
|
+
}
|
|
181
|
+
catch { }
|
|
182
|
+
const start = (u) => {
|
|
183
|
+
undiciMod = u;
|
|
184
|
+
if (!u || typeof u.fetch !== 'function' || typeof u.ProxyAgent !== 'function') {
|
|
185
|
+
diag = 'no-undici-fetch';
|
|
186
|
+
try {
|
|
187
|
+
appendFileSync(logPath(), `# start: undici fetch/ProxyAgent unavailable\n`);
|
|
188
|
+
}
|
|
189
|
+
catch { }
|
|
190
|
+
ctx.logger?.error?.('dsh-host-router: undici fetch/ProxyAgent unavailable — disabled');
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
const cfg = readConfig();
|
|
195
|
+
patchFetch(u, cfg);
|
|
196
|
+
diag = 'patched';
|
|
197
|
+
ctx.logger?.info?.(`dsh-host-router: globalThis.fetch patched (proxy=${cfg.proxyHost}:${cfg.proxyPort}, proxied=[${cfg.proxiedHosts.join(',') || '(none)'}])`);
|
|
198
|
+
}
|
|
199
|
+
catch (e) {
|
|
200
|
+
diag = 'patch-failed:' + String(e?.message ?? e);
|
|
201
|
+
try {
|
|
202
|
+
appendFileSync(logPath(), `# patch FAILED: ${diag}\n`);
|
|
203
|
+
}
|
|
204
|
+
catch { }
|
|
205
|
+
ctx.logger?.error?.('dsh-host-router: patch failed: ' + String(e?.message ?? e));
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
// undici:插件目录无 node_modules,从 profile node_modules 解析绝对路径再 import。
|
|
209
|
+
const undiciMain = resolveUndiciPath();
|
|
210
|
+
try {
|
|
211
|
+
appendFileSync(logPath(), `# undiciMain=${undiciMain ?? '(null)'}\n`);
|
|
212
|
+
}
|
|
213
|
+
catch { }
|
|
214
|
+
if (!undiciMain) {
|
|
215
|
+
diag = 'undici-not-found';
|
|
216
|
+
ctx.logger?.error?.('dsh-host-router: undici not found in profile node_modules — disabled');
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
import(undiciMain).then(async (u) => {
|
|
220
|
+
try {
|
|
221
|
+
appendFileSync(logPath(), `# undici imported OK\n`);
|
|
222
|
+
}
|
|
223
|
+
catch { }
|
|
224
|
+
start(u);
|
|
225
|
+
}).catch((e) => {
|
|
226
|
+
diag = 'import-failed:' + String(e?.message ?? e);
|
|
227
|
+
try {
|
|
228
|
+
appendFileSync(logPath(), `# import FAILED: ${diag}\n`);
|
|
229
|
+
}
|
|
230
|
+
catch { }
|
|
231
|
+
ctx.logger?.error?.('dsh-host-router: undici load failed: ' + String(e));
|
|
232
|
+
});
|
|
233
|
+
// 设置页 API:webServer.register 前缀路由(照 dsh-super-injector /api 同款)。
|
|
234
|
+
ctx.effect(() => ctx.webServer.register({
|
|
235
|
+
kind: 'prefix',
|
|
236
|
+
path: '/api/dsh-host-router',
|
|
237
|
+
handler: async (req, res) => {
|
|
238
|
+
if (!isTrusted(req)) {
|
|
239
|
+
json(res, 403, { error: 'forbidden' });
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const url = req.url ?? '';
|
|
243
|
+
try {
|
|
244
|
+
if (url.endsWith('/seen') && req.method === 'GET') {
|
|
245
|
+
let fetchPatched = false;
|
|
246
|
+
try {
|
|
247
|
+
fetchPatched = patched && globalThis.fetch !== originalFetch;
|
|
248
|
+
}
|
|
249
|
+
catch { }
|
|
250
|
+
const cfg = readConfig();
|
|
251
|
+
const proxied = new Set(cfg.proxiedHosts ?? []);
|
|
252
|
+
const now = Date.now();
|
|
253
|
+
// 合并:嗅探到的 host + 白名单 host(没嗅探到也显示,灰)
|
|
254
|
+
const map = new Map();
|
|
255
|
+
for (const h of seen.list())
|
|
256
|
+
map.set(h.host, { ...h });
|
|
257
|
+
for (const h of proxied) {
|
|
258
|
+
if (!map.has(h))
|
|
259
|
+
map.set(h, { host: h, count: 0, owner: '未知', source: 'config', mode: 'passive' });
|
|
260
|
+
}
|
|
261
|
+
const hosts = [...map.values()].map((h) => {
|
|
262
|
+
const hits = h.count ?? 0;
|
|
263
|
+
const stale = proxied.has(h.host) && (hits === 0 || (h.lastSeen ? now - h.lastSeen > STALE_MS : true));
|
|
264
|
+
return { ...h, proxied: proxied.has(h.host), hits, stale, alive: hits > 0 };
|
|
265
|
+
});
|
|
266
|
+
json(res, 200, { ok: true, hosts, installed, diag, fetchPatched });
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (url.endsWith('/scan') && req.method === 'POST') {
|
|
270
|
+
// 候选带 owner/source,批量直连探测(2.5s),结果 upsert 进 store
|
|
271
|
+
const candidates = scanCandidates();
|
|
272
|
+
const probe = async (c) => {
|
|
273
|
+
const t = Date.now();
|
|
274
|
+
try {
|
|
275
|
+
const r = await fetch(`https://${c.host}/`, { method: 'HEAD', signal: AbortSignal.timeout(2500) });
|
|
276
|
+
return { ...c, direct: r.status, ms: Date.now() - t, needsProxy: false };
|
|
277
|
+
}
|
|
278
|
+
catch (e) {
|
|
279
|
+
return { ...c, direct: 'fail', ms: Date.now() - t, needsProxy: true, reason: String(e.message).slice(0, 80) };
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
const results = [];
|
|
283
|
+
let idx = 0;
|
|
284
|
+
async function worker() { while (idx < candidates.length) {
|
|
285
|
+
const c = candidates[idx++];
|
|
286
|
+
results.push(await probe(c));
|
|
287
|
+
} }
|
|
288
|
+
await Promise.all(Array.from({ length: 8 }, worker));
|
|
289
|
+
// upsert 进 store(带探测结果,不增 hits)
|
|
290
|
+
for (const r of results)
|
|
291
|
+
seen.upsert(r.host, { owner: r.owner, source: r.source, mode: 'active', lastStatus: r.direct, lastMs: r.ms });
|
|
292
|
+
results.sort((a, b) => Number(b.needsProxy) - Number(a.needsProxy) || a.host.localeCompare(b.host));
|
|
293
|
+
json(res, 200, { ok: true, hosts: results });
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (url.endsWith('/clean-stale') && req.method === 'POST') {
|
|
297
|
+
// 一键清灰:移除 stale(本次 run hits=0 或 7 天没动)的白名单 host
|
|
298
|
+
const cfg = readConfig();
|
|
299
|
+
const proxied = new Set(cfg.proxiedHosts ?? []);
|
|
300
|
+
const now = Date.now();
|
|
301
|
+
const seenList = new Map(seen.list().map((h) => [h.host, h]));
|
|
302
|
+
const removed = [];
|
|
303
|
+
cfg.proxiedHosts = (cfg.proxiedHosts ?? []).filter((h) => {
|
|
304
|
+
const s = seenList.get(h);
|
|
305
|
+
const hits = s?.count ?? 0;
|
|
306
|
+
const stale = hits === 0 || (s?.lastSeen ? now - s.lastSeen > STALE_MS : true);
|
|
307
|
+
if (stale) {
|
|
308
|
+
removed.push(h);
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
return true;
|
|
312
|
+
});
|
|
313
|
+
writeConfig(cfg);
|
|
314
|
+
try {
|
|
315
|
+
reloadFetch(undiciMod, cfg);
|
|
316
|
+
}
|
|
317
|
+
catch { }
|
|
318
|
+
json(res, 200, { ok: true, removed });
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (url.endsWith('/save') && req.method === 'POST') {
|
|
322
|
+
const body = await readBody(req);
|
|
323
|
+
const cfg = readConfig();
|
|
324
|
+
if (body && Array.isArray(body.proxiedHosts))
|
|
325
|
+
cfg.proxiedHosts = body.proxiedHosts;
|
|
326
|
+
if (body && typeof body.sniff === 'boolean')
|
|
327
|
+
cfg.sniff = body.sniff;
|
|
328
|
+
if (body && typeof body.proxyHost === 'string')
|
|
329
|
+
cfg.proxyHost = body.proxyHost;
|
|
330
|
+
if (body && typeof body.proxyPort === 'number')
|
|
331
|
+
cfg.proxyPort = body.proxyPort;
|
|
332
|
+
writeConfig(cfg);
|
|
333
|
+
// live 重载:用新 config 重建 myFetch,保存即生效(免重启)
|
|
334
|
+
try {
|
|
335
|
+
reloadFetch(undiciMod, cfg);
|
|
336
|
+
}
|
|
337
|
+
catch (e) {
|
|
338
|
+
try {
|
|
339
|
+
appendFileSync(logPath(), `# reload FAILED: ${String(e?.message ?? e)}\n`);
|
|
340
|
+
}
|
|
341
|
+
catch { }
|
|
342
|
+
}
|
|
343
|
+
json(res, 200, { ok: true, note: '已保存并即时生效' });
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (url.endsWith('/test') && req.method === 'POST') {
|
|
347
|
+
const body = await readBody(req);
|
|
348
|
+
const r = await testHost(body?.host ?? '');
|
|
349
|
+
json(res, 200, r);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (url.endsWith('/config') && req.method === 'GET') {
|
|
353
|
+
json(res, 200, { ok: true, value: readConfig() });
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
json(res, 404, { error: 'not found' });
|
|
357
|
+
}
|
|
358
|
+
catch (e) {
|
|
359
|
+
json(res, 500, { error: String(e?.message ?? e) });
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
}), 'dsh-host-router: api');
|
|
363
|
+
ctx.on('dispose', () => {
|
|
364
|
+
unpatchFetch();
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/host/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-host-router · hostname 归一化工具(直连/代理判断共用)。
|
|
3
|
+
*
|
|
4
|
+
* 早期设计里的 duck-typed HostRouter dispatcher(undici setGlobalDispatcher)
|
|
5
|
+
* 已由 globalThis.fetch 替换方案取代(见 index.ts),这里只保留被
|
|
6
|
+
* index.ts / seen.ts 实际使用的两个纯函数。
|
|
7
|
+
*/
|
|
8
|
+
/** 归一化 hostname(去端口/括号/scheme)。 */
|
|
9
|
+
export function normalizeHost(host) {
|
|
10
|
+
if (!host)
|
|
11
|
+
return '';
|
|
12
|
+
let h = String(host).trim().toLowerCase();
|
|
13
|
+
if (h.includes('://')) {
|
|
14
|
+
try {
|
|
15
|
+
h = new URL(h).hostname;
|
|
16
|
+
}
|
|
17
|
+
catch { /* fall through */ }
|
|
18
|
+
}
|
|
19
|
+
h = h.replace(/:\d+$/, '').replace(/^\[(.+)\]$/, '$1');
|
|
20
|
+
return h;
|
|
21
|
+
}
|
|
22
|
+
/** loopback 永远直连。 */
|
|
23
|
+
export function isLoopback(host) {
|
|
24
|
+
return host === 'localhost' || host === '127.0.0.1' || host === '::1' || host === '[::1]';
|
|
25
|
+
}
|
|
26
|
+
/** host 是否命中代理白名单(含子域后缀 `xxx.example.com` 命中 `example.com`);loopback 永远否。
|
|
27
|
+
* 快路径用 Set,白名单很小,成员遍历开销可忽略。 */
|
|
28
|
+
export function matchesProxied(host, proxied) {
|
|
29
|
+
if (!host || isLoopback(host))
|
|
30
|
+
return false;
|
|
31
|
+
if (proxied.has(host))
|
|
32
|
+
return true;
|
|
33
|
+
for (const entry of proxied)
|
|
34
|
+
if (host.endsWith(`.${entry}`))
|
|
35
|
+
return true;
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=routing-dispatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routing-dispatcher.js","sourceRoot":"","sources":["../src/host/routing-dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,mCAAmC;AACnC,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAA;IACpB,IAAI,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IACzC,IAAI,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACtB,IAAI,CAAC;YAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAA;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;IAC9D,CAAC;IACD,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,IAAI,CAAC,CAAA;IACtD,OAAO,CAAC,CAAA;AACV,CAAC;AAED,qBAAqB;AACrB,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,IAAI,KAAK,WAAW,IAAI,IAAI,KAAK,WAAW,IAAI,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,OAAO,CAAA;AAC3F,CAAC;AAED;gCACgC;AAChC,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,OAA4B;IACvE,IAAI,CAAC,IAAI,IAAI,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAA;IAC3C,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IAClC,KAAK,MAAM,KAAK,IAAI,OAAO;QAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,KAAK,EAAE,CAAC;YAAE,OAAO,IAAI,CAAA;IACxE,OAAO,KAAK,CAAA;AACd,CAAC"}
|
package/lib/seen.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-host-router · 嗅探聚合 + 主动扫描候选。
|
|
3
|
+
*
|
|
4
|
+
* seenStore:按 host 聚合(首次/最近/命中数/状态/owner/source),给设置页清单。
|
|
5
|
+
* count = 本次进程(run)命中数,>0 绿(本次扫到)、=0 灰(本次没扫到)。
|
|
6
|
+
* dsh 重启 = 新进程 = count 清零,看几次重启都灰 = 可删。
|
|
7
|
+
* scanCandidates:读 ~/.dsh/settings.yaml provider baseURL host + 已知境外兜底,
|
|
8
|
+
* 每个带 owner(粗归属)+ source(settings/known),给主动扫描不靠猜的候选表。
|
|
9
|
+
*/
|
|
10
|
+
import { readFileSync, appendFileSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { homedir } from 'node:os';
|
|
13
|
+
import { normalizeHost, isLoopback } from './routing-dispatcher.js';
|
|
14
|
+
import { logPath } from './config.js';
|
|
15
|
+
/** owner 映射:已知 host → 归属(粗标)。 */
|
|
16
|
+
const OWNER_MAP = {
|
|
17
|
+
'api.openai.com': 'openai',
|
|
18
|
+
'awesome-dsh-plugin.com': 'dshmarket 目录',
|
|
19
|
+
'raw.githubusercontent.com': 'dshmarket 截图/README',
|
|
20
|
+
'user-images.githubusercontent.com': 'dshmarket 截图',
|
|
21
|
+
'camo.githubusercontent.com': 'dshmarket 截图',
|
|
22
|
+
'api.anthropic.com': 'anthropic',
|
|
23
|
+
'generativelanguage.googleapis.com': 'gemini',
|
|
24
|
+
'api.deepseek.com': 'deepseek',
|
|
25
|
+
'api.siliconflow.cn': 'siliconflow',
|
|
26
|
+
'open.bigmodel.cn': 'zhipu',
|
|
27
|
+
'dashscope.aliyuncs.com': 'qwen',
|
|
28
|
+
};
|
|
29
|
+
function ownerOf(host) {
|
|
30
|
+
if (OWNER_MAP[host])
|
|
31
|
+
return OWNER_MAP[host];
|
|
32
|
+
for (const k of Object.keys(OWNER_MAP))
|
|
33
|
+
if (host.endsWith(`.${k}`))
|
|
34
|
+
return OWNER_MAP[k];
|
|
35
|
+
return '未知';
|
|
36
|
+
}
|
|
37
|
+
/** 嗅探收集器:按 host 聚合 + 落 log.txt。 */
|
|
38
|
+
export function makeSeenStore() {
|
|
39
|
+
const byHost = new Map();
|
|
40
|
+
function record(r) {
|
|
41
|
+
const e = byHost.get(r.host);
|
|
42
|
+
if (e) {
|
|
43
|
+
e.lastSeen = r.ts;
|
|
44
|
+
e.count = (e.count ?? 1) + 1;
|
|
45
|
+
e.lastStatus = r.status;
|
|
46
|
+
e.lastMs = r.ms;
|
|
47
|
+
e.proxy = r.proxy;
|
|
48
|
+
if (r.error)
|
|
49
|
+
e.error = r.error;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
byHost.set(r.host, { host: r.host, firstSeen: r.ts, lastSeen: r.ts, count: 1, lastStatus: r.status, lastMs: r.ms, proxy: r.proxy, error: r.error, owner: ownerOf(r.host), source: 'sniff', mode: 'passive' });
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
appendFileSync(logPath(), `${r.ts} | ${r.proxy ? 'PROXY ' : 'DIRECT'} | ${r.host}${r.path} | ${r.status} | ${r.ms}ms\n`);
|
|
56
|
+
}
|
|
57
|
+
catch { /* ignore */ }
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
record,
|
|
61
|
+
/** upsert 一个 host(主动扫描/白名单补显用),不增 count。 */
|
|
62
|
+
upsert(host, patch) {
|
|
63
|
+
const e = byHost.get(host);
|
|
64
|
+
if (e)
|
|
65
|
+
Object.assign(e, patch);
|
|
66
|
+
else
|
|
67
|
+
byHost.set(host, { host, count: 0, owner: ownerOf(host), ...patch });
|
|
68
|
+
},
|
|
69
|
+
list: () => [...byHost.values()].sort((a, b) => (b.lastSeen ?? 0) - (a.lastSeen ?? 0)),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// 已知常见境外 host 兜底(settings.yaml 没记、但 dsh 运行时会打的)。
|
|
73
|
+
const KNOWN_REMOTE = Object.keys(OWNER_MAP);
|
|
74
|
+
/** dsh settings.yaml 路径(优先 DSH_HOME)。 */
|
|
75
|
+
function settingsYamlPath() {
|
|
76
|
+
const dshHome = process.env.DSH_HOME || join(homedir(), '.dsh');
|
|
77
|
+
return join(dshHome, 'settings.yaml');
|
|
78
|
+
}
|
|
79
|
+
/** 从 settings.yaml 提所有 provider baseURL 的 host(正则:只取 `baseURL: <url>`)。 */
|
|
80
|
+
function providersFromSettings() {
|
|
81
|
+
const hosts = new Set();
|
|
82
|
+
try {
|
|
83
|
+
const txt = readFileSync(settingsYamlPath(), 'utf8');
|
|
84
|
+
for (const m of txt.matchAll(/^\s*baseURL:\s*['"]?(https?:\/\/[^'"\s#]+)/gm)) {
|
|
85
|
+
const h = normalizeHost(m[1]);
|
|
86
|
+
if (h && !isLoopback(h))
|
|
87
|
+
hosts.add(h);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch { /* settings.yaml 缺 */ }
|
|
91
|
+
return [...hosts];
|
|
92
|
+
}
|
|
93
|
+
/** 扫描候选 = settings.yaml provider host + 已知境外兜底,带 owner/source。 */
|
|
94
|
+
export function scanCandidates() {
|
|
95
|
+
const byHost = new Map();
|
|
96
|
+
for (const h of providersFromSettings())
|
|
97
|
+
byHost.set(h, { host: h, count: 0, owner: ownerOf(h), source: 'settings', mode: 'active' });
|
|
98
|
+
for (const h of KNOWN_REMOTE) {
|
|
99
|
+
if (!byHost.has(h))
|
|
100
|
+
byHost.set(h, { host: h, count: 0, owner: OWNER_MAP[h] ?? '未知', source: 'known', mode: 'active' });
|
|
101
|
+
}
|
|
102
|
+
return [...byHost.values()].filter((s) => !isLoopback(s.host));
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=seen.js.map
|
package/lib/seen.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seen.js","sourceRoot":"","sources":["../src/host/seen.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface HostRouterConfig {
|
|
2
|
+
proxyHost: string;
|
|
3
|
+
proxyPort: number;
|
|
4
|
+
proxiedHosts: string[];
|
|
5
|
+
sniff: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function configDir(): string;
|
|
8
|
+
export declare function configPath(): string;
|
|
9
|
+
export declare function logPath(): string;
|
|
10
|
+
/** 读配置(缺字段用默认;文件不存在用默认并落盘)。 */
|
|
11
|
+
export declare function readConfig(): HostRouterConfig;
|
|
12
|
+
/** 原子写配置(先 tmp 再 rename,防中途崩溃留半截 JSON)。 */
|
|
13
|
+
export declare function writeConfig(cfg: HostRouterConfig): void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-host-router · hostname 归一化工具(直连/代理判断共用)。
|
|
3
|
+
*
|
|
4
|
+
* 早期设计里的 duck-typed HostRouter dispatcher(undici setGlobalDispatcher)
|
|
5
|
+
* 已由 globalThis.fetch 替换方案取代(见 index.ts),这里只保留被
|
|
6
|
+
* index.ts / seen.ts 实际使用的两个纯函数。
|
|
7
|
+
*/
|
|
8
|
+
/** 归一化 hostname(去端口/括号/scheme)。 */
|
|
9
|
+
export declare function normalizeHost(host: string): string;
|
|
10
|
+
/** loopback 永远直连。 */
|
|
11
|
+
export declare function isLoopback(host: string): boolean;
|
|
12
|
+
/** host 是否命中代理白名单(含子域后缀 `xxx.example.com` 命中 `example.com`);loopback 永远否。
|
|
13
|
+
* 快路径用 Set,白名单很小,成员遍历开销可忽略。 */
|
|
14
|
+
export declare function matchesProxied(host: string, proxied: ReadonlySet<string>): boolean;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface SeenHost {
|
|
2
|
+
host: string;
|
|
3
|
+
firstSeen?: number;
|
|
4
|
+
lastSeen?: number;
|
|
5
|
+
count?: number;
|
|
6
|
+
lastStatus?: number | string;
|
|
7
|
+
lastMs?: number;
|
|
8
|
+
proxy?: boolean;
|
|
9
|
+
error?: string;
|
|
10
|
+
owner?: string;
|
|
11
|
+
source?: 'sniff' | 'settings' | 'known';
|
|
12
|
+
mode?: 'passive' | 'active';
|
|
13
|
+
}
|
|
14
|
+
/** 嗅探收集器:按 host 聚合 + 落 log.txt。 */
|
|
15
|
+
export declare function makeSeenStore(): {
|
|
16
|
+
record: (r: any) => void;
|
|
17
|
+
/** upsert 一个 host(主动扫描/白名单补显用),不增 count。 */
|
|
18
|
+
upsert(host: string, patch: Partial<SeenHost>): void;
|
|
19
|
+
list: () => SeenHost[];
|
|
20
|
+
};
|
|
21
|
+
/** 扫描候选 = settings.yaml provider host + 已知境外兜底,带 owner/source。 */
|
|
22
|
+
export declare function scanCandidates(): SeenHost[];
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-host-router",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"description": "dsh 外挂式网络路由:按 hostname 给 dsh 请求分流——勾选的域名走本地代理(Clash 等),其余直连。内置嗅探,自动扫描 dsh 访问过的 hostname,设置页勾选即生效(保存即热生效,免重启)。纯外挂:dependencies 空,运行时零 require,全靠宿主 ctx 注入;不碰 settings.yaml provider 配置。",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"author": "",
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=18"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": ""
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"deepseek",
|
|
18
|
+
"dsh",
|
|
19
|
+
"deepseek-harness",
|
|
20
|
+
"proxy",
|
|
21
|
+
"clash",
|
|
22
|
+
"undici",
|
|
23
|
+
"routing",
|
|
24
|
+
"dispatcher",
|
|
25
|
+
"network",
|
|
26
|
+
"sniff",
|
|
27
|
+
"cordis-plugin"
|
|
28
|
+
],
|
|
29
|
+
"dsh": {
|
|
30
|
+
"bundle": {
|
|
31
|
+
"patch": "./cordis.patch.yml"
|
|
32
|
+
},
|
|
33
|
+
"client": {
|
|
34
|
+
"platform": "web",
|
|
35
|
+
"inject": [
|
|
36
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
37
|
+
"@deepseek-ai/dsh-client-ui-slots",
|
|
38
|
+
"@deepseek-ai/dsh-client-ui-settings"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"exports": {
|
|
43
|
+
".": "./lib/index.js",
|
|
44
|
+
"./client": "./dist/client.cjs",
|
|
45
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
46
|
+
"./package.json": "./package.json"
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
"lib",
|
|
50
|
+
"dist",
|
|
51
|
+
"cordis.patch.yml",
|
|
52
|
+
"README.md"
|
|
53
|
+
],
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build:host": "tsc -p tsconfig.json",
|
|
56
|
+
"build:client": "tsdown",
|
|
57
|
+
"build": "npm run build:host && npm run build:client",
|
|
58
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
59
|
+
"prepublishOnly": "node scripts/build.mjs"
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {},
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
|
|
64
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
|
|
65
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6"
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
|
|
69
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
|
|
70
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6",
|
|
71
|
+
"typescript": "^5.6.0",
|
|
72
|
+
"tsdown": "^0.3.0",
|
|
73
|
+
"@types/node": "^22.0.0"
|
|
74
|
+
},
|
|
75
|
+
"pnpm": {
|
|
76
|
+
"onlyBuiltDependencies": ["esbuild"]
|
|
77
|
+
},
|
|
78
|
+
"publishConfig": {
|
|
79
|
+
"access": "public"
|
|
80
|
+
}
|
|
81
|
+
}
|