dsh-deeppilot 0.7.0 → 0.8.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 +70 -4
- package/README.md +22 -15
- package/README.zh-CN.md +15 -11
- package/icon.svg +13 -0
- package/lib/client.js +364 -165
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +118 -97
- package/lib/index.js +209 -77
- package/lib/index.js.map +1 -1
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +22 -17
package/lib/client.js
CHANGED
|
@@ -286,6 +286,35 @@ window.__ModuleLoader__.load({
|
|
|
286
286
|
if (!Array.isArray(value) || value.some((scope) => typeof scope !== "string" || !DEVICE_SCOPES.includes(scope))) reject("scopes");
|
|
287
287
|
return normalizeDeviceScopes(value);
|
|
288
288
|
} };
|
|
289
|
+
const booleanSchema = { parse(value) {
|
|
290
|
+
if (typeof value !== "boolean") reject("boolean");
|
|
291
|
+
return value;
|
|
292
|
+
} };
|
|
293
|
+
/**
|
|
294
|
+
* One strict boundary codec carrying both generation shapes of the supported
|
|
295
|
+
* `0.1.x` host family.
|
|
296
|
+
*
|
|
297
|
+
* Through `0.1.6-alpha.1` a descriptor published the schema value itself and
|
|
298
|
+
* every consumer parsed through `codec.schema.parse(value)`. `0.1.6-alpha.2`
|
|
299
|
+
* switched to a lazily materialized process-realm factory: `TypertRegistry`
|
|
300
|
+
* now rejects a strict codec without `create()`, and the Gateway parses
|
|
301
|
+
* through `codec.create().parse(value)`.
|
|
302
|
+
*
|
|
303
|
+
* Publishing both keys — `create()` returning the same hand-written,
|
|
304
|
+
* dependency-free codec — keeps one package valid across the entire declared
|
|
305
|
+
* peer range instead of forking the wire contract per host generation. The
|
|
306
|
+
* cast is required because each generation's `TypertCodec` declares only its
|
|
307
|
+
* own key, so a fresh object literal carrying both fails excess-property
|
|
308
|
+
* checking against either one.
|
|
309
|
+
*/
|
|
310
|
+
function strictCodec(typeSymbol, schema) {
|
|
311
|
+
return {
|
|
312
|
+
mode: "strict",
|
|
313
|
+
typeSymbol,
|
|
314
|
+
schema,
|
|
315
|
+
create: () => schema
|
|
316
|
+
};
|
|
317
|
+
}
|
|
289
318
|
const REPORT_REMOTE_CONTRIBUTION = {
|
|
290
319
|
package: REPORT_REMOTE_PACKAGE,
|
|
291
320
|
descriptors: [
|
|
@@ -296,11 +325,7 @@ window.__ModuleLoader__.load({
|
|
|
296
325
|
method: "report",
|
|
297
326
|
invocation: { kind: "direct" },
|
|
298
327
|
parameters: [],
|
|
299
|
-
result: {
|
|
300
|
-
mode: "strict",
|
|
301
|
-
typeSymbol: `${REPORT_REMOTE_PACKAGE}#DeepPilotReport`,
|
|
302
|
-
schema: reportSchema
|
|
303
|
-
}
|
|
328
|
+
result: strictCodec(`${REPORT_REMOTE_PACKAGE}#DeepPilotReport`, reportSchema)
|
|
304
329
|
},
|
|
305
330
|
{
|
|
306
331
|
id: `${REPORT_REMOTE_PACKAGE}#${BEGIN_PAIRING_ENDPOINT}`,
|
|
@@ -309,11 +334,7 @@ window.__ModuleLoader__.load({
|
|
|
309
334
|
method: "beginPairing",
|
|
310
335
|
invocation: { kind: "direct" },
|
|
311
336
|
parameters: [],
|
|
312
|
-
result: {
|
|
313
|
-
mode: "strict",
|
|
314
|
-
typeSymbol: `${REPORT_REMOTE_PACKAGE}#PairingGrantSnapshot`,
|
|
315
|
-
schema: pairingGrantSchema
|
|
316
|
-
}
|
|
337
|
+
result: strictCodec(`${REPORT_REMOTE_PACKAGE}#PairingGrantSnapshot`, pairingGrantSchema)
|
|
317
338
|
},
|
|
318
339
|
{
|
|
319
340
|
id: `${REPORT_REMOTE_PACKAGE}#${REVOKE_DEVICE_ENDPOINT}`,
|
|
@@ -325,20 +346,9 @@ window.__ModuleLoader__.load({
|
|
|
325
346
|
name: "deviceId",
|
|
326
347
|
wire: "deviceId",
|
|
327
348
|
source: "json",
|
|
328
|
-
codec: {
|
|
329
|
-
mode: "strict",
|
|
330
|
-
typeSymbol: `${REPORT_REMOTE_PACKAGE}#DeviceId`,
|
|
331
|
-
schema: deviceIdSchema
|
|
332
|
-
}
|
|
349
|
+
codec: strictCodec(`${REPORT_REMOTE_PACKAGE}#DeviceId`, deviceIdSchema)
|
|
333
350
|
}],
|
|
334
|
-
result: {
|
|
335
|
-
mode: "strict",
|
|
336
|
-
typeSymbol: `${REPORT_REMOTE_PACKAGE}#Boolean`,
|
|
337
|
-
schema: { parse(value) {
|
|
338
|
-
if (typeof value !== "boolean") reject("boolean");
|
|
339
|
-
return value;
|
|
340
|
-
} }
|
|
341
|
-
}
|
|
351
|
+
result: strictCodec(`${REPORT_REMOTE_PACKAGE}#Boolean`, booleanSchema)
|
|
342
352
|
},
|
|
343
353
|
{
|
|
344
354
|
id: `${REPORT_REMOTE_PACKAGE}#${SET_DEVICE_SCOPES_ENDPOINT}`,
|
|
@@ -350,26 +360,14 @@ window.__ModuleLoader__.load({
|
|
|
350
360
|
name: "deviceId",
|
|
351
361
|
wire: "deviceId",
|
|
352
362
|
source: "json",
|
|
353
|
-
codec: {
|
|
354
|
-
mode: "strict",
|
|
355
|
-
typeSymbol: `${REPORT_REMOTE_PACKAGE}#DeviceId`,
|
|
356
|
-
schema: deviceIdSchema
|
|
357
|
-
}
|
|
363
|
+
codec: strictCodec(`${REPORT_REMOTE_PACKAGE}#DeviceId`, deviceIdSchema)
|
|
358
364
|
}, {
|
|
359
365
|
name: "scopes",
|
|
360
366
|
wire: "scopes",
|
|
361
367
|
source: "json",
|
|
362
|
-
codec: {
|
|
363
|
-
mode: "strict",
|
|
364
|
-
typeSymbol: `${REPORT_REMOTE_PACKAGE}#DeviceScopes`,
|
|
365
|
-
schema: scopesSchema
|
|
366
|
-
}
|
|
368
|
+
codec: strictCodec(`${REPORT_REMOTE_PACKAGE}#DeviceScopes`, scopesSchema)
|
|
367
369
|
}],
|
|
368
|
-
result: {
|
|
369
|
-
mode: "strict",
|
|
370
|
-
typeSymbol: `${REPORT_REMOTE_PACKAGE}#DeviceScopes`,
|
|
371
|
-
schema: scopesSchema
|
|
372
|
-
}
|
|
370
|
+
result: strictCodec(`${REPORT_REMOTE_PACKAGE}#DeviceScopes`, scopesSchema)
|
|
373
371
|
},
|
|
374
372
|
{
|
|
375
373
|
id: `${REPORT_REMOTE_PACKAGE}#deeppilot/testRelay`,
|
|
@@ -378,11 +376,7 @@ window.__ModuleLoader__.load({
|
|
|
378
376
|
method: "testRelay",
|
|
379
377
|
invocation: { kind: "direct" },
|
|
380
378
|
parameters: [],
|
|
381
|
-
result: {
|
|
382
|
-
mode: "strict",
|
|
383
|
-
typeSymbol: `${REPORT_REMOTE_PACKAGE}#RelayTestResult`,
|
|
384
|
-
schema: relayTestSchema
|
|
385
|
-
}
|
|
379
|
+
result: strictCodec(`${REPORT_REMOTE_PACKAGE}#RelayTestResult`, relayTestSchema)
|
|
386
380
|
},
|
|
387
381
|
{
|
|
388
382
|
id: `${REPORT_REMOTE_PACKAGE}#deeppilot/testPush`,
|
|
@@ -391,11 +385,7 @@ window.__ModuleLoader__.load({
|
|
|
391
385
|
method: "testPush",
|
|
392
386
|
invocation: { kind: "direct" },
|
|
393
387
|
parameters: [],
|
|
394
|
-
result: {
|
|
395
|
-
mode: "strict",
|
|
396
|
-
typeSymbol: `${REPORT_REMOTE_PACKAGE}#PushTestResult`,
|
|
397
|
-
schema: pushTestSchema
|
|
398
|
-
}
|
|
388
|
+
result: strictCodec(`${REPORT_REMOTE_PACKAGE}#PushTestResult`, pushTestSchema)
|
|
399
389
|
}
|
|
400
390
|
]
|
|
401
391
|
};
|
|
@@ -506,14 +496,13 @@ window.__ModuleLoader__.load({
|
|
|
506
496
|
"pair.qrShow": "显示二维码",
|
|
507
497
|
"pair.qrHide": "隐藏二维码",
|
|
508
498
|
"pair.qrGenerating": "生成中…",
|
|
509
|
-
"pair.
|
|
499
|
+
"pair.qrExpired": "配对码已过期,请重新显示二维码",
|
|
510
500
|
"pair.qrFailed": "二维码生成失败:",
|
|
511
|
-
"pair.
|
|
512
|
-
"pair.
|
|
513
|
-
"pair.
|
|
514
|
-
"pair.
|
|
515
|
-
"pair.
|
|
516
|
-
"pair.qrHint": "二维码包含{kind}地址、一次性配对码以及(内网时)证书指纹;配对码 5 分钟后失效,二维码将在 60 秒后自动隐藏。",
|
|
501
|
+
"pair.qrRetry": "重试",
|
|
502
|
+
"pair.infoLabel": "配对信息(复制一次,粘贴到 App 即可)",
|
|
503
|
+
"pair.infoCopyDone": "配对信息已复制",
|
|
504
|
+
"pair.infoCopyFailed": "配对信息复制失败:",
|
|
505
|
+
"pair.qrHint": "二维码与下面的「配对信息」内容相同:包含{kind}地址、一次性配对码、证书指纹(内网时)与主机标识。在局域网与公网之间切换会立即生成对应的新配对信息,配对码 5 分钟后失效。",
|
|
517
506
|
"advanced.summary": "高级信息",
|
|
518
507
|
"advanced.protocolVersion": "协议版本",
|
|
519
508
|
"advanced.serverVersion": "服务器版本",
|
|
@@ -690,14 +679,13 @@ window.__ModuleLoader__.load({
|
|
|
690
679
|
"pair.qrShow": "Show QR code",
|
|
691
680
|
"pair.qrHide": "Hide QR code",
|
|
692
681
|
"pair.qrGenerating": "Generating…",
|
|
693
|
-
"pair.
|
|
682
|
+
"pair.qrExpired": "Pairing code expired — show the QR code again",
|
|
694
683
|
"pair.qrFailed": "QR generation failed: ",
|
|
695
|
-
"pair.
|
|
696
|
-
"pair.
|
|
697
|
-
"pair.
|
|
698
|
-
"pair.
|
|
699
|
-
"pair.
|
|
700
|
-
"pair.qrHint": "The QR contains a {kind} address, a single-use pairing code and, for LAN, the certificate fingerprint. The code expires after 5 minutes; the QR auto-hides after 60 seconds.",
|
|
684
|
+
"pair.qrRetry": "Retry",
|
|
685
|
+
"pair.infoLabel": "Pairing info (copy once, paste it in the app)",
|
|
686
|
+
"pair.infoCopyDone": "Pairing info copied",
|
|
687
|
+
"pair.infoCopyFailed": "Failed to copy pairing info: ",
|
|
688
|
+
"pair.qrHint": "The QR code and the pairing info below carry the same {kind} address, single-use code, certificate fingerprint (for LAN) and host identity. Switching between LAN and public issues a matching new string immediately; the code expires after 5 minutes.",
|
|
701
689
|
"advanced.summary": "Advanced info",
|
|
702
690
|
"advanced.protocolVersion": "Protocol version",
|
|
703
691
|
"advanced.serverVersion": "Server version",
|
|
@@ -948,11 +936,29 @@ window.__ModuleLoader__.load({
|
|
|
948
936
|
".pbb-versionFooter a{color:var(--dsw-alias-state-success-primary,#22a06b);text-decoration:none}",
|
|
949
937
|
".pbb-versionFooter a:hover{text-decoration:underline;text-underline-offset:2px}"
|
|
950
938
|
].join("\n");
|
|
939
|
+
/**
|
|
940
|
+
* Client bundle id, identical to the `window.__ModuleLoader__.load({ id })`
|
|
941
|
+
* identity emitted by tsdown.config.ts. DSH's module system owns style
|
|
942
|
+
* teardown by package id, so this tag must carry the same value.
|
|
943
|
+
*/
|
|
944
|
+
const PLUGIN_ID = "dsh-deeppilot";
|
|
945
|
+
/**
|
|
946
|
+
* Claim this plugin's page stylesheet against DSH's style ownership.
|
|
947
|
+
*
|
|
948
|
+
* `ClientModuleSystem` tags untagged `<style>` elements only during factory
|
|
949
|
+
* materialization; this sheet is injected from `apply()`, which runs after
|
|
950
|
+
* that point, so it would never be attributed to us. Stamping `data-plugin`
|
|
951
|
+
* ourselves lets the module system's `removeOwnedStyles(id)` delete the sheet
|
|
952
|
+
* when the plugin is unloaded at runtime — the Plugin Manager and the client
|
|
953
|
+
* entry reconciler both support live removal from 0.1.6-alpha.2 on, and an
|
|
954
|
+
* orphaned sheet would otherwise keep styling a page we no longer own.
|
|
955
|
+
*/
|
|
951
956
|
function injectCss() {
|
|
952
957
|
if (typeof document === "undefined") return;
|
|
953
958
|
const id = "dsh-deeppilot/page.css";
|
|
954
959
|
if (document.querySelector("style[data-plugin-css=\"" + id + "\"]") !== null) return;
|
|
955
960
|
const tag = document.createElement("style");
|
|
961
|
+
tag.setAttribute("data-plugin", PLUGIN_ID);
|
|
956
962
|
tag.setAttribute("data-plugin-css", id);
|
|
957
963
|
tag.textContent = CSS;
|
|
958
964
|
document.head.appendChild(tag);
|
|
@@ -3361,7 +3367,6 @@ window.__ModuleLoader__.load({
|
|
|
3361
3367
|
return SvgRenderer.render(data, opts);
|
|
3362
3368
|
});
|
|
3363
3369
|
})))(), 1);
|
|
3364
|
-
const PAIRING_QR_TYPE = "deeppilot-pairing";
|
|
3365
3370
|
/** `sha256:` + base64url(SHA-256(SubjectPublicKeyInfo DER)) of the LAN certificate. */
|
|
3366
3371
|
const LAN_TLS_FINGERPRINT_PATTERN = /^sha256:[A-Za-z0-9_-]{43}$/;
|
|
3367
3372
|
function isLanTlsFingerprint(value) {
|
|
@@ -3400,23 +3405,105 @@ window.__ModuleLoader__.load({
|
|
|
3400
3405
|
return false;
|
|
3401
3406
|
}
|
|
3402
3407
|
}
|
|
3403
|
-
/**
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3408
|
+
/**
|
|
3409
|
+
* One copyable string carrying everything the app needs to pair: host, the
|
|
3410
|
+
* single-use code, the LAN certificate pin and the host audience.
|
|
3411
|
+
*
|
|
3412
|
+
* Users copy this once instead of moving three separate values, and the app
|
|
3413
|
+
* accepts it from the paste field, a deep link and (from this version on) the
|
|
3414
|
+
* QR code. Field names are short because the whole string ends up in a QR code
|
|
3415
|
+
* and in a text field: `h` host, `c` code, `f` fingerprint, `a` audience,
|
|
3416
|
+
* `e` expiry in milliseconds.
|
|
3417
|
+
*/
|
|
3418
|
+
function encodePairingLink(host, grant, tlsFingerprint) {
|
|
3419
|
+
assertPairingInput(host, grant, tlsFingerprint);
|
|
3420
|
+
const params = new URLSearchParams();
|
|
3421
|
+
params.set("h", host.trim());
|
|
3422
|
+
params.set("c", grant.code.trim());
|
|
3423
|
+
if (tlsFingerprint !== void 0) params.set("f", tlsFingerprint);
|
|
3424
|
+
params.set("a", grant.audience);
|
|
3425
|
+
params.set("e", String(grant.expiresAt));
|
|
3426
|
+
return `${PAIRING_LINK_PREFIX}?${params.toString()}`;
|
|
3427
|
+
}
|
|
3428
|
+
/** `deeppilot://pair` — the deep-link route the iOS app parses. */
|
|
3429
|
+
const PAIRING_LINK_PREFIX = "deeppilot://pair";
|
|
3430
|
+
function assertPairingInput(host, grant, tlsFingerprint) {
|
|
3431
|
+
if (!validTargetHost(host.trim())) throw new TypeError("pairing QR requires a valid HTTPS/WSS host");
|
|
3407
3432
|
if (grant.code.trim().length < 32 || !Number.isInteger(grant.expiresAt) || grant.expiresAt <= Date.now()) throw new TypeError("pairing grant is invalid or expired");
|
|
3408
3433
|
if (!grant.audience.startsWith("deeppilot:")) throw new TypeError("pairing audience is invalid");
|
|
3409
3434
|
if (tlsFingerprint !== void 0 && !isLanTlsFingerprint(tlsFingerprint)) throw new TypeError("pairing TLS fingerprint is invalid");
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3435
|
+
}
|
|
3436
|
+
//#endregion
|
|
3437
|
+
//#region src/client/pairing-panel.ts
|
|
3438
|
+
const initialPairingPanelState = {
|
|
3439
|
+
target: null,
|
|
3440
|
+
busy: false,
|
|
3441
|
+
grant: null,
|
|
3442
|
+
link: null,
|
|
3443
|
+
qrDataURL: null,
|
|
3444
|
+
message: "",
|
|
3445
|
+
notice: "",
|
|
3446
|
+
requestId: 0
|
|
3447
|
+
};
|
|
3448
|
+
function pairingPanelReduce(state, action) {
|
|
3449
|
+
switch (action.type) {
|
|
3450
|
+
case "show": return {
|
|
3451
|
+
target: action.target,
|
|
3452
|
+
busy: true,
|
|
3453
|
+
grant: null,
|
|
3454
|
+
link: null,
|
|
3455
|
+
qrDataURL: null,
|
|
3456
|
+
message: "",
|
|
3457
|
+
notice: "",
|
|
3458
|
+
requestId: action.requestId
|
|
3459
|
+
};
|
|
3460
|
+
case "issued":
|
|
3461
|
+
if (action.requestId !== state.requestId) return state;
|
|
3462
|
+
return {
|
|
3463
|
+
...state,
|
|
3464
|
+
busy: false,
|
|
3465
|
+
grant: action.grant,
|
|
3466
|
+
link: action.link,
|
|
3467
|
+
qrDataURL: action.qrDataURL
|
|
3468
|
+
};
|
|
3469
|
+
case "failed":
|
|
3470
|
+
if (action.requestId !== state.requestId) return state;
|
|
3471
|
+
return {
|
|
3472
|
+
...state,
|
|
3473
|
+
busy: false,
|
|
3474
|
+
grant: null,
|
|
3475
|
+
link: null,
|
|
3476
|
+
qrDataURL: null,
|
|
3477
|
+
message: action.message
|
|
3478
|
+
};
|
|
3479
|
+
case "notice": return {
|
|
3480
|
+
...state,
|
|
3481
|
+
notice: action.message
|
|
3482
|
+
};
|
|
3483
|
+
case "dismissNotice": return state.notice === "" ? state : {
|
|
3484
|
+
...state,
|
|
3485
|
+
notice: ""
|
|
3486
|
+
};
|
|
3487
|
+
case "close": return {
|
|
3488
|
+
...initialPairingPanelState,
|
|
3489
|
+
requestId: state.requestId + 1,
|
|
3490
|
+
message: action.message ?? ""
|
|
3491
|
+
};
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
function pairingPanelView(state) {
|
|
3495
|
+
const open = state.target !== null;
|
|
3496
|
+
return {
|
|
3497
|
+
open,
|
|
3498
|
+
busy: state.busy,
|
|
3499
|
+
failed: open && !state.busy && state.grant === null,
|
|
3500
|
+
action: state.busy ? "pair.qrGenerating" : state.target === null ? "pair.qrShow" : "pair.qrHide",
|
|
3501
|
+
target: state.target,
|
|
3502
|
+
link: state.link,
|
|
3503
|
+
qrDataURL: state.qrDataURL,
|
|
3504
|
+
message: state.message,
|
|
3505
|
+
notice: state.notice
|
|
3418
3506
|
};
|
|
3419
|
-
return JSON.stringify(payload);
|
|
3420
3507
|
}
|
|
3421
3508
|
function normalizeFunnelConnectionLimit(value) {
|
|
3422
3509
|
return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 16 ? value : 8;
|
|
@@ -3519,16 +3606,13 @@ window.__ModuleLoader__.load({
|
|
|
3519
3606
|
};
|
|
3520
3607
|
/** Slot component: hooks come from the slot renderer, named use<Key>. */
|
|
3521
3608
|
function DeepPilotSettingsPage(props) {
|
|
3522
|
-
const [addressMessage, setAddressMessage] = (0, react.useState)("");
|
|
3523
3609
|
const [remoteLimitDraft, setRemoteLimitDraft] = (0, react.useState)(String(8));
|
|
3524
3610
|
const [remoteLimitMessage, setRemoteLimitMessage] = (0, react.useState)("");
|
|
3525
3611
|
const [localPortDraft, setLocalPortDraft] = (0, react.useState)(String(DEFAULT_LOCAL_PORT));
|
|
3526
3612
|
const [localPortMessage, setLocalPortMessage] = (0, react.useState)("");
|
|
3527
3613
|
const [selectedPairingHost, setSelectedPairingHost] = (0, react.useState)(null);
|
|
3528
|
-
const [
|
|
3529
|
-
const
|
|
3530
|
-
const [qrBusy, setQRBusy] = (0, react.useState)(false);
|
|
3531
|
-
const [qrMessage, setQRMessage] = (0, react.useState)("");
|
|
3614
|
+
const [pairingPanel, dispatchPairingPanel] = (0, react.useReducer)(pairingPanelReduce, initialPairingPanelState);
|
|
3615
|
+
const qrRequestId = (0, react.useRef)(0);
|
|
3532
3616
|
const [relayTestBusy, setRelayTestBusy] = (0, react.useState)(false);
|
|
3533
3617
|
const [relayTestResult, setRelayTestResult] = (0, react.useState)(null);
|
|
3534
3618
|
const [relayTestError, setRelayTestError] = (0, react.useState)("");
|
|
@@ -3575,11 +3659,6 @@ window.__ModuleLoader__.load({
|
|
|
3575
3659
|
setRelayTestBusy(false);
|
|
3576
3660
|
});
|
|
3577
3661
|
};
|
|
3578
|
-
(0, react.useEffect)(() => {
|
|
3579
|
-
if (!addressMessage) return;
|
|
3580
|
-
const timer = globalThis.setTimeout(() => setAddressMessage(""), 2500);
|
|
3581
|
-
return () => globalThis.clearTimeout(timer);
|
|
3582
|
-
}, [addressMessage]);
|
|
3583
3662
|
(0, react.useEffect)(() => {
|
|
3584
3663
|
if (!remoteLimitMessage) return;
|
|
3585
3664
|
const timer = globalThis.setTimeout(() => setRemoteLimitMessage(""), 4e3);
|
|
@@ -3591,19 +3670,21 @@ window.__ModuleLoader__.load({
|
|
|
3591
3670
|
return () => globalThis.clearTimeout(timer);
|
|
3592
3671
|
}, [localPortMessage]);
|
|
3593
3672
|
(0, react.useEffect)(() => {
|
|
3594
|
-
if (
|
|
3673
|
+
if (pairingPanel.grant === null) return;
|
|
3674
|
+
const remaining = Math.max(0, pairingPanel.grant.expiresAt - Date.now());
|
|
3595
3675
|
const timer = globalThis.setTimeout(() => {
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3676
|
+
dispatchPairingPanel({
|
|
3677
|
+
type: "close",
|
|
3678
|
+
message: translateWith(props.t, "pair.qrExpired")
|
|
3679
|
+
});
|
|
3680
|
+
}, remaining);
|
|
3600
3681
|
return () => globalThis.clearTimeout(timer);
|
|
3601
|
-
}, [
|
|
3682
|
+
}, [pairingPanel.grant]);
|
|
3602
3683
|
(0, react.useEffect)(() => {
|
|
3603
|
-
if (!
|
|
3604
|
-
const timer = globalThis.setTimeout(() =>
|
|
3684
|
+
if (!pairingPanel.notice) return;
|
|
3685
|
+
const timer = globalThis.setTimeout(() => dispatchPairingPanel({ type: "dismissNotice" }), 2500);
|
|
3605
3686
|
return () => globalThis.clearTimeout(timer);
|
|
3606
|
-
}, [
|
|
3687
|
+
}, [pairingPanel.notice]);
|
|
3607
3688
|
const diag = [];
|
|
3608
3689
|
let report = null;
|
|
3609
3690
|
let enabled = true;
|
|
@@ -3705,50 +3786,75 @@ window.__ModuleLoader__.load({
|
|
|
3705
3786
|
};
|
|
3706
3787
|
const pairingTargets = report === null ? [] : selectPairingTargets(report.local, report.remote);
|
|
3707
3788
|
const pairingTarget = pairingTargets.find(({ host }) => host === selectedPairingHost) ?? pairingTargets[0] ?? null;
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3789
|
+
const panel = pairingPanelView(pairingPanel);
|
|
3790
|
+
/**
|
|
3791
|
+
* Issue a grant and render the panel for one explicit target.
|
|
3792
|
+
*
|
|
3793
|
+
* The target is a parameter rather than component state on purpose: a click
|
|
3794
|
+
* handler must not depend on whether React has already re-rendered with the
|
|
3795
|
+
* newly selected host. A grant is bound to the host it was issued for, so
|
|
3796
|
+
* switching between LAN and public always mints a fresh one.
|
|
3797
|
+
*/
|
|
3798
|
+
const showPairingQR = (target) => {
|
|
3799
|
+
if (typeof props.beginPairing !== "function") return;
|
|
3800
|
+
const requestId = qrRequestId.current + 1;
|
|
3801
|
+
qrRequestId.current = requestId;
|
|
3802
|
+
dispatchPairingPanel({
|
|
3803
|
+
type: "show",
|
|
3804
|
+
target,
|
|
3805
|
+
requestId
|
|
3718
3806
|
});
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3807
|
+
props.beginPairing().then(async (grant) => {
|
|
3808
|
+
const link = encodePairingLink(target.host, grant, target.tlsFingerprint);
|
|
3809
|
+
return {
|
|
3810
|
+
grant,
|
|
3811
|
+
link,
|
|
3812
|
+
svg: await import_browser.toString(link, {
|
|
3813
|
+
type: "svg",
|
|
3814
|
+
errorCorrectionLevel: "M",
|
|
3815
|
+
margin: 2,
|
|
3816
|
+
width: 512
|
|
3817
|
+
})
|
|
3818
|
+
};
|
|
3819
|
+
}).then(({ grant, link, svg }) => {
|
|
3820
|
+
if (qrRequestId.current !== requestId) return;
|
|
3821
|
+
dispatchPairingPanel({
|
|
3822
|
+
type: "issued",
|
|
3823
|
+
requestId,
|
|
3824
|
+
grant,
|
|
3825
|
+
link,
|
|
3826
|
+
qrDataURL: "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg)
|
|
3827
|
+
});
|
|
3737
3828
|
}, (error) => {
|
|
3738
|
-
|
|
3739
|
-
|
|
3829
|
+
if (qrRequestId.current !== requestId) return;
|
|
3830
|
+
dispatchPairingPanel({
|
|
3831
|
+
type: "failed",
|
|
3832
|
+
requestId,
|
|
3833
|
+
message: translateWith(props.t, "pair.qrFailed") + (error instanceof Error ? error.message : String(error))
|
|
3834
|
+
});
|
|
3835
|
+
});
|
|
3740
3836
|
};
|
|
3741
3837
|
const hidePairingQR = () => {
|
|
3742
|
-
|
|
3743
|
-
|
|
3838
|
+
qrRequestId.current += 1;
|
|
3839
|
+
dispatchPairingPanel({ type: "close" });
|
|
3744
3840
|
};
|
|
3745
|
-
const
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3841
|
+
const copyPairingInfo = () => {
|
|
3842
|
+
const link = pairingPanel.link;
|
|
3843
|
+
if (link === null) return;
|
|
3844
|
+
dispatchPairingPanel({
|
|
3845
|
+
type: "notice",
|
|
3846
|
+
message: ""
|
|
3847
|
+
});
|
|
3848
|
+
writeClipboard(props.t, link).then(() => {
|
|
3849
|
+
dispatchPairingPanel({
|
|
3850
|
+
type: "notice",
|
|
3851
|
+
message: translateWith(props.t, "pair.infoCopyDone")
|
|
3852
|
+
});
|
|
3750
3853
|
}, (error) => {
|
|
3751
|
-
|
|
3854
|
+
dispatchPairingPanel({
|
|
3855
|
+
type: "notice",
|
|
3856
|
+
message: translateWith(props.t, "pair.infoCopyFailed") + (error instanceof Error ? error.message : String(error))
|
|
3857
|
+
});
|
|
3752
3858
|
});
|
|
3753
3859
|
};
|
|
3754
3860
|
const primaryRows = [];
|
|
@@ -3906,36 +4012,36 @@ window.__ModuleLoader__.load({
|
|
|
3906
4012
|
}, 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", {
|
|
3907
4013
|
type: "button",
|
|
3908
4014
|
className: "pbb-action",
|
|
3909
|
-
disabled:
|
|
4015
|
+
disabled: panel.busy || !report.pairingReady,
|
|
3910
4016
|
onClick: () => {
|
|
3911
|
-
if (
|
|
3912
|
-
else
|
|
4017
|
+
if (panel.open) hidePairingQR();
|
|
4018
|
+
else showPairingQR(pairingTarget);
|
|
3913
4019
|
}
|
|
3914
|
-
},
|
|
4020
|
+
}, translateWith(props.t, panel.action)))), 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", {
|
|
3915
4021
|
type: "button",
|
|
3916
4022
|
key: target.host,
|
|
3917
4023
|
className: "pbb-action" + (target.host === pairingTarget?.host ? " pbb-actionSelected" : ""),
|
|
4024
|
+
disabled: panel.busy,
|
|
3918
4025
|
onClick: () => {
|
|
3919
4026
|
setSelectedPairingHost(target.host);
|
|
3920
|
-
|
|
4027
|
+
if (panel.open) showPairingQR(target);
|
|
4028
|
+
else hidePairingQR();
|
|
3921
4029
|
}
|
|
3922
|
-
}, (target.kind === "public" ? translateWith(props.t, "pair.kind.public") : translateWith(props.t, "pair.kind.lan")) + " · " + target.host))),
|
|
3923
|
-
className: "pbb-qrImage",
|
|
3924
|
-
src: qrDataURL,
|
|
3925
|
-
alt: translateWith(props.t, "pair.qrAlt")
|
|
3926
|
-
}), (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", {
|
|
4030
|
+
}, (target.kind === "public" ? translateWith(props.t, "pair.kind.public") : translateWith(props.t, "pair.kind.lan")) + " · " + target.host))), panel.message ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, panel.message) : null, panel.notice ? (0, react.createElement)("p", { className: "pbb-diag" }, panel.notice) : null, panel.failed && pairingTarget !== null ? (0, react.createElement)("div", { className: "pbb-rowAction" }, (0, react.createElement)("button", {
|
|
3927
4031
|
type: "button",
|
|
3928
4032
|
className: "pbb-action",
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
className: "pbb-
|
|
3933
|
-
|
|
3934
|
-
|
|
4033
|
+
disabled: panel.busy || !report.pairingReady,
|
|
4034
|
+
onClick: () => showPairingQR(pairingTarget)
|
|
4035
|
+
}, translateWith(props.t, "pair.qrRetry"))) : null, panel.open ? (0, react.createElement)("div", { className: "pbb-qrPanel" }, panel.qrDataURL === null ? (0, react.createElement)("p", { className: "pbb-diag" }, translateWith(props.t, "pair.qrGenerating")) : (0, react.createElement)("img", {
|
|
4036
|
+
className: "pbb-qrImage",
|
|
4037
|
+
src: panel.qrDataURL,
|
|
4038
|
+
alt: translateWith(props.t, "pair.qrAlt")
|
|
4039
|
+
}), panel.link === null ? null : (0, react.createElement)("div", { className: "pbb-pairCodeBlock" }, (0, react.createElement)("span", { className: "pbb-pairCodeLabel" }, translateWith(props.t, "pair.infoLabel")), (0, react.createElement)("div", { className: "pbb-pairCodeRow" }, (0, react.createElement)("code", { className: "pbb-pairCode" }, panel.link), (0, react.createElement)("button", {
|
|
3935
4040
|
type: "button",
|
|
3936
4041
|
className: "pbb-action",
|
|
3937
|
-
onClick:
|
|
3938
|
-
|
|
4042
|
+
onClick: copyPairingInfo,
|
|
4043
|
+
"data-testid": "pairingInfoCopy"
|
|
4044
|
+
}, translateWith(props.t, "panel.tokenAction.copy")))), (0, react.createElement)("p", { className: "pbb-qrHint" }, translateWith(props.t, "pair.qrHint", { kind: panel.target?.kind === "public" ? translateWith(props.t, "pair.kind.public") : translateWith(props.t, "pair.kind.lan") }))) : null)), (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", {
|
|
3939
4045
|
type: "button",
|
|
3940
4046
|
className: "pbb-action",
|
|
3941
4047
|
disabled: relayTestBusy,
|
|
@@ -3958,6 +4064,74 @@ window.__ModuleLoader__.load({
|
|
|
3958
4064
|
}, translateWith(props.t, "update.badge")) : null), diag.length > 0 ? (0, react.createElement)("p", { className: "pbb-diag" + (failed ? " pbb-diagBad" : "") }, translateWith(props.t, "diag.prefix") + diag.join(" | ")) : null);
|
|
3959
4065
|
}
|
|
3960
4066
|
//#endregion
|
|
4067
|
+
//#region src/client/settings-scope.ts
|
|
4068
|
+
/** Read one optional settings seam without an inject declaration on this entry. */
|
|
4069
|
+
function readSeam(ctx, name) {
|
|
4070
|
+
if (typeof ctx.get === "function") return ctx.get(name) ?? void 0;
|
|
4071
|
+
return ctx[name];
|
|
4072
|
+
}
|
|
4073
|
+
/**
|
|
4074
|
+
* Deep-unwrap live-update references in a snapshot section.
|
|
4075
|
+
*
|
|
4076
|
+
* If a host parses section values through a volatile-capable schema, leaves
|
|
4077
|
+
* can arrive as `{ get() }` references; the settings page compares plain
|
|
4078
|
+
* values, so unwrap defensively at this single boundary regardless of which
|
|
4079
|
+
* schemastery instance produced the snapshot.
|
|
4080
|
+
*/
|
|
4081
|
+
function plainSection(value) {
|
|
4082
|
+
if (value === null || typeof value !== "object") return value;
|
|
4083
|
+
if (typeof value.get === "function") return plainSection(value.get());
|
|
4084
|
+
const plain = {};
|
|
4085
|
+
for (const [key, item] of Object.entries(value)) plain[key] = item !== null && typeof item === "object" ? plainSection(item) : item;
|
|
4086
|
+
return plain;
|
|
4087
|
+
}
|
|
4088
|
+
/**
|
|
4089
|
+
* Adapt one 0.1.7 config form to the settings-scope face.
|
|
4090
|
+
*
|
|
4091
|
+
* The form resolves writes as `Promise<boolean>` (false = the Host rejected or
|
|
4092
|
+
* could not recover the write) where the scope face signals failure by
|
|
4093
|
+
* rejecting — the page's optimistic-write rollback only runs on rejection, so
|
|
4094
|
+
* a `false` must be translated into one.
|
|
4095
|
+
*
|
|
4096
|
+
* @param forms - the 0.1.7 configForms service, if the host provides it.
|
|
4097
|
+
* @param entryId - profile entry id; identical to the legacy namespace here.
|
|
4098
|
+
* @returns the adapted scope, or undefined when the service cannot resolve it.
|
|
4099
|
+
*/
|
|
4100
|
+
function adaptConfigForm(forms, entryId) {
|
|
4101
|
+
const form = forms?.get?.(entryId);
|
|
4102
|
+
if (form === void 0) return void 0;
|
|
4103
|
+
const accept = async (pending) => {
|
|
4104
|
+
if (!await pending) throw new Error("host rejected the settings write");
|
|
4105
|
+
};
|
|
4106
|
+
return {
|
|
4107
|
+
getSnapshot: () => {
|
|
4108
|
+
const snapshot = form.getSnapshot();
|
|
4109
|
+
return {
|
|
4110
|
+
status: snapshot.status,
|
|
4111
|
+
value: plainSection(snapshot.value),
|
|
4112
|
+
writable: snapshot.writable
|
|
4113
|
+
};
|
|
4114
|
+
},
|
|
4115
|
+
subscribe: (listener) => form.subscribe(listener),
|
|
4116
|
+
set: (field, value) => accept(form.set(field, value)),
|
|
4117
|
+
unset: (field) => accept(form.unset(field))
|
|
4118
|
+
};
|
|
4119
|
+
}
|
|
4120
|
+
/**
|
|
4121
|
+
* Bind the settings scope for whichever host generation is running.
|
|
4122
|
+
*
|
|
4123
|
+
* Prefers the legacy settingsScope (present ≤ 0.1.6); falls back to the 0.1.7
|
|
4124
|
+
* config form when its service is already provisioned. Safe to call from any
|
|
4125
|
+
* context — including one that declares neither service, which on a Cordis
|
|
4126
|
+
* context is the difference between `undefined` and a thrown
|
|
4127
|
+
* `cannot get property "settingsScope" without inject`. Callers that apply
|
|
4128
|
+
* before `configForms` is provided re-run this inside the optional
|
|
4129
|
+
* `ctx.inject(['configForms'], …)` callback (see client index.ts).
|
|
4130
|
+
*/
|
|
4131
|
+
function bindSettingsScope(ctx) {
|
|
4132
|
+
return readSeam(ctx, "settingsScope")?.bind({ namespace: "deeppilot" }) ?? adaptConfigForm(readSeam(ctx, "configForms"), "deeppilot");
|
|
4133
|
+
}
|
|
4134
|
+
//#endregion
|
|
3961
4135
|
//#region src/client/index.ts
|
|
3962
4136
|
/** Polls the report remote and owns the page state transitions. */
|
|
3963
4137
|
var ReportController = class {
|
|
@@ -4010,11 +4184,21 @@ window.__ModuleLoader__.load({
|
|
|
4010
4184
|
} catch {}
|
|
4011
4185
|
}
|
|
4012
4186
|
};
|
|
4187
|
+
/**
|
|
4188
|
+
* Services this client entry cannot start without.
|
|
4189
|
+
*
|
|
4190
|
+
* The settings seam is deliberately NOT here: DSH ≤ 0.1.6 provides
|
|
4191
|
+
* `settingsScope`, 0.1.7 removed it in favor of `configForms`, and a name no
|
|
4192
|
+
* running host provides keeps the whole entry pending forever — the 0.1.7 boot
|
|
4193
|
+
* reported exactly `dsh-deeppilot: pending (waiting for service:
|
|
4194
|
+
* settingsScope)` while every other service resolved and the entry never
|
|
4195
|
+
* applied. Both settings seams are optional injections inside `apply`
|
|
4196
|
+
* instead; see the scope binding below.
|
|
4197
|
+
*/
|
|
4013
4198
|
const inject = [
|
|
4014
4199
|
"slots",
|
|
4015
4200
|
"locale",
|
|
4016
|
-
"remote"
|
|
4017
|
-
"settingsScope"
|
|
4201
|
+
"remote"
|
|
4018
4202
|
];
|
|
4019
4203
|
function apply(ctx) {
|
|
4020
4204
|
if (typeof document !== "undefined") injectCss();
|
|
@@ -4090,7 +4274,10 @@ window.__ModuleLoader__.load({
|
|
|
4090
4274
|
status: "loading",
|
|
4091
4275
|
enabled: true
|
|
4092
4276
|
});
|
|
4093
|
-
|
|
4277
|
+
let scope = bindSettingsScope(anyCtx);
|
|
4278
|
+
/** Adopters registered before attachScope runs, so a late-bound scope (0.1.7
|
|
4279
|
+
* configForms provisioned after this plugin applies) still subscribes all. */
|
|
4280
|
+
const scopeAdoptions = [];
|
|
4094
4281
|
const adoptEnabled = () => {
|
|
4095
4282
|
if (scope === void 0) return;
|
|
4096
4283
|
const snap = scope.getSnapshot();
|
|
@@ -4103,10 +4290,7 @@ window.__ModuleLoader__.load({
|
|
|
4103
4290
|
enabled: true
|
|
4104
4291
|
});
|
|
4105
4292
|
};
|
|
4106
|
-
|
|
4107
|
-
scope.subscribe(adoptEnabled);
|
|
4108
|
-
adoptEnabled();
|
|
4109
|
-
}
|
|
4293
|
+
scopeAdoptions.push(adoptEnabled);
|
|
4110
4294
|
const lastConfirmedEnabled = () => {
|
|
4111
4295
|
const snap = scope?.getSnapshot();
|
|
4112
4296
|
if (snap?.status === "ready" && snap.value !== void 0) return snap.value.enabled !== false;
|
|
@@ -4179,10 +4363,7 @@ window.__ModuleLoader__.load({
|
|
|
4179
4363
|
});
|
|
4180
4364
|
}
|
|
4181
4365
|
};
|
|
4182
|
-
|
|
4183
|
-
scope.subscribe(adoptLocal);
|
|
4184
|
-
adoptLocal();
|
|
4185
|
-
}
|
|
4366
|
+
scopeAdoptions.push(adoptLocal);
|
|
4186
4367
|
const setDeepPilotLocalEnabled = (value) => {
|
|
4187
4368
|
const previous = lastConfirmedLocalEnabled();
|
|
4188
4369
|
localEnabledStore.set({
|
|
@@ -4257,9 +4438,27 @@ window.__ModuleLoader__.load({
|
|
|
4257
4438
|
});
|
|
4258
4439
|
}
|
|
4259
4440
|
};
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4441
|
+
scopeAdoptions.push(adoptRemoteEnabled);
|
|
4442
|
+
const attachScope = () => {
|
|
4443
|
+
if (scope === void 0) return;
|
|
4444
|
+
for (const adopt of scopeAdoptions) {
|
|
4445
|
+
scope.subscribe(adopt);
|
|
4446
|
+
adopt();
|
|
4447
|
+
}
|
|
4448
|
+
};
|
|
4449
|
+
attachScope();
|
|
4450
|
+
if (scope === void 0) {
|
|
4451
|
+
const bindScopeFrom = (sub) => {
|
|
4452
|
+
if (scope !== void 0) return;
|
|
4453
|
+
scope = bindSettingsScope(sub);
|
|
4454
|
+
attachScope();
|
|
4455
|
+
};
|
|
4456
|
+
ctx.inject(["settingsScope"], (sub) => {
|
|
4457
|
+
bindScopeFrom(sub);
|
|
4458
|
+
});
|
|
4459
|
+
ctx.inject(["configForms"], (sub) => {
|
|
4460
|
+
bindScopeFrom(sub);
|
|
4461
|
+
});
|
|
4263
4462
|
}
|
|
4264
4463
|
const setDeepPilotRemoteEnabled = (value) => {
|
|
4265
4464
|
const previous = lastConfirmedRemoteEnabled();
|