dsh-pocket 1.0.10 → 1.0.12

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.en.md CHANGED
@@ -28,6 +28,12 @@ That's what DSH Pocket does: **install it, scan a QR code, and your phone shows
28
28
 
29
29
  > One-liner: `dsh web` only allows local access by default; DSH Pocket puts a proxy in front of it that safely bridges your phone (see [How it works](#-how-it-works)).
30
30
 
31
+ What it looks like — the phone shows the exact same UI as your computer, live:
32
+
33
+ <p align="center">
34
+ <img src="docs/interface.jpg" alt="DSH UI on the phone" width="100%">
35
+ </p>
36
+
31
37
  ## ✨ Features
32
38
 
33
39
  | Feature | Description |
package/README.md CHANGED
@@ -28,6 +28,12 @@ DSH Pocket 就是干这个的:**装上它,手机扫个码,就能实时看
28
28
 
29
29
  > 原理一句话:`dsh web` 默认只允许本机访问,DSH Pocket 在它前面加一层代理,把手机访问安全地接入(详见[「原理」](#-原理))。
30
30
 
31
+ 实际效果——手机上的界面就是电脑上的界面,实时同步:
32
+
33
+ <p align="center">
34
+ <img src="docs/interface.jpg" alt="手机上的 DSH 界面" width="100%">
35
+ </p>
36
+
31
37
  ## ✨ 特性
32
38
 
33
39
  | 特性 | 说明 |
package/client/api.js CHANGED
@@ -12,6 +12,7 @@ export const POCKET_ENDPOINTS = Object.freeze({
12
12
  pushSetEnabled: 'push.setEnabled',
13
13
  version: 'pocket.version',
14
14
  update: 'pocket.update',
15
+ restart: 'pocket.restart',
15
16
  });
16
17
 
17
18
  /** 语义化版本比较:a > b 返回正数,相等 0,a < b 负数(仅数字段;带预发布后缀的更旧)。 */
package/client/client.js CHANGED
@@ -44,7 +44,8 @@ var POCKET_ENDPOINTS = Object.freeze({
44
44
  pushStatus: "push.status",
45
45
  pushSetEnabled: "push.setEnabled",
46
46
  version: "pocket.version",
47
- update: "pocket.update"
47
+ update: "pocket.update",
48
+ restart: "pocket.restart"
48
49
  });
49
50
  function compareVersions(a, b) {
50
51
  const pa = String(a).replace(/^v/, "").split(".");
@@ -1346,6 +1347,14 @@ function PocketSettingsTab({ rpcCall }) {
1346
1347
  alive = false;
1347
1348
  };
1348
1349
  }, []);
1350
+ const restartPocket = async () => {
1351
+ setUpdateInfo((u) => ({ ...u, restarting: true }));
1352
+ try {
1353
+ await call(POCKET_ENDPOINTS.restart, {});
1354
+ } catch (err) {
1355
+ setUpdateInfo((u) => ({ ...u, restarting: false, result: "fail", output: err.message }));
1356
+ }
1357
+ };
1349
1358
  const runUpdate = async () => {
1350
1359
  setUpdateInfo((u) => ({ ...u, updating: true, result: null }));
1351
1360
  try {
@@ -1414,9 +1423,9 @@ function PocketSettingsTab({ rpcCall }) {
1414
1423
  (0, import_react2.createElement)("div", { style: styles.muted }, "\u624B\u673A\u626B\u7801\u6253\u5F00\u7684\u5C31\u662F\u7535\u8111\u4E0A\u7684\u8FD9\u4E2A\u754C\u9762\uFF0C\u5B9E\u65F6\u540C\u6B65 | the phone shows this exact screen, live")
1415
1424
  ),
1416
1425
  (0, import_react2.createElement)(
1417
- "a",
1418
- { href: "https://github.com/shaobeichen/dsh-pocket/issues", target: "_blank", rel: "noreferrer", style: { fontSize: 12, color: "var(--dsw-alias-label-tertiary,#8b93a1)", textDecoration: "none", whiteSpace: "nowrap" } },
1419
- "\u5F00\u53D1\u8005\uFF1A\u7A0B\u5E8F\u5458\u5C11\u5317\u6668 | by \u5C11\u5317\u6668"
1426
+ "div",
1427
+ { style: { fontSize: 12, color: "var(--dsw-alias-label-tertiary,#8b93a1)", whiteSpace: "nowrap" } },
1428
+ "\u5F00\u53D1\u8005\uFF1A\u7A0B\u5E8F\u5458\u5C11\u5317\u6668"
1420
1429
  )
1421
1430
  ),
1422
1431
  // 更新提示
@@ -1427,7 +1436,7 @@ function PocketSettingsTab({ rpcCall }) {
1427
1436
  "div",
1428
1437
  { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8 } },
1429
1438
  (0, import_react2.createElement)("div", { style: { fontWeight: 600, fontSize: 13 } }, `\u{1F4E6} \u65B0\u7248\u672C v${updateInfo.latest} | Update available`),
1430
- updateInfo.result !== "ok" ? (0, import_react2.createElement)("button", { style: styles.primary, onClick: runUpdate, disabled: updateInfo.updating }, updateInfo.updating ? "\u66F4\u65B0\u4E2D\u2026" : `\u66F4\u65B0\u5230 v${updateInfo.latest} | Update`) : (0, import_react2.createElement)("button", { style: styles.btn, onClick: () => window.location.reload() }, "\u{1F504} \u5237\u65B0\u9875\u9762 | Refresh")
1439
+ updateInfo.result !== "ok" ? (0, import_react2.createElement)("button", { style: styles.primary, onClick: runUpdate, disabled: updateInfo.updating }, updateInfo.updating ? "\u66F4\u65B0\u4E2D\u2026" : `\u66F4\u65B0\u5230 v${updateInfo.latest} | Update`) : (0, import_react2.createElement)("button", { style: styles.primary, onClick: restartPocket, disabled: updateInfo.restarting }, updateInfo.restarting ? "\u91CD\u542F\u4E2D\u2026" : "\u{1F504} \u91CD\u542F dsh web \u751F\u6548 | Restart now")
1431
1440
  ),
1432
1441
  (0, import_react2.createElement)(
1433
1442
  "div",
@@ -1487,7 +1496,17 @@ function PocketSettingsTab({ rpcCall }) {
1487
1496
  !pushEnabled ? "\u5DF2\u5173\u95ED\uFF1Aagent \u8DD1\u5B8C\u4E0D\u4F1A\u63A8\u9001 | off: no notifications" : pushState === "on" ? "\u5DF2\u5F00\u542F\uFF1Aagent \u8DD1\u5B8C/\u51FA\u9519\u65F6\u624B\u673A\u6536\u5230\u901A\u77E5 | on: notified when tasks finish or fail" : pushState === "unsupported" ? "\u5DF2\u5F00\u542F\uFF08\u4F46\u5F53\u524D\u6D4F\u89C8\u5668\u4E0D\u652F\u6301\u63A8\u9001\uFF09| on, but this browser does not support push" : pushState === "insecure" ? "\u5DF2\u5F00\u542F\uFF0C\u4F46\u5F53\u524D\u8DEF\u5F84\u4E0D\u662F HTTPS\u2014\u2014\u63A8\u9001\u9700\u8981\u516C\u7F51\u96A7\u9053\u6216 localhost | on, but push needs HTTPS (public tunnel) or localhost" : pushState === "checking" ? "\u68C0\u67E5\u4E2D\u2026 | checking\u2026" : "\u63A8\u9001\u672A\u751F\u6548 | push not active"
1488
1497
  )
1489
1498
  ),
1490
- error ? (0, import_react2.createElement)("div", { style: { color: "var(--dsw-alias-state-error-primary,#dc2626)", fontSize: 12, marginTop: 8 } }, `\u274C ${error}`) : null
1499
+ error ? (0, import_react2.createElement)("div", { style: { color: "var(--dsw-alias-state-error-primary,#dc2626)", fontSize: 12, marginTop: 8 } }, `\u274C ${error}`) : null,
1500
+ // 页面最底部:反馈入口
1501
+ (0, import_react2.createElement)(
1502
+ "div",
1503
+ { style: { ...styles.block, textAlign: "center" } },
1504
+ (0, import_react2.createElement)(
1505
+ "a",
1506
+ { href: "https://github.com/shaobeichen/dsh-pocket/issues", target: "_blank", rel: "noreferrer", style: { fontSize: 12, color: "var(--dsw-alias-label-secondary,#6b7280)", textDecoration: "none" } },
1507
+ "\u6709\u95EE\u9898\uFF1F\u6B22\u8FCE\u5230 GitHub Issues \u53CD\u9988 \u{1F64F} | Questions? Open an issue on GitHub"
1508
+ )
1509
+ )
1491
1510
  );
1492
1511
  }
1493
1512
  function apply(ctx) {
package/client/index.jsx CHANGED
@@ -105,6 +105,17 @@ function PocketSettingsTab({ rpcCall }) {
105
105
  return () => { alive = false; };
106
106
  }, []);
107
107
 
108
+ // 重启宿主(更新生效必需:刷新页面不会重载服务端代码)
109
+ const restartPocket = async () => {
110
+ setUpdateInfo((u) => ({ ...u, restarting: true }));
111
+ try {
112
+ await call(POCKET_ENDPOINTS.restart, {});
113
+ // 宿主即将退出重启,无需更多处理
114
+ } catch (err) {
115
+ setUpdateInfo((u) => ({ ...u, restarting: false, result: 'fail', output: err.message }));
116
+ }
117
+ };
118
+
108
119
  // 一键更新:调宿主 dsh plugin update
109
120
  const runUpdate = async () => {
110
121
  setUpdateInfo((u) => ({ ...u, updating: true, result: null }));
@@ -171,8 +182,8 @@ function PocketSettingsTab({ rpcCall }) {
171
182
  h('strong', null, '📱 手机访问 | Phone access'),
172
183
  h('div', { style: styles.muted }, '手机扫码打开的就是电脑上的这个界面,实时同步 | the phone shows this exact screen, live'),
173
184
  ),
174
- h('a', { href: 'https://github.com/shaobeichen/dsh-pocket/issues', target: '_blank', rel: 'noreferrer', style: { fontSize: 12, color: 'var(--dsw-alias-label-tertiary,#8b93a1)', textDecoration: 'none', whiteSpace: 'nowrap' } },
175
- '开发者:程序员少北晨 | by 少北晨'),
185
+ h('div', { style: { fontSize: 12, color: 'var(--dsw-alias-label-tertiary,#8b93a1)', whiteSpace: 'nowrap' } },
186
+ '开发者:程序员少北晨'),
176
187
  ),
177
188
 
178
189
  // 更新提示
@@ -181,7 +192,7 @@ function PocketSettingsTab({ rpcCall }) {
181
192
  h('div', { style: { fontWeight: 600, fontSize: 13 } }, `📦 新版本 v${updateInfo.latest} | Update available`),
182
193
  updateInfo.result !== 'ok'
183
194
  ? h('button', { style: styles.primary, onClick: runUpdate, disabled: updateInfo.updating }, updateInfo.updating ? '更新中…' : `更新到 v${updateInfo.latest} | Update`)
184
- : h('button', { style: styles.btn, onClick: () => window.location.reload() }, '🔄 刷新页面 | Refresh'),
195
+ : h('button', { style: styles.primary, onClick: restartPocket, disabled: updateInfo.restarting }, updateInfo.restarting ? '重启中…' : '🔄 重启 dsh web 生效 | Restart now'),
185
196
  ),
186
197
  h('div', { style: styles.muted, marginTop: 4 },
187
198
  updateInfo.result === 'ok' ? '✅ 已更新,重启 dsh web 生效 | updated — restart dsh web'
@@ -238,6 +249,12 @@ function PocketSettingsTab({ rpcCall }) {
238
249
  ),
239
250
 
240
251
  error ? h('div', { style: { color: 'var(--dsw-alias-state-error-primary,#dc2626)', fontSize: 12, marginTop: 8 } }, `❌ ${error}`) : null,
252
+
253
+ // 页面最底部:反馈入口
254
+ h('div', { style: { ...styles.block, textAlign: 'center' } },
255
+ h('a', { href: 'https://github.com/shaobeichen/dsh-pocket/issues', target: '_blank', rel: 'noreferrer', style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary,#6b7280)', textDecoration: 'none' } },
256
+ '有问题?欢迎到 GitHub Issues 反馈 🙏 | Questions? Open an issue on GitHub'),
257
+ ),
241
258
  );
242
259
  }
243
260
 
package/lib/index.js CHANGED
@@ -15,6 +15,7 @@ import { createRequire } from 'node:module';
15
15
  import { createPocketService } from './service.mjs';
16
16
  import { installPocketRpc } from './web-rpc.js';
17
17
  import { createPushService } from './push.mjs';
18
+ import { restartHost } from './restart.js';
18
19
 
19
20
  const name = 'dsh-pocket';
20
21
  const inject = ['connection', 'webServer'];
@@ -83,6 +84,7 @@ export function apply(ctx, config = {}, internals = {}) {
83
84
  service,
84
85
  push: internals.push ?? { vapidPublicKey: () => '', count: () => 0, subscribe: async () => false, unsubscribe: async () => false, isEnabled: () => true, setEnabled: async () => true },
85
86
  runUpdate: internals.runUpdate ?? { currentVersion, perform: performUpdate },
87
+ restart: internals.restart ?? (() => restartHost({ internals })),
86
88
  log: logger,
87
89
  });
