dsh-deeppilot 0.4.0 → 0.5.0
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/COMPATIBILITY.md +20 -17
- package/PRIVACY.md +15 -43
- package/README.md +18 -8
- package/README.zh-CN.md +13 -7
- package/SECURITY.md +27 -29
- package/bin/SHA256SUMS +6 -6
- package/bin/darwin-amd64/dsh-deeppilot-tunnel +0 -0
- package/bin/darwin-arm64/dsh-deeppilot-tunnel +0 -0
- package/bin/linux-amd64/dsh-deeppilot-tunnel +0 -0
- package/bin/linux-arm64/dsh-deeppilot-tunnel +0 -0
- package/bin/windows-amd64/dsh-deeppilot-tunnel.exe +0 -0
- package/bin/windows-arm64/dsh-deeppilot-tunnel.exe +0 -0
- package/docs/SECURITY_ROADMAP.md +24 -0
- package/docs/SECURITY_ROADMAP.zh-CN.md +24 -0
- package/lib/client.js +392 -177
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +63 -16
- package/lib/index.js +1147 -299
- package/lib/index.js.map +1 -1
- package/package.json +37 -7
package/lib/client.js
CHANGED
|
@@ -27,21 +27,48 @@ window.__ModuleLoader__.load({
|
|
|
27
27
|
enumerable: true
|
|
28
28
|
}) : target, mod));
|
|
29
29
|
//#endregion
|
|
30
|
-
let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
|
|
31
30
|
let react = require("react");
|
|
31
|
+
//#region src/client/snapshot-store.ts
|
|
32
|
+
function createSnapshotStore(initial) {
|
|
33
|
+
let snapshot = initial;
|
|
34
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
35
|
+
return {
|
|
36
|
+
getSnapshot: () => snapshot,
|
|
37
|
+
subscribe(listener) {
|
|
38
|
+
listeners.add(listener);
|
|
39
|
+
return () => listeners.delete(listener);
|
|
40
|
+
},
|
|
41
|
+
set(value) {
|
|
42
|
+
if (Object.is(value, snapshot)) return;
|
|
43
|
+
snapshot = value;
|
|
44
|
+
for (const listener of listeners) listener();
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/device-auth.ts
|
|
50
|
+
const DEVICE_SCOPES = [
|
|
51
|
+
"sessions.read",
|
|
52
|
+
"prompt.send",
|
|
53
|
+
"sessions.manage",
|
|
54
|
+
"interactions.respond",
|
|
55
|
+
"notifications.register"
|
|
56
|
+
];
|
|
57
|
+
const DEFAULT_DEVICE_SCOPES = DEVICE_SCOPES;
|
|
58
|
+
function normalizeDeviceScopes(value) {
|
|
59
|
+
if (!Array.isArray(value)) return [...DEFAULT_DEVICE_SCOPES];
|
|
60
|
+
const allowed = new Set(DEVICE_SCOPES);
|
|
61
|
+
return [...new Set(value.filter((scope) => typeof scope === "string" && allowed.has(scope)))];
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
32
64
|
//#region src/report-wire.ts
|
|
33
65
|
/** The npm package identity both contribution registrations claim. */
|
|
34
66
|
const REPORT_REMOTE_PACKAGE = "dsh-deeppilot";
|
|
35
67
|
/** Canonical `<namespace>/<method>` endpoint of the report Remote. */
|
|
36
68
|
const REPORT_ENDPOINT = "deeppilot/report";
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
* Explicit, user-triggered endpoint that replaces the pairing secret. The old
|
|
41
|
-
* token stops working immediately; the fresh one is returned so the page can
|
|
42
|
-
* show/QR it right away.
|
|
43
|
-
*/
|
|
44
|
-
const ROTATE_TOKEN_ENDPOINT = "deeppilot/rotateToken";
|
|
69
|
+
const BEGIN_PAIRING_ENDPOINT = "deeppilot/beginPairing";
|
|
70
|
+
const REVOKE_DEVICE_ENDPOINT = "deeppilot/revokeDevice";
|
|
71
|
+
const SET_DEVICE_SCOPES_ENDPOINT = "deeppilot/setDeviceScopes";
|
|
45
72
|
function reject(field) {
|
|
46
73
|
throw new TypeError(`deeppilot/report result: invalid ${field}`);
|
|
47
74
|
}
|
|
@@ -89,6 +116,9 @@ window.__ModuleLoader__.load({
|
|
|
89
116
|
appVersion: str(s, "appVersion", "device.appVersion"),
|
|
90
117
|
firstSeenTs: int(s, "firstSeenTs", "device.firstSeenTs"),
|
|
91
118
|
lastSeenTs: int(s, "lastSeenTs", "device.lastSeenTs"),
|
|
119
|
+
fingerprint: str(s, "fingerprint", "device.fingerprint"),
|
|
120
|
+
scopes: normalizeDeviceScopes(s.scopes),
|
|
121
|
+
...s.revokedAt !== void 0 ? { revokedAt: int(s, "revokedAt", "device.revokedAt") } : {},
|
|
92
122
|
...apns ? { apns } : {}
|
|
93
123
|
};
|
|
94
124
|
}
|
|
@@ -196,8 +226,8 @@ window.__ModuleLoader__.load({
|
|
|
196
226
|
...s.updateAvailable === true ? { updateAvailable: true } : {},
|
|
197
227
|
...typeof releaseUrl === "string" && releaseUrl.length > 0 ? { releaseUrl } : {},
|
|
198
228
|
enabled: bool(s, "enabled", "enabled"),
|
|
199
|
-
|
|
200
|
-
|
|
229
|
+
identityPath: str(s, "identityPath", "identityPath"),
|
|
230
|
+
pairingReady: bool(s, "pairingReady", "pairingReady"),
|
|
201
231
|
activeConnections: int(s, "activeConnections", "activeConnections"),
|
|
202
232
|
historyBufferMax: int(s, "historyBufferMax", "historyBufferMax"),
|
|
203
233
|
debug: bool(s, "debug", "debug"),
|
|
@@ -209,10 +239,24 @@ window.__ModuleLoader__.load({
|
|
|
209
239
|
const reportSchema = { parse: parseReport };
|
|
210
240
|
const relayTestSchema = { parse: parseRelayTestResult };
|
|
211
241
|
const pushTestSchema = { parse: parsePushTestResult };
|
|
212
|
-
const
|
|
213
|
-
|
|
242
|
+
const pairingGrantSchema = { parse(value) {
|
|
243
|
+
const s = rec(value, "pairing grant");
|
|
244
|
+
const code = str(s, "code", "pairingGrant.code");
|
|
245
|
+
if (code.length < 32) reject("pairingGrant.code");
|
|
246
|
+
return {
|
|
247
|
+
code,
|
|
248
|
+
expiresAt: int(s, "expiresAt", "pairingGrant.expiresAt"),
|
|
249
|
+
audience: str(s, "audience", "pairingGrant.audience")
|
|
250
|
+
};
|
|
251
|
+
} };
|
|
252
|
+
const deviceIdSchema = { parse(value) {
|
|
253
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(value)) reject("deviceId");
|
|
214
254
|
return value;
|
|
215
255
|
} };
|
|
256
|
+
const scopesSchema = { parse(value) {
|
|
257
|
+
if (!Array.isArray(value) || value.some((scope) => typeof scope !== "string" || !DEVICE_SCOPES.includes(scope))) reject("scopes");
|
|
258
|
+
return normalizeDeviceScopes(value);
|
|
259
|
+
} };
|
|
216
260
|
const REPORT_REMOTE_CONTRIBUTION = {
|
|
217
261
|
package: REPORT_REMOTE_PACKAGE,
|
|
218
262
|
descriptors: [
|
|
@@ -230,29 +274,72 @@ window.__ModuleLoader__.load({
|
|
|
230
274
|
}
|
|
231
275
|
},
|
|
232
276
|
{
|
|
233
|
-
id: `${REPORT_REMOTE_PACKAGE}#${
|
|
277
|
+
id: `${REPORT_REMOTE_PACKAGE}#${BEGIN_PAIRING_ENDPOINT}`,
|
|
234
278
|
service: "deeppilotReport",
|
|
235
279
|
namespace: "deeppilot",
|
|
236
|
-
method: "
|
|
280
|
+
method: "beginPairing",
|
|
237
281
|
invocation: { kind: "direct" },
|
|
238
282
|
parameters: [],
|
|
239
283
|
result: {
|
|
240
284
|
mode: "strict",
|
|
241
|
-
typeSymbol: `${REPORT_REMOTE_PACKAGE}#
|
|
242
|
-
schema:
|
|
285
|
+
typeSymbol: `${REPORT_REMOTE_PACKAGE}#PairingGrantSnapshot`,
|
|
286
|
+
schema: pairingGrantSchema
|
|
243
287
|
}
|
|
244
288
|
},
|
|
245
289
|
{
|
|
246
|
-
id: `${REPORT_REMOTE_PACKAGE}#${
|
|
290
|
+
id: `${REPORT_REMOTE_PACKAGE}#${REVOKE_DEVICE_ENDPOINT}`,
|
|
247
291
|
service: "deeppilotReport",
|
|
248
292
|
namespace: "deeppilot",
|
|
249
|
-
method: "
|
|
293
|
+
method: "revokeDevice",
|
|
250
294
|
invocation: { kind: "direct" },
|
|
251
|
-
parameters: [
|
|
295
|
+
parameters: [{
|
|
296
|
+
name: "deviceId",
|
|
297
|
+
wire: "deviceId",
|
|
298
|
+
source: "json",
|
|
299
|
+
codec: {
|
|
300
|
+
mode: "strict",
|
|
301
|
+
typeSymbol: `${REPORT_REMOTE_PACKAGE}#DeviceId`,
|
|
302
|
+
schema: deviceIdSchema
|
|
303
|
+
}
|
|
304
|
+
}],
|
|
305
|
+
result: {
|
|
306
|
+
mode: "strict",
|
|
307
|
+
typeSymbol: `${REPORT_REMOTE_PACKAGE}#Boolean`,
|
|
308
|
+
schema: { parse(value) {
|
|
309
|
+
if (typeof value !== "boolean") reject("boolean");
|
|
310
|
+
return value;
|
|
311
|
+
} }
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
id: `${REPORT_REMOTE_PACKAGE}#${SET_DEVICE_SCOPES_ENDPOINT}`,
|
|
316
|
+
service: "deeppilotReport",
|
|
317
|
+
namespace: "deeppilot",
|
|
318
|
+
method: "setDeviceScopes",
|
|
319
|
+
invocation: { kind: "direct" },
|
|
320
|
+
parameters: [{
|
|
321
|
+
name: "deviceId",
|
|
322
|
+
wire: "deviceId",
|
|
323
|
+
source: "json",
|
|
324
|
+
codec: {
|
|
325
|
+
mode: "strict",
|
|
326
|
+
typeSymbol: `${REPORT_REMOTE_PACKAGE}#DeviceId`,
|
|
327
|
+
schema: deviceIdSchema
|
|
328
|
+
}
|
|
329
|
+
}, {
|
|
330
|
+
name: "scopes",
|
|
331
|
+
wire: "scopes",
|
|
332
|
+
source: "json",
|
|
333
|
+
codec: {
|
|
334
|
+
mode: "strict",
|
|
335
|
+
typeSymbol: `${REPORT_REMOTE_PACKAGE}#DeviceScopes`,
|
|
336
|
+
schema: scopesSchema
|
|
337
|
+
}
|
|
338
|
+
}],
|
|
252
339
|
result: {
|
|
253
340
|
mode: "strict",
|
|
254
|
-
typeSymbol: `${REPORT_REMOTE_PACKAGE}#
|
|
255
|
-
schema:
|
|
341
|
+
typeSymbol: `${REPORT_REMOTE_PACKAGE}#DeviceScopes`,
|
|
342
|
+
schema: scopesSchema
|
|
256
343
|
}
|
|
257
344
|
},
|
|
258
345
|
{
|
|
@@ -319,7 +406,7 @@ window.__ModuleLoader__.load({
|
|
|
319
406
|
zh: {
|
|
320
407
|
"nav": "DeepPilot",
|
|
321
408
|
"meta.title": "DeepPilot",
|
|
322
|
-
"meta.intro": "把 iPhone 与这台电脑上的 DeepSeek Harness(DSH)连接起来(协议
|
|
409
|
+
"meta.intro": "把 iPhone 与这台电脑上的 DeepSeek Harness(DSH)连接起来(协议 v2)。",
|
|
323
410
|
"meta.refresh": "刷新",
|
|
324
411
|
"master.title": "DeepPilot 连接",
|
|
325
412
|
"master.loading": "正在读取配置…",
|
|
@@ -329,6 +416,13 @@ window.__ModuleLoader__.load({
|
|
|
329
416
|
"remote.on": "已配置:内嵌 Funnel 会自动启动并同步状态。",
|
|
330
417
|
"remote.off": "关闭:仅保留局域网连接。",
|
|
331
418
|
"remote.openAuth": "打开授权页面",
|
|
419
|
+
"remote.advancedSettings": "高级设置",
|
|
420
|
+
"remote.limitTitle": "每个公网来源的连接上限",
|
|
421
|
+
"remote.limitDescription": "默认 8,范围 1–16。应用后 Funnel 会重启,已有远程连接将短暂重连。",
|
|
422
|
+
"remote.limitApply": "应用",
|
|
423
|
+
"remote.limitApplied": "连接上限已保存,远程入口正在重新加载。",
|
|
424
|
+
"remote.limitInvalid": "请输入 1–16 之间的整数。",
|
|
425
|
+
"remote.limitFailed": "连接上限保存失败:",
|
|
332
426
|
"phase.disabled": "未启用",
|
|
333
427
|
"phase.starting": "正在启动",
|
|
334
428
|
"phase.login_required": "等待 Tailscale 授权",
|
|
@@ -338,6 +432,9 @@ window.__ModuleLoader__.load({
|
|
|
338
432
|
"phase.stopped": "已停止",
|
|
339
433
|
"phase.unknown": "状态未知",
|
|
340
434
|
"panel.activeConnections": "当前连接",
|
|
435
|
+
"panel.identity": "设备身份认证",
|
|
436
|
+
"panel.identityReady": "v2 已就绪",
|
|
437
|
+
"panel.identityNotReady": "不可用",
|
|
341
438
|
"panel.token": "配对 Token",
|
|
342
439
|
"panel.tokenReady": "已就绪",
|
|
343
440
|
"panel.tokenNotReady": "未生成",
|
|
@@ -367,13 +464,17 @@ window.__ModuleLoader__.load({
|
|
|
367
464
|
"pair.qrGenerating": "生成中…",
|
|
368
465
|
"pair.qrAutoHidden": "配对二维码已自动隐藏",
|
|
369
466
|
"pair.qrFailed": "二维码生成失败:",
|
|
467
|
+
"pair.codeLabel": "配对码(模拟器可手动输入)",
|
|
468
|
+
"pair.codeCopyDone": "配对码已复制",
|
|
469
|
+
"pair.codeCopyFailed": "配对码复制失败:",
|
|
370
470
|
"pair.publicCopyDone": "公网地址已复制",
|
|
371
471
|
"pair.publicCopyFailed": "复制失败:",
|
|
372
|
-
"pair.qrHint": "二维码包含{kind}
|
|
472
|
+
"pair.qrHint": "二维码包含{kind}地址和一次性配对码;配对码 5 分钟后失效,二维码将在 60 秒后自动隐藏。",
|
|
373
473
|
"advanced.summary": "高级信息",
|
|
374
474
|
"advanced.protocolVersion": "协议版本",
|
|
375
475
|
"advanced.serverVersion": "服务器版本",
|
|
376
476
|
"advanced.tokenPath": "Token 路径",
|
|
477
|
+
"advanced.identityPath": "设备注册表路径",
|
|
377
478
|
"advanced.bufferMax": "重放缓冲上限",
|
|
378
479
|
"advanced.frames": " 帧",
|
|
379
480
|
"push.relayTitle": "离线推送中继",
|
|
@@ -394,16 +495,22 @@ window.__ModuleLoader__.load({
|
|
|
394
495
|
"push.relayStep.enroll": "自动注册",
|
|
395
496
|
"push.prefix.ok": "✓ ",
|
|
396
497
|
"push.prefix.fail": "✗ ",
|
|
397
|
-
"devices.title": "
|
|
498
|
+
"devices.title": "设备",
|
|
398
499
|
"devices.col.name": "设备",
|
|
399
500
|
"devices.col.appVersion": "App 版本",
|
|
400
501
|
"devices.col.push": "离线推送",
|
|
401
502
|
"devices.col.lastSeen": "最近在线",
|
|
503
|
+
"devices.col.fingerprint": "密钥指纹",
|
|
504
|
+
"devices.col.actions": "操作",
|
|
505
|
+
"devices.revoke": "删除",
|
|
506
|
+
"devices.revokeConfirm": "确定删除设备“{name}”吗?该设备会立即断开,再次使用时需要重新配对。",
|
|
507
|
+
"devices.revoked": "设备已删除",
|
|
508
|
+
"devices.revokeFailed": "设备删除失败:",
|
|
402
509
|
"devices.pushRegistered": "已注册(",
|
|
403
510
|
"devices.pushNotRegistered": "未注册",
|
|
404
511
|
"devices.pushEnvProduction": "生产",
|
|
405
512
|
"devices.pushEnvDevelopment": "开发",
|
|
406
|
-
"devices.empty": "
|
|
513
|
+
"devices.empty": "还没有 v2 设备。请在 iPhone 上扫描一次性配对二维码。",
|
|
407
514
|
"help.remoteTitle": "远程连接帮助",
|
|
408
515
|
"help.recommended": "推荐设置流程",
|
|
409
516
|
"help.step1": "先打开\"DeepPilot 连接\",再打开\"远程连接(Tailscale Funnel)\"。",
|
|
@@ -425,9 +532,9 @@ window.__ModuleLoader__.load({
|
|
|
425
532
|
"help.faq3": "已有公网地址但手机超时:等待几分钟后重试,同时确认这台电脑未休眠、DSH 正在运行,再重新扫描最新二维码。",
|
|
426
533
|
"help.faq4": "没有公网地址:检查 Tailscale 授权是否完成,然后点击\"刷新\"或重启 DSH。",
|
|
427
534
|
"help.securityTitle": "安全说明",
|
|
428
|
-
"help.security1": "Funnel
|
|
429
|
-
"help.security2": "
|
|
430
|
-
"help.security3": "插件只通过 Funnel 转发 /phone 和 /phone/health,不会新增 3098 端口。",
|
|
535
|
+
"help.security1": "Funnel 公网地址可从互联网访问,但连接必须使用已配对设备的 P-256 私钥签名;不要分享仍在有效期内的配对二维码。",
|
|
536
|
+
"help.security2": "丢失设备时可在设备列表中单独删除;该设备会立即断开,不影响其他手机。",
|
|
537
|
+
"help.security3": "插件只通过 Funnel 转发 /phone、/phone/pair 和 /phone/health,不会新增 3098 端口。",
|
|
431
538
|
"help.security4": "局域网连接沿用 DSH 的 3080 端口;仅在可信网络中使用,不建议直接把 3080 暴露到公网。",
|
|
432
539
|
"help.security5": "关闭远程连接开关会停止 Funnel,但不会影响可用的局域网连接。",
|
|
433
540
|
"help.funnelDocs": "Tailscale Funnel 官方文档",
|
|
@@ -441,13 +548,15 @@ window.__ModuleLoader__.load({
|
|
|
441
548
|
"diag.missingReportHook": "useDeepPilotReport hook 缺失",
|
|
442
549
|
"diag.missingEnabledHook": "useDeepPilotEnabled hook 缺失",
|
|
443
550
|
"diag.missingRemoteEnabledHook": "useDeepPilotRemoteEnabled hook 缺失",
|
|
551
|
+
"diag.missingRemoteLimitHook": "useDeepPilotRemoteConnectionLimit hook 缺失",
|
|
444
552
|
"diag.missingRefresh": "refresh 回调缺失",
|
|
445
|
-
"diag.missingReveal": "
|
|
446
|
-
"diag.missingRotate": "
|
|
553
|
+
"diag.missingReveal": "beginPairing 回调缺失",
|
|
554
|
+
"diag.missingRotate": "revokeDevice 回调缺失",
|
|
447
555
|
"diag.missingTestRelay": "testRelay 回调缺失",
|
|
448
556
|
"diag.missingTestPush": "testPush 回调缺失",
|
|
449
557
|
"diag.missingSetEnabled": "setDeepPilotEnabled 回调缺失",
|
|
450
558
|
"diag.missingSetRemote": "setDeepPilotRemoteEnabled 回调缺失",
|
|
559
|
+
"diag.missingSetRemoteLimit": "setDeepPilotRemoteConnectionLimit 回调缺失",
|
|
451
560
|
"diag.renderError": "渲染异常: ",
|
|
452
561
|
"diag.prefix": "diag: ",
|
|
453
562
|
"clipboard.rejected": "浏览器拒绝了剪贴板写入",
|
|
@@ -460,7 +569,7 @@ window.__ModuleLoader__.load({
|
|
|
460
569
|
en: {
|
|
461
570
|
"nav": "DeepPilot",
|
|
462
571
|
"meta.title": "DeepPilot",
|
|
463
|
-
"meta.intro": "Connect your iPhone to DeepSeek Harness (DSH) on this Mac (protocol
|
|
572
|
+
"meta.intro": "Connect your iPhone to DeepSeek Harness (DSH) on this Mac (protocol v2).",
|
|
464
573
|
"meta.refresh": "Refresh",
|
|
465
574
|
"master.title": "DeepPilot connection",
|
|
466
575
|
"master.loading": "Loading settings…",
|
|
@@ -470,6 +579,13 @@ window.__ModuleLoader__.load({
|
|
|
470
579
|
"remote.on": "Configured: the embedded Funnel will start and sync state automatically.",
|
|
471
580
|
"remote.off": "Off: LAN connections only.",
|
|
472
581
|
"remote.openAuth": "Open authorization page",
|
|
582
|
+
"remote.advancedSettings": "Advanced settings",
|
|
583
|
+
"remote.limitTitle": "Connections per public source",
|
|
584
|
+
"remote.limitDescription": "Default 8; range 1–16. Applying restarts Funnel and briefly reconnects existing remote clients.",
|
|
585
|
+
"remote.limitApply": "Apply",
|
|
586
|
+
"remote.limitApplied": "Connection limit saved; the remote endpoint is reloading.",
|
|
587
|
+
"remote.limitInvalid": "Enter an integer from 1 to 16.",
|
|
588
|
+
"remote.limitFailed": "Failed to save connection limit: ",
|
|
473
589
|
"phase.disabled": "Disabled",
|
|
474
590
|
"phase.starting": "Starting",
|
|
475
591
|
"phase.login_required": "Awaiting Tailscale authorization",
|
|
@@ -479,6 +595,9 @@ window.__ModuleLoader__.load({
|
|
|
479
595
|
"phase.stopped": "Stopped",
|
|
480
596
|
"phase.unknown": "Status unknown",
|
|
481
597
|
"panel.activeConnections": "Active connections",
|
|
598
|
+
"panel.identity": "Device authentication",
|
|
599
|
+
"panel.identityReady": "v2 ready",
|
|
600
|
+
"panel.identityNotReady": "Unavailable",
|
|
482
601
|
"panel.token": "Pairing token",
|
|
483
602
|
"panel.tokenReady": "Ready",
|
|
484
603
|
"panel.tokenNotReady": "Not generated",
|
|
@@ -508,13 +627,17 @@ window.__ModuleLoader__.load({
|
|
|
508
627
|
"pair.qrGenerating": "Generating…",
|
|
509
628
|
"pair.qrAutoHidden": "Pairing QR auto-hidden",
|
|
510
629
|
"pair.qrFailed": "QR generation failed: ",
|
|
630
|
+
"pair.codeLabel": "Pairing code (enter manually in Simulator)",
|
|
631
|
+
"pair.codeCopyDone": "Pairing code copied",
|
|
632
|
+
"pair.codeCopyFailed": "Failed to copy pairing code: ",
|
|
511
633
|
"pair.publicCopyDone": "Public URL copied",
|
|
512
634
|
"pair.publicCopyFailed": "Copy failed: ",
|
|
513
|
-
"pair.qrHint": "The QR contains a {kind} address and
|
|
635
|
+
"pair.qrHint": "The QR contains a {kind} address and a single-use pairing code. The code expires after 5 minutes; the QR auto-hides after 60 seconds.",
|
|
514
636
|
"advanced.summary": "Advanced info",
|
|
515
637
|
"advanced.protocolVersion": "Protocol version",
|
|
516
638
|
"advanced.serverVersion": "Server version",
|
|
517
639
|
"advanced.tokenPath": "Token path",
|
|
640
|
+
"advanced.identityPath": "Device registry path",
|
|
518
641
|
"advanced.bufferMax": "Replay buffer cap",
|
|
519
642
|
"advanced.frames": " frames",
|
|
520
643
|
"push.relayTitle": "Offline push relay",
|
|
@@ -535,16 +658,22 @@ window.__ModuleLoader__.load({
|
|
|
535
658
|
"push.relayStep.enroll": "Auto enrollment",
|
|
536
659
|
"push.prefix.ok": "✓ ",
|
|
537
660
|
"push.prefix.fail": "✗ ",
|
|
538
|
-
"devices.title": "
|
|
661
|
+
"devices.title": "Devices",
|
|
539
662
|
"devices.col.name": "Device",
|
|
540
663
|
"devices.col.appVersion": "App version",
|
|
541
664
|
"devices.col.push": "Push",
|
|
542
665
|
"devices.col.lastSeen": "Last seen",
|
|
666
|
+
"devices.col.fingerprint": "Key fingerprint",
|
|
667
|
+
"devices.col.actions": "Actions",
|
|
668
|
+
"devices.revoke": "Delete",
|
|
669
|
+
"devices.revokeConfirm": "Delete “{name}”? It will disconnect immediately and must pair again before reconnecting.",
|
|
670
|
+
"devices.revoked": "Device deleted",
|
|
671
|
+
"devices.revokeFailed": "Failed to delete device: ",
|
|
543
672
|
"devices.pushRegistered": "Registered (",
|
|
544
673
|
"devices.pushNotRegistered": "Not registered",
|
|
545
674
|
"devices.pushEnvProduction": "Production",
|
|
546
675
|
"devices.pushEnvDevelopment": "Development",
|
|
547
|
-
"devices.empty": "No devices
|
|
676
|
+
"devices.empty": "No v2 devices yet. Scan a single-use pairing QR code in DeepPilot on iPhone.",
|
|
548
677
|
"help.remoteTitle": "Remote connection help",
|
|
549
678
|
"help.recommended": "Recommended setup",
|
|
550
679
|
"help.step1": "Turn on \"DeepPilot connection\" first, then \"Remote connection (Tailscale Funnel)\".",
|
|
@@ -566,9 +695,9 @@ window.__ModuleLoader__.load({
|
|
|
566
695
|
"help.faq3": "Public URL exists but the phone times out: wait a few minutes, confirm the Mac is awake and DSH is running, then re-scan the latest QR code.",
|
|
567
696
|
"help.faq4": "No public URL: check that Tailscale authorization completed, then click \"Refresh\" or restart DSH.",
|
|
568
697
|
"help.securityTitle": "Security notes",
|
|
569
|
-
"help.security1": "A Funnel
|
|
570
|
-
"help.security2": "If
|
|
571
|
-
"help.security3": "The plugin only forwards /phone and /phone/health through Funnel; no new port 3098 is opened.",
|
|
698
|
+
"help.security1": "A Funnel URL is public, but every connection requires a signature from a paired device P-256 private key. Do not share a pairing QR while it is valid.",
|
|
699
|
+
"help.security2": "If a device is lost, delete only that device from the list. It disconnects immediately; other phones stay paired.",
|
|
700
|
+
"help.security3": "The plugin only forwards /phone, /phone/pair, and /phone/health through Funnel; no new port 3098 is opened.",
|
|
572
701
|
"help.security4": "LAN connections reuse DSH's existing 3080 port. Use them only on trusted networks; do not expose 3080 directly to the public Internet.",
|
|
573
702
|
"help.security5": "Disabling the remote switch stops Funnel but does not affect any LAN connection you already have.",
|
|
574
703
|
"help.funnelDocs": "Tailscale Funnel docs",
|
|
@@ -582,13 +711,15 @@ window.__ModuleLoader__.load({
|
|
|
582
711
|
"diag.missingReportHook": "useDeepPilotReport hook missing",
|
|
583
712
|
"diag.missingEnabledHook": "useDeepPilotEnabled hook missing",
|
|
584
713
|
"diag.missingRemoteEnabledHook": "useDeepPilotRemoteEnabled hook missing",
|
|
714
|
+
"diag.missingRemoteLimitHook": "useDeepPilotRemoteConnectionLimit hook missing",
|
|
585
715
|
"diag.missingRefresh": "refresh callback missing",
|
|
586
|
-
"diag.missingReveal": "
|
|
587
|
-
"diag.missingRotate": "
|
|
716
|
+
"diag.missingReveal": "beginPairing callback missing",
|
|
717
|
+
"diag.missingRotate": "revokeDevice callback missing",
|
|
588
718
|
"diag.missingTestRelay": "testRelay callback missing",
|
|
589
719
|
"diag.missingTestPush": "testPush callback missing",
|
|
590
720
|
"diag.missingSetEnabled": "setDeepPilotEnabled callback missing",
|
|
591
721
|
"diag.missingSetRemote": "setDeepPilotRemoteEnabled callback missing",
|
|
722
|
+
"diag.missingSetRemoteLimit": "setDeepPilotRemoteConnectionLimit callback missing",
|
|
592
723
|
"diag.renderError": "Render error: ",
|
|
593
724
|
"diag.prefix": "diag: ",
|
|
594
725
|
"clipboard.rejected": "Clipboard write rejected by the browser",
|
|
@@ -694,6 +825,10 @@ window.__ModuleLoader__.load({
|
|
|
694
825
|
".pbb-actionDanger:not(:disabled){color:#fff;background:var(--dsw-alias-label-error);border-color:var(--dsw-alias-label-error)}",
|
|
695
826
|
".pbb-qrPanel{display:flex;flex-direction:column;align-items:center;gap:9px;padding:12px 0 4px}",
|
|
696
827
|
".pbb-qrImage{width:240px;height:240px;max-width:100%;background:#fff;border-radius:10px;padding:8px;box-sizing:border-box}",
|
|
828
|
+
".pbb-pairCodeBlock{width:min(100%,420px);display:flex;flex-direction:column;gap:5px}",
|
|
829
|
+
".pbb-pairCodeLabel{font-size:11px;color:var(--dsw-alias-label-tertiary);text-align:center}",
|
|
830
|
+
".pbb-pairCodeRow{display:flex;align-items:center;justify-content:center;gap:8px;flex-wrap:wrap}",
|
|
831
|
+
".pbb-pairCode{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:15px;line-height:1.4;letter-spacing:.04em;color:var(--dsw-alias-label-primary);word-break:break-all;text-align:center;user-select:all}",
|
|
697
832
|
".pbb-qrHint{max-width:420px;text-align:center;font-size:11px;line-height:1.5;color:var(--dsw-alias-label-tertiary);margin:0}",
|
|
698
833
|
".pbb-switchRow{padding:12px 0;display:flex;align-items:center;gap:12px}",
|
|
699
834
|
".pbb-switchRow+.pbb-field{border-top:1px solid var(--dsw-alias-border-l2)}",
|
|
@@ -711,6 +846,12 @@ window.__ModuleLoader__.load({
|
|
|
711
846
|
".pbb-switchOn{background:var(--dsw-alias-state-success-primary,var(--dsw-alias-label-secondary))}",
|
|
712
847
|
".pbb-switchOn::after{transform:translateX(16px);background:#fff}",
|
|
713
848
|
".pbb-switch:disabled{opacity:.5;cursor:default}",
|
|
849
|
+
".pbb-limitRow{border-top:1px solid var(--dsw-alias-border-l2);padding:12px 0;display:flex;align-items:center;gap:12px}",
|
|
850
|
+
".pbb-limitNested{border-top:0;padding:0}",
|
|
851
|
+
".pbb-limitControl{display:flex;align-items:center;gap:8px;flex:none}",
|
|
852
|
+
".pbb-numberInput{width:72px;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;padding:5px 8px}",
|
|
853
|
+
".pbb-numberInput:focus{outline:2px solid var(--dsw-alias-state-success-primary,#22a06b);outline-offset:1px}",
|
|
854
|
+
".pbb-numberInput:disabled{opacity:.5}",
|
|
714
855
|
".pbb-help{border-top:1px solid var(--dsw-alias-border-l2);padding:12px 0}",
|
|
715
856
|
".pbb-help summary{cursor:pointer;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;user-select:none}",
|
|
716
857
|
".pbb-help summary:hover{color:var(--dsw-alias-label-secondary)}",
|
|
@@ -3139,11 +3280,7 @@ window.__ModuleLoader__.load({
|
|
|
3139
3280
|
return SvgRenderer.render(data, opts);
|
|
3140
3281
|
});
|
|
3141
3282
|
})))(), 1);
|
|
3142
|
-
|
|
3143
|
-
* Protocol-v1 compatibility identifier used by the TestFlight build currently
|
|
3144
|
-
* under review. This is wire data, not the plugin or product display name.
|
|
3145
|
-
*/
|
|
3146
|
-
const PAIRING_QR_TYPE = "dsh-pocket-pairing";
|
|
3283
|
+
const PAIRING_QR_TYPE = "deeppilot-pairing";
|
|
3147
3284
|
function isLoopbackHostname(hostname) {
|
|
3148
3285
|
const normalized = hostname.toLowerCase();
|
|
3149
3286
|
return normalized === "localhost" || normalized.endsWith(".localhost") || normalized === "[::1]" || normalized === "::1" || normalized.startsWith("127.");
|
|
@@ -3173,8 +3310,8 @@ window.__ModuleLoader__.load({
|
|
|
3173
3310
|
kind: "lan"
|
|
3174
3311
|
};
|
|
3175
3312
|
}
|
|
3176
|
-
/** Encode
|
|
3177
|
-
function encodePairingQRPayload(host,
|
|
3313
|
+
/** Encode a short-lived, single-use pairing grant without URL credentials. */
|
|
3314
|
+
function encodePairingQRPayload(host, grant) {
|
|
3178
3315
|
const normalizedHost = host.trim();
|
|
3179
3316
|
const parsed = new URL(normalizedHost);
|
|
3180
3317
|
if (![
|
|
@@ -3183,15 +3320,21 @@ window.__ModuleLoader__.load({
|
|
|
3183
3320
|
"ws:",
|
|
3184
3321
|
"wss:"
|
|
3185
3322
|
].includes(parsed.protocol) || parsed.hostname === "" || parsed.username !== "" || parsed.password !== "") throw new TypeError("pairing QR requires a valid HTTP(S)/WS(S) host");
|
|
3186
|
-
if (
|
|
3323
|
+
if (grant.code.trim().length < 32 || !Number.isInteger(grant.expiresAt) || grant.expiresAt <= Date.now()) throw new TypeError("pairing grant is invalid or expired");
|
|
3324
|
+
if (!grant.audience.startsWith("deeppilot:")) throw new TypeError("pairing audience is invalid");
|
|
3187
3325
|
const payload = {
|
|
3188
|
-
v:
|
|
3326
|
+
v: 2,
|
|
3189
3327
|
type: PAIRING_QR_TYPE,
|
|
3190
3328
|
host: normalizedHost,
|
|
3191
|
-
|
|
3329
|
+
code: grant.code.trim(),
|
|
3330
|
+
expiresAt: grant.expiresAt,
|
|
3331
|
+
audience: grant.audience
|
|
3192
3332
|
};
|
|
3193
3333
|
return JSON.stringify(payload);
|
|
3194
3334
|
}
|
|
3335
|
+
function normalizeFunnelConnectionLimit(value) {
|
|
3336
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 16 ? value : 8;
|
|
3337
|
+
}
|
|
3195
3338
|
//#endregion
|
|
3196
3339
|
//#region src/client/settings-page.ts
|
|
3197
3340
|
async function writeClipboard(t, value) {
|
|
@@ -3245,12 +3388,11 @@ window.__ModuleLoader__.load({
|
|
|
3245
3388
|
};
|
|
3246
3389
|
/** Slot component: hooks come from the slot renderer, named use<Key>. */
|
|
3247
3390
|
function DeepPilotSettingsPage(props) {
|
|
3248
|
-
const [
|
|
3249
|
-
const [
|
|
3250
|
-
const [
|
|
3251
|
-
const [rotateArmed, setRotateArmed] = (0, react.useState)(false);
|
|
3252
|
-
const [remoteMessage, setRemoteMessage] = (0, react.useState)("");
|
|
3391
|
+
const [addressMessage, setAddressMessage] = (0, react.useState)("");
|
|
3392
|
+
const [remoteLimitDraft, setRemoteLimitDraft] = (0, react.useState)(String(8));
|
|
3393
|
+
const [remoteLimitMessage, setRemoteLimitMessage] = (0, react.useState)("");
|
|
3253
3394
|
const [qrDataURL, setQRDataURL] = (0, react.useState)(null);
|
|
3395
|
+
const [pairingGrant, setPairingGrant] = (0, react.useState)(null);
|
|
3254
3396
|
const [qrBusy, setQRBusy] = (0, react.useState)(false);
|
|
3255
3397
|
const [qrMessage, setQRMessage] = (0, react.useState)("");
|
|
3256
3398
|
const [relayTestBusy, setRelayTestBusy] = (0, react.useState)(false);
|
|
@@ -3259,6 +3401,8 @@ window.__ModuleLoader__.load({
|
|
|
3259
3401
|
const [pushTestBusy, setPushTestBusy] = (0, react.useState)(false);
|
|
3260
3402
|
const [pushTestResult, setPushTestResult] = (0, react.useState)(null);
|
|
3261
3403
|
const [pushTestError, setPushTestError] = (0, react.useState)("");
|
|
3404
|
+
const [deviceBusy, setDeviceBusy] = (0, react.useState)(null);
|
|
3405
|
+
const [deviceMessage, setDeviceMessage] = (0, react.useState)("");
|
|
3262
3406
|
(0, react.useEffect)(() => {
|
|
3263
3407
|
if (typeof props.refresh !== "function") return;
|
|
3264
3408
|
props.refresh();
|
|
@@ -3298,32 +3442,20 @@ window.__ModuleLoader__.load({
|
|
|
3298
3442
|
});
|
|
3299
3443
|
};
|
|
3300
3444
|
(0, react.useEffect)(() => {
|
|
3301
|
-
if (
|
|
3302
|
-
const timer = globalThis.setTimeout(() =>
|
|
3303
|
-
setRevealedToken(null);
|
|
3304
|
-
setTokenMessage(translateWith(props.t, "panel.tokenAutoHidden"));
|
|
3305
|
-
}, 3e4);
|
|
3306
|
-
return () => globalThis.clearTimeout(timer);
|
|
3307
|
-
}, [revealedToken]);
|
|
3308
|
-
(0, react.useEffect)(() => {
|
|
3309
|
-
if (!tokenMessage) return;
|
|
3310
|
-
const timer = globalThis.setTimeout(() => setTokenMessage(""), 4e3);
|
|
3445
|
+
if (!addressMessage) return;
|
|
3446
|
+
const timer = globalThis.setTimeout(() => setAddressMessage(""), 2500);
|
|
3311
3447
|
return () => globalThis.clearTimeout(timer);
|
|
3312
|
-
}, [
|
|
3448
|
+
}, [addressMessage]);
|
|
3313
3449
|
(0, react.useEffect)(() => {
|
|
3314
|
-
if (!
|
|
3315
|
-
const timer = globalThis.setTimeout(() =>
|
|
3450
|
+
if (!remoteLimitMessage) return;
|
|
3451
|
+
const timer = globalThis.setTimeout(() => setRemoteLimitMessage(""), 4e3);
|
|
3316
3452
|
return () => globalThis.clearTimeout(timer);
|
|
3317
|
-
}, [
|
|
3318
|
-
(0, react.useEffect)(() => {
|
|
3319
|
-
if (!remoteMessage) return;
|
|
3320
|
-
const timer = globalThis.setTimeout(() => setRemoteMessage(""), 2500);
|
|
3321
|
-
return () => globalThis.clearTimeout(timer);
|
|
3322
|
-
}, [remoteMessage]);
|
|
3453
|
+
}, [remoteLimitMessage]);
|
|
3323
3454
|
(0, react.useEffect)(() => {
|
|
3324
3455
|
if (qrDataURL === null) return;
|
|
3325
3456
|
const timer = globalThis.setTimeout(() => {
|
|
3326
3457
|
setQRDataURL(null);
|
|
3458
|
+
setPairingGrant(null);
|
|
3327
3459
|
setQRMessage(translateWith(props.t, "pair.qrAutoHidden"));
|
|
3328
3460
|
}, 6e4);
|
|
3329
3461
|
return () => globalThis.clearTimeout(timer);
|
|
@@ -3339,6 +3471,8 @@ window.__ModuleLoader__.load({
|
|
|
3339
3471
|
let switchReady = false;
|
|
3340
3472
|
let remoteEnabled = false;
|
|
3341
3473
|
let remoteSwitchReady = false;
|
|
3474
|
+
let remoteConnectionLimit = 8;
|
|
3475
|
+
let remoteConnectionLimitReady = false;
|
|
3342
3476
|
let failed = false;
|
|
3343
3477
|
try {
|
|
3344
3478
|
if (typeof props.useDeepPilotReport !== "function") {
|
|
@@ -3359,8 +3493,8 @@ window.__ModuleLoader__.load({
|
|
|
3359
3493
|
if (state.status === "unavailable") diag.push(translateWith(props.t, "diag.settingsUnavailable"));
|
|
3360
3494
|
} else diag.push(translateWith(props.t, "diag.missingEnabledHook"));
|
|
3361
3495
|
if (typeof props.refresh !== "function") diag.push(translateWith(props.t, "diag.missingRefresh"));
|
|
3362
|
-
if (typeof props.
|
|
3363
|
-
if (typeof props.
|
|
3496
|
+
if (typeof props.beginPairing !== "function") diag.push(translateWith(props.t, "diag.missingReveal"));
|
|
3497
|
+
if (typeof props.revokeDevice !== "function") diag.push(translateWith(props.t, "diag.missingRotate"));
|
|
3364
3498
|
if (typeof props.testRelay !== "function") diag.push(translateWith(props.t, "diag.missingTestRelay"));
|
|
3365
3499
|
if (typeof props.testPush !== "function") diag.push(translateWith(props.t, "diag.missingTestPush"));
|
|
3366
3500
|
if (typeof props.setDeepPilotEnabled !== "function") diag.push(translateWith(props.t, "diag.missingSetEnabled"));
|
|
@@ -3370,78 +3504,80 @@ window.__ModuleLoader__.load({
|
|
|
3370
3504
|
remoteSwitchReady = state.status === "ready";
|
|
3371
3505
|
} else diag.push(translateWith(props.t, "diag.missingRemoteEnabledHook"));
|
|
3372
3506
|
if (typeof props.setDeepPilotRemoteEnabled !== "function") diag.push(translateWith(props.t, "diag.missingSetRemote"));
|
|
3507
|
+
if (typeof props.useDeepPilotRemoteConnectionLimit === "function") {
|
|
3508
|
+
const state = props.useDeepPilotRemoteConnectionLimit((s) => s);
|
|
3509
|
+
remoteConnectionLimit = state.value;
|
|
3510
|
+
remoteConnectionLimitReady = state.status === "ready";
|
|
3511
|
+
} else diag.push(translateWith(props.t, "diag.missingRemoteLimitHook"));
|
|
3512
|
+
if (typeof props.setDeepPilotRemoteConnectionLimit !== "function") diag.push(translateWith(props.t, "diag.missingSetRemoteLimit"));
|
|
3373
3513
|
} catch (error) {
|
|
3374
3514
|
diag.push(translateWith(props.t, "diag.renderError") + (error instanceof Error ? error.message : String(error)));
|
|
3375
3515
|
failed = true;
|
|
3376
3516
|
}
|
|
3377
|
-
const pairingTarget = report === null ? null : selectPairingTarget(report.remote, report.lanAddresses, typeof window === "undefined" ? void 0 : window.location.origin);
|
|
3378
3517
|
(0, react.useEffect)(() => {
|
|
3379
|
-
|
|
3380
|
-
}, [
|
|
3381
|
-
const
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
}
|
|
3387
|
-
if (typeof props.revealPairingToken !== "function") return;
|
|
3388
|
-
setTokenBusy(true);
|
|
3389
|
-
setTokenMessage("");
|
|
3390
|
-
props.revealPairingToken().then((token) => {
|
|
3391
|
-
setRevealedToken(token);
|
|
3392
|
-
}, (error) => {
|
|
3393
|
-
setTokenMessage(translateWith(props.t, "panel.tokenRevealFailed") + (error instanceof Error ? error.message : String(error)));
|
|
3394
|
-
}).finally(() => setTokenBusy(false));
|
|
3395
|
-
};
|
|
3396
|
-
const copyToken = () => {
|
|
3397
|
-
if (typeof props.revealPairingToken !== "function") return;
|
|
3398
|
-
setTokenBusy(true);
|
|
3399
|
-
setTokenMessage("");
|
|
3400
|
-
(revealedToken !== null ? Promise.resolve(revealedToken) : props.revealPairingToken()).then((value) => writeClipboard(props.t, value)).then(() => setTokenMessage(translateWith(props.t, "panel.tokenCopied")), (error) => {
|
|
3401
|
-
setTokenMessage(translateWith(props.t, "pair.publicCopyFailed") + (error instanceof Error ? error.message : String(error)));
|
|
3402
|
-
}).finally(() => setTokenBusy(false));
|
|
3403
|
-
};
|
|
3404
|
-
const rotateToken = () => {
|
|
3405
|
-
if (typeof props.rotatePairingToken !== "function") return;
|
|
3406
|
-
if (!rotateArmed) {
|
|
3407
|
-
setRotateArmed(true);
|
|
3408
|
-
setRevealedToken(null);
|
|
3409
|
-
setQRDataURL(null);
|
|
3410
|
-
setTokenMessage(translateWith(props.t, "panel.tokenRotateWarning"));
|
|
3518
|
+
setRemoteLimitDraft(String(remoteConnectionLimit));
|
|
3519
|
+
}, [remoteConnectionLimit]);
|
|
3520
|
+
const parsedRemoteLimit = Number(remoteLimitDraft);
|
|
3521
|
+
const remoteLimitValid = Number.isInteger(parsedRemoteLimit) && parsedRemoteLimit >= 1 && parsedRemoteLimit <= 16;
|
|
3522
|
+
const applyRemoteLimit = () => {
|
|
3523
|
+
if (!remoteLimitValid || typeof props.setDeepPilotRemoteConnectionLimit !== "function") {
|
|
3524
|
+
setRemoteLimitMessage(translateWith(props.t, "remote.limitInvalid"));
|
|
3411
3525
|
return;
|
|
3412
3526
|
}
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
props.rotatePairingToken().then((token) => {
|
|
3417
|
-
setRevealedToken(token);
|
|
3418
|
-
setTokenMessage(translateWith(props.t, "panel.tokenRotated"));
|
|
3527
|
+
setRemoteLimitMessage("");
|
|
3528
|
+
props.setDeepPilotRemoteConnectionLimit(parsedRemoteLimit).then(() => {
|
|
3529
|
+
setRemoteLimitMessage(translateWith(props.t, "remote.limitApplied"));
|
|
3419
3530
|
}, (error) => {
|
|
3420
|
-
|
|
3421
|
-
})
|
|
3531
|
+
setRemoteLimitMessage(translateWith(props.t, "remote.limitFailed") + (error instanceof Error ? error.message : String(error)));
|
|
3532
|
+
});
|
|
3422
3533
|
};
|
|
3534
|
+
const pairingTarget = report === null ? null : selectPairingTarget(report.remote, report.lanAddresses, typeof window === "undefined" ? void 0 : window.location.origin);
|
|
3535
|
+
(0, react.useEffect)(() => {
|
|
3536
|
+
setQRDataURL(null);
|
|
3537
|
+
setPairingGrant(null);
|
|
3538
|
+
}, [pairingTarget?.host]);
|
|
3423
3539
|
const copyRemoteURL = (url) => {
|
|
3424
|
-
|
|
3540
|
+
setAddressMessage("");
|
|
3425
3541
|
writeClipboard(props.t, url).then(() => {
|
|
3426
|
-
|
|
3542
|
+
setAddressMessage(translateWith(props.t, "pair.publicCopyDone"));
|
|
3427
3543
|
}, (error) => {
|
|
3428
|
-
|
|
3544
|
+
setAddressMessage(translateWith(props.t, "pair.publicCopyFailed") + (error instanceof Error ? error.message : String(error)));
|
|
3429
3545
|
});
|
|
3430
3546
|
};
|
|
3431
3547
|
const showPairingQR = () => {
|
|
3432
|
-
if (typeof props.
|
|
3548
|
+
if (typeof props.beginPairing !== "function" || pairingTarget === null) return;
|
|
3433
3549
|
setQRBusy(true);
|
|
3434
3550
|
setQRMessage("");
|
|
3435
3551
|
setQRDataURL(null);
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3552
|
+
setPairingGrant(null);
|
|
3553
|
+
props.beginPairing().then(async (grant) => ({
|
|
3554
|
+
grant,
|
|
3555
|
+
svg: await import_browser.toString(encodePairingQRPayload(pairingTarget.host, grant), {
|
|
3556
|
+
type: "svg",
|
|
3557
|
+
errorCorrectionLevel: "M",
|
|
3558
|
+
margin: 2,
|
|
3559
|
+
width: 512
|
|
3560
|
+
})
|
|
3561
|
+
})).then(({ grant, svg }) => {
|
|
3562
|
+
setPairingGrant(grant);
|
|
3563
|
+
setQRDataURL("data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg));
|
|
3564
|
+
}, (error) => {
|
|
3442
3565
|
setQRMessage(translateWith(props.t, "pair.qrFailed") + (error instanceof Error ? error.message : String(error)));
|
|
3443
3566
|
}).finally(() => setQRBusy(false));
|
|
3444
3567
|
};
|
|
3568
|
+
const hidePairingQR = () => {
|
|
3569
|
+
setQRDataURL(null);
|
|
3570
|
+
setPairingGrant(null);
|
|
3571
|
+
};
|
|
3572
|
+
const copyPairingCode = () => {
|
|
3573
|
+
if (pairingGrant === null) return;
|
|
3574
|
+
setQRMessage("");
|
|
3575
|
+
writeClipboard(props.t, pairingGrant.code).then(() => {
|
|
3576
|
+
setQRMessage(translateWith(props.t, "pair.codeCopyDone"));
|
|
3577
|
+
}, (error) => {
|
|
3578
|
+
setQRMessage(translateWith(props.t, "pair.codeCopyFailed") + (error instanceof Error ? error.message : String(error)));
|
|
3579
|
+
});
|
|
3580
|
+
};
|
|
3445
3581
|
const primaryRows = [];
|
|
3446
3582
|
const advancedRows = [];
|
|
3447
3583
|
if (report !== null) {
|
|
@@ -3450,23 +3586,8 @@ window.__ModuleLoader__.load({
|
|
|
3450
3586
|
key: "conn"
|
|
3451
3587
|
}, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "panel.activeConnections")), (0, react.createElement)("span", { className: "pbb-value" }, String(report.activeConnections)))), (0, react.createElement)("div", {
|
|
3452
3588
|
className: "pbb-field",
|
|
3453
|
-
key: "
|
|
3454
|
-
}, (0, react.createElement)("div", { className: "pbb-row
|
|
3455
|
-
type: "button",
|
|
3456
|
-
className: "pbb-action",
|
|
3457
|
-
disabled: tokenBusy,
|
|
3458
|
-
onClick: toggleToken
|
|
3459
|
-
}, tokenBusy ? translateWith(props.t, "panel.tokenAction.showing") : revealedToken === null ? translateWith(props.t, "panel.tokenAction.show") : translateWith(props.t, "panel.tokenAction.hide")), (0, react.createElement)("button", {
|
|
3460
|
-
type: "button",
|
|
3461
|
-
className: "pbb-action",
|
|
3462
|
-
disabled: tokenBusy,
|
|
3463
|
-
onClick: copyToken
|
|
3464
|
-
}, translateWith(props.t, "panel.tokenAction.copy")), (0, react.createElement)("button", {
|
|
3465
|
-
type: "button",
|
|
3466
|
-
className: "pbb-action" + (rotateArmed ? " pbb-actionDanger" : ""),
|
|
3467
|
-
disabled: tokenBusy,
|
|
3468
|
-
onClick: rotateToken
|
|
3469
|
-
}, rotateArmed ? translateWith(props.t, "panel.tokenAction.rotateConfirm") : tokenBusy ? translateWith(props.t, "panel.tokenAction.rotating") : translateWith(props.t, "panel.tokenAction.rotate"))) : null), tokenMessage ? (0, react.createElement)("p", { className: "pbb-diag" }, tokenMessage) : null));
|
|
3589
|
+
key: "identity"
|
|
3590
|
+
}, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "panel.identity")), (0, react.createElement)("code", { className: "pbb-token " + (report.pairingReady ? "pbb-ok" : "pbb-bad") }, report.pairingReady ? translateWith(props.t, "panel.identityReady") : translateWith(props.t, "panel.identityNotReady")))));
|
|
3470
3591
|
advancedRows.push((0, react.createElement)("div", {
|
|
3471
3592
|
className: "pbb-field",
|
|
3472
3593
|
key: "proto"
|
|
@@ -3476,12 +3597,29 @@ window.__ModuleLoader__.load({
|
|
|
3476
3597
|
}, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "advanced.serverVersion")), (0, react.createElement)("span", { className: "pbb-value" }, report.serverVersion))), (0, react.createElement)("div", {
|
|
3477
3598
|
className: "pbb-field",
|
|
3478
3599
|
key: "path"
|
|
3479
|
-
}, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "advanced.
|
|
3600
|
+
}, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "advanced.identityPath")), (0, react.createElement)("span", { className: "pbb-value" }, report.identityPath))), (0, react.createElement)("div", {
|
|
3480
3601
|
className: "pbb-field",
|
|
3481
3602
|
key: "buffer"
|
|
3482
3603
|
}, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "advanced.bufferMax")), (0, react.createElement)("span", { className: "pbb-value" }, String(report.historyBufferMax) + translateWith(props.t, "advanced.frames")))));
|
|
3483
3604
|
}
|
|
3484
|
-
const
|
|
3605
|
+
const revokeDevice = (deviceId, name) => {
|
|
3606
|
+
if (typeof props.revokeDevice !== "function") return;
|
|
3607
|
+
if (typeof window !== "undefined" && !window.confirm(translateWith(props.t, "devices.revokeConfirm", { name }))) return;
|
|
3608
|
+
setDeviceBusy(deviceId);
|
|
3609
|
+
setDeviceMessage("");
|
|
3610
|
+
props.revokeDevice(deviceId).then(() => {
|
|
3611
|
+
setDeviceMessage(translateWith(props.t, "devices.revoked"));
|
|
3612
|
+
}, (error) => {
|
|
3613
|
+
setDeviceMessage(translateWith(props.t, "devices.revokeFailed") + (error instanceof Error ? error.message : String(error)));
|
|
3614
|
+
}).finally(() => setDeviceBusy(null));
|
|
3615
|
+
};
|
|
3616
|
+
const visibleDevices = report?.devices.filter((device) => device.revokedAt === void 0) ?? [];
|
|
3617
|
+
const deviceTable = visibleDevices.length > 0 ? (0, react.createElement)("table", { className: "pbb-table" }, (0, react.createElement)("thead", null, (0, react.createElement)("tr", null, (0, react.createElement)("th", null, translateWith(props.t, "devices.col.name")), (0, react.createElement)("th", null, translateWith(props.t, "devices.col.fingerprint")), (0, react.createElement)("th", null, translateWith(props.t, "devices.col.lastSeen")), (0, react.createElement)("th", null, translateWith(props.t, "devices.col.actions")))), (0, react.createElement)("tbody", null, visibleDevices.map((d) => (0, react.createElement)("tr", { key: d.deviceId }, (0, react.createElement)("td", null, d.deviceName, (0, react.createElement)("div", { className: "pbb-diag" }, d.appVersion)), (0, react.createElement)("td", null, (0, react.createElement)("code", { className: "pbb-token" }, d.fingerprint.slice(0, 12))), (0, react.createElement)("td", null, new Date(d.lastSeenTs).toLocaleString()), (0, react.createElement)("td", null, (0, react.createElement)("button", {
|
|
3618
|
+
type: "button",
|
|
3619
|
+
className: "pbb-action pbb-actionDanger",
|
|
3620
|
+
disabled: deviceBusy === d.deviceId,
|
|
3621
|
+
onClick: () => revokeDevice(d.deviceId, d.deviceName)
|
|
3622
|
+
}, translateWith(props.t, "devices.revoke"))))))) : (0, react.createElement)("p", { className: "pbb-empty" }, translateWith(props.t, "devices.empty"));
|
|
3485
3623
|
const switchTitle = translateWith(props.t, "master.title");
|
|
3486
3624
|
const switchDesc = switchReady ? enabled ? translateWith(props.t, "master.on") : translateWith(props.t, "master.off") : translateWith(props.t, "master.loading");
|
|
3487
3625
|
return (0, react.createElement)("div", { className: "pbb-section" }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("h2", { className: "pbb-title" }, "DeepPilot"), (0, react.createElement)("div", { style: { flex: "1" } }), (0, react.createElement)("button", {
|
|
@@ -3509,7 +3647,7 @@ window.__ModuleLoader__.load({
|
|
|
3509
3647
|
href: report.remote.authURL,
|
|
3510
3648
|
target: "_blank",
|
|
3511
3649
|
rel: "noreferrer"
|
|
3512
|
-
}, translateWith(props.t, "remote.openAuth"))) : null, report !== null && report.remote.message && (report.remote.phase === "error" || report.remote.phase === "unavailable") ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, report.remote.message) : null
|
|
3650
|
+
}, translateWith(props.t, "remote.openAuth"))) : null, report !== null && report.remote.message && (report.remote.phase === "error" || report.remote.phase === "unavailable") ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, report.remote.message) : null), (0, react.createElement)("button", {
|
|
3513
3651
|
type: "button",
|
|
3514
3652
|
role: "switch",
|
|
3515
3653
|
"aria-checked": remoteEnabled,
|
|
@@ -3519,7 +3657,33 @@ window.__ModuleLoader__.load({
|
|
|
3519
3657
|
onClick: () => {
|
|
3520
3658
|
if (typeof props.setDeepPilotRemoteEnabled === "function") props.setDeepPilotRemoteEnabled(!remoteEnabled);
|
|
3521
3659
|
}
|
|
3522
|
-
})), (0, react.createElement)("details", { className: "pbb-help" }, (0, react.createElement)("summary", null, translateWith(props.t, "
|
|
3660
|
+
})), (0, react.createElement)("details", { className: "pbb-help" }, (0, react.createElement)("summary", null, translateWith(props.t, "remote.advancedSettings")), (0, react.createElement)("div", { className: "pbb-helpBody" }, (0, react.createElement)("div", { className: "pbb-limitRow pbb-limitNested" }, (0, react.createElement)("div", { className: "pbb-switchText" }, (0, react.createElement)("label", {
|
|
3661
|
+
className: "pbb-switchTitle",
|
|
3662
|
+
htmlFor: "deeppilot-funnel-source-limit"
|
|
3663
|
+
}, translateWith(props.t, "remote.limitTitle")), (0, react.createElement)("span", { className: "pbb-switchDesc" }, translateWith(props.t, "remote.limitDescription")), !remoteLimitValid ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, translateWith(props.t, "remote.limitInvalid")) : remoteLimitMessage ? (0, react.createElement)("p", { className: "pbb-diag" + (remoteLimitMessage.startsWith(translateWith(props.t, "remote.limitFailed")) ? " pbb-diagBad" : "") }, remoteLimitMessage) : null), (0, react.createElement)("div", { className: "pbb-limitControl" }, (0, react.createElement)("input", {
|
|
3664
|
+
id: "deeppilot-funnel-source-limit",
|
|
3665
|
+
className: "pbb-numberInput",
|
|
3666
|
+
type: "number",
|
|
3667
|
+
min: 1,
|
|
3668
|
+
max: 16,
|
|
3669
|
+
step: 1,
|
|
3670
|
+
inputMode: "numeric",
|
|
3671
|
+
value: remoteLimitDraft,
|
|
3672
|
+
disabled: !remoteConnectionLimitReady,
|
|
3673
|
+
"aria-label": translateWith(props.t, "remote.limitTitle"),
|
|
3674
|
+
onChange: (event) => setRemoteLimitDraft(event.currentTarget.value),
|
|
3675
|
+
onKeyDown: (event) => {
|
|
3676
|
+
if (event.key === "Enter") {
|
|
3677
|
+
event.preventDefault();
|
|
3678
|
+
applyRemoteLimit();
|
|
3679
|
+
}
|
|
3680
|
+
}
|
|
3681
|
+
}), (0, react.createElement)("button", {
|
|
3682
|
+
type: "button",
|
|
3683
|
+
className: "pbb-action",
|
|
3684
|
+
disabled: !remoteConnectionLimitReady || !remoteLimitValid || parsedRemoteLimit === remoteConnectionLimit,
|
|
3685
|
+
onClick: applyRemoteLimit
|
|
3686
|
+
}, translateWith(props.t, "remote.limitApply")))))), (0, react.createElement)("details", { className: "pbb-help" }, (0, react.createElement)("summary", null, translateWith(props.t, "help.remoteTitle")), (0, react.createElement)("div", { className: "pbb-helpBody" }, (0, react.createElement)("div", { className: "pbb-helpSection" }, (0, react.createElement)("div", { className: "pbb-helpHeading" }, translateWith(props.t, "help.recommended")), (0, react.createElement)("ol", { className: "pbb-helpList" }, (0, react.createElement)("li", null, translateWith(props.t, "help.step1")), (0, react.createElement)("li", null, translateWith(props.t, "help.step2")), (0, react.createElement)("li", null, translateWith(props.t, "help.step3")), (0, react.createElement)("li", null, translateWith(props.t, "help.step4"))), (0, react.createElement)("p", { className: "pbb-helpText" }, translateWith(props.t, "help.funnelHint"))), (0, react.createElement)("div", { className: "pbb-helpSection" }, (0, react.createElement)("div", { className: "pbb-helpHeading" }, translateWith(props.t, "help.httpsTitle")), (0, react.createElement)("ol", { className: "pbb-helpList" }, (0, react.createElement)("li", null, translateWith(props.t, "help.httpsStep1")), (0, react.createElement)("li", null, translateWith(props.t, "help.httpsStep2")), (0, react.createElement)("li", null, translateWith(props.t, "help.httpsStep3"))), (0, react.createElement)("p", { className: "pbb-helpText" }, translateWith(props.t, "help.httpsHint"))), (0, react.createElement)("div", { className: "pbb-helpSection" }, (0, react.createElement)("div", { className: "pbb-helpHeading" }, translateWith(props.t, "help.allowTitle")), (0, react.createElement)("p", { className: "pbb-helpText" }, translateWith(props.t, "help.allowBody")), (0, react.createElement)("code", { className: "pbb-helpCode" }, "\"nodeAttrs\": [\n {\n \"target\": [\"autogroup:member\"],\n \"attr\": [\"funnel\"],\n },\n],"), (0, react.createElement)("p", { className: "pbb-helpText" }, translateWith(props.t, "help.allowHint"))), (0, react.createElement)("div", { className: "pbb-helpSection" }, (0, react.createElement)("div", { className: "pbb-helpHeading" }, translateWith(props.t, "help.faqTitle")), (0, react.createElement)("ul", { className: "pbb-helpList" }, (0, react.createElement)("li", null, translateWith(props.t, "help.faq1")), (0, react.createElement)("li", null, translateWith(props.t, "help.faq2")), (0, react.createElement)("li", null, translateWith(props.t, "help.faq3")), (0, react.createElement)("li", null, translateWith(props.t, "help.faq4")))), (0, react.createElement)("div", { className: "pbb-helpSection" }, (0, react.createElement)("div", { className: "pbb-helpHeading" }, translateWith(props.t, "help.securityTitle")), (0, react.createElement)("ul", { className: "pbb-helpList" }, (0, react.createElement)("li", null, translateWith(props.t, "help.security1")), (0, react.createElement)("li", null, translateWith(props.t, "help.security2")), (0, react.createElement)("li", null, translateWith(props.t, "help.security3")), (0, react.createElement)("li", null, translateWith(props.t, "help.security4")), (0, react.createElement)("li", null, translateWith(props.t, "help.security5"))), (0, react.createElement)("p", { className: "pbb-helpText" }, translateWith(props.t, "help.docsPrefix"), (0, react.createElement)("a", {
|
|
3523
3687
|
className: "pbb-helpLink",
|
|
3524
3688
|
href: "https://tailscale.com/docs/features/tailscale-funnel",
|
|
3525
3689
|
target: "_blank",
|
|
@@ -3527,20 +3691,24 @@ window.__ModuleLoader__.load({
|
|
|
3527
3691
|
}, translateWith(props.t, "help.funnelDocs"))))))), report === null ? null : (0, react.createElement)("div", { className: "pbb-card" }, (0, react.createElement)("div", { className: "pbb-field" }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "pair.qrPanelTitle")), pairingTarget === null ? (0, react.createElement)("span", { className: "pbb-value pbb-bad" }, translateWith(props.t, "pair.noAddress")) : (0, react.createElement)("span", { className: "pbb-tokenActions" }, (0, react.createElement)("span", { className: "pbb-badge" }, pairingTarget.kind === "public" ? translateWith(props.t, "pair.kind.public") : translateWith(props.t, "pair.kind.lan")), (0, react.createElement)("button", {
|
|
3528
3692
|
type: "button",
|
|
3529
3693
|
className: "pbb-action",
|
|
3530
|
-
disabled: qrBusy || !report.
|
|
3694
|
+
disabled: qrBusy || !report.pairingReady,
|
|
3531
3695
|
onClick: () => {
|
|
3532
3696
|
if (qrDataURL === null) showPairingQR();
|
|
3533
|
-
else
|
|
3697
|
+
else hidePairingQR();
|
|
3534
3698
|
}
|
|
3535
|
-
}, qrBusy ? translateWith(props.t, "pair.qrGenerating") : qrDataURL === null ? translateWith(props.t, "pair.qrShow") : translateWith(props.t, "pair.qrHide")))), pairingTarget === null ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, translateWith(props.t, "pair.noAddressHelp")) : null, qrMessage ? (0, react.createElement)("p", { className: "pbb-diag" }, qrMessage) : null, pairingTarget === null || qrDataURL === null ? null : (0, react.createElement)("div", { className: "pbb-qrPanel" }, (0, react.createElement)("img", {
|
|
3699
|
+
}, qrBusy ? translateWith(props.t, "pair.qrGenerating") : qrDataURL === null ? translateWith(props.t, "pair.qrShow") : translateWith(props.t, "pair.qrHide")))), pairingTarget === null ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, translateWith(props.t, "pair.noAddressHelp")) : null, qrMessage ? (0, react.createElement)("p", { className: "pbb-diag" }, qrMessage) : null, pairingTarget === null || qrDataURL === null || pairingGrant === null ? null : (0, react.createElement)("div", { className: "pbb-qrPanel" }, (0, react.createElement)("img", {
|
|
3536
3700
|
className: "pbb-qrImage",
|
|
3537
3701
|
src: qrDataURL,
|
|
3538
3702
|
alt: translateWith(props.t, "pair.qrAlt")
|
|
3539
|
-
}), (0, react.createElement)("div", { className: "pbb-
|
|
3703
|
+
}), (0, react.createElement)("div", { className: "pbb-pairCodeBlock" }, (0, react.createElement)("span", { className: "pbb-pairCodeLabel" }, translateWith(props.t, "pair.codeLabel")), (0, react.createElement)("div", { className: "pbb-pairCodeRow" }, (0, react.createElement)("code", { className: "pbb-pairCode" }, pairingGrant.code), (0, react.createElement)("button", {
|
|
3704
|
+
type: "button",
|
|
3705
|
+
className: "pbb-action",
|
|
3706
|
+
onClick: copyPairingCode
|
|
3707
|
+
}, translateWith(props.t, "panel.tokenAction.copy")))), (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("code", { className: "pbb-token" }, pairingTarget.host), (0, react.createElement)("button", {
|
|
3540
3708
|
type: "button",
|
|
3541
3709
|
className: "pbb-action",
|
|
3542
3710
|
onClick: () => copyRemoteURL(pairingTarget.host)
|
|
3543
|
-
}, translateWith(props.t, "panel.tokenAction.copy"))), (0, react.createElement)("p", { className: "pbb-qrHint" }, translateWith(props.t, "pair.qrHint", { kind: pairingTarget.kind === "public" ? translateWith(props.t, "pair.kind.public") : translateWith(props.t, "pair.kind.lan") }))))), (0, react.createElement)("div", { className: "pbb-card" }, (0, react.createElement)("div", { className: "pbb-field" }, primaryRows, advancedRows.length > 0 ? (0, react.createElement)("details", { className: "pbb-help" }, (0, react.createElement)("summary", null, translateWith(props.t, "advanced.summary")), (0, react.createElement)("div", { className: "pbb-helpBody" }, advancedRows)) : null)), (0, react.createElement)("div", { className: "pbb-card" }, (0, react.createElement)("div", { className: "pbb-field" }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "push.relayTitle")), (0, react.createElement)("span", { className: "pbb-tokenActions" }, (0, react.createElement)("button", {
|
|
3711
|
+
}, translateWith(props.t, "panel.tokenAction.copy"))), addressMessage ? (0, react.createElement)("p", { className: "pbb-diag" }, addressMessage) : null, (0, react.createElement)("p", { className: "pbb-qrHint" }, translateWith(props.t, "pair.qrHint", { kind: pairingTarget.kind === "public" ? translateWith(props.t, "pair.kind.public") : translateWith(props.t, "pair.kind.lan") }))))), (0, react.createElement)("div", { className: "pbb-card" }, (0, react.createElement)("div", { className: "pbb-field" }, primaryRows, advancedRows.length > 0 ? (0, react.createElement)("details", { className: "pbb-help" }, (0, react.createElement)("summary", null, translateWith(props.t, "advanced.summary")), (0, react.createElement)("div", { className: "pbb-helpBody" }, advancedRows)) : null)), (0, react.createElement)("div", { className: "pbb-card" }, (0, react.createElement)("div", { className: "pbb-field" }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "push.relayTitle")), (0, react.createElement)("span", { className: "pbb-tokenActions" }, (0, react.createElement)("button", {
|
|
3544
3712
|
type: "button",
|
|
3545
3713
|
className: "pbb-action",
|
|
3546
3714
|
disabled: relayTestBusy,
|
|
@@ -3556,7 +3724,7 @@ window.__ModuleLoader__.load({
|
|
|
3556
3724
|
}, (step.ok ? "✓ " : "✗ ") + (step.id === "health" ? translateWith(props.t, "push.relayStep.health") : translateWith(props.t, "push.relayStep.enroll")) + (step.latencyMs !== void 0 ? ` (${String(step.latencyMs)}ms)` : "") + " — " + step.message))), pushTestError ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, pushTestError) : null, pushTestResult === null ? (0, react.createElement)("p", { className: "pbb-diag" }, translateWith(props.t, "push.pushDefault")) : (0, react.createElement)("div", { className: "pbb-helpBody" }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("code", { className: "pbb-token " + (pushTestResult.overall === "sent" ? "pbb-ok" : pushTestResult.overall === "failed" ? "pbb-bad" : "") }, pushTestResult.overall === "sent" ? translateWith(props.t, "push.pushSent") : pushTestResult.overall === "failed" ? translateWith(props.t, "push.pushFailed") : pushTestResult.overall === "no-targets" ? translateWith(props.t, "push.pushNoTargets") : translateWith(props.t, "push.pushNotEnabled"))), pushTestResult.message ? (0, react.createElement)("p", { className: "pbb-diag" }, pushTestResult.message) : null, pushTestResult.results.map((r, index) => (0, react.createElement)("p", {
|
|
3557
3725
|
className: "pbb-diag",
|
|
3558
3726
|
key: String(index)
|
|
3559
|
-
}, (r.outcome === "sent" ? "✓ " : "✗ ") + r.name + " [" + r.environment + "] — " + r.outcome + (r.reason ? "(" + r.reason + ")" : "") + (r.tokenFingerprint ? " token:" + r.tokenFingerprint + "…" : "")))))), (0, react.createElement)("div", { className: "pbb-card" }, (0, react.createElement)("div", { className: "pbb-field" }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "devices.title")), (0, react.createElement)("span", { className: "pbb-badge" }, String(
|
|
3727
|
+
}, (r.outcome === "sent" ? "✓ " : "✗ ") + r.name + " [" + r.environment + "] — " + r.outcome + (r.reason ? "(" + r.reason + ")" : "") + (r.tokenFingerprint ? " token:" + r.tokenFingerprint + "…" : "")))))), (0, react.createElement)("div", { className: "pbb-card" }, (0, react.createElement)("div", { className: "pbb-field" }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "devices.title")), (0, react.createElement)("span", { className: "pbb-badge" }, String(visibleDevices.length))), deviceMessage ? (0, react.createElement)("p", { className: "pbb-diag" }, deviceMessage) : null, deviceTable)), report === null ? null : (0, react.createElement)("div", { className: "pbb-versionFooter" }, (0, react.createElement)("span", null, "DeepPilot v" + report.pluginVersion), report.updateAvailable === true ? (0, react.createElement)("a", {
|
|
3560
3728
|
href: typeof report.releaseUrl === "string" && /^https:\/\//.test(report.releaseUrl) ? report.releaseUrl : "https://github.com/Mars-Sea/dsh-deeppilot/releases",
|
|
3561
3729
|
target: "_blank",
|
|
3562
3730
|
rel: "noreferrer"
|
|
@@ -3636,7 +3804,7 @@ window.__ModuleLoader__.load({
|
|
|
3636
3804
|
const tPage = (key, vars) => t(ctx, key, vars);
|
|
3637
3805
|
const controller = new ReportController(fetchReport, tPage);
|
|
3638
3806
|
ctx.effect(() => () => controller.dispose(), "dsh-deeppilot: report controller");
|
|
3639
|
-
const store =
|
|
3807
|
+
const store = createSnapshotStore(controller.state());
|
|
3640
3808
|
controller.subscribe(() => store.set(controller.state()));
|
|
3641
3809
|
ctx.effect(() => {
|
|
3642
3810
|
let cancelled = false;
|
|
@@ -3665,12 +3833,18 @@ window.__ModuleLoader__.load({
|
|
|
3665
3833
|
if (unmount !== void 0) unmount();
|
|
3666
3834
|
};
|
|
3667
3835
|
}, "dsh-deeppilot: report remote mount");
|
|
3668
|
-
const
|
|
3836
|
+
const beginPairing = async () => {
|
|
3669
3837
|
if (namespace === void 0) throw new Error(mountError !== void 0 ? t(ctx, "diag.mountFailedShort") + mountError : t(ctx, "diag.remoteUnmounted"));
|
|
3670
|
-
const result = await namespace.
|
|
3671
|
-
if (!result.ok) throw new Error(result.error.message ?? t(ctx, "
|
|
3838
|
+
const result = await namespace.beginPairing();
|
|
3839
|
+
if (!result.ok) throw new Error(result.error.message ?? t(ctx, "pair.qrFailed"));
|
|
3672
3840
|
return result.value;
|
|
3673
3841
|
};
|
|
3842
|
+
const revokeDevice = async (deviceId) => {
|
|
3843
|
+
if (namespace === void 0) throw new Error(t(ctx, "diag.remoteUnmounted"));
|
|
3844
|
+
const result = await namespace.revokeDevice(deviceId);
|
|
3845
|
+
if (!result.ok || result.value !== true) throw new Error(result.ok ? t(ctx, "devices.revokeFailed") : result.error.message ?? t(ctx, "devices.revokeFailed"));
|
|
3846
|
+
await controller.refresh();
|
|
3847
|
+
};
|
|
3674
3848
|
const sendTestPush = async () => {
|
|
3675
3849
|
if (namespace === void 0) throw new Error(mountError !== void 0 ? t(ctx, "diag.mountFailedShort") + mountError : t(ctx, "diag.remoteUnmounted"));
|
|
3676
3850
|
if (typeof namespace.testPush !== "function") throw new Error(t(ctx, "push.staleHostPush"));
|
|
@@ -3685,13 +3859,7 @@ window.__ModuleLoader__.load({
|
|
|
3685
3859
|
if (!result.ok) throw new Error(result.error.message ?? t(ctx, "push.relayBad"));
|
|
3686
3860
|
return result.value;
|
|
3687
3861
|
};
|
|
3688
|
-
const
|
|
3689
|
-
if (namespace === void 0) throw new Error(mountError !== void 0 ? t(ctx, "diag.mountFailedShort") + mountError : t(ctx, "diag.remoteUnmounted"));
|
|
3690
|
-
const result = await namespace.rotateToken();
|
|
3691
|
-
if (!result.ok) throw new Error(result.error.message ?? t(ctx, "panel.tokenRotateFailed"));
|
|
3692
|
-
return result.value;
|
|
3693
|
-
};
|
|
3694
|
-
const enabledStore = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)({
|
|
3862
|
+
const enabledStore = createSnapshotStore({
|
|
3695
3863
|
status: "loading",
|
|
3696
3864
|
enabled: true
|
|
3697
3865
|
});
|
|
@@ -3722,6 +3890,11 @@ window.__ModuleLoader__.load({
|
|
|
3722
3890
|
if (snap?.status === "ready") return snap.value?.remote?.enabled === true;
|
|
3723
3891
|
return remoteEnabledStore.getSnapshot().enabled;
|
|
3724
3892
|
};
|
|
3893
|
+
const lastConfirmedRemoteConnectionLimit = () => {
|
|
3894
|
+
const snap = scope?.getSnapshot();
|
|
3895
|
+
if (snap?.status === "ready") return normalizeFunnelConnectionLimit(snap.value?.remote?.maxConnectionsPerSource);
|
|
3896
|
+
return remoteConnectionLimitStore.getSnapshot().value;
|
|
3897
|
+
};
|
|
3725
3898
|
const setDeepPilotEnabled = (value) => {
|
|
3726
3899
|
const previous = lastConfirmedEnabled();
|
|
3727
3900
|
enabledStore.set({
|
|
@@ -3738,21 +3911,36 @@ window.__ModuleLoader__.load({
|
|
|
3738
3911
|
console.error("[deeppilot] failed to persist enabled=" + String(value) + ": " + message);
|
|
3739
3912
|
});
|
|
3740
3913
|
};
|
|
3741
|
-
const remoteEnabledStore =
|
|
3914
|
+
const remoteEnabledStore = createSnapshotStore({
|
|
3742
3915
|
status: "loading",
|
|
3743
3916
|
enabled: false
|
|
3744
3917
|
});
|
|
3918
|
+
const remoteConnectionLimitStore = createSnapshotStore({
|
|
3919
|
+
status: "loading",
|
|
3920
|
+
value: 8
|
|
3921
|
+
});
|
|
3745
3922
|
const adoptRemoteEnabled = () => {
|
|
3746
3923
|
if (scope === void 0) return;
|
|
3747
3924
|
const snap = scope.getSnapshot();
|
|
3748
|
-
if (snap.status === "ready")
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3925
|
+
if (snap.status === "ready") {
|
|
3926
|
+
remoteEnabledStore.set({
|
|
3927
|
+
status: "ready",
|
|
3928
|
+
enabled: snap.value?.remote?.enabled === true
|
|
3929
|
+
});
|
|
3930
|
+
remoteConnectionLimitStore.set({
|
|
3931
|
+
status: "ready",
|
|
3932
|
+
value: normalizeFunnelConnectionLimit(snap.value?.remote?.maxConnectionsPerSource)
|
|
3933
|
+
});
|
|
3934
|
+
} else if (snap.status === "unavailable") {
|
|
3935
|
+
remoteEnabledStore.set({
|
|
3936
|
+
status: "unavailable",
|
|
3937
|
+
enabled: false
|
|
3938
|
+
});
|
|
3939
|
+
remoteConnectionLimitStore.set({
|
|
3940
|
+
status: "unavailable",
|
|
3941
|
+
value: 8
|
|
3942
|
+
});
|
|
3943
|
+
}
|
|
3756
3944
|
};
|
|
3757
3945
|
if (scope !== void 0) {
|
|
3758
3946
|
scope.subscribe(adoptRemoteEnabled);
|
|
@@ -3778,6 +3966,31 @@ window.__ModuleLoader__.load({
|
|
|
3778
3966
|
console.error("[deeppilot] failed to persist remote.enabled=" + String(value) + ": " + message);
|
|
3779
3967
|
});
|
|
3780
3968
|
};
|
|
3969
|
+
const setDeepPilotRemoteConnectionLimit = async (value) => {
|
|
3970
|
+
const next = normalizeFunnelConnectionLimit(value);
|
|
3971
|
+
if (next !== value) throw new RangeError("maxConnectionsPerSource must be an integer between 1 and 16");
|
|
3972
|
+
if (scope === void 0) throw new Error("settings scope unavailable");
|
|
3973
|
+
const previous = lastConfirmedRemoteConnectionLimit();
|
|
3974
|
+
remoteConnectionLimitStore.set({
|
|
3975
|
+
status: "ready",
|
|
3976
|
+
value: next
|
|
3977
|
+
});
|
|
3978
|
+
const currentRemote = scope.getSnapshot().value?.remote ?? {};
|
|
3979
|
+
try {
|
|
3980
|
+
await scope.set("remote", {
|
|
3981
|
+
...currentRemote,
|
|
3982
|
+
maxConnectionsPerSource: next
|
|
3983
|
+
});
|
|
3984
|
+
} catch (error) {
|
|
3985
|
+
remoteConnectionLimitStore.set({
|
|
3986
|
+
status: "ready",
|
|
3987
|
+
value: previous
|
|
3988
|
+
});
|
|
3989
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3990
|
+
console.error("[deeppilot] failed to persist remote.maxConnectionsPerSource=" + String(next) + ": " + message);
|
|
3991
|
+
throw error;
|
|
3992
|
+
}
|
|
3993
|
+
};
|
|
3781
3994
|
if (anyCtx.slots === void 0) console.error("[deeppilot] settings slots service unavailable; the DeepPilot section will not appear");
|
|
3782
3995
|
anyCtx.slots?.inject("settings.section", () => anyCtx.slots.register({
|
|
3783
3996
|
name: "settings.section",
|
|
@@ -3792,17 +4005,19 @@ window.__ModuleLoader__.load({
|
|
|
3792
4005
|
hooks: {
|
|
3793
4006
|
deepPilotReport: store,
|
|
3794
4007
|
deepPilotEnabled: enabledStore,
|
|
3795
|
-
deepPilotRemoteEnabled: remoteEnabledStore
|
|
4008
|
+
deepPilotRemoteEnabled: remoteEnabledStore,
|
|
4009
|
+
deepPilotRemoteConnectionLimit: remoteConnectionLimitStore
|
|
3796
4010
|
},
|
|
3797
4011
|
refresh: () => {
|
|
3798
4012
|
controller.refresh();
|
|
3799
4013
|
},
|
|
3800
|
-
|
|
3801
|
-
|
|
4014
|
+
beginPairing,
|
|
4015
|
+
revokeDevice,
|
|
3802
4016
|
testRelay: testRelayConnection,
|
|
3803
4017
|
testPush: sendTestPush,
|
|
3804
4018
|
setDeepPilotEnabled,
|
|
3805
4019
|
setDeepPilotRemoteEnabled,
|
|
4020
|
+
setDeepPilotRemoteConnectionLimit,
|
|
3806
4021
|
t: (key, vars) => t(ctx, key, vars)
|
|
3807
4022
|
})
|
|
3808
4023
|
}, DeepPilotSettingsPage));
|