dsh-pocket 1.6.0 → 1.7.1
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/client/client.js +6 -7
- package/client/index.jsx +5 -5
- package/lib/index.js +42 -1
- package/lib/proxy.mjs +108 -1
- package/lib/service.mjs +7 -0
- package/lib/web-rpc.js +3 -3
- package/package.json +1 -1
package/client/client.js
CHANGED
|
@@ -1517,7 +1517,11 @@ function PocketSettingsTab({ rpcCall }) {
|
|
|
1517
1517
|
(0, import_react2.createElement)("img", { src: status.tunnelQr, alt: "Tunnel QR", style: styles.qr }),
|
|
1518
1518
|
(0, import_react2.createElement)("div", { style: styles.code }, tunnelUrl),
|
|
1519
1519
|
(0, import_react2.createElement)("div", { style: styles.muted }, "\u4EFB\u4F55\u7F51\u7EDC\u626B\u7801\u5373\u7528\uFF08URL \u6BCF\u6B21\u91CD\u542F\u81EA\u52A8\u6362\u65B0\uFF09"),
|
|
1520
|
-
(0, import_react2.createElement)(
|
|
1520
|
+
status.accessToken ? (0, import_react2.createElement)(
|
|
1521
|
+
"div",
|
|
1522
|
+
{ style: { marginTop: 6, fontSize: 12, color: "var(--dsw-alias-label-secondary,#6b7280)", lineHeight: 1.5 } },
|
|
1523
|
+
`\u{1F510} \u8BBF\u95EE\u5BC6\u7801\uFF1A${status.accessToken}\uFF08\u6BCF\u6B21\u5F00\u542F\u516C\u7F51\u53D8\u65B0\uFF1B\u624B\u673A\u6253\u5F00\u94FE\u63A5\u9700\u8F93\u5165\u6B64\u5BC6\u7801\uFF09| PIN: ${status.accessToken} \u2014 required on the phone`
|
|
1524
|
+
) : null,
|
|
1521
1525
|
(0, import_react2.createElement)("button", { style: styles.btn, onClick: stopTunnel }, "\u5173\u95ED\u516C\u7F51 | Stop")
|
|
1522
1526
|
) : (0, import_react2.createElement)(
|
|
1523
1527
|
"div",
|
|
@@ -1531,12 +1535,7 @@ function PocketSettingsTab({ rpcCall }) {
|
|
|
1531
1535
|
"div",
|
|
1532
1536
|
{ style: { marginTop: 4, fontSize: 12, color: "var(--dsw-alias-state-error-primary,#dc2626)" } },
|
|
1533
1537
|
`\u274C \u5F00\u542F\u5931\u8D25\uFF1A${tunnelStateDetail || "\u672A\u77E5\u9519\u8BEF | failed"}\uFF08\u53EF\u91CD\u8BD5\uFF1B\u82E5\u662F\u4EE3\u7406/VPN \u95EE\u9898\u89C1 README \u6392\u969C\uFF09`
|
|
1534
|
-
) :
|
|
1535
|
-
"div",
|
|
1536
|
-
null,
|
|
1537
|
-
(0, import_react2.createElement)("div", { style: styles.warn, marginTop: 4 }, "\u26A0\uFE0F DSH \u80FD\u6267\u884C\u7535\u8111\u4EE3\u7801\uFF1A\u4E8C\u7EF4\u7801/URL \u5C31\u662F\u94A5\u5319\uFF0C\u8BF7\u52FF\u53D1\u7ED9\u522B\u4EBA | the QR/URL is the key \u2014 never share it"),
|
|
1538
|
-
(0, import_react2.createElement)("div", { style: styles.muted, marginTop: 4 }, "\u4E0D\u614E\u6CC4\u9732\u4E86\uFF1F\u91CD\u542F dsh web\uFF0CURL \u81EA\u52A8\u6362\u65B0\u3001\u65E7\u94FE\u63A5\u7ACB\u5373\u5931\u6548 | Leaked it? Restart dsh web \u2014 the URL rotates and the old one dies instantly")
|
|
1539
|
-
)
|
|
1538
|
+
) : null
|
|
1540
1539
|
)
|
|
1541
1540
|
),
|
|
1542
1541
|
error ? (0, import_react2.createElement)("div", { style: { color: "var(--dsw-alias-state-error-primary,#dc2626)", fontSize: 12, marginTop: 8 } }, `\u274C ${error}`) : null,
|
package/client/index.jsx
CHANGED
|
@@ -247,7 +247,10 @@ function PocketSettingsTab({ rpcCall }) {
|
|
|
247
247
|
h('img', { src: status.tunnelQr, alt: 'Tunnel QR', style: styles.qr }),
|
|
248
248
|
h('div', { style: styles.code }, tunnelUrl),
|
|
249
249
|
h('div', { style: styles.muted }, '任何网络扫码即用(URL 每次重启自动换新)'),
|
|
250
|
-
|
|
250
|
+
status.accessToken
|
|
251
|
+
? h('div', { style: { marginTop: 6, fontSize: 12, color: 'var(--dsw-alias-label-secondary,#6b7280)', lineHeight: 1.5 } },
|
|
252
|
+
`🔐 访问密码:${status.accessToken}(每次开启公网变新;手机打开链接需输入此密码)| PIN: ${status.accessToken} — required on the phone`)
|
|
253
|
+
: null,
|
|
251
254
|
h('button', { style: styles.btn, onClick: stopTunnel }, '关闭公网 | Stop'),
|
|
252
255
|
)
|
|
253
256
|
: h('div', null,
|
|
@@ -260,10 +263,7 @@ function PocketSettingsTab({ rpcCall }) {
|
|
|
260
263
|
: tunnelPhase === 'error'
|
|
261
264
|
? h('div', { style: { marginTop: 4, fontSize: 12, color: 'var(--dsw-alias-state-error-primary,#dc2626)' } },
|
|
262
265
|
`❌ 开启失败:${tunnelStateDetail || '未知错误 | failed'}(可重试;若是代理/VPN 问题见 README 排障)`)
|
|
263
|
-
:
|
|
264
|
-
h('div', { style: styles.warn, marginTop: 4 }, '⚠️ DSH 能执行电脑代码:二维码/URL 就是钥匙,请勿发给别人 | the QR/URL is the key — never share it'),
|
|
265
|
-
h('div', { style: styles.muted, marginTop: 4 }, '不慎泄露了?重启 dsh web,URL 自动换新、旧链接立即失效 | Leaked it? Restart dsh web — the URL rotates and the old one dies instantly'),
|
|
266
|
-
),
|
|
266
|
+
: null,
|
|
267
267
|
),
|
|
268
268
|
),
|
|
269
269
|
|
package/lib/index.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import { fileURLToPath } from 'node:url';
|
|
13
13
|
import { spawn } from 'node:child_process';
|
|
14
|
-
import { readFileSync } from 'node:fs';
|
|
14
|
+
import { readFileSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
15
15
|
import { writeFile, readFile, mkdir, rm } from 'node:fs/promises';
|
|
16
16
|
import { join, dirname } from 'node:path';
|
|
17
17
|
import { homedir } from 'node:os';
|
|
@@ -41,6 +41,35 @@ function currentVersion() {
|
|
|
41
41
|
/** 进程启动时加载的版本(模块加载瞬间固化;用于识别「磁盘已更新但进程还是旧代码」)。 */
|
|
42
42
|
const loadedVersion = currentVersion();
|
|
43
43
|
|
|
44
|
+
// ---------- 公网访问密码(issue #13) ----------
|
|
45
|
+
// 8 位数字,存本机 $DSH_HOME/dsh-pocket/token,每次开启公网时轮换(旧链接作废)。
|
|
46
|
+
// 局域网 IP 直连免密码;仅公网隧道 Host 强制。
|
|
47
|
+
const tokenRel = join('dsh-pocket', 'token');
|
|
48
|
+
function tokenPath() {
|
|
49
|
+
return join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), tokenRel);
|
|
50
|
+
}
|
|
51
|
+
/** 生成 8 位数字密码并写文件(同步,保证后续读取一致)。 */
|
|
52
|
+
function writeNewToken() {
|
|
53
|
+
const fresh = String(Math.floor(10000000 + Math.random() * 90000000));
|
|
54
|
+
try {
|
|
55
|
+
mkdirSync(dirname(tokenPath()), { recursive: true });
|
|
56
|
+
writeFileSync(tokenPath(), fresh, { mode: 0o600 });
|
|
57
|
+
} catch { /* 忽略 */ }
|
|
58
|
+
return fresh;
|
|
59
|
+
}
|
|
60
|
+
/** 当前公网访问密码;文件不存在时生成一个。 */
|
|
61
|
+
function getAccessToken() {
|
|
62
|
+
try {
|
|
63
|
+
const existing = readFileSync(tokenPath(), 'utf8').trim();
|
|
64
|
+
if (/^\d{8}$/.test(existing)) return existing;
|
|
65
|
+
} catch { /* 无文件 */ }
|
|
66
|
+
return writeNewToken();
|
|
67
|
+
}
|
|
68
|
+
/** 轮换:生成新的 8 位数字密码(开启公网时调用,旧密码/链接立即作废)。 */
|
|
69
|
+
function rotateAccessToken() {
|
|
70
|
+
return writeNewToken();
|
|
71
|
+
}
|
|
72
|
+
|
|
44
73
|
const restartNoticeRel = join('dsh-pocket', 'restarted.json');
|
|
45
74
|
function restartNoticePath() {
|
|
46
75
|
return join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), restartNoticeRel);
|
|
@@ -132,12 +161,24 @@ export function apply(ctx, config = {}, internals = {}) {
|
|
|
132
161
|
// client 崩溃(issue #3/#4)。给代理注入「桌面参数补丁」(history.replaceState 补齐参数,
|
|
133
162
|
// 无跳转;compatibility 模式不套桌面布局,避免移动端按钮叠加)。保留默认 polyfill。
|
|
134
163
|
injectHtml: isDesktop ? DEFAULT_INJECT + desktopEnvPatchScript(process.platform) : undefined,
|
|
164
|
+
// 公网访问密码(issue #13):仅公网隧道 Host 强制;局域网免密码
|
|
165
|
+
auth: {
|
|
166
|
+
getToken: () => getAccessToken(),
|
|
167
|
+
isProtected: (host) => /trycloudflare\.com$/i.test(String(host ?? '')),
|
|
168
|
+
},
|
|
169
|
+
// 每次公网隧道就绪 → 轮换 8 位密码(旧密码/旧链接立即作废)
|
|
170
|
+
onTunnelReady: () => {
|
|
171
|
+
const fresh = rotateAccessToken();
|
|
172
|
+
logger.info('dsh-pocket: public access PIN rotated | 公网访问密码已更新(旧链接作废)');
|
|
173
|
+
return fresh;
|
|
174
|
+
},
|
|
135
175
|
});
|
|
136
176
|
|
|
137
177
|
const disposers = [];
|
|
138
178
|
const disposeRpc = installPocketRpc(ctx, {
|
|
139
179
|
service,
|
|
140
180
|
desktop: isDesktop,
|
|
181
|
+
getToken: () => getAccessToken(),
|
|
141
182
|
runUpdate: internals.runUpdate ?? { currentVersion, perform: performUpdate, loadedVersion: () => loadedVersion },
|
|
142
183
|
restart: internals.restart ?? (() => pocketRestart(service)),
|
|
143
184
|
restartNotice: internals.restartNotice ?? consumeRestartNotice,
|
package/lib/proxy.mjs
CHANGED
|
@@ -52,6 +52,64 @@ function isCompressed(headers) {
|
|
|
52
52
|
/** 默认注入到经代理的 HTML 文档里:crypto.randomUUID polyfill(非安全上下文必需)。 */
|
|
53
53
|
export const DEFAULT_INJECT = RANDOM_UUID_POLYFILL;
|
|
54
54
|
|
|
55
|
+
// ---------- 可选访问令牌认证(issue #13) ----------
|
|
56
|
+
// 只对公网隧道 Host(trycloudflare.com)强制;局域网免密码。
|
|
57
|
+
// 登录成功后种 HttpOnly 会话 cookie(浏览器关闭失效)→ SPA 内部 API/WS 自动携带。
|
|
58
|
+
const TOKEN_COOKIE = 'dsh_pocket_token';
|
|
59
|
+
|
|
60
|
+
function parseCookies(header) {
|
|
61
|
+
const out = {};
|
|
62
|
+
for (const part of String(header ?? '').split(';')) {
|
|
63
|
+
const eq = part.indexOf('=');
|
|
64
|
+
if (eq > 0) out[part.slice(0, eq).trim()] = part.slice(eq + 1).trim();
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function loginPageHtml(error) {
|
|
70
|
+
return `<!doctype html><html lang="zh"><head><meta charset="utf-8">
|
|
71
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
72
|
+
<title>DSH Pocket · 访问验证</title>
|
|
73
|
+
<style>
|
|
74
|
+
body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#f3f4f6;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}
|
|
75
|
+
.card{background:#fff;border:1px solid #e5e7eb;border-radius:12px;padding:28px 24px;max-width:320px;width:calc(100% - 48px);text-align:center}
|
|
76
|
+
h1{font-size:16px;margin:0 0 4px;color:#111827}
|
|
77
|
+
p{font-size:13px;color:#6b7280;margin:0 0 16px}
|
|
78
|
+
input{width:100%;box-sizing:border-box;padding:10px 12px;font-size:18px;letter-spacing:6px;text-align:center;border:1px solid #d1d5db;border-radius:8px;outline:none;margin-bottom:12px}
|
|
79
|
+
input:focus{border-color:#4f6ef7}
|
|
80
|
+
button{width:100%;padding:10px;font-size:15px;background:#4f6ef7;color:#fff;border:none;border-radius:8px;cursor:pointer}
|
|
81
|
+
.err{color:#dc2626;font-size:12px;margin-bottom:10px;min-height:16px}
|
|
82
|
+
</style></head><body><div class="card">
|
|
83
|
+
<h1>🔐 DSH Pocket</h1>
|
|
84
|
+
<p>此公网地址受访问密码保护,请输入 8 位数字密码</p>
|
|
85
|
+
<div class="err">${error ? '密码错误,请重试' : ''}</div>
|
|
86
|
+
<form method="post" action="/pocket-login">
|
|
87
|
+
<input name="token" type="password" inputmode="numeric" maxlength="8" autocomplete="one-time-code" autofocus required>
|
|
88
|
+
<button type="submit">进入 | Enter</button>
|
|
89
|
+
</form>
|
|
90
|
+
</div></body></html>`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** 该 Host 是否受访问密码保护(公网隧道;局域网 IP 直连免密码)。 */
|
|
94
|
+
function isProtectedHost(host, isProtected) {
|
|
95
|
+
return isProtected ? isProtected(host) : /trycloudflare\.com$/i.test(String(host ?? ''));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** 请求是否期望 HTML(浏览器导航 → 返回登录页;API/WS → 401)。 */
|
|
99
|
+
function isHtmlRequest(req) {
|
|
100
|
+
const accept = String(req.headers.accept ?? '');
|
|
101
|
+
return accept.includes('text/html') || req.url === '/' || /\.html?$/i.test(String(req.url));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** 校验请求是否已认证;返回 true 放行。 */
|
|
105
|
+
function authCheck(req, token) {
|
|
106
|
+
if (!token) return true; // 无 token(未配置密码)→ 放行
|
|
107
|
+
const cookieTok = parseCookies(req.headers.cookie)[TOKEN_COOKIE];
|
|
108
|
+
if (cookieTok === token) return true;
|
|
109
|
+
const qTok = new URL(req.url ?? '/', 'http://x').searchParams.get('token');
|
|
110
|
+
return qTok === token;
|
|
111
|
+
}
|
|
112
|
+
|
|
55
113
|
/** 把浏览器可见的权威改写成 loopback 权威(Host 和 Origin 都改)。 */
|
|
56
114
|
function loopbackAuthority(headers, upstream) {
|
|
57
115
|
const authority = `${upstream.host}:${upstream.port}`;
|
|
@@ -68,10 +126,49 @@ function loopbackAuthority(headers, upstream) {
|
|
|
68
126
|
* @param {string} [opts.host] 监听地址(默认 0.0.0.0:LAN 与隧道都能到)
|
|
69
127
|
* @param {{host:string,port:number}} [opts.upstream] 上游 dsh web(默认 127.0.0.1:3080)
|
|
70
128
|
* @param {string} [opts.injectHtml] 注入 HTML 的内容(默认 polyfill + 移动端适配;传 '' 关闭)
|
|
129
|
+
* @param {object} [opts.auth] 可选访问令牌认证(issue #13):{ getToken, isProtected }
|
|
71
130
|
* @returns {Promise<{server:import('node:http').Server, close:()=>Promise<void>}>}
|
|
72
131
|
*/
|
|
73
|
-
export function createPocketProxy({ port = 3081, host = '0.0.0.0', upstream = DEFAULT_UPSTREAM, log = null, injectHtml = DEFAULT_INJECT } = {}) {
|
|
132
|
+
export function createPocketProxy({ port = 3081, host = '0.0.0.0', upstream = DEFAULT_UPSTREAM, log = null, injectHtml = DEFAULT_INJECT, auth = null } = {}) {
|
|
74
133
|
const server = createServer((req, res) => {
|
|
134
|
+
// 访问令牌认证(issue #13):仅公网隧道 Host 强制;局域网 IP 直连免密码
|
|
135
|
+
if (auth) {
|
|
136
|
+
const host = String(req.headers.host ?? '');
|
|
137
|
+
const protectedHost = isProtectedHost(host, auth.isProtected);
|
|
138
|
+
const token = protectedHost ? (auth.getToken?.() ?? null) : null;
|
|
139
|
+
if (protectedHost && token) {
|
|
140
|
+
// 登录提交:校验密码 → 种 HttpOnly 会话 cookie → 回首页
|
|
141
|
+
if (req.method === 'POST' && req.url?.startsWith('/pocket-login')) {
|
|
142
|
+
let body = '';
|
|
143
|
+
req.on('data', (c) => { body += c; if (body.length > 1024) req.destroy(); });
|
|
144
|
+
req.on('end', () => {
|
|
145
|
+
const submitted = String(new URLSearchParams(body).get('token') ?? '');
|
|
146
|
+
if (submitted === token) {
|
|
147
|
+
res.writeHead(302, {
|
|
148
|
+
location: '/',
|
|
149
|
+
'set-cookie': `${TOKEN_COOKIE}=${token}; HttpOnly; SameSite=Lax; Path=/`,
|
|
150
|
+
'cache-control': 'no-store',
|
|
151
|
+
});
|
|
152
|
+
res.end();
|
|
153
|
+
} else {
|
|
154
|
+
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store' });
|
|
155
|
+
res.end(loginPageHtml(true));
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (!authCheck(req, token)) {
|
|
161
|
+
if (isHtmlRequest(req)) {
|
|
162
|
+
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store' });
|
|
163
|
+
res.end(loginPageHtml(false));
|
|
164
|
+
} else {
|
|
165
|
+
res.writeHead(401, { 'content-type': 'application/json', 'cache-control': 'no-store' });
|
|
166
|
+
res.end('{"error":"unauthorized"}');
|
|
167
|
+
}
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
75
172
|
const headers = loopbackAuthority({ ...req.headers }, upstream);
|
|
76
173
|
const proxyReq = httpRequest(
|
|
77
174
|
{ host: upstream.host, port: upstream.port, method: req.method, path: req.url, headers, agent: false },
|
|
@@ -148,6 +245,16 @@ export function createPocketProxy({ port = 3081, host = '0.0.0.0', upstream = DE
|
|
|
148
245
|
|
|
149
246
|
// WebSocket upgrade(DSH 的 /api/events.mux + events.host 流式通道)原样透传
|
|
150
247
|
server.on('upgrade', (req, socket, head) => {
|
|
248
|
+
// WebSocket 同样校验(防止绕过 HTTP 认证从 WS 进入)
|
|
249
|
+
if (auth) {
|
|
250
|
+
const host = String(req.headers.host ?? '');
|
|
251
|
+
const token = isProtectedHost(host, auth.isProtected) ? (auth.getToken?.() ?? null) : null;
|
|
252
|
+
if (token && !authCheck(req, token)) {
|
|
253
|
+
socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
|
|
254
|
+
socket.destroy();
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
151
258
|
const headers = loopbackAuthority({ ...req.headers }, upstream);
|
|
152
259
|
const proxyReq = httpRequest({
|
|
153
260
|
host: upstream.host, port: upstream.port, method: req.method, path: req.url, headers, agent: false,
|
package/lib/service.mjs
CHANGED
|
@@ -86,6 +86,10 @@ export function createPocketService({
|
|
|
86
86
|
internals = {},
|
|
87
87
|
/** 代理注入 HTML 的内容(桌面端补丁等由 lib/index.js 传入;默认 randomUUID polyfill) */
|
|
88
88
|
injectHtml,
|
|
89
|
+
/** 访问令牌认证配置(issue #13):{ getToken, isProtected },传给代理 */
|
|
90
|
+
auth,
|
|
91
|
+
/** 隧道就绪回调(lib/index.js 用它轮换公网密码) */
|
|
92
|
+
onTunnelReady,
|
|
89
93
|
} = {}) {
|
|
90
94
|
const createProxy = internals.createProxy ?? createPocketProxy;
|
|
91
95
|
const startTunnel = internals.startTunnel ?? startQuickTunnel;
|
|
@@ -142,6 +146,7 @@ export function createPocketService({
|
|
|
142
146
|
host: '0.0.0.0',
|
|
143
147
|
upstream: { host: '127.0.0.1', port: dshPort },
|
|
144
148
|
...(injectHtml ? { injectHtml } : {}),
|
|
149
|
+
...(auth ? { auth } : {}),
|
|
145
150
|
});
|
|
146
151
|
if (p !== port) {
|
|
147
152
|
console.log(`dsh-pocket: port ${port} busy, proxy on ${p} | 端口 ${port} 被占用,代理改用 ${p}`);
|
|
@@ -185,6 +190,8 @@ export function createPocketService({
|
|
|
185
190
|
});
|
|
186
191
|
// 记录「隧道开启中」,供重启后自动恢复(issue #11)
|
|
187
192
|
void persistAutoTunnel();
|
|
193
|
+
// 公网隧道就绪 → 轮换访问密码(issue #13:每次开启变新,旧链接作废)
|
|
194
|
+
try { onTunnelReady?.(); } catch { /* 忽略 */ }
|
|
188
195
|
return tunnel.url;
|
|
189
196
|
} catch (err) {
|
|
190
197
|
// stopTunnel 触发的 abort 不算错误:保持 idle,别把状态刷成 error
|
package/lib/web-rpc.js
CHANGED
|
@@ -25,7 +25,7 @@ export function killHint(port) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/** 注册 /dsh-pocket 逻辑通道(仅本机 loopback 可调)。 */
|
|
28
|
-
export function installPocketRpc(ctx, { service, log = console, desktop = false, runUpdate = null, restart = null, restartNotice = null }) {
|
|
28
|
+
export function installPocketRpc(ctx, { service, log = console, desktop = false, runUpdate = null, restart = null, restartNotice = null, getToken = null }) {
|
|
29
29
|
if (!ctx?.connection?.rpc?.handle) {
|
|
30
30
|
log.warn?.('dsh-pocket: DSH Host Connection RPC unavailable — settings tab disabled | 无 Connection RPC,设置页不可用');
|
|
31
31
|
return () => {};
|
|
@@ -33,12 +33,12 @@ export function installPocketRpc(ctx, { service, log = console, desktop = false,
|
|
|
33
33
|
return ctx.connection.rpc.handle(POCKET_RPC_CHANNEL, async (endpoint, payload = {}, signal) => {
|
|
34
34
|
if (signal?.aborted) return fail('cancelled', 'The request was cancelled.');
|
|
35
35
|
|
|
36
|
-
// status 响应:redact 后的服务状态 + 重启提示(重启后 30 分钟内有效)+ 停止命令(按平台)+ 桌面端标志
|
|
36
|
+
// status 响应:redact 后的服务状态 + 重启提示(重启后 30 分钟内有效)+ 停止命令(按平台)+ 桌面端标志 + 公网访问密码
|
|
37
37
|
const statusPayload = async () => {
|
|
38
38
|
let notice = null;
|
|
39
39
|
try { notice = (await restartNotice?.()) ?? null; } catch { notice = null; }
|
|
40
40
|
const s = await service.status();
|
|
41
|
-
return ok({ ...redactStatus(s), desktop, restartNotice: notice, killHint: killHint(s.dshPort ?? 3080) });
|
|
41
|
+
return ok({ ...redactStatus(s), desktop, restartNotice: notice, killHint: killHint(s.dshPort ?? 3080), accessToken: getToken?.() ?? null });
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
try {
|
package/package.json
CHANGED