dsh-deeppilot 0.6.0-alpha.3 → 0.6.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 +7 -12
- package/README.md +23 -22
- package/README.zh-CN.md +20 -19
- 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 +313 -37
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +23 -4
- package/lib/index.js +455 -113
- package/lib/index.js.map +1 -1
- package/package.json +17 -17
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,6 +879,8 @@ 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}",
|
|
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}",
|
|
825
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}",
|
|
826
885
|
".pbb-actionDanger:hover:not(:disabled){color:#fff;background:#b91c1c;border-color:#b91c1c;box-shadow:0 2px 5px rgba(127,29,29,.24)}",
|
|
827
886
|
".pbb-actionDanger:active:not(:disabled){background:#991b1b;border-color:#991b1b;box-shadow:0 1px 2px rgba(127,29,29,.2);transform:translateY(1px)}",
|
|
@@ -3284,34 +3343,38 @@ window.__ModuleLoader__.load({
|
|
|
3284
3343
|
});
|
|
3285
3344
|
})))(), 1);
|
|
3286
3345
|
const PAIRING_QR_TYPE = "deeppilot-pairing";
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
function
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
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({
|
|
3302
3360
|
host: remote.publicURL,
|
|
3303
3361
|
kind: "public"
|
|
3304
|
-
};
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
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
|
+
}
|
|
3315
3378
|
}
|
|
3316
3379
|
/** Encode a short-lived, single-use pairing grant without URL credentials. */
|
|
3317
3380
|
function encodePairingQRPayload(host, grant) {
|
|
@@ -3339,6 +3402,14 @@ window.__ModuleLoader__.load({
|
|
|
3339
3402
|
return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 16 ? value : 8;
|
|
3340
3403
|
}
|
|
3341
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
|
|
3342
3413
|
//#region src/client/settings-page.ts
|
|
3343
3414
|
async function writeClipboard(t, value) {
|
|
3344
3415
|
try {
|
|
@@ -3389,11 +3460,36 @@ window.__ModuleLoader__.load({
|
|
|
3389
3460
|
labelKey: "phase.stopped"
|
|
3390
3461
|
}
|
|
3391
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
|
+
};
|
|
3392
3485
|
/** Slot component: hooks come from the slot renderer, named use<Key>. */
|
|
3393
3486
|
function DeepPilotSettingsPage(props) {
|
|
3394
3487
|
const [addressMessage, setAddressMessage] = (0, react.useState)("");
|
|
3395
3488
|
const [remoteLimitDraft, setRemoteLimitDraft] = (0, react.useState)(String(8));
|
|
3396
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);
|
|
3397
3493
|
const [qrDataURL, setQRDataURL] = (0, react.useState)(null);
|
|
3398
3494
|
const [pairingGrant, setPairingGrant] = (0, react.useState)(null);
|
|
3399
3495
|
const [qrBusy, setQRBusy] = (0, react.useState)(false);
|
|
@@ -3454,13 +3550,18 @@ window.__ModuleLoader__.load({
|
|
|
3454
3550
|
const timer = globalThis.setTimeout(() => setRemoteLimitMessage(""), 4e3);
|
|
3455
3551
|
return () => globalThis.clearTimeout(timer);
|
|
3456
3552
|
}, [remoteLimitMessage]);
|
|
3553
|
+
(0, react.useEffect)(() => {
|
|
3554
|
+
if (!localPortMessage) return;
|
|
3555
|
+
const timer = globalThis.setTimeout(() => setLocalPortMessage(""), 4e3);
|
|
3556
|
+
return () => globalThis.clearTimeout(timer);
|
|
3557
|
+
}, [localPortMessage]);
|
|
3457
3558
|
(0, react.useEffect)(() => {
|
|
3458
3559
|
if (qrDataURL === null) return;
|
|
3459
3560
|
const timer = globalThis.setTimeout(() => {
|
|
3460
3561
|
setQRDataURL(null);
|
|
3461
3562
|
setPairingGrant(null);
|
|
3462
3563
|
setQRMessage(translateWith(props.t, "pair.qrAutoHidden"));
|
|
3463
|
-
},
|
|
3564
|
+
}, 1728e5);
|
|
3464
3565
|
return () => globalThis.clearTimeout(timer);
|
|
3465
3566
|
}, [qrDataURL]);
|
|
3466
3567
|
(0, react.useEffect)(() => {
|
|
@@ -3472,6 +3573,10 @@ window.__ModuleLoader__.load({
|
|
|
3472
3573
|
let report = null;
|
|
3473
3574
|
let enabled = true;
|
|
3474
3575
|
let switchReady = false;
|
|
3576
|
+
let localEnabled = true;
|
|
3577
|
+
let localSwitchReady = false;
|
|
3578
|
+
let localPort = DEFAULT_LOCAL_PORT;
|
|
3579
|
+
let localPortReady = false;
|
|
3475
3580
|
let remoteEnabled = false;
|
|
3476
3581
|
let remoteSwitchReady = false;
|
|
3477
3582
|
let remoteConnectionLimit = 8;
|
|
@@ -3501,6 +3606,18 @@ window.__ModuleLoader__.load({
|
|
|
3501
3606
|
if (typeof props.testRelay !== "function") diag.push(translateWith(props.t, "diag.missingTestRelay"));
|
|
3502
3607
|
if (typeof props.testPush !== "function") diag.push(translateWith(props.t, "diag.missingTestPush"));
|
|
3503
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"));
|
|
3504
3621
|
if (typeof props.useDeepPilotRemoteEnabled === "function") {
|
|
3505
3622
|
const state = props.useDeepPilotRemoteEnabled((s) => s);
|
|
3506
3623
|
remoteEnabled = state.enabled;
|
|
@@ -3520,6 +3637,23 @@ window.__ModuleLoader__.load({
|
|
|
3520
3637
|
(0, react.useEffect)(() => {
|
|
3521
3638
|
setRemoteLimitDraft(String(remoteConnectionLimit));
|
|
3522
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
|
+
};
|
|
3523
3657
|
const parsedRemoteLimit = Number(remoteLimitDraft);
|
|
3524
3658
|
const remoteLimitValid = Number.isInteger(parsedRemoteLimit) && parsedRemoteLimit >= 1 && parsedRemoteLimit <= 16;
|
|
3525
3659
|
const applyRemoteLimit = () => {
|
|
@@ -3534,7 +3668,8 @@ window.__ModuleLoader__.load({
|
|
|
3534
3668
|
setRemoteLimitMessage(translateWith(props.t, "remote.limitFailed") + (error instanceof Error ? error.message : String(error)));
|
|
3535
3669
|
});
|
|
3536
3670
|
};
|
|
3537
|
-
const
|
|
3671
|
+
const pairingTargets = report === null ? [] : selectPairingTargets(report.local, report.remote);
|
|
3672
|
+
const pairingTarget = pairingTargets.find(({ host }) => host === selectedPairingHost) ?? pairingTargets[0] ?? null;
|
|
3538
3673
|
(0, react.useEffect)(() => {
|
|
3539
3674
|
setQRDataURL(null);
|
|
3540
3675
|
setPairingGrant(null);
|
|
@@ -3640,6 +3775,21 @@ window.__ModuleLoader__.load({
|
|
|
3640
3775
|
onClick: () => {
|
|
3641
3776
|
if (typeof props.setDeepPilotEnabled === "function") props.setDeepPilotEnabled(!enabled);
|
|
3642
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
|
+
}
|
|
3643
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", {
|
|
3644
3794
|
className: "pbb-dot" + (report !== null ? REMOTE_PHASE_META[report.remote.phase].dot : ""),
|
|
3645
3795
|
role: "img",
|
|
@@ -3661,6 +3811,32 @@ window.__ModuleLoader__.load({
|
|
|
3661
3811
|
if (typeof props.setDeepPilotRemoteEnabled === "function") props.setDeepPilotRemoteEnabled(!remoteEnabled);
|
|
3662
3812
|
}
|
|
3663
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", {
|
|
3664
3840
|
className: "pbb-switchTitle",
|
|
3665
3841
|
htmlFor: "deeppilot-funnel-source-limit"
|
|
3666
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", {
|
|
@@ -3699,7 +3875,15 @@ window.__ModuleLoader__.load({
|
|
|
3699
3875
|
if (qrDataURL === null) showPairingQR();
|
|
3700
3876
|
else hidePairingQR();
|
|
3701
3877
|
}
|
|
3702
|
-
}, 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", {
|
|
3703
3887
|
className: "pbb-qrImage",
|
|
3704
3888
|
src: qrDataURL,
|
|
3705
3889
|
alt: translateWith(props.t, "pair.qrAlt")
|
|
@@ -3888,6 +4072,24 @@ window.__ModuleLoader__.load({
|
|
|
3888
4072
|
if (snap?.status === "ready" && snap.value !== void 0) return snap.value.enabled !== false;
|
|
3889
4073
|
return enabledStore.getSnapshot().enabled;
|
|
3890
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
|
+
};
|
|
3891
4093
|
const lastConfirmedRemoteEnabled = () => {
|
|
3892
4094
|
const snap = scope?.getSnapshot();
|
|
3893
4095
|
if (snap?.status === "ready") return snap.value?.remote?.enabled === true;
|
|
@@ -3914,6 +4116,76 @@ window.__ModuleLoader__.load({
|
|
|
3914
4116
|
console.error("[deeppilot] failed to persist enabled=" + String(value) + ": " + message);
|
|
3915
4117
|
});
|
|
3916
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
|
+
};
|
|
3917
4189
|
const remoteEnabledStore = createSnapshotStore({
|
|
3918
4190
|
status: "loading",
|
|
3919
4191
|
enabled: false
|
|
@@ -4008,6 +4280,8 @@ window.__ModuleLoader__.load({
|
|
|
4008
4280
|
hooks: {
|
|
4009
4281
|
deepPilotReport: store,
|
|
4010
4282
|
deepPilotEnabled: enabledStore,
|
|
4283
|
+
deepPilotLocalEnabled: localEnabledStore,
|
|
4284
|
+
deepPilotLocalPort: localPortStore,
|
|
4011
4285
|
deepPilotRemoteEnabled: remoteEnabledStore,
|
|
4012
4286
|
deepPilotRemoteConnectionLimit: remoteConnectionLimitStore
|
|
4013
4287
|
},
|
|
@@ -4019,6 +4293,8 @@ window.__ModuleLoader__.load({
|
|
|
4019
4293
|
testRelay: testRelayConnection,
|
|
4020
4294
|
testPush: sendTestPush,
|
|
4021
4295
|
setDeepPilotEnabled,
|
|
4296
|
+
setDeepPilotLocalEnabled,
|
|
4297
|
+
setDeepPilotLocalPort,
|
|
4022
4298
|
setDeepPilotRemoteEnabled,
|
|
4023
4299
|
setDeepPilotRemoteConnectionLimit,
|
|
4024
4300
|
t: (key, vars) => t(ctx, key, vars)
|