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 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 save = () => op("save", () => api.saveSettings(form), (r) => {
1137
- setSaved(r.settings);
1138
- setForm(formOf(r.settings));
1139
- return { tone: "success", text: t("saved") };
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-vpn",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
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",