dsh-vpn 0.1.6 → 0.1.7
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 +2 -0
- package/lib/client.js +42 -5
- package/lib/index.js +29 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,6 +38,8 @@ dsh plugin --profile web add dsh-vpn
|
|
|
38
38
|
3. 在「节点二维码」里点「刷新二维码」,用手机或电脑上的客户端扫码导入。
|
|
39
39
|
4. 服务器防火墙的端口由插件自动放行,不用手动操作;安装完成后提示框会列出放行了哪些端口。
|
|
40
40
|
|
|
41
|
+
**找回已有节点**:如果重装过 dsh 或 dsh-vpn,本地保存的设置会丢失,但 VPS 上的节点还在。重新填写服务器 IP、端口、用户名和密码后点「保存」,插件会自动检查这台服务器上是否已有安装(也可以随时点「检查已有安装」)。找到后直接显示原来的节点二维码,并按服务器上的实际情况勾选协议和证书类型,已经导入手机或电脑客户端的配置可以继续使用,不用重新扫码。
|
|
42
|
+
|
|
41
43
|
页面第一行显示插件版本,有新版本时旁边会提示,点「更新」即可升级插件,重启 dsh 后生效(需要 dsh 0.2 及以上;更早的版本请在「设置 → 插件」中更新)。为了供应链安全,发布不足 24 小时的新版本会等满 24 小时再提供更新。
|
|
42
44
|
|
|
43
45
|
## 语言
|
package/lib/client.js
CHANGED
|
@@ -54,6 +54,8 @@ var vpnApi = {
|
|
|
54
54
|
test: () => post("/test"),
|
|
55
55
|
status: () => post("/status"),
|
|
56
56
|
links: () => post("/links"),
|
|
57
|
+
/** Find an earlier installation on the server and adopt its protocols and certificate mode. */
|
|
58
|
+
detect: () => post("/detect"),
|
|
57
59
|
install: (regenerate) => post("/install", { regenerate }),
|
|
58
60
|
uninstall: () => post("/uninstall"),
|
|
59
61
|
/** Installed version; with `check`, also the newest release on npm. */
|
|
@@ -132,6 +134,10 @@ var zh = {
|
|
|
132
134
|
working: "\u6267\u884C\u4E2D\u2026",
|
|
133
135
|
qrSection: "\u8282\u70B9\u4E8C\u7EF4\u7801",
|
|
134
136
|
qrHint: "\u7528 v2rayN\u3001NekoBox\u3001Shadowrocket\u3001sing-box\u3001Clash Meta \u7B49\u5BA2\u6237\u7AEF\u626B\u63CF\u5BFC\u5165\u3002\u4E8C\u7EF4\u7801\u7B49\u540C\u4E8E\u5BC6\u7801\uFF0C\u8BF7\u52FF\u5916\u4F20\u3002",
|
|
137
|
+
detect: "\u68C0\u67E5\u5DF2\u6709\u5B89\u88C5",
|
|
138
|
+
detectFound: "\u5DF2\u627E\u5230\u4E4B\u524D\u7684\u5B89\u88C5\uFF1Asing-box {version}\uFF0C\u534F\u8BAE\uFF1A{protocols}\uFF08{cert}\uFF09\u3002\u8282\u70B9\u4E8C\u7EF4\u7801\u5DF2\u663E\u793A\u5728\u4E0B\u65B9\uFF0C\u539F\u6709\u7684\u5BA2\u6237\u7AEF\u914D\u7F6E\u53EF\u4EE5\u7EE7\u7EED\u4F7F\u7528\u3002",
|
|
139
|
+
detectSynced: "\u5DF2\u6309\u670D\u52A1\u5668\u4E0A\u7684\u5B9E\u9645\u60C5\u51B5\u66F4\u65B0\u4E86\u534F\u8BAE\u52FE\u9009\u548C\u8BC1\u4E66\u7C7B\u578B\u3002",
|
|
140
|
+
detectNone: "\u8FD9\u53F0\u670D\u52A1\u5668\u4E0A\u6CA1\u6709\u627E\u5230\u4E4B\u524D\u7684\u5B89\u88C5\uFF0C\u53EF\u4EE5\u76F4\u63A5\u70B9\u300C\u5B89\u88C5 / \u66F4\u65B0\u300D\u3002",
|
|
135
141
|
refresh: "\u5237\u65B0\u4E8C\u7EF4\u7801",
|
|
136
142
|
refreshing: "\u6B63\u5728\u5237\u65B0\u2026",
|
|
137
143
|
refreshedAt: "\u4E0A\u6B21\u5237\u65B0 {time}",
|
|
@@ -234,6 +240,10 @@ var en = {
|
|
|
234
240
|
working: "Working\u2026",
|
|
235
241
|
qrSection: "Node QR codes",
|
|
236
242
|
qrHint: "Scan with v2rayN, NekoBox, Shadowrocket, sing-box, Clash Meta and similar apps. A QR code is as secret as a password.",
|
|
243
|
+
detect: "Find existing install",
|
|
244
|
+
detectFound: "Found an earlier installation: sing-box {version}, protocols: {protocols} ({cert}). Its QR codes are shown below; clients you already set up keep working.",
|
|
245
|
+
detectSynced: "The protocol selection and certificate type now match the server.",
|
|
246
|
+
detectNone: "No earlier installation was found on this server. Click Install / Update to set one up.",
|
|
237
247
|
refresh: "Refresh QR codes",
|
|
238
248
|
refreshing: "Refreshing\u2026",
|
|
239
249
|
refreshedAt: "Last refreshed {time}",
|
|
@@ -1133,11 +1143,35 @@ function VpnSettingsPage({ view, t, api }) {
|
|
|
1133
1143
|
setBusy(null);
|
|
1134
1144
|
}
|
|
1135
1145
|
}
|
|
1136
|
-
const
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1146
|
+
const detect = async () => {
|
|
1147
|
+
await op("detect", api.detect, (r) => {
|
|
1148
|
+
const res = r.result ?? {};
|
|
1149
|
+
if (res.installed !== true) return { tone: "info", text: t("detectNone") };
|
|
1150
|
+
if (r.links) {
|
|
1151
|
+
setLinks(r.links);
|
|
1152
|
+
setRefreshedAt(/* @__PURE__ */ new Date());
|
|
1153
|
+
}
|
|
1154
|
+
const protocols = (res.protocols ?? []).map((id) => PROTOCOLS.find((p) => p.id === id)?.name ?? id).join("\u3001");
|
|
1155
|
+
const cert = res.certMode === "ip" ? t("certIp") : t("certSelf");
|
|
1156
|
+
const text = t("detectFound", { version: String(res.version ?? ""), protocols, cert });
|
|
1157
|
+
return { tone: "success", text: res.synced === true ? `${text}
|
|
1158
|
+
${t("detectSynced")}` : text };
|
|
1159
|
+
});
|
|
1160
|
+
const s = await api.getSettings();
|
|
1161
|
+
if (s.ok) {
|
|
1162
|
+
setSaved(s.settings);
|
|
1163
|
+
setForm(formOf(s.settings));
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
const save = async () => {
|
|
1167
|
+
const first = !saved.configured;
|
|
1168
|
+
const r = await op("save", () => api.saveSettings(form), (r2) => {
|
|
1169
|
+
setSaved(r2.settings);
|
|
1170
|
+
setForm(formOf(r2.settings));
|
|
1171
|
+
return { tone: "success", text: t("saved") };
|
|
1172
|
+
});
|
|
1173
|
+
if (r.ok && first) await detect();
|
|
1174
|
+
};
|
|
1141
1175
|
const refreshSettings = async () => {
|
|
1142
1176
|
const r = await api.getSettings();
|
|
1143
1177
|
if (r.ok) {
|
|
@@ -1300,6 +1334,9 @@ ${t("cloudHint")}`;
|
|
|
1300
1334
|
children: t("test")
|
|
1301
1335
|
}
|
|
1302
1336
|
),
|
|
1337
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", disabled: disabled || needsSave, onClick: () => {
|
|
1338
|
+
void detect();
|
|
1339
|
+
}, children: busy === "detect" ? t("working") : t("detect") }),
|
|
1303
1340
|
dirty && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { className: "dshvpn-warn", children: t("unsaved") })
|
|
1304
1341
|
] }),
|
|
1305
1342
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { children: [
|
package/lib/index.js
CHANGED
|
@@ -20298,6 +20298,7 @@ function apiHandlers(service, uiLang, onUiLang, updater) {
|
|
|
20298
20298
|
"/test": (_body, signal) => service.test(signal),
|
|
20299
20299
|
"/status": (_body, signal) => service.status(signal),
|
|
20300
20300
|
"/links": (_body, signal) => service.links(signal),
|
|
20301
|
+
"/detect": (_body, signal) => service.detect(signal),
|
|
20301
20302
|
"/install": (body, signal) => service.install({ regenerate: body.regenerate === true }, signal),
|
|
20302
20303
|
"/uninstall": (_body, signal) => service.uninstall(signal)
|
|
20303
20304
|
};
|
|
@@ -20615,6 +20616,34 @@ var VpnService = class {
|
|
|
20615
20616
|
links(signal) {
|
|
20616
20617
|
return this.run("links", (s) => ["--server", s.linkAddress ?? s.host], signal);
|
|
20617
20618
|
}
|
|
20619
|
+
/**
|
|
20620
|
+
* Find an existing installation (e.g. after dsh-vpn was removed and its local
|
|
20621
|
+
* settings lost): read the server's protocols, certificate mode and links,
|
|
20622
|
+
* and adopt the protocols and certificate mode into local settings so a
|
|
20623
|
+
* later 安装 / 更新 keeps the node as it is.
|
|
20624
|
+
*/
|
|
20625
|
+
async detect(signal) {
|
|
20626
|
+
const status = await this.status(signal);
|
|
20627
|
+
if (!status.ok || status.result?.installed !== true) return status.ok ? { ok: true, result: { installed: false } } : status;
|
|
20628
|
+
const r = status.result;
|
|
20629
|
+
const protocols = String(r.protocols ?? "").split(",").filter(isProtocolId);
|
|
20630
|
+
const mode = r.cert?.mode;
|
|
20631
|
+
const certMode = mode === "ip" || mode === "self" ? mode : void 0;
|
|
20632
|
+
let synced = false;
|
|
20633
|
+
await this.store.update((cur) => {
|
|
20634
|
+
if (!cur) return void 0;
|
|
20635
|
+
const next = { ...cur, ...protocols.length > 0 ? { protocols } : {}, ...certMode ? { certMode } : {} };
|
|
20636
|
+
synced = next.certMode !== cur.certMode || next.protocols.join() !== cur.protocols.join();
|
|
20637
|
+
return synced ? next : void 0;
|
|
20638
|
+
});
|
|
20639
|
+
const links = await this.links(signal);
|
|
20640
|
+
return {
|
|
20641
|
+
ok: links.ok,
|
|
20642
|
+
...links.error ? { error: links.error, message: links.message } : {},
|
|
20643
|
+
result: { installed: true, active: r.active, version: r.version, protocols, certMode, synced },
|
|
20644
|
+
...links.links ? { links: links.links } : {}
|
|
20645
|
+
};
|
|
20646
|
+
}
|
|
20618
20647
|
uninstall(signal) {
|
|
20619
20648
|
return this.run("uninstall", () => [], signal);
|
|
20620
20649
|
}
|
package/package.json
CHANGED