88
90
  disposers.push(disposeRpc);
package/lib/restart.js ADDED
@@ -0,0 +1,45 @@
1
+ // dsh-pocket 自重启:重新拉起启动本宿主的确切 dsh 调用(脱离父进程),
2
+ // 让更新后的插件代码生效——用户无需离开界面手动重启(借鉴 dshmarket 的
3
+ // self-restart 方案,见 https://github.com/dsh-market/dsh-market lib/restart.js)。
4
+ //
5
+ // 原理:本插件运行在 dsh web 进程内,process.argv 即 dsh CLI 的启动参数
6
+ // (如 node <bin.js> web [flags])。用相同参数 detached 拉起新进程,
7
+ // 短暂等待后旧进程退出,新进程接管端口。
8
+
9
+ import { spawn } from 'node:child_process';
10
+
11
+ /**
12
+ * 重建启动调用(与当前宿主相同的命令)。
13
+ * @returns {{file:string, args:string[]}}
14
+ */
15
+ export function restartLaunch() {
16
+ return {
17
+ file: process.argv[0], // node
18
+ args: [process.argv[1], ...process.argv.slice(2)], // <bin.js> + web [flags]
19
+ };
20
+ }
21
+
22
+ /**
23
+ * 拉起替代宿主(detached),随后退出当前进程。
24
+ * @param {object} opts
25
+ * @param {number} [opts.exitDelayMs] 等待新进程接管后再退出(默认 800ms)
26
+ * @param {object} [opts.internals] 测试注入:spawnFn / exitFn
27
+ * @returns {{spawned:boolean, file:string}}
28
+ */
29
+ export function restartHost({ exitDelayMs = 800, internals = {} } = {}) {
30
+ const spawnFn = internals.spawn ?? spawn;
31
+ const exitFn = internals.exit ?? ((code) => process.exit(code));
32
+ const { file, args } = restartLaunch();
33
+ try {
34
+ const child = spawnFn(file, args, {
35
+ detached: true,
36
+ stdio: 'ignore',
37
+ env: process.env,
38
+ });
39
+ child.unref?.();
40
+ setTimeout(() => exitFn(0), exitDelayMs);
41
+ return { spawned: true, file };
42
+ } catch (err) {
43
+ return { spawned: false, error: err?.message ?? String(err) };
44
+ }
45
+ }
package/lib/web-rpc.js CHANGED
@@ -17,7 +17,7 @@ function fail(code, message) {
17
17
  }
