dsh-vpn 0.1.12 → 0.1.13
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 +1 -1
- package/lib/client.js +148 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ dsh plugin --profile web add dsh-vpn
|
|
|
35
35
|
|
|
36
36
|
1. 填写服务器 IP、SSH 端口、用户名、密码,点「保存」和「测试连接」。服务器只允许密钥登录的,见下面的「密钥登录」。
|
|
37
37
|
2. 勾选要安装的协议,选择证书类型(推荐「IP 证书」),保存后点「安装 / 更新」。
|
|
38
|
-
3.
|
|
38
|
+
3. 在「节点二维码」里点「刷新二维码」,用手机或电脑上的客户端扫码导入。用 **sing-box 官方客户端**(Mac 版 SFM、iPhone 版 SFI、Android 版 SFA、Windows 版 SFW,1.14 及以上)的,点二维码上方的「下载配置文件」,在客户端里「新建配置 → 导入本地文件」,一次导入全部节点。官方客户端不能扫单条链接的二维码,用这个配置文件即可;配置里 TUIC 和 AnyTLS 已写入证书锁定,不需要跳过证书校验,默认自动选择延迟最低的节点。
|
|
39
39
|
4. 服务器防火墙的端口由插件自动放行,不用手动操作;安装完成后提示框会列出放行了哪些端口。
|
|
40
40
|
|
|
41
41
|
**密钥登录**:服务器只允许密钥登录时,把「登录方式」选成「密钥」。插件按这个顺序尝试登录,哪把被服务器认可就用哪把:
|
package/lib/client.js
CHANGED
|
@@ -166,7 +166,11 @@ var zh = {
|
|
|
166
166
|
uninstalled: "\u5DF2\u5378\u8F7D\u3002",
|
|
167
167
|
working: "\u6267\u884C\u4E2D\u2026",
|
|
168
168
|
qrSection: "\u8282\u70B9\u4E8C\u7EF4\u7801",
|
|
169
|
-
qrHint: "\u7528
|
|
169
|
+
qrHint: "\u7528 Shadowrocket\uFF08\u5C0F\u706B\u7BAD\uFF09\u3001v2rayN\u3001NekoBox\u3001Clash Meta \u7B49\u5BA2\u6237\u7AEF\u626B\u63CF\u5BFC\u5165\u3002sing-box \u5B98\u65B9\u5BA2\u6237\u7AEF\u8BF7\u7528\u4E0B\u9762\u7684\u914D\u7F6E\u6587\u4EF6\u3002\u4E8C\u7EF4\u7801\u548C\u914D\u7F6E\u6587\u4EF6\u90FD\u7B49\u540C\u4E8E\u5BC6\u7801\uFF0C\u8BF7\u52FF\u5916\u4F20\u3002",
|
|
170
|
+
profileTitle: "sing-box \u5B98\u65B9\u5BA2\u6237\u7AEF\u914D\u7F6E",
|
|
171
|
+
profileDownload: "\u4E0B\u8F7D\u914D\u7F6E\u6587\u4EF6",
|
|
172
|
+
profileCopy: "\u590D\u5236\u914D\u7F6E",
|
|
173
|
+
profileHint: "\u4E00\u6B21\u5BFC\u5165\u5168\u90E8\u8282\u70B9\uFF0C\u9002\u7528\u4E8E sing-box \u5B98\u65B9\u5BA2\u6237\u7AEF 1.14 \u53CA\u4EE5\u4E0A\uFF08Mac \u7248 SFM\u3001iPhone \u7248 SFI\u3001Android \u7248 SFA\u3001Windows \u7248 SFW\uFF09\u3002\u5728\u5BA2\u6237\u7AEF\u91CC\u300C\u65B0\u5EFA\u914D\u7F6E \u2192 \u5BFC\u5165\u672C\u5730\u6587\u4EF6\u300D\u9009\u62E9 {file}\uFF08\u624B\u673A\u53EF\u4EE5\u7528\u9694\u7A7A\u6295\u9001\u6216\u6587\u4EF6 App \u4F20\u8FC7\u53BB\uFF09\u3002TUIC \u548C AnyTLS \u5DF2\u5199\u5165\u8BC1\u4E66\u9501\u5B9A\uFF0C\u4E0D\u9700\u8981\u8DF3\u8FC7\u8BC1\u4E66\u6821\u9A8C\uFF1B\u9ED8\u8BA4\u81EA\u52A8\u9009\u62E9\u5EF6\u8FDF\u6700\u4F4E\u7684\u8282\u70B9\u3002",
|
|
170
174
|
detect: "\u68C0\u67E5\u5DF2\u6709\u5B89\u88C5",
|
|
171
175
|
detecting: "\u6B63\u5728\u8BFB\u53D6\u670D\u52A1\u5668\u2026",
|
|
172
176
|
detectHint: "\u91CD\u88C5\u8FC7 dsh \u6216\u672C\u63D2\u4EF6\uFF1F\u586B\u597D\u4E0A\u9762\u7684\u767B\u5F55\u4FE1\u606F\u76F4\u63A5\u70B9\u8FD9\u91CC\uFF08\u4E0D\u7528\u5148\u4FDD\u5B58\uFF09\uFF0C\u4F1A\u767B\u5F55\u670D\u52A1\u5668\u8BFB\u53D6\u4ECD\u5728\u8FD0\u884C\u7684\u8282\u70B9\uFF0C\u663E\u793A\u539F\u6765\u7684\u4E8C\u7EF4\u7801\uFF0C\u5E76\u81EA\u52A8\u4FDD\u5B58\u8BBE\u7F6E\u3002",
|
|
@@ -306,7 +310,11 @@ var en = {
|
|
|
306
310
|
uninstalled: "Uninstalled.",
|
|
307
311
|
working: "Working\u2026",
|
|
308
312
|
qrSection: "Node QR codes",
|
|
309
|
-
qrHint: "Scan with v2rayN, NekoBox,
|
|
313
|
+
qrHint: "Scan with Shadowrocket, v2rayN, NekoBox, Clash Meta and similar apps. For the official sing-box apps use the profile below. QR codes and the profile are as secret as a password.",
|
|
314
|
+
profileTitle: "Profile for the official sing-box apps",
|
|
315
|
+
profileDownload: "Download profile",
|
|
316
|
+
profileCopy: "Copy profile",
|
|
317
|
+
profileHint: "All nodes in one import, for the official sing-box apps 1.14+ (SFM on Mac, SFI on iPhone, SFA on Android, SFW on Windows). In the app choose New Profile \u2192 Import local file and pick {file} (send it to a phone with AirDrop or the Files app). TUIC and AnyTLS have their certificates pinned, so nothing skips verification; the fastest node is chosen automatically.",
|
|
310
318
|
detect: "Find existing install",
|
|
311
319
|
detecting: "Reading the server\u2026",
|
|
312
320
|
detectHint: "Reinstalled dsh or this plugin? Fill in the login details above and click here (no need to save first): it logs in, reads the node that is still running, shows its QR codes and saves the settings.",
|
|
@@ -407,6 +415,7 @@ var STYLES = `
|
|
|
407
415
|
.dshvpn-detect small { flex: 1; min-width: 220px; }
|
|
408
416
|
.dshvpn-keycard-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
|
409
417
|
.dshvpn-loggedin { font-size: 13px; color: var(--dsw-alias-state-success-primary, #2f855a); }
|
|
418
|
+
.dshvpn-profile { margin: 10px 0 4px; }
|
|
410
419
|
.dshvpn-section { display: flex; flex-direction: column; gap: 16px; }
|
|
411
420
|
.dshvpn-section-head h2 { margin: 0; font-size: 18px; font-weight: 600; }
|
|
412
421
|
.dshvpn-title-row { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin-bottom: 4px; }
|
|
@@ -1118,6 +1127,106 @@ function renderSVG(data, options = {}) {
|
|
|
1118
1127
|
return svg;
|
|
1119
1128
|
}
|
|
1120
1129
|
|
|
1130
|
+
// src/client-profile.ts
|
|
1131
|
+
var NAMES = {
|
|
1132
|
+
reality: "VLESS Reality",
|
|
1133
|
+
hy2: "Hysteria2",
|
|
1134
|
+
tuic: "TUIC v5",
|
|
1135
|
+
anytls: "AnyTLS",
|
|
1136
|
+
ss: "Shadowsocks 2022"
|
|
1137
|
+
};
|
|
1138
|
+
function base64UrlDecode(value) {
|
|
1139
|
+
const b64 = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
1140
|
+
const bin = atob(b64 + "=".repeat((4 - b64.length % 4) % 4));
|
|
1141
|
+
return new TextDecoder().decode(Uint8Array.from(bin, (c) => c.charCodeAt(0)));
|
|
1142
|
+
}
|
|
1143
|
+
function outboundOf(item) {
|
|
1144
|
+
const u = new URL(item.link);
|
|
1145
|
+
const q = Object.fromEntries(u.searchParams);
|
|
1146
|
+
const server = u.hostname.replace(/^\[|\]$/g, "");
|
|
1147
|
+
const port = Number(u.port);
|
|
1148
|
+
const user = decodeURIComponent(u.username);
|
|
1149
|
+
const tls = (alpn) => ({
|
|
1150
|
+
enabled: true,
|
|
1151
|
+
server_name: q.sni,
|
|
1152
|
+
...alpn ? { alpn } : {},
|
|
1153
|
+
...item.pubkey_sha256 ? { certificate_public_key_sha256: [item.pubkey_sha256] } : {}
|
|
1154
|
+
});
|
|
1155
|
+
switch (item.id) {
|
|
1156
|
+
case "reality":
|
|
1157
|
+
return {
|
|
1158
|
+
type: "vless",
|
|
1159
|
+
server,
|
|
1160
|
+
server_port: port,
|
|
1161
|
+
uuid: user,
|
|
1162
|
+
flow: q.flow,
|
|
1163
|
+
tls: { enabled: true, server_name: q.sni, utls: { enabled: true, fingerprint: q.fp }, reality: { enabled: true, public_key: q.pbk, short_id: q.sid } }
|
|
1164
|
+
};
|
|
1165
|
+
case "hy2":
|
|
1166
|
+
return { type: "hysteria2", server, server_port: port, password: user, tls: tls(["h3"]) };
|
|
1167
|
+
case "tuic":
|
|
1168
|
+
return {
|
|
1169
|
+
type: "tuic",
|
|
1170
|
+
server,
|
|
1171
|
+
server_port: port,
|
|
1172
|
+
uuid: user,
|
|
1173
|
+
password: decodeURIComponent(u.password),
|
|
1174
|
+
congestion_control: "bbr",
|
|
1175
|
+
tls: tls(["h3"])
|
|
1176
|
+
};
|
|
1177
|
+
case "anytls":
|
|
1178
|
+
return { type: "anytls", server, server_port: port, password: user, tls: tls() };
|
|
1179
|
+
case "ss": {
|
|
1180
|
+
const [method, ...rest] = base64UrlDecode(user).split(":");
|
|
1181
|
+
return { type: "shadowsocks", server, server_port: port, method, password: rest.join(":") };
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
var AUTO_TAG = "\u81EA\u52A8\u9009\u62E9 Auto";
|
|
1186
|
+
var SELECT_TAG = "\u8282\u70B9\u9009\u62E9 Select";
|
|
1187
|
+
function singBoxProfile(links) {
|
|
1188
|
+
const nodes = links.items.map((item) => ({ ...outboundOf(item), tag: `${NAMES[item.id]} \xB7 dsh-vpn` }));
|
|
1189
|
+
const tags = nodes.map((n) => n.tag);
|
|
1190
|
+
return {
|
|
1191
|
+
log: { level: "warn" },
|
|
1192
|
+
dns: {
|
|
1193
|
+
servers: [
|
|
1194
|
+
{ type: "tls", tag: "dns-remote", server: "1.1.1.1", detour: SELECT_TAG },
|
|
1195
|
+
{ type: "local", tag: "dns-local" }
|
|
1196
|
+
],
|
|
1197
|
+
final: "dns-remote"
|
|
1198
|
+
},
|
|
1199
|
+
inbounds: [{
|
|
1200
|
+
type: "tun",
|
|
1201
|
+
tag: "tun-in",
|
|
1202
|
+
address: ["172.19.0.1/30", "fdfe:dcba:9876::1/126"],
|
|
1203
|
+
auto_route: true,
|
|
1204
|
+
strict_route: true,
|
|
1205
|
+
stack: "mixed"
|
|
1206
|
+
}],
|
|
1207
|
+
outbounds: [
|
|
1208
|
+
{ type: "selector", tag: SELECT_TAG, outbounds: [AUTO_TAG, ...tags], default: AUTO_TAG },
|
|
1209
|
+
{ type: "urltest", tag: AUTO_TAG, outbounds: tags },
|
|
1210
|
+
...nodes,
|
|
1211
|
+
{ type: "direct", tag: "direct" }
|
|
1212
|
+
],
|
|
1213
|
+
route: {
|
|
1214
|
+
rules: [
|
|
1215
|
+
{ action: "sniff" },
|
|
1216
|
+
{ protocol: "dns", action: "hijack-dns" },
|
|
1217
|
+
{ ip_is_private: true, outbound: "direct" }
|
|
1218
|
+
],
|
|
1219
|
+
final: SELECT_TAG,
|
|
1220
|
+
auto_detect_interface: true,
|
|
1221
|
+
default_domain_resolver: "dns-local"
|
|
1222
|
+
},
|
|
1223
|
+
experimental: { cache_file: { enabled: true } }
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
function profileFileName(links) {
|
|
1227
|
+
return `dsh-vpn-${links.server.replace(/[^0-9a-z.-]/gi, "_")}.json`;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1121
1230
|
// src/protocols.ts
|
|
1122
1231
|
var PROTOCOLS = [
|
|
1123
1232
|
{ id: "reality", name: "VLESS Reality", transport: "TCP", insecureLink: false, defaultSelected: true },
|
|
@@ -1306,6 +1415,39 @@ function KeyPanel({ t, api, form, saved, set, setForm, disabled, onLoggedIn }) {
|
|
|
1306
1415
|
] })
|
|
1307
1416
|
] });
|
|
1308
1417
|
}
|
|
1418
|
+
function ProfileCard({ links, t }) {
|
|
1419
|
+
const [copied, setCopied] = (0, import_react.useState)(false);
|
|
1420
|
+
const text = (0, import_react.useMemo)(() => JSON.stringify(singBoxProfile(links), null, 2), [links]);
|
|
1421
|
+
const download = () => {
|
|
1422
|
+
const url = URL.createObjectURL(new Blob([text], { type: "application/json" }));
|
|
1423
|
+
const a = document.createElement("a");
|
|
1424
|
+
a.href = url;
|
|
1425
|
+
a.download = profileFileName(links);
|
|
1426
|
+
document.body.append(a);
|
|
1427
|
+
a.click();
|
|
1428
|
+
a.remove();
|
|
1429
|
+
setTimeout(() => {
|
|
1430
|
+
URL.revokeObjectURL(url);
|
|
1431
|
+
}, 1e4);
|
|
1432
|
+
};
|
|
1433
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dshvpn-keycard dshvpn-profile", children: [
|
|
1434
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dshvpn-keycard-head", children: [
|
|
1435
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: t("profileTitle") }),
|
|
1436
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dshvpn-actions", children: [
|
|
1437
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "primary", onClick: download, children: t("profileDownload") }),
|
|
1438
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "outline", onClick: () => {
|
|
1439
|
+
void (0, import_dsh_client_ui_primitives.writeClipboard)(text).then(() => {
|
|
1440
|
+
setCopied(true);
|
|
1441
|
+
setTimeout(() => {
|
|
1442
|
+
setCopied(false);
|
|
1443
|
+
}, 1500);
|
|
1444
|
+
});
|
|
1445
|
+
}, children: copied ? t("copied") : t("profileCopy") })
|
|
1446
|
+
] })
|
|
1447
|
+
] }),
|
|
1448
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { className: "dshvpn-muted", children: t("profileHint", { file: profileFileName(links) }) })
|
|
1449
|
+
] });
|
|
1450
|
+
}
|
|
1309
1451
|
function QrCard({ item, t }) {
|
|
1310
1452
|
const [copied, setCopied] = (0, import_react.useState)(false);
|
|
1311
1453
|
const info = PROTOCOLS.find((p) => p.id === item.id);
|
|
@@ -1698,7 +1840,10 @@ ${fw.backend === "none" ? t("fwNone") : t("fwStatus", { backend: fw.backend, cou
|
|
|
1698
1840
|
] })
|
|
1699
1841
|
] }),
|
|
1700
1842
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { className: "dshvpn-muted", children: t("qrHint") }),
|
|
1701
|
-
links ? /* @__PURE__ */ (0, import_jsx_runtime.
|
|
1843
|
+
links ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1844
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProfileCard, { links, t }),
|
|
1845
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dshvpn-qr-grid", children: links.items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QrCard, { item, t }, item.id)) })
|
|
1846
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dshvpn-qr-empty", children: [
|
|
1702
1847
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dshvpn-qr-empty-icon", "aria-hidden": "true", children: "\u25A6" }),
|
|
1703
1848
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: needsSave ? t("qrNeedsSave") : t("qrEmpty") }),
|
|
1704
1849
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_dsh_client_ui_primitives.Button, { variant: "primary", className: "dshvpn-refresh dshvpn-refresh-lg", disabled: disabled || needsSave, onClick: () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vpn",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "DeepSeek Harness plugin: deploy an encrypted VPN (sing-box) to your own VPS in one click, to protect your connections and privacy on untrusted networks",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"anytls",
|