dsh-deeppilot 0.6.0-alpha.2 → 0.6.0-alpha.4
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 +7 -8
- package/README.md +23 -17
- package/README.zh-CN.md +20 -15
- package/SECURITY.md +2 -2
- 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/lib/client.js +317 -38
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +35 -7
- package/lib/index.js +771 -185
- package/lib/index.js.map +1 -1
- package/package.json +23 -12
package/lib/client.js
CHANGED
|
@@ -151,6 +151,28 @@ window.__ModuleLoader__.load({
|
|
|
151
151
|
updatedAt: int(s, "updatedAt", "remote.updatedAt")
|
|
152
152
|
};
|
|
153
153
|
}
|
|
154
|
+
function parseLocal(value) {
|
|
155
|
+
const s = rec(value, "local");
|
|
156
|
+
const phase = str(s, "phase", "local.phase");
|
|
157
|
+
if (![
|
|
158
|
+
"disabled",
|
|
159
|
+
"starting",
|
|
160
|
+
"online",
|
|
161
|
+
"error",
|
|
162
|
+
"stopped"
|
|
163
|
+
].includes(phase)) reject("local.phase");
|
|
164
|
+
const endpoints = s.endpoints;
|
|
165
|
+
if (!Array.isArray(endpoints) || endpoints.some((entry) => typeof entry !== "string")) reject("local.endpoints");
|
|
166
|
+
const message = s.message;
|
|
167
|
+
if (message !== void 0 && typeof message !== "string") reject("local.message");
|
|
168
|
+
return {
|
|
169
|
+
phase,
|
|
170
|
+
port: int(s, "port", "local.port"),
|
|
171
|
+
endpoints,
|
|
172
|
+
...typeof message === "string" ? { message } : {},
|
|
173
|
+
updatedAt: int(s, "updatedAt", "local.updatedAt")
|
|
174
|
+
};
|
|
175
|
+
}
|
|
154
176
|
function parseRelayTestStep(value) {
|
|
155
177
|
const st = rec(value, "step");
|
|
156
178
|
const id = str(st, "id", "step.id");
|
|
@@ -232,6 +254,7 @@ window.__ModuleLoader__.load({
|
|
|
232
254
|
historyBufferMax: int(s, "historyBufferMax", "historyBufferMax"),
|
|
233
255
|
debug: bool(s, "debug", "debug"),
|
|
234
256
|
lanAddresses,
|
|
257
|
+
local: parseLocal(s.local),
|
|
235
258
|
remote: parseRemote(s.remote),
|
|
236
259
|
devices: devices.map(parseDevice)
|
|
237
260
|
};
|
|
@@ -412,6 +435,19 @@ window.__ModuleLoader__.load({
|
|
|
412
435
|
"master.loading": "正在读取配置…",
|
|
413
436
|
"master.on": "已开启:接受手机连接。",
|
|
414
437
|
"master.off": "已关闭:不接受手机连接。",
|
|
438
|
+
"local.title": "局域网连接",
|
|
439
|
+
"local.on": "已开启:手机可通过独立端口 {port} 连接。",
|
|
440
|
+
"local.off": "已关闭:不接受局域网连接。",
|
|
441
|
+
"local.phaseDisabled": "局域网连接未启用",
|
|
442
|
+
"local.phaseStarting": "局域网连接正在启动",
|
|
443
|
+
"local.phaseOnline": "局域网连接已就绪",
|
|
444
|
+
"local.phaseError": "局域网连接失败",
|
|
445
|
+
"local.phaseStopped": "局域网连接已停止",
|
|
446
|
+
"local.portTitle": "局域网端口",
|
|
447
|
+
"local.portDescription": "默认 3098,范围 1024–65535。该端口只提供 DeepPilot 的三个连接端点,不会开放完整 DSH。",
|
|
448
|
+
"local.portApplied": "局域网端口已保存,监听器正在重新加载。",
|
|
449
|
+
"local.portInvalid": "请输入 1024–65535 之间的整数。",
|
|
450
|
+
"local.portFailed": "局域网端口保存失败:",
|
|
415
451
|
"remote.title": "远程连接(Tailscale Funnel)",
|
|
416
452
|
"remote.on": "已配置:内嵌 Funnel 会自动启动并同步状态。",
|
|
417
453
|
"remote.off": "关闭:仅保留局域网连接。",
|
|
@@ -457,7 +493,7 @@ window.__ModuleLoader__.load({
|
|
|
457
493
|
"pair.kind.public": "公网",
|
|
458
494
|
"pair.kind.lan": "内网",
|
|
459
495
|
"pair.noAddress": "没有可用地址",
|
|
460
|
-
"pair.noAddressHelp": "
|
|
496
|
+
"pair.noAddressHelp": "没有可用的局域网或远程入口。请确认局域网连接已启动,或启用远程连接。",
|
|
461
497
|
"pair.qrAlt": "DeepPilot 配对二维码",
|
|
462
498
|
"pair.qrShow": "显示二维码",
|
|
463
499
|
"pair.qrHide": "隐藏二维码",
|
|
@@ -467,7 +503,7 @@ window.__ModuleLoader__.load({
|
|
|
467
503
|
"pair.codeLabel": "配对码(模拟器可手动输入)",
|
|
468
504
|
"pair.codeCopyDone": "配对码已复制",
|
|
469
505
|
"pair.codeCopyFailed": "配对码复制失败:",
|
|
470
|
-
"pair.publicCopyDone": "
|
|
506
|
+
"pair.publicCopyDone": "连接地址已复制",
|
|
471
507
|
"pair.publicCopyFailed": "复制失败:",
|
|
472
508
|
"pair.qrHint": "二维码包含{kind}地址和一次性配对码;配对码 5 分钟后失效,二维码将在 60 秒后自动隐藏。",
|
|
473
509
|
"advanced.summary": "高级信息",
|
|
@@ -534,8 +570,8 @@ window.__ModuleLoader__.load({
|
|
|
534
570
|
"help.securityTitle": "安全说明",
|
|
535
571
|
"help.security1": "Funnel 公网地址可从互联网访问,但连接必须使用已配对设备的 P-256 私钥签名;不要分享仍在有效期内的配对二维码。",
|
|
536
572
|
"help.security2": "丢失设备时可在设备列表中单独删除;该设备会立即断开,不影响其他手机。",
|
|
537
|
-
"help.security3": "
|
|
538
|
-
"help.security4": "
|
|
573
|
+
"help.security3": "Funnel 和局域网监听器都只提供 /phone、/phone/pair 和 /phone/health,不会暴露完整 DSH Web/API。",
|
|
574
|
+
"help.security4": "局域网连接使用插件独立的 3098 端口和明文 WebSocket。只在可信网络中使用,不要把 3098 转发到公网。",
|
|
539
575
|
"help.security5": "关闭远程连接开关会停止 Funnel,但不会影响可用的局域网连接。",
|
|
540
576
|
"help.funnelDocs": "Tailscale Funnel 官方文档",
|
|
541
577
|
"help.docsPrefix": "更多信息:",
|
|
@@ -547,6 +583,8 @@ window.__ModuleLoader__.load({
|
|
|
547
583
|
"diag.settingsUnavailable": "设置命名空间不可用(settingsScope 未提供)",
|
|
548
584
|
"diag.missingReportHook": "useDeepPilotReport hook 缺失",
|
|
549
585
|
"diag.missingEnabledHook": "useDeepPilotEnabled hook 缺失",
|
|
586
|
+
"diag.missingLocalEnabledHook": "useDeepPilotLocalEnabled hook 缺失",
|
|
587
|
+
"diag.missingLocalPortHook": "useDeepPilotLocalPort hook 缺失",
|
|
550
588
|
"diag.missingRemoteEnabledHook": "useDeepPilotRemoteEnabled hook 缺失",
|
|
551
589
|
"diag.missingRemoteLimitHook": "useDeepPilotRemoteConnectionLimit hook 缺失",
|
|
552
590
|
"diag.missingRefresh": "refresh 回调缺失",
|
|
@@ -555,6 +593,8 @@ window.__ModuleLoader__.load({
|
|
|
555
593
|
"diag.missingTestRelay": "testRelay 回调缺失",
|
|
556
594
|
"diag.missingTestPush": "testPush 回调缺失",
|
|
557
595
|
"diag.missingSetEnabled": "setDeepPilotEnabled 回调缺失",
|
|
596
|
+
"diag.missingSetLocal": "setDeepPilotLocalEnabled 回调缺失",
|
|
597
|
+
"diag.missingSetLocalPort": "setDeepPilotLocalPort 回调缺失",
|
|
558
598
|
"diag.missingSetRemote": "setDeepPilotRemoteEnabled 回调缺失",
|
|
559
599
|
"diag.missingSetRemoteLimit": "setDeepPilotRemoteConnectionLimit 回调缺失",
|
|
560
600
|
"diag.renderError": "渲染异常: ",
|
|
@@ -575,6 +615,19 @@ window.__ModuleLoader__.load({
|
|
|
575
615
|
"master.loading": "Loading settings…",
|
|
576
616
|
"master.on": "On: accepting phone connections.",
|
|
577
617
|
"master.off": "Off: not accepting phone connections.",
|
|
618
|
+
"local.title": "LAN connection",
|
|
619
|
+
"local.on": "On: phones can connect through the independent port {port}.",
|
|
620
|
+
"local.off": "Off: LAN connections are not accepted.",
|
|
621
|
+
"local.phaseDisabled": "LAN connection disabled",
|
|
622
|
+
"local.phaseStarting": "LAN connection starting",
|
|
623
|
+
"local.phaseOnline": "LAN connection ready",
|
|
624
|
+
"local.phaseError": "LAN connection failed",
|
|
625
|
+
"local.phaseStopped": "LAN connection stopped",
|
|
626
|
+
"local.portTitle": "LAN port",
|
|
627
|
+
"local.portDescription": "Default 3098; range 1024–65535. This port exposes only the three DeepPilot endpoints, never the full DSH server.",
|
|
628
|
+
"local.portApplied": "LAN port saved; the listener is reloading.",
|
|
629
|
+
"local.portInvalid": "Enter an integer from 1024 to 65535.",
|
|
630
|
+
"local.portFailed": "Failed to save LAN port: ",
|
|
578
631
|
"remote.title": "Remote connection (Tailscale Funnel)",
|
|
579
632
|
"remote.on": "Configured: the embedded Funnel will start and sync state automatically.",
|
|
580
633
|
"remote.off": "Off: LAN connections only.",
|
|
@@ -620,7 +673,7 @@ window.__ModuleLoader__.load({
|
|
|
620
673
|
"pair.kind.public": "Public",
|
|
621
674
|
"pair.kind.lan": "LAN",
|
|
622
675
|
"pair.noAddress": "No address available",
|
|
623
|
-
"pair.noAddressHelp": "No LAN
|
|
676
|
+
"pair.noAddressHelp": "No LAN or remote endpoint is available. Make sure LAN is running or enable remote access.",
|
|
624
677
|
"pair.qrAlt": "DeepPilot pairing QR code",
|
|
625
678
|
"pair.qrShow": "Show QR code",
|
|
626
679
|
"pair.qrHide": "Hide QR code",
|
|
@@ -630,7 +683,7 @@ window.__ModuleLoader__.load({
|
|
|
630
683
|
"pair.codeLabel": "Pairing code (enter manually in Simulator)",
|
|
631
684
|
"pair.codeCopyDone": "Pairing code copied",
|
|
632
685
|
"pair.codeCopyFailed": "Failed to copy pairing code: ",
|
|
633
|
-
"pair.publicCopyDone": "
|
|
686
|
+
"pair.publicCopyDone": "Connection address copied",
|
|
634
687
|
"pair.publicCopyFailed": "Copy failed: ",
|
|
635
688
|
"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.",
|
|
636
689
|
"advanced.summary": "Advanced info",
|
|
@@ -697,8 +750,8 @@ window.__ModuleLoader__.load({
|
|
|
697
750
|
"help.securityTitle": "Security notes",
|
|
698
751
|
"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
752
|
"help.security2": "If a device is lost, delete only that device from the list. It disconnects immediately; other phones stay paired.",
|
|
700
|
-
"help.security3": "
|
|
701
|
-
"help.security4": "LAN
|
|
753
|
+
"help.security3": "Funnel and the LAN listener expose only /phone, /phone/pair, and /phone/health, never the full DSH Web/API surface.",
|
|
754
|
+
"help.security4": "LAN uses the plugin's independent port 3098 over plaintext WebSocket. Use trusted networks only and never forward 3098 to the public Internet.",
|
|
702
755
|
"help.security5": "Disabling the remote switch stops Funnel but does not affect any LAN connection you already have.",
|
|
703
756
|
"help.funnelDocs": "Tailscale Funnel docs",
|
|
704
757
|
"help.docsPrefix": "More info: ",
|
|
@@ -710,6 +763,8 @@ window.__ModuleLoader__.load({
|
|
|
710
763
|
"diag.settingsUnavailable": "Settings namespace unavailable (settingsScope not provided)",
|
|
711
764
|
"diag.missingReportHook": "useDeepPilotReport hook missing",
|
|
712
765
|
"diag.missingEnabledHook": "useDeepPilotEnabled hook missing",
|
|
766
|
+
"diag.missingLocalEnabledHook": "useDeepPilotLocalEnabled hook missing",
|
|
767
|
+
"diag.missingLocalPortHook": "useDeepPilotLocalPort hook missing",
|
|
713
768
|
"diag.missingRemoteEnabledHook": "useDeepPilotRemoteEnabled hook missing",
|
|
714
769
|
"diag.missingRemoteLimitHook": "useDeepPilotRemoteConnectionLimit hook missing",
|
|
715
770
|
"diag.missingRefresh": "refresh callback missing",
|
|
@@ -718,6 +773,8 @@ window.__ModuleLoader__.load({
|
|
|
718
773
|
"diag.missingTestRelay": "testRelay callback missing",
|
|
719
774
|
"diag.missingTestPush": "testPush callback missing",
|
|
720
775
|
"diag.missingSetEnabled": "setDeepPilotEnabled callback missing",
|
|
776
|
+
"diag.missingSetLocal": "setDeepPilotLocalEnabled callback missing",
|
|
777
|
+
"diag.missingSetLocalPort": "setDeepPilotLocalPort callback missing",
|
|
721
778
|
"diag.missingSetRemote": "setDeepPilotRemoteEnabled callback missing",
|
|
722
779
|
"diag.missingSetRemoteLimit": "setDeepPilotRemoteConnectionLimit callback missing",
|
|
723
780
|
"diag.renderError": "Render error: ",
|
|
@@ -822,7 +879,12 @@ window.__ModuleLoader__.load({
|
|
|
822
879
|
".pbb-action{font:inherit;font-size:12px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:none;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;padding:3px 8px}",
|
|
823
880
|
".pbb-action:hover:not(:disabled){color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-module-platform)}",
|
|
824
881
|
".pbb-action:disabled{cursor:default;opacity:.5}",
|
|
825
|
-
".pbb-
|
|
882
|
+
".pbb-actionSelected{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-state-success-primary,#22a06b);background:var(--dsw-alias-bg-module-platform)}",
|
|
883
|
+
".pbb-targetList{display:flex;flex-wrap:wrap;gap:6px;padding-top:4px}",
|
|
884
|
+
".pbb-actionDanger{color:#fff;background:#dc2626;border-color:#dc2626;line-height:1.35;padding:4px 10px;box-shadow:0 1px 2px rgba(127,29,29,.18);transition:background-color .15s ease,border-color .15s ease,box-shadow .15s ease,transform .1s ease}",
|
|
885
|
+
".pbb-actionDanger:hover:not(:disabled){color:#fff;background:#b91c1c;border-color:#b91c1c;box-shadow:0 2px 5px rgba(127,29,29,.24)}",
|
|
886
|
+
".pbb-actionDanger:active:not(:disabled){background:#991b1b;border-color:#991b1b;box-shadow:0 1px 2px rgba(127,29,29,.2);transform:translateY(1px)}",
|
|
887
|
+
".pbb-actionDanger:focus-visible{outline:2px solid rgba(220,38,38,.32);outline-offset:2px}",
|
|
826
888
|
".pbb-qrPanel{display:flex;flex-direction:column;align-items:center;gap:9px;padding:12px 0 4px}",
|
|
827
889
|
".pbb-qrImage{width:240px;height:240px;max-width:100%;background:#fff;border-radius:10px;padding:8px;box-sizing:border-box}",
|
|
828
890
|
".pbb-pairCodeBlock{width:min(100%,420px);display:flex;flex-direction:column;gap:5px}",
|
|
@@ -3281,34 +3343,38 @@ window.__ModuleLoader__.load({
|
|
|
3281
3343
|
});
|
|
3282
3344
|
})))(), 1);
|
|
3283
3345
|
const PAIRING_QR_TYPE = "deeppilot-pairing";
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
function
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
if (remote.phase === "online" && remote.publicURL) return {
|
|
3346
|
+
/**
|
|
3347
|
+
* Return every explicit DeepPilot transport target. LAN comes first because
|
|
3348
|
+
* it is private and lower latency; an online Funnel remains available as a
|
|
3349
|
+
* separate choice instead of silently replacing the local address.
|
|
3350
|
+
*/
|
|
3351
|
+
function selectPairingTargets(local, remote) {
|
|
3352
|
+
const targets = [];
|
|
3353
|
+
if (local.phase === "online") {
|
|
3354
|
+
for (const host of local.endpoints) if (validTargetHost(host)) targets.push({
|
|
3355
|
+
host,
|
|
3356
|
+
kind: "lan"
|
|
3357
|
+
});
|
|
3358
|
+
}
|
|
3359
|
+
if (remote.phase === "online" && remote.publicURL && validTargetHost(remote.publicURL)) targets.push({
|
|
3299
3360
|
host: remote.publicURL,
|
|
3300
3361
|
kind: "public"
|
|
3301
|
-
};
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3362
|
+
});
|
|
3363
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3364
|
+
return targets.filter(({ host }) => !seen.has(host) && seen.add(host));
|
|
3365
|
+
}
|
|
3366
|
+
function validTargetHost(value) {
|
|
3367
|
+
try {
|
|
3368
|
+
const parsed = new URL(value);
|
|
3369
|
+
return [
|
|
3370
|
+
"http:",
|
|
3371
|
+
"https:",
|
|
3372
|
+
"ws:",
|
|
3373
|
+
"wss:"
|
|
3374
|
+
].includes(parsed.protocol) && parsed.hostname !== "" && parsed.username === "" && parsed.password === "";
|
|
3375
|
+
} catch {
|
|
3376
|
+
return false;
|
|
3377
|
+
}
|
|
3312
3378
|
}
|
|
3313
3379
|
/** Encode a short-lived, single-use pairing grant without URL credentials. */
|
|
3314
3380
|
function encodePairingQRPayload(host, grant) {
|
|
@@ -3336,6 +3402,14 @@ window.__ModuleLoader__.load({
|
|
|
3336
3402
|
return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 16 ? value : 8;
|
|
3337
3403
|
}
|
|
3338
3404
|
//#endregion
|
|
3405
|
+
//#region src/local-policy.ts
|
|
3406
|
+
const DEFAULT_LOCAL_PORT = 3098;
|
|
3407
|
+
const MIN_LOCAL_PORT = 1024;
|
|
3408
|
+
const MAX_LOCAL_PORT = 65535;
|
|
3409
|
+
function normalizeLocalPort(value) {
|
|
3410
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 1024 && value <= 65535 ? value : DEFAULT_LOCAL_PORT;
|
|
3411
|
+
}
|
|
3412
|
+
//#endregion
|
|
3339
3413
|
//#region src/client/settings-page.ts
|
|
3340
3414
|
async function writeClipboard(t, value) {
|
|
3341
3415
|
try {
|
|
@@ -3386,11 +3460,36 @@ window.__ModuleLoader__.load({
|
|
|
3386
3460
|
labelKey: "phase.stopped"
|
|
3387
3461
|
}
|
|
3388
3462
|
};
|
|
3463
|
+
const LOCAL_PHASE_META = {
|
|
3464
|
+
disabled: {
|
|
3465
|
+
dot: "",
|
|
3466
|
+
labelKey: "local.phaseDisabled"
|
|
3467
|
+
},
|
|
3468
|
+
starting: {
|
|
3469
|
+
dot: " pbb-dotWarn",
|
|
3470
|
+
labelKey: "local.phaseStarting"
|
|
3471
|
+
},
|
|
3472
|
+
online: {
|
|
3473
|
+
dot: " pbb-dotOk",
|
|
3474
|
+
labelKey: "local.phaseOnline"
|
|
3475
|
+
},
|
|
3476
|
+
error: {
|
|
3477
|
+
dot: " pbb-dotBad",
|
|
3478
|
+
labelKey: "local.phaseError"
|
|
3479
|
+
},
|
|
3480
|
+
stopped: {
|
|
3481
|
+
dot: "",
|
|
3482
|
+
labelKey: "local.phaseStopped"
|
|
3483
|
+
}
|
|
3484
|
+
};
|
|
3389
3485
|
/** Slot component: hooks come from the slot renderer, named use<Key>. */
|
|
3390
3486
|
function DeepPilotSettingsPage(props) {
|
|
3391
3487
|
const [addressMessage, setAddressMessage] = (0, react.useState)("");
|
|
3392
3488
|
const [remoteLimitDraft, setRemoteLimitDraft] = (0, react.useState)(String(8));
|
|
3393
3489
|
const [remoteLimitMessage, setRemoteLimitMessage] = (0, react.useState)("");
|
|
3490
|
+
const [localPortDraft, setLocalPortDraft] = (0, react.useState)(String(DEFAULT_LOCAL_PORT));
|
|
3491
|
+
const [localPortMessage, setLocalPortMessage] = (0, react.useState)("");
|
|
3492
|
+
const [selectedPairingHost, setSelectedPairingHost] = (0, react.useState)(null);
|
|
3394
3493
|
const [qrDataURL, setQRDataURL] = (0, react.useState)(null);
|
|
3395
3494
|
const [pairingGrant, setPairingGrant] = (0, react.useState)(null);
|
|
3396
3495
|
const [qrBusy, setQRBusy] = (0, react.useState)(false);
|
|
@@ -3451,13 +3550,18 @@ window.__ModuleLoader__.load({
|
|
|
3451
3550
|
const timer = globalThis.setTimeout(() => setRemoteLimitMessage(""), 4e3);
|
|
3452
3551
|
return () => globalThis.clearTimeout(timer);
|
|
3453
3552
|
}, [remoteLimitMessage]);
|
|
3553
|
+
(0, react.useEffect)(() => {
|
|
3554
|
+
if (!localPortMessage) return;
|
|
3555
|
+
const timer = globalThis.setTimeout(() => setLocalPortMessage(""), 4e3);
|
|
3556
|
+
return () => globalThis.clearTimeout(timer);
|
|
3557
|
+
}, [localPortMessage]);
|
|
3454
3558
|
(0, react.useEffect)(() => {
|
|
3455
3559
|
if (qrDataURL === null) return;
|
|
3456
3560
|
const timer = globalThis.setTimeout(() => {
|
|
3457
3561
|
setQRDataURL(null);
|
|
3458
3562
|
setPairingGrant(null);
|
|
3459
3563
|
setQRMessage(translateWith(props.t, "pair.qrAutoHidden"));
|
|
3460
|
-
},
|
|
3564
|
+
}, 1728e5);
|
|
3461
3565
|
return () => globalThis.clearTimeout(timer);
|
|
3462
3566
|
}, [qrDataURL]);
|
|
3463
3567
|
(0, react.useEffect)(() => {
|
|
@@ -3469,6 +3573,10 @@ window.__ModuleLoader__.load({
|
|
|
3469
3573
|
let report = null;
|
|
3470
3574
|
let enabled = true;
|
|
3471
3575
|
let switchReady = false;
|
|
3576
|
+
let localEnabled = true;
|
|
3577
|
+
let localSwitchReady = false;
|
|
3578
|
+
let localPort = DEFAULT_LOCAL_PORT;
|
|
3579
|
+
let localPortReady = false;
|
|
3472
3580
|
let remoteEnabled = false;
|
|
3473
3581
|
let remoteSwitchReady = false;
|
|
3474
3582
|
let remoteConnectionLimit = 8;
|
|
@@ -3498,6 +3606,18 @@ window.__ModuleLoader__.load({
|
|
|
3498
3606
|
if (typeof props.testRelay !== "function") diag.push(translateWith(props.t, "diag.missingTestRelay"));
|
|
3499
3607
|
if (typeof props.testPush !== "function") diag.push(translateWith(props.t, "diag.missingTestPush"));
|
|
3500
3608
|
if (typeof props.setDeepPilotEnabled !== "function") diag.push(translateWith(props.t, "diag.missingSetEnabled"));
|
|
3609
|
+
if (typeof props.useDeepPilotLocalEnabled === "function") {
|
|
3610
|
+
const state = props.useDeepPilotLocalEnabled((s) => s);
|
|
3611
|
+
localEnabled = state.enabled;
|
|
3612
|
+
localSwitchReady = state.status === "ready";
|
|
3613
|
+
} else diag.push(translateWith(props.t, "diag.missingLocalEnabledHook"));
|
|
3614
|
+
if (typeof props.useDeepPilotLocalPort === "function") {
|
|
3615
|
+
const state = props.useDeepPilotLocalPort((s) => s);
|
|
3616
|
+
localPort = state.value;
|
|
3617
|
+
localPortReady = state.status === "ready";
|
|
3618
|
+
} else diag.push(translateWith(props.t, "diag.missingLocalPortHook"));
|
|
3619
|
+
if (typeof props.setDeepPilotLocalEnabled !== "function") diag.push(translateWith(props.t, "diag.missingSetLocal"));
|
|
3620
|
+
if (typeof props.setDeepPilotLocalPort !== "function") diag.push(translateWith(props.t, "diag.missingSetLocalPort"));
|
|
3501
3621
|
if (typeof props.useDeepPilotRemoteEnabled === "function") {
|
|
3502
3622
|
const state = props.useDeepPilotRemoteEnabled((s) => s);
|
|
3503
3623
|
remoteEnabled = state.enabled;
|
|
@@ -3517,6 +3637,23 @@ window.__ModuleLoader__.load({
|
|
|
3517
3637
|
(0, react.useEffect)(() => {
|
|
3518
3638
|
setRemoteLimitDraft(String(remoteConnectionLimit));
|
|
3519
3639
|
}, [remoteConnectionLimit]);
|
|
3640
|
+
(0, react.useEffect)(() => {
|
|
3641
|
+
setLocalPortDraft(String(localPort));
|
|
3642
|
+
}, [localPort]);
|
|
3643
|
+
const parsedLocalPort = Number(localPortDraft);
|
|
3644
|
+
const localPortValid = Number.isInteger(parsedLocalPort) && parsedLocalPort >= 1024 && parsedLocalPort <= 65535;
|
|
3645
|
+
const applyLocalPort = () => {
|
|
3646
|
+
if (!localPortValid || typeof props.setDeepPilotLocalPort !== "function") {
|
|
3647
|
+
setLocalPortMessage(translateWith(props.t, "local.portInvalid"));
|
|
3648
|
+
return;
|
|
3649
|
+
}
|
|
3650
|
+
setLocalPortMessage("");
|
|
3651
|
+
props.setDeepPilotLocalPort(parsedLocalPort).then(() => {
|
|
3652
|
+
setLocalPortMessage(translateWith(props.t, "local.portApplied"));
|
|
3653
|
+
}, (error) => {
|
|
3654
|
+
setLocalPortMessage(translateWith(props.t, "local.portFailed") + (error instanceof Error ? error.message : String(error)));
|
|
3655
|
+
});
|
|
3656
|
+
};
|
|
3520
3657
|
const parsedRemoteLimit = Number(remoteLimitDraft);
|
|
3521
3658
|
const remoteLimitValid = Number.isInteger(parsedRemoteLimit) && parsedRemoteLimit >= 1 && parsedRemoteLimit <= 16;
|
|
3522
3659
|
const applyRemoteLimit = () => {
|
|
@@ -3531,7 +3668,8 @@ window.__ModuleLoader__.load({
|
|
|
3531
3668
|
setRemoteLimitMessage(translateWith(props.t, "remote.limitFailed") + (error instanceof Error ? error.message : String(error)));
|
|
3532
3669
|
});
|
|
3533
3670
|
};
|
|
3534
|
-
const
|
|
3671
|
+
const pairingTargets = report === null ? [] : selectPairingTargets(report.local, report.remote);
|
|
3672
|
+
const pairingTarget = pairingTargets.find(({ host }) => host === selectedPairingHost) ?? pairingTargets[0] ?? null;
|
|
3535
3673
|
(0, react.useEffect)(() => {
|
|
3536
3674
|
setQRDataURL(null);
|
|
3537
3675
|
setPairingGrant(null);
|
|
@@ -3637,6 +3775,21 @@ window.__ModuleLoader__.load({
|
|
|
3637
3775
|
onClick: () => {
|
|
3638
3776
|
if (typeof props.setDeepPilotEnabled === "function") props.setDeepPilotEnabled(!enabled);
|
|
3639
3777
|
}
|
|
3778
|
+
})), (0, react.createElement)("div", { className: "pbb-switchRow" }, (0, react.createElement)("div", { className: "pbb-switchText" }, (0, react.createElement)("span", { className: "pbb-switchTitle pbb-dotRow" }, (0, react.createElement)("span", {
|
|
3779
|
+
className: "pbb-dot" + (report !== null ? LOCAL_PHASE_META[report.local.phase].dot : ""),
|
|
3780
|
+
role: "img",
|
|
3781
|
+
"aria-label": report !== null ? translateWith(props.t, LOCAL_PHASE_META[report.local.phase].labelKey) : translateWith(props.t, "phase.unknown"),
|
|
3782
|
+
title: report !== null ? translateWith(props.t, LOCAL_PHASE_META[report.local.phase].labelKey) : void 0
|
|
3783
|
+
}), translateWith(props.t, "local.title")), (0, react.createElement)("span", { className: "pbb-switchDesc" }, localSwitchReady ? localEnabled ? translateWith(props.t, "local.on", { port: localPort }) : translateWith(props.t, "local.off") : translateWith(props.t, "master.loading")), report !== null && report.local.message && report.local.phase === "error" ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, report.local.message) : null), (0, react.createElement)("button", {
|
|
3784
|
+
type: "button",
|
|
3785
|
+
role: "switch",
|
|
3786
|
+
"aria-checked": localEnabled,
|
|
3787
|
+
"aria-label": translateWith(props.t, "local.title"),
|
|
3788
|
+
disabled: !localSwitchReady,
|
|
3789
|
+
className: "pbb-switch" + (localEnabled ? " pbb-switchOn" : ""),
|
|
3790
|
+
onClick: () => {
|
|
3791
|
+
if (typeof props.setDeepPilotLocalEnabled === "function") props.setDeepPilotLocalEnabled(!localEnabled);
|
|
3792
|
+
}
|
|
3640
3793
|
})), (0, react.createElement)("div", { className: "pbb-switchRow" }, (0, react.createElement)("div", { className: "pbb-switchText" }, (0, react.createElement)("span", { className: "pbb-switchTitle pbb-dotRow" }, (0, react.createElement)("span", {
|
|
3641
3794
|
className: "pbb-dot" + (report !== null ? REMOTE_PHASE_META[report.remote.phase].dot : ""),
|
|
3642
3795
|
role: "img",
|
|
@@ -3658,6 +3811,32 @@ window.__ModuleLoader__.load({
|
|
|
3658
3811
|
if (typeof props.setDeepPilotRemoteEnabled === "function") props.setDeepPilotRemoteEnabled(!remoteEnabled);
|
|
3659
3812
|
}
|
|
3660
3813
|
})), (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", {
|
|
3814
|
+
className: "pbb-switchTitle",
|
|
3815
|
+
htmlFor: "deeppilot-local-port"
|
|
3816
|
+
}, translateWith(props.t, "local.portTitle")), (0, react.createElement)("span", { className: "pbb-switchDesc" }, translateWith(props.t, "local.portDescription")), !localPortValid ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, translateWith(props.t, "local.portInvalid")) : localPortMessage ? (0, react.createElement)("p", { className: "pbb-diag" + (localPortMessage.startsWith(translateWith(props.t, "local.portFailed")) ? " pbb-diagBad" : "") }, localPortMessage) : null), (0, react.createElement)("div", { className: "pbb-limitControl" }, (0, react.createElement)("input", {
|
|
3817
|
+
id: "deeppilot-local-port",
|
|
3818
|
+
className: "pbb-numberInput",
|
|
3819
|
+
type: "number",
|
|
3820
|
+
min: MIN_LOCAL_PORT,
|
|
3821
|
+
max: MAX_LOCAL_PORT,
|
|
3822
|
+
step: 1,
|
|
3823
|
+
inputMode: "numeric",
|
|
3824
|
+
value: localPortDraft,
|
|
3825
|
+
disabled: !localPortReady,
|
|
3826
|
+
"aria-label": translateWith(props.t, "local.portTitle"),
|
|
3827
|
+
onChange: (event) => setLocalPortDraft(event.currentTarget.value),
|
|
3828
|
+
onKeyDown: (event) => {
|
|
3829
|
+
if (event.key === "Enter") {
|
|
3830
|
+
event.preventDefault();
|
|
3831
|
+
applyLocalPort();
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
}), (0, react.createElement)("button", {
|
|
3835
|
+
type: "button",
|
|
3836
|
+
className: "pbb-action",
|
|
3837
|
+
disabled: !localPortReady || !localPortValid || parsedLocalPort === localPort,
|
|
3838
|
+
onClick: applyLocalPort
|
|
3839
|
+
}, translateWith(props.t, "remote.limitApply")))), (0, react.createElement)("div", { className: "pbb-limitRow pbb-limitNested" }, (0, react.createElement)("div", { className: "pbb-switchText" }, (0, react.createElement)("label", {
|
|
3661
3840
|
className: "pbb-switchTitle",
|
|
3662
3841
|
htmlFor: "deeppilot-funnel-source-limit"
|
|
3663
3842
|
}, 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", {
|
|
@@ -3696,7 +3875,15 @@ window.__ModuleLoader__.load({
|
|
|
3696
3875
|
if (qrDataURL === null) showPairingQR();
|
|
3697
3876
|
else hidePairingQR();
|
|
3698
3877
|
}
|
|
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,
|
|
3878
|
+
}, 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, pairingTargets.length < 2 ? null : (0, react.createElement)("div", { className: "pbb-targetList" }, pairingTargets.map((target) => (0, react.createElement)("button", {
|
|
3879
|
+
type: "button",
|
|
3880
|
+
key: target.host,
|
|
3881
|
+
className: "pbb-action" + (target.host === pairingTarget?.host ? " pbb-actionSelected" : ""),
|
|
3882
|
+
onClick: () => {
|
|
3883
|
+
setSelectedPairingHost(target.host);
|
|
3884
|
+
hidePairingQR();
|
|
3885
|
+
}
|
|
3886
|
+
}, (target.kind === "public" ? translateWith(props.t, "pair.kind.public") : translateWith(props.t, "pair.kind.lan")) + " · " + target.host))), 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", {
|
|
3700
3887
|
className: "pbb-qrImage",
|
|
3701
3888
|
src: qrDataURL,
|
|
3702
3889
|
alt: translateWith(props.t, "pair.qrAlt")
|
|
@@ -3885,6 +4072,24 @@ window.__ModuleLoader__.load({
|
|
|
3885
4072
|
if (snap?.status === "ready" && snap.value !== void 0) return snap.value.enabled !== false;
|
|
3886
4073
|
return enabledStore.getSnapshot().enabled;
|
|
3887
4074
|
};
|
|
4075
|
+
const localEnabledStore = createSnapshotStore({
|
|
4076
|
+
status: "loading",
|
|
4077
|
+
enabled: true
|
|
4078
|
+
});
|
|
4079
|
+
const localPortStore = createSnapshotStore({
|
|
4080
|
+
status: "loading",
|
|
4081
|
+
value: DEFAULT_LOCAL_PORT
|
|
4082
|
+
});
|
|
4083
|
+
const lastConfirmedLocalEnabled = () => {
|
|
4084
|
+
const snap = scope?.getSnapshot();
|
|
4085
|
+
if (snap?.status === "ready") return snap.value?.local?.enabled !== false;
|
|
4086
|
+
return localEnabledStore.getSnapshot().enabled;
|
|
4087
|
+
};
|
|
4088
|
+
const lastConfirmedLocalPort = () => {
|
|
4089
|
+
const snap = scope?.getSnapshot();
|
|
4090
|
+
if (snap?.status === "ready") return normalizeLocalPort(snap.value?.local?.port);
|
|
4091
|
+
return localPortStore.getSnapshot().value;
|
|
4092
|
+
};
|
|
3888
4093
|
const lastConfirmedRemoteEnabled = () => {
|
|
3889
4094
|
const snap = scope?.getSnapshot();
|
|
3890
4095
|
if (snap?.status === "ready") return snap.value?.remote?.enabled === true;
|
|
@@ -3911,6 +4116,76 @@ window.__ModuleLoader__.load({
|
|
|
3911
4116
|
console.error("[deeppilot] failed to persist enabled=" + String(value) + ": " + message);
|
|
3912
4117
|
});
|
|
3913
4118
|
};
|
|
4119
|
+
const adoptLocal = () => {
|
|
4120
|
+
if (scope === void 0) return;
|
|
4121
|
+
const snap = scope.getSnapshot();
|
|
4122
|
+
if (snap.status === "ready") {
|
|
4123
|
+
localEnabledStore.set({
|
|
4124
|
+
status: "ready",
|
|
4125
|
+
enabled: snap.value?.local?.enabled !== false
|
|
4126
|
+
});
|
|
4127
|
+
localPortStore.set({
|
|
4128
|
+
status: "ready",
|
|
4129
|
+
value: normalizeLocalPort(snap.value?.local?.port)
|
|
4130
|
+
});
|
|
4131
|
+
} else if (snap.status === "unavailable") {
|
|
4132
|
+
localEnabledStore.set({
|
|
4133
|
+
status: "unavailable",
|
|
4134
|
+
enabled: true
|
|
4135
|
+
});
|
|
4136
|
+
localPortStore.set({
|
|
4137
|
+
status: "unavailable",
|
|
4138
|
+
value: DEFAULT_LOCAL_PORT
|
|
4139
|
+
});
|
|
4140
|
+
}
|
|
4141
|
+
};
|
|
4142
|
+
if (scope !== void 0) {
|
|
4143
|
+
scope.subscribe(adoptLocal);
|
|
4144
|
+
adoptLocal();
|
|
4145
|
+
}
|
|
4146
|
+
const setDeepPilotLocalEnabled = (value) => {
|
|
4147
|
+
const previous = lastConfirmedLocalEnabled();
|
|
4148
|
+
localEnabledStore.set({
|
|
4149
|
+
status: "ready",
|
|
4150
|
+
enabled: value
|
|
4151
|
+
});
|
|
4152
|
+
if (scope === void 0) return;
|
|
4153
|
+
const currentLocal = scope.getSnapshot().value?.local ?? {};
|
|
4154
|
+
scope.set("local", {
|
|
4155
|
+
...currentLocal,
|
|
4156
|
+
enabled: value
|
|
4157
|
+
}).then(() => {}, (error) => {
|
|
4158
|
+
localEnabledStore.set({
|
|
4159
|
+
status: "ready",
|
|
4160
|
+
enabled: previous
|
|
4161
|
+
});
|
|
4162
|
+
console.error("[deeppilot] failed to persist local.enabled=" + String(value) + ": " + (error instanceof Error ? error.message : String(error)));
|
|
4163
|
+
});
|
|
4164
|
+
};
|
|
4165
|
+
const setDeepPilotLocalPort = async (value) => {
|
|
4166
|
+
const next = normalizeLocalPort(value);
|
|
4167
|
+
if (next !== value) throw new RangeError("local port must be an integer between 1024 and 65535");
|
|
4168
|
+
if (scope === void 0) throw new Error("settings scope unavailable");
|
|
4169
|
+
const previous = lastConfirmedLocalPort();
|
|
4170
|
+
localPortStore.set({
|
|
4171
|
+
status: "ready",
|
|
4172
|
+
value: next
|
|
4173
|
+
});
|
|
4174
|
+
const currentLocal = scope.getSnapshot().value?.local ?? {};
|
|
4175
|
+
try {
|
|
4176
|
+
await scope.set("local", {
|
|
4177
|
+
...currentLocal,
|
|
4178
|
+
port: next
|
|
4179
|
+
});
|
|
4180
|
+
} catch (error) {
|
|
4181
|
+
localPortStore.set({
|
|
4182
|
+
status: "ready",
|
|
4183
|
+
value: previous
|
|
4184
|
+
});
|
|
4185
|
+
console.error("[deeppilot] failed to persist local.port=" + String(next) + ": " + (error instanceof Error ? error.message : String(error)));
|
|
4186
|
+
throw error;
|
|
4187
|
+
}
|
|
4188
|
+
};
|
|
3914
4189
|
const remoteEnabledStore = createSnapshotStore({
|
|
3915
4190
|
status: "loading",
|
|
3916
4191
|
enabled: false
|
|
@@ -4005,6 +4280,8 @@ window.__ModuleLoader__.load({
|
|
|
4005
4280
|
hooks: {
|
|
4006
4281
|
deepPilotReport: store,
|
|
4007
4282
|
deepPilotEnabled: enabledStore,
|
|
4283
|
+
deepPilotLocalEnabled: localEnabledStore,
|
|
4284
|
+
deepPilotLocalPort: localPortStore,
|
|
4008
4285
|
deepPilotRemoteEnabled: remoteEnabledStore,
|
|
4009
4286
|
deepPilotRemoteConnectionLimit: remoteConnectionLimitStore
|
|
4010
4287
|
},
|
|
@@ -4016,6 +4293,8 @@ window.__ModuleLoader__.load({
|
|
|
4016
4293
|
testRelay: testRelayConnection,
|
|
4017
4294
|
testPush: sendTestPush,
|
|
4018
4295
|
setDeepPilotEnabled,
|
|
4296
|
+
setDeepPilotLocalEnabled,
|
|
4297
|
+
setDeepPilotLocalPort,
|
|
4019
4298
|
setDeepPilotRemoteEnabled,
|
|
4020
4299
|
setDeepPilotRemoteConnectionLimit,
|
|
4021
4300
|
t: (key, vars) => t(ctx, key, vars)
|