18
18
 
19
19
  /** 注册 /dsh-pocket 逻辑通道(仅本机 loopback 可调)。 */
20
- export function installPocketRpc(ctx, { service, push, log = console, runUpdate = null }) {
20
+ export function installPocketRpc(ctx, { service, push, log = console, runUpdate = null, restart = null }) {
21
21
  if (!ctx?.connection?.rpc?.handle) {
22
22
  log.warn?.('dsh-pocket: DSH Host Connection RPC unavailable — settings tab disabled | 无 Connection RPC,设置页不可用');
23
23
  return () => {};
@@ -63,6 +63,11 @@ export function installPocketRpc(ctx, { service, push, log = console, runUpdate
63
63
  const result = await runUpdate.perform(payload?.profile ?? 'web');
64
64
  return ok(result);
65
65
  }
66
+ if (endpoint === POCKET_ENDPOINTS.restart) {
67
+ if (!restart) return fail('bad-request', '重启不可用 | restart unavailable');
68
+ const result = restart();
69
+ return ok(result);
70
+ }
66
71
  return fail('bad-request', `Unknown endpoint: ${endpoint}`);
67
72
  } catch (err) {
68
73
  log.error?.('dsh-pocket: rpc %s failed | RPC 失败: %s', endpoint, err?.message ?? err);
package/package.json CHANGED
@@ -77,5 +77,5 @@
77
77
  "access": "public",
78
78
  "registry": "https://registry.npmjs.org/"
79
79
  },
80
- "version": "1.0.10"
80
+ "version": "1.0.12"
81
81
  }