dsh-mobile 0.6.15 → 0.6.16
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/CHANGELOG.md +16 -0
- package/CONTRIBUTORS.md +12 -2
- package/README.en.md +8 -6
- package/README.md +8 -6
- package/SECURITY.md +1 -0
- package/apps/mobile/README.md +29 -5
- package/apps/mobile/README.zh-CN.md +29 -5
- package/apps/mobile/release.json +3 -3
- package/docs/ATTACH_EXISTING_FRPS.en.md +8 -0
- package/docs/ATTACH_EXISTING_FRPS.md +8 -0
- package/lib/client.js +840 -59
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +4 -0
- package/lib/index.mjs +45 -10
- package/lib/index.mjs.map +1 -1
- package/package.json +7 -2
- package/packages/question-fixes/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -6,6 +6,44 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
8
|
//#region src/client-messages.ts
|
|
9
|
+
const frpFormMessages = {
|
|
10
|
+
en: {
|
|
11
|
+
frpServerAddressInvalid: "Enter the frps IP address or full hostname only, without https://, a port, or a path.",
|
|
12
|
+
frpServerPortInvalid: "Enter the frps control port as a whole number from 1 to 65535.",
|
|
13
|
+
frpTokenInvalid: "Enter a shared token of 16–512 characters without spaces or line breaks. Re-enter it when changing saved connection settings.",
|
|
14
|
+
frpPublicOriginInvalid: "Enter an HTTPS address such as https://phone.example.com or https://1.2.3.4, without a path, credentials, or a non-standard port. An IPv4 address must be publicly routable.",
|
|
15
|
+
frpPublicOriginHint: "Include https://. Do not include a path or non-standard port. For self-signed ingress, enter https://PUBLIC_IP here and set its port separately below.",
|
|
16
|
+
frpModeInvalid: "Choose either managed deployment or an existing frps.",
|
|
17
|
+
frpVhostPortInvalid: "Enter the real frps vhostHTTPPort as a whole number from 1 to 65535.",
|
|
18
|
+
frpPublicPortInvalid: "Enter the public ingress port as a whole number from 1 to 65535, excluding 3080, 3443, and 3444. Keep this port out of the HTTPS address above.",
|
|
19
|
+
frpActionBusy: "Another remote operation is in progress. Wait for it to finish before saving.",
|
|
20
|
+
frpInstallBeforeConnect: "Install the local frpc component in step 3 before saving and verifying the connection."
|
|
21
|
+
},
|
|
22
|
+
it: {
|
|
23
|
+
frpServerAddressInvalid: "Inserisci solo l’indirizzo IP o il nome host completo di frps, senza https://, porta o percorso.",
|
|
24
|
+
frpServerPortInvalid: "Inserisci la porta di controllo frps come numero intero da 1 a 65535.",
|
|
25
|
+
frpTokenInvalid: "Inserisci un token condiviso di 16–512 caratteri senza spazi o ritorni a capo. Reinseriscilo quando modifichi una connessione salvata.",
|
|
26
|
+
frpPublicOriginInvalid: "Inserisci un indirizzo HTTPS come https://phone.example.com o https://1.2.3.4, senza percorso, credenziali o porta non standard. Un indirizzo IPv4 deve essere pubblico e instradabile.",
|
|
27
|
+
frpPublicOriginHint: "Includi https://, senza percorso o porta non standard. Per l’ingresso autofirmato usa https://IP_PUBBLICO e indica la porta separatamente qui sotto.",
|
|
28
|
+
frpModeInvalid: "Scegli la distribuzione gestita oppure un frps esistente.",
|
|
29
|
+
frpVhostPortInvalid: "Inserisci il vero vhostHTTPPort di frps come numero intero da 1 a 65535.",
|
|
30
|
+
frpPublicPortInvalid: "Inserisci una porta pubblica intera da 1 a 65535, esclusi 3080, 3443 e 3444. Non aggiungerla all’indirizzo HTTPS sopra.",
|
|
31
|
+
frpActionBusy: "È in corso un’altra operazione remota. Attendi prima di salvare.",
|
|
32
|
+
frpInstallBeforeConnect: "Installa il componente frpc locale al punto 3 prima di salvare e verificare la connessione."
|
|
33
|
+
},
|
|
34
|
+
zh: {
|
|
35
|
+
frpServerAddressInvalid: "请只填写 frps 的 IP 或完整域名,不要包含 https://、端口或路径。",
|
|
36
|
+
frpServerPortInvalid: "请填写 1–65535 的整数,作为 frps 控制端口。",
|
|
37
|
+
frpTokenInvalid: "共享 Token 必须为 16–512 个字符,不能包含空格或换行。修改已保存的连接信息时,请重新输入 Token。",
|
|
38
|
+
frpPublicOriginInvalid: "请填写 https://phone.example.com 或 https://1.2.3.4 形式的 HTTPS 地址,不含路径、账号密码或非标准端口;IPv4 必须为可路由的公网地址。",
|
|
39
|
+
frpPublicOriginHint: "必须包含 https://,不要带路径或非标准端口。自签入口在此填 https://公网IP,端口在下方单独填写。",
|
|
40
|
+
frpModeInvalid: "请选择“由插件部署 frps”或“接入我已有的 frps”。",
|
|
41
|
+
frpVhostPortInvalid: "请填写你 frps 真实的 vhostHTTPPort,必须为 1–65535 的整数。",
|
|
42
|
+
frpPublicPortInvalid: "公网入口端口必须为 1–65535 的整数,不能使用 3080、3443、3444;不要把它加到上方 HTTPS 地址中。",
|
|
43
|
+
frpActionBusy: "另一项远程操作正在进行,请等待完成后再保存。",
|
|
44
|
+
frpInstallBeforeConnect: "请先在第 3 步安装本机 frpc 组件,再保存并验证连接。"
|
|
45
|
+
}
|
|
46
|
+
};
|
|
9
47
|
const storageMessages = {
|
|
10
48
|
en: {
|
|
11
49
|
funnelStateInvalid: "The private Tailscale state folder cannot be accessed safely. Check its permissions or directory links; reinstalling the component is not required.",
|
|
@@ -61,6 +99,7 @@ window.__ModuleLoader__.load({
|
|
|
61
99
|
};
|
|
62
100
|
const MOBILE_CONTROL_MESSAGES = {
|
|
63
101
|
en: {
|
|
102
|
+
...frpFormMessages.en,
|
|
64
103
|
mobileAccess: "Mobile access",
|
|
65
104
|
collapseMobileAccess: "Collapse Mobile access",
|
|
66
105
|
downloadAndroid: "Download latest Android app",
|
|
@@ -229,6 +268,7 @@ window.__ModuleLoader__.load({
|
|
|
229
268
|
wsPathsAdvancedHint: "Manual entries are rarely necessary: use the buttons above. Only fill this in when you know the exact path."
|
|
230
269
|
},
|
|
231
270
|
it: {
|
|
271
|
+
...frpFormMessages.it,
|
|
232
272
|
mobileAccess: "Accesso mobile",
|
|
233
273
|
collapseMobileAccess: "Riduci Accesso mobile",
|
|
234
274
|
downloadAndroid: "Scarica l’ultima app Android",
|
|
@@ -396,7 +436,7 @@ window.__ModuleLoader__.load({
|
|
|
396
436
|
wsPathsAnnounce: "{n} connessioni di plugin in attesa di approvazione",
|
|
397
437
|
wsPathsAdvancedHint: "Le voci manuali servono raramente: usa i pulsanti qui sopra. Compila solo se conosci il percorso esatto."
|
|
398
438
|
},
|
|
399
|
-
zh: {}
|
|
439
|
+
zh: { ...frpFormMessages.zh }
|
|
400
440
|
};
|
|
401
441
|
for (const locale of [
|
|
402
442
|
"en",
|
|
@@ -2494,6 +2534,85 @@ window.__ModuleLoader__.load({
|
|
|
2494
2534
|
return createFrpAttachTemplateParts(settings, options).text;
|
|
2495
2535
|
}
|
|
2496
2536
|
//#endregion
|
|
2537
|
+
//#region src/frp-form-validation.ts
|
|
2538
|
+
/** Field ownership for errors returned by the Host parser. */
|
|
2539
|
+
const FRP_FORM_ERRORS = {
|
|
2540
|
+
frp_server_address_invalid: {
|
|
2541
|
+
field: "serverAddress",
|
|
2542
|
+
code: "frp_server_address_invalid",
|
|
2543
|
+
messageKey: "frpServerAddressInvalid"
|
|
2544
|
+
},
|
|
2545
|
+
frp_server_port_invalid: {
|
|
2546
|
+
field: "serverPort",
|
|
2547
|
+
code: "frp_server_port_invalid",
|
|
2548
|
+
messageKey: "frpServerPortInvalid"
|
|
2549
|
+
},
|
|
2550
|
+
frp_token_invalid: {
|
|
2551
|
+
field: "token",
|
|
2552
|
+
code: "frp_token_invalid",
|
|
2553
|
+
messageKey: "frpTokenInvalid"
|
|
2554
|
+
},
|
|
2555
|
+
frp_public_origin_invalid: {
|
|
2556
|
+
field: "publicOrigin",
|
|
2557
|
+
code: "frp_public_origin_invalid",
|
|
2558
|
+
messageKey: "frpPublicOriginInvalid"
|
|
2559
|
+
},
|
|
2560
|
+
frp_attach_mode_requires_vhost_port: {
|
|
2561
|
+
field: "vhostHttpPort",
|
|
2562
|
+
code: "frp_attach_mode_requires_vhost_port",
|
|
2563
|
+
messageKey: "frpAttachModeRequiresVhostPort"
|
|
2564
|
+
},
|
|
2565
|
+
frp_entry_tls_invalid: {
|
|
2566
|
+
field: "entryTls",
|
|
2567
|
+
code: "frp_entry_tls_invalid",
|
|
2568
|
+
messageKey: "frpEntryTlsInvalid"
|
|
2569
|
+
},
|
|
2570
|
+
frp_self_signed_requires_public_ipv4: {
|
|
2571
|
+
field: "publicOrigin",
|
|
2572
|
+
code: "frp_self_signed_requires_public_ipv4",
|
|
2573
|
+
messageKey: "frpSelfSignedRequiresPublicIpv4"
|
|
2574
|
+
}
|
|
2575
|
+
};
|
|
2576
|
+
/** Collect field errors using the Host validators; token omission is permitted only for saved configuration. */
|
|
2577
|
+
function validateFrpForm(form, allowSavedToken = false) {
|
|
2578
|
+
const issues = [];
|
|
2579
|
+
const check = (field, messageKey, validate) => {
|
|
2580
|
+
try {
|
|
2581
|
+
validate();
|
|
2582
|
+
} catch (error) {
|
|
2583
|
+
issues.push({
|
|
2584
|
+
field,
|
|
2585
|
+
messageKey,
|
|
2586
|
+
code: error instanceof Error ? error.message : "frp_settings_invalid"
|
|
2587
|
+
});
|
|
2588
|
+
}
|
|
2589
|
+
};
|
|
2590
|
+
check("serverAddress", "frpServerAddressInvalid", () => validateFrpServerAddress(form.serverAddress));
|
|
2591
|
+
check("serverPort", "frpServerPortInvalid", () => validateFrpServerPort(form.serverPort));
|
|
2592
|
+
if (form.token !== "" || !allowSavedToken) check("token", "frpTokenInvalid", () => validateFrpToken(form.token));
|
|
2593
|
+
check("publicOrigin", "frpPublicOriginInvalid", () => validateFrpPublicOrigin(form.publicOrigin));
|
|
2594
|
+
check("mode", "frpModeInvalid", () => validateFrpMode(form.mode));
|
|
2595
|
+
check("entryTls", "frpEntryTlsInvalid", () => validateFrpEntryTls(form.entryTls));
|
|
2596
|
+
if (form.entryTls === "self-signed" && form.mode !== "attach") issues.push(FRP_FORM_ERRORS.frp_entry_tls_invalid);
|
|
2597
|
+
if (form.entryTls !== "self-signed" && form.vhostHttpPort !== void 0) check("vhostHttpPort", "frpVhostPortInvalid", () => validateFrpVhostHttpPort(form.vhostHttpPort));
|
|
2598
|
+
else if (form.entryTls !== "self-signed" && form.mode === "attach") issues.push(FRP_FORM_ERRORS.frp_attach_mode_requires_vhost_port);
|
|
2599
|
+
if (form.publicPort !== void 0 || form.entryTls === "self-signed") check("publicPort", "frpPublicPortInvalid", () => validateFrpPublicPort(form.publicPort));
|
|
2600
|
+
if (issues.length === 0) try {
|
|
2601
|
+
parseFrpSettings({
|
|
2602
|
+
...form,
|
|
2603
|
+
token: form.token === "" && allowSavedToken ? "0123456789abcdef" : form.token
|
|
2604
|
+
});
|
|
2605
|
+
} catch (error) {
|
|
2606
|
+
const code = error instanceof Error ? error.message : "frp_settings_invalid";
|
|
2607
|
+
issues.push(FRP_FORM_ERRORS[code] ?? {
|
|
2608
|
+
field: "mode",
|
|
2609
|
+
code,
|
|
2610
|
+
messageKey: "frpInputInvalid"
|
|
2611
|
+
});
|
|
2612
|
+
}
|
|
2613
|
+
return issues;
|
|
2614
|
+
}
|
|
2615
|
+
//#endregion
|
|
2497
2616
|
//#region node_modules/@noble/hashes/esm/utils.js
|
|
2498
2617
|
/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
2499
2618
|
/** Checks if something is Uint8Array. Be careful: nodejs Buffer will return true. */
|
|
@@ -4362,14 +4481,27 @@ window.__ModuleLoader__.load({
|
|
|
4362
4481
|
icon.append(body, lens);
|
|
4363
4482
|
return icon;
|
|
4364
4483
|
};
|
|
4365
|
-
const createMediaAction = (text) => {
|
|
4484
|
+
const createMediaAction = (text, kind = "camera") => {
|
|
4366
4485
|
const button = document.createElement("button");
|
|
4367
4486
|
button.type = "button";
|
|
4368
4487
|
button.className = "dsh-mobile-media-action";
|
|
4369
4488
|
button.lang = language;
|
|
4370
|
-
button.dataset.dshMobileMediaAction =
|
|
4489
|
+
button.dataset.dshMobileMediaAction = kind;
|
|
4490
|
+
button.setAttribute("role", "option");
|
|
4491
|
+
button.setAttribute("aria-selected", "false");
|
|
4492
|
+
button.setAttribute("aria-label", text);
|
|
4371
4493
|
const icon = document.createElement("span");
|
|
4372
|
-
|
|
4494
|
+
const graphic = mediaIcon();
|
|
4495
|
+
if (kind === "gallery") {
|
|
4496
|
+
graphic.replaceChildren();
|
|
4497
|
+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
4498
|
+
path.setAttribute("d", "M2 2h12v12H2z M2 11l4-4 3 3 2-2 3 3 M10 5h.01");
|
|
4499
|
+
path.setAttribute("stroke", "currentColor");
|
|
4500
|
+
path.setAttribute("stroke-width", "1.3");
|
|
4501
|
+
path.setAttribute("stroke-linejoin", "round");
|
|
4502
|
+
graphic.append(path);
|
|
4503
|
+
}
|
|
4504
|
+
icon.append(graphic);
|
|
4373
4505
|
button.append(icon);
|
|
4374
4506
|
const caption = document.createElement("span");
|
|
4375
4507
|
caption.textContent = text;
|
|
@@ -4462,6 +4594,9 @@ window.__ModuleLoader__.load({
|
|
|
4462
4594
|
document.body.append(mediaToast);
|
|
4463
4595
|
const cameraButton = createMediaAction(label("Scatta foto", "Take photo", "拍照"));
|
|
4464
4596
|
cameraButton.disabled = true;
|
|
4597
|
+
const galleryButton = createMediaAction(label("Galleria", "Gallery", "相册"), "gallery");
|
|
4598
|
+
galleryButton.disabled = true;
|
|
4599
|
+
let galleryBusy = false;
|
|
4465
4600
|
let branchToastTimer = 0;
|
|
4466
4601
|
let mediaToastTimer = 0;
|
|
4467
4602
|
const showMediaToast = (message) => {
|
|
@@ -4549,40 +4684,48 @@ window.__ModuleLoader__.load({
|
|
|
4549
4684
|
const sectionTitle = Array.from(commandMenu.querySelectorAll("[role=\"listbox\"] [role=\"presentation\"]")).find((candidate) => !candidate.hasAttribute("data-source") && candidate.textContent?.trim() === addGroupLabel);
|
|
4550
4685
|
if (sectionTitle === void 0) {
|
|
4551
4686
|
cameraButton.remove();
|
|
4687
|
+
galleryButton.remove();
|
|
4552
4688
|
return false;
|
|
4553
4689
|
}
|
|
4554
|
-
cameraButton.className = "dsh-mobile-media-action";
|
|
4555
4690
|
const nativeItem = (() => {
|
|
4556
4691
|
let sibling = sectionTitle.nextElementSibling;
|
|
4557
4692
|
while (sibling instanceof HTMLElement && sibling.getAttribute("role") !== "presentation") {
|
|
4558
|
-
if (sibling.matches("button[role=\"option\"]:not([aria-selected=\"true\"])")) return sibling;
|
|
4693
|
+
if (!sibling.hasAttribute("data-dsh-mobile-media-action") && sibling.matches("button[role=\"option\"]:not([aria-selected=\"true\"])")) return sibling;
|
|
4559
4694
|
sibling = sibling.nextElementSibling;
|
|
4560
4695
|
}
|
|
4561
4696
|
})();
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
const
|
|
4566
|
-
const
|
|
4567
|
-
const
|
|
4568
|
-
|
|
4569
|
-
if (
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
cameraButton.setAttribute("aria-selected", "false");
|
|
4573
|
-
cameraButton.setAttribute("aria-label", label("Scatta foto", "Take photo", "拍照"));
|
|
4697
|
+
for (const button of [cameraButton, galleryButton]) {
|
|
4698
|
+
const className = nativeItem?.className ?? "dsh-mobile-media-action";
|
|
4699
|
+
if (button.className !== className) button.className = className;
|
|
4700
|
+
const icon = button.firstElementChild;
|
|
4701
|
+
const caption = button.lastElementChild;
|
|
4702
|
+
const nativeIcon = nativeItem === void 0 ? void 0 : firstByClassSuffix(nativeItem, "_itemIcon");
|
|
4703
|
+
const nativeLabel = nativeItem === void 0 ? void 0 : firstByClassSuffix(nativeItem, "_itemName");
|
|
4704
|
+
if (icon instanceof HTMLElement && nativeIcon !== void 0 && icon.className !== nativeIcon.className) icon.className = nativeIcon.className;
|
|
4705
|
+
if (caption instanceof HTMLElement && nativeLabel !== void 0 && caption.className !== nativeLabel.className) caption.className = nativeLabel.className;
|
|
4706
|
+
}
|
|
4574
4707
|
let lastItem = sectionTitle;
|
|
4575
4708
|
let sibling = sectionTitle.nextElementSibling;
|
|
4576
4709
|
while (sibling instanceof HTMLElement && sibling.getAttribute("role") !== "presentation") {
|
|
4577
|
-
if (sibling !== cameraButton) lastItem = sibling;
|
|
4710
|
+
if (sibling !== cameraButton && sibling !== galleryButton) lastItem = sibling;
|
|
4578
4711
|
sibling = sibling.nextElementSibling;
|
|
4579
4712
|
}
|
|
4580
|
-
if (
|
|
4713
|
+
if (boundComposer?.closest(".dshm-main") !== null && boundComposer !== null) {
|
|
4714
|
+
if (galleryButton.parentElement !== sectionTitle.parentElement || galleryButton.previousElementSibling !== sectionTitle) sectionTitle.after(galleryButton);
|
|
4715
|
+
if (cameraButton.parentElement !== galleryButton.parentElement || cameraButton.previousElementSibling !== galleryButton) galleryButton.after(cameraButton);
|
|
4716
|
+
} else {
|
|
4717
|
+
galleryButton.remove();
|
|
4718
|
+
if (cameraButton.parentElement !== sectionTitle.parentElement || cameraButton.previousElementSibling !== lastItem) lastItem.after(cameraButton);
|
|
4719
|
+
}
|
|
4581
4720
|
return true;
|
|
4582
4721
|
};
|
|
4583
4722
|
const setCameraActionDisabled = (disabled, title) => {
|
|
4584
4723
|
if (cameraButton.disabled !== disabled) cameraButton.disabled = disabled;
|
|
4585
4724
|
if (cameraButton.title !== title) cameraButton.title = title;
|
|
4725
|
+
const galleryDisabled = disabled || galleryBusy;
|
|
4726
|
+
if (galleryButton.disabled !== galleryDisabled) galleryButton.disabled = galleryDisabled;
|
|
4727
|
+
const galleryTitle = disabled ? title : label("Scegli immagini da allegare", "Choose images to attach", "选择图片并附加到当前消息");
|
|
4728
|
+
if (galleryButton.title !== galleryTitle) galleryButton.title = galleryTitle;
|
|
4586
4729
|
};
|
|
4587
4730
|
const quietMediaPointer = (event) => {
|
|
4588
4731
|
event.preventDefault();
|
|
@@ -4614,6 +4757,34 @@ window.__ModuleLoader__.load({
|
|
|
4614
4757
|
showMediaToast(code === "payload_too_large" ? label("La foto supera il limite di 8 MiB", "The photo exceeds the 8 MiB limit", "照片超过 8 MiB 限制") : label("Impossibile aggiungere la foto", "Could not attach the photo", "无法附加照片"));
|
|
4615
4758
|
});
|
|
4616
4759
|
};
|
|
4760
|
+
const chooseGallery = () => {
|
|
4761
|
+
if (disposed || galleryBusy || galleryButton.disabled || boundComposer?.closest(".dshm-main") == null) return;
|
|
4762
|
+
if (!canAcceptComposerDrop()) {
|
|
4763
|
+
setCameraActionDisabled(true, label("Allegati immagine non disponibili", "Image attachments are unavailable", "图片附件不可用"));
|
|
4764
|
+
dismissCommandMenu();
|
|
4765
|
+
return;
|
|
4766
|
+
}
|
|
4767
|
+
const context = mediaRequestContext();
|
|
4768
|
+
galleryBusy = true;
|
|
4769
|
+
galleryButton.disabled = true;
|
|
4770
|
+
galleryButton.setAttribute("aria-busy", "true");
|
|
4771
|
+
dismissCommandMenu();
|
|
4772
|
+
pickBrowserFiles({
|
|
4773
|
+
accept: "image/*",
|
|
4774
|
+
multiple: true,
|
|
4775
|
+
signal: mediaPickerAbortController.signal
|
|
4776
|
+
}).then((files) => {
|
|
4777
|
+
if (files !== void 0) deliverImages(files, context);
|
|
4778
|
+
}).catch((_error) => {
|
|
4779
|
+
if (mediaRequestIsCurrent(context)) showMediaToast(label("Impossibile aggiungere le immagini", "Could not attach the images", "无法附加图片"));
|
|
4780
|
+
}).finally(() => {
|
|
4781
|
+
galleryBusy = false;
|
|
4782
|
+
galleryButton.removeAttribute("aria-busy");
|
|
4783
|
+
if (!disposed) schedule();
|
|
4784
|
+
});
|
|
4785
|
+
};
|
|
4786
|
+
galleryButton.addEventListener("pointerdown", quietMediaPointer);
|
|
4787
|
+
galleryButton.addEventListener("click", chooseGallery);
|
|
4617
4788
|
cameraButton.addEventListener("pointerdown", quietMediaPointer);
|
|
4618
4789
|
cameraButton.addEventListener("click", takePhoto);
|
|
4619
4790
|
const showBranchToast = () => {
|
|
@@ -4878,6 +5049,7 @@ window.__ModuleLoader__.load({
|
|
|
4878
5049
|
syncMediaBinding(null);
|
|
4879
5050
|
setCameraActionDisabled(true, label("Apri prima una sessione", "Open a session first", "请先打开会话"));
|
|
4880
5051
|
cameraButton.remove();
|
|
5052
|
+
galleryButton.remove();
|
|
4881
5053
|
return;
|
|
4882
5054
|
}
|
|
4883
5055
|
if (center !== void 0) {
|
|
@@ -4917,6 +5089,7 @@ window.__ModuleLoader__.load({
|
|
|
4917
5089
|
syncMediaBinding(null);
|
|
4918
5090
|
setCameraActionDisabled(true, label("Apri prima una sessione", "Open a session first", "请先打开会话"));
|
|
4919
5091
|
cameraButton.remove();
|
|
5092
|
+
galleryButton.remove();
|
|
4920
5093
|
}
|
|
4921
5094
|
if (composerRow instanceof HTMLElement) {
|
|
4922
5095
|
composerRow.dataset.dshMobileComposerRow = "true";
|
|
@@ -4936,7 +5109,10 @@ window.__ModuleLoader__.load({
|
|
|
4936
5109
|
setCameraActionDisabled(mediaDisabled, mediaTitle);
|
|
4937
5110
|
const commandMenu = composerCard?.querySelector("[data-trigger-menu]") ?? null;
|
|
4938
5111
|
if (commandMenu !== null) placeCameraAction(commandMenu);
|
|
4939
|
-
else
|
|
5112
|
+
else {
|
|
5113
|
+
cameraButton.remove();
|
|
5114
|
+
galleryButton.remove();
|
|
5115
|
+
}
|
|
4940
5116
|
} else syncComposerEnterNewline(null);
|
|
4941
5117
|
if (composerTrailing !== void 0 && composerTrailing !== composerTools) {
|
|
4942
5118
|
composerTrailing.dataset.dshMobileComposerTrailing = "true";
|
|
@@ -5052,6 +5228,9 @@ window.__ModuleLoader__.load({
|
|
|
5052
5228
|
applyStrip(0);
|
|
5053
5229
|
cameraButton.removeEventListener("pointerdown", quietMediaPointer);
|
|
5054
5230
|
cameraButton.removeEventListener("click", takePhoto);
|
|
5231
|
+
galleryButton.removeEventListener("pointerdown", quietMediaPointer);
|
|
5232
|
+
galleryButton.removeEventListener("click", chooseGallery);
|
|
5233
|
+
galleryButton.remove();
|
|
5055
5234
|
if (branchToastTimer !== 0) window.clearTimeout(branchToastTimer);
|
|
5056
5235
|
if (mediaToastTimer !== 0) window.clearTimeout(mediaToastTimer);
|
|
5057
5236
|
branchToast.remove();
|
|
@@ -5184,6 +5363,363 @@ window.__ModuleLoader__.load({
|
|
|
5184
5363
|
release();
|
|
5185
5364
|
};
|
|
5186
5365
|
}
|
|
5366
|
+
const LEASE_MS = 3e4;
|
|
5367
|
+
function transferableText(text) {
|
|
5368
|
+
return !/[\uE100-\uE11D\uFFFC]/u.test(text) && new TextEncoder().encode(text).length <= 262144;
|
|
5369
|
+
}
|
|
5370
|
+
function snapshotShape(value) {
|
|
5371
|
+
if (value === null || typeof value !== "object") return false;
|
|
5372
|
+
const s = value;
|
|
5373
|
+
return s.version === 1 && typeof s.token === "string" && /^[a-f0-9]{48}$/u.test(s.token) && typeof s.href === "string" && s.href.length <= 8192 && typeof s.sessionId === "string" && s.sessionId.length > 0 && s.sessionId.length <= 1024 && typeof s.text === "string" && transferableText(s.text) && Number.isSafeInteger(s.revision) && (s.revision ?? -1) >= 0;
|
|
5374
|
+
}
|
|
5375
|
+
function plain(s) {
|
|
5376
|
+
return typeof s.draft === "string" && Number.isSafeInteger(s.draftRev) && s.draftRev >= 0 && s.phase === "plain" && s.claim === void 0 && Array.isArray(s.attachmentIds) && s.attachmentIds.length === 0 && Array.isArray(s.occurrences) && s.occurrences.length === 0 && Array.isArray(s.queue) && s.queue.length === 0;
|
|
5377
|
+
}
|
|
5378
|
+
/** Public state subscriptions are kept for the lifetime of each observed composer,
|
|
5379
|
+
* not just the lease: attachment/submit uncertainty stays sticky until reload. */
|
|
5380
|
+
function createDraftTransfer(view, page) {
|
|
5381
|
+
const observed = /* @__PURE__ */ new Map();
|
|
5382
|
+
const subscriptions = /* @__PURE__ */ new Set();
|
|
5383
|
+
let epoch = 0, risky = false, disposed = false, composing = false;
|
|
5384
|
+
let lease;
|
|
5385
|
+
let timer;
|
|
5386
|
+
let imported;
|
|
5387
|
+
let importTimer;
|
|
5388
|
+
let importClosed = false;
|
|
5389
|
+
const clearImport = () => {
|
|
5390
|
+
if (imported !== void 0) importClosed = true;
|
|
5391
|
+
imported = void 0;
|
|
5392
|
+
if (importTimer !== void 0) view.clearTimeout(importTimer);
|
|
5393
|
+
importTimer = void 0;
|
|
5394
|
+
};
|
|
5395
|
+
const invalidate = () => {
|
|
5396
|
+
epoch += 1;
|
|
5397
|
+
};
|
|
5398
|
+
const clearLease = () => {
|
|
5399
|
+
lease = void 0;
|
|
5400
|
+
if (timer !== void 0) view.clearTimeout(timer);
|
|
5401
|
+
timer = void 0;
|
|
5402
|
+
};
|
|
5403
|
+
const risk = () => {
|
|
5404
|
+
risky = true;
|
|
5405
|
+
invalidate();
|
|
5406
|
+
};
|
|
5407
|
+
const onActivity = (event) => {
|
|
5408
|
+
const inComposer = Boolean(event.target?.closest?.("[data-composer-card]"));
|
|
5409
|
+
if ([
|
|
5410
|
+
"input",
|
|
5411
|
+
"beforeinput",
|
|
5412
|
+
"paste",
|
|
5413
|
+
"compositionstart",
|
|
5414
|
+
"compositionend"
|
|
5415
|
+
].includes(event.type) && !inComposer) {
|
|
5416
|
+
risk();
|
|
5417
|
+
return;
|
|
5418
|
+
}
|
|
5419
|
+
if (event.type === "compositionstart" || event.type === "compositionend") {
|
|
5420
|
+
composing = event.type === "compositionstart";
|
|
5421
|
+
invalidate();
|
|
5422
|
+
return;
|
|
5423
|
+
}
|
|
5424
|
+
if (event.type === "change" && event.target?.type !== "file") return;
|
|
5425
|
+
if (event.type === "paste") {
|
|
5426
|
+
const clipboard = event.clipboardData;
|
|
5427
|
+
if (clipboard !== null && clipboard !== void 0 && clipboard.files.length === 0 && clipboard.types.length === 1 && clipboard.types[0] === "text/plain" && Array.from(clipboard.items).every((item) => item.kind === "string" && item.type === "text/plain")) {
|
|
5428
|
+
invalidate();
|
|
5429
|
+
return;
|
|
5430
|
+
}
|
|
5431
|
+
}
|
|
5432
|
+
if (event.type === "input" || event.type === "beforeinput") invalidate();
|
|
5433
|
+
else risk();
|
|
5434
|
+
};
|
|
5435
|
+
const onClick = (event) => {
|
|
5436
|
+
if (event.target?.closest?.("[data-composer-card] button, [data-dsh-mobile-media-action], [data-voice-activity]")) risk();
|
|
5437
|
+
};
|
|
5438
|
+
const checkVoice = () => {
|
|
5439
|
+
if (page.querySelector("[data-voice-activity=\"requesting\"], [data-voice-activity=\"recording\"], [data-voice-activity=\"transcribing\"]") !== null) risk();
|
|
5440
|
+
};
|
|
5441
|
+
const voiceObserver = new MutationObserver(checkVoice);
|
|
5442
|
+
voiceObserver.observe(page.documentElement, {
|
|
5443
|
+
subtree: true,
|
|
5444
|
+
childList: true,
|
|
5445
|
+
attributes: true,
|
|
5446
|
+
attributeFilter: ["data-voice-activity"]
|
|
5447
|
+
});
|
|
5448
|
+
checkVoice();
|
|
5449
|
+
page.addEventListener("click", onClick, true);
|
|
5450
|
+
const events = [
|
|
5451
|
+
"compositionstart",
|
|
5452
|
+
"compositionend",
|
|
5453
|
+
"drop",
|
|
5454
|
+
"paste",
|
|
5455
|
+
"change",
|
|
5456
|
+
"submit",
|
|
5457
|
+
"input",
|
|
5458
|
+
"beforeinput",
|
|
5459
|
+
"dsh-mobile:voice-start",
|
|
5460
|
+
"dsh-mobile:voice-stop"
|
|
5461
|
+
];
|
|
5462
|
+
for (const name of events) page.addEventListener(name, onActivity, true);
|
|
5463
|
+
const current = () => {
|
|
5464
|
+
if (disposed || risky || composing || observed.size !== 1) return void 0;
|
|
5465
|
+
for (const modal of page.querySelectorAll("[role=\"dialog\"][aria-modal=\"true\"]")) if (!modal.hidden && !modal.closest("[hidden], [inert], [aria-hidden=\"true\"]") && modal.getClientRects().length > 0) {
|
|
5466
|
+
const style = view.getComputedStyle(modal);
|
|
5467
|
+
if (style.display !== "none" && style.visibility !== "hidden") return void 0;
|
|
5468
|
+
}
|
|
5469
|
+
const entry = observed.values().next().value;
|
|
5470
|
+
if (entry === void 0 || !plain(entry.input.state.getSnapshot())) return void 0;
|
|
5471
|
+
return entry;
|
|
5472
|
+
};
|
|
5473
|
+
const api = {
|
|
5474
|
+
capture() {
|
|
5475
|
+
if (lease !== void 0) return null;
|
|
5476
|
+
const entry = current();
|
|
5477
|
+
if (entry === void 0) return null;
|
|
5478
|
+
const state = entry.input.state.getSnapshot();
|
|
5479
|
+
if (!transferableText(state.draft)) return null;
|
|
5480
|
+
const bytes = /* @__PURE__ */ new Uint8Array(24);
|
|
5481
|
+
try {
|
|
5482
|
+
view.crypto.getRandomValues(bytes);
|
|
5483
|
+
} catch {
|
|
5484
|
+
return null;
|
|
5485
|
+
}
|
|
5486
|
+
lease = {
|
|
5487
|
+
version: 1,
|
|
5488
|
+
token: Array.from(bytes, (n) => n.toString(16).padStart(2, "0")).join(""),
|
|
5489
|
+
href: view.location.href,
|
|
5490
|
+
sessionId: entry.sessionId,
|
|
5491
|
+
text: state.draft,
|
|
5492
|
+
revision: epoch
|
|
5493
|
+
};
|
|
5494
|
+
timer = view.setTimeout(clearLease, LEASE_MS);
|
|
5495
|
+
return { ...lease };
|
|
5496
|
+
},
|
|
5497
|
+
validate(value) {
|
|
5498
|
+
if (!snapshotShape(value) || lease === void 0) return false;
|
|
5499
|
+
const entry = current();
|
|
5500
|
+
return entry !== void 0 && value.token === lease.token && value.href === lease.href && value.href === view.location.href && value.sessionId === lease.sessionId && value.sessionId === entry.sessionId && value.revision === lease.revision && value.revision === epoch && value.text === lease.text && value.text === entry.input.state.getSnapshot().draft;
|
|
5501
|
+
},
|
|
5502
|
+
restore(value) {
|
|
5503
|
+
if (!snapshotShape(value)) return false;
|
|
5504
|
+
if (imported !== void 0) return api.verify(value);
|
|
5505
|
+
if (importClosed) return false;
|
|
5506
|
+
const entry = current();
|
|
5507
|
+
if (entry === void 0 || entry.sessionId !== value.sessionId) return false;
|
|
5508
|
+
const state = entry.input.state.getSnapshot();
|
|
5509
|
+
if (state.draft !== "" && state.draft !== value.text) return false;
|
|
5510
|
+
const wrote = state.draft !== value.text;
|
|
5511
|
+
imported = {
|
|
5512
|
+
snapshot: { ...value },
|
|
5513
|
+
entry,
|
|
5514
|
+
epoch,
|
|
5515
|
+
href: view.location.href,
|
|
5516
|
+
wrote,
|
|
5517
|
+
actualText: state.draft,
|
|
5518
|
+
successful: false
|
|
5519
|
+
};
|
|
5520
|
+
importTimer = view.setTimeout(clearImport, LEASE_MS);
|
|
5521
|
+
let writeFailed = false;
|
|
5522
|
+
if (wrote) try {
|
|
5523
|
+
entry.input.setDraft(value.text);
|
|
5524
|
+
} catch {
|
|
5525
|
+
writeFailed = true;
|
|
5526
|
+
}
|
|
5527
|
+
const actual = entry.input.state.getSnapshot();
|
|
5528
|
+
if (current() !== entry || !plain(actual)) return false;
|
|
5529
|
+
imported.epoch = epoch;
|
|
5530
|
+
imported.actualText = actual.draft;
|
|
5531
|
+
imported.successful = !writeFailed && actual.draft === value.text;
|
|
5532
|
+
return imported.successful;
|
|
5533
|
+
},
|
|
5534
|
+
verify(value) {
|
|
5535
|
+
if (!snapshotShape(value) || imported === void 0) return false;
|
|
5536
|
+
const saved = imported.snapshot;
|
|
5537
|
+
return imported.successful && value.token === saved.token && value.href === saved.href && value.sessionId === saved.sessionId && value.text === saved.text && value.revision === saved.revision && current() === imported.entry && epoch === imported.epoch && view.location.href === imported.href && imported.entry.input.state.getSnapshot().draft === saved.text;
|
|
5538
|
+
},
|
|
5539
|
+
rollback(value) {
|
|
5540
|
+
if (!snapshotShape(value) || imported === void 0) return false;
|
|
5541
|
+
const record = imported;
|
|
5542
|
+
const saved = record.snapshot;
|
|
5543
|
+
if (value.token !== saved.token || value.href !== saved.href || value.sessionId !== saved.sessionId || value.text !== saved.text || value.revision !== saved.revision || current() !== record.entry || epoch !== record.epoch || view.location.href !== record.href || record.entry.input.state.getSnapshot().draft !== record.actualText) return false;
|
|
5544
|
+
if (record.wrote) record.entry.input.setDraft("");
|
|
5545
|
+
const restored = !record.wrote || record.entry.input.state.getSnapshot().draft === "";
|
|
5546
|
+
clearImport();
|
|
5547
|
+
return restored;
|
|
5548
|
+
},
|
|
5549
|
+
release(value) {
|
|
5550
|
+
if (!snapshotShape(value)) return;
|
|
5551
|
+
if (value.token === lease?.token) clearLease();
|
|
5552
|
+
if (value.token === imported?.snapshot.token) clearImport();
|
|
5553
|
+
}
|
|
5554
|
+
};
|
|
5555
|
+
return {
|
|
5556
|
+
api: {
|
|
5557
|
+
capture() {
|
|
5558
|
+
try {
|
|
5559
|
+
return api.capture();
|
|
5560
|
+
} catch {
|
|
5561
|
+
risk();
|
|
5562
|
+
clearLease();
|
|
5563
|
+
return null;
|
|
5564
|
+
}
|
|
5565
|
+
},
|
|
5566
|
+
validate(value) {
|
|
5567
|
+
try {
|
|
5568
|
+
return api.validate(value);
|
|
5569
|
+
} catch {
|
|
5570
|
+
risk();
|
|
5571
|
+
return false;
|
|
5572
|
+
}
|
|
5573
|
+
},
|
|
5574
|
+
restore(value) {
|
|
5575
|
+
try {
|
|
5576
|
+
return api.restore(value);
|
|
5577
|
+
} catch {
|
|
5578
|
+
risk();
|
|
5579
|
+
return false;
|
|
5580
|
+
}
|
|
5581
|
+
},
|
|
5582
|
+
verify(value) {
|
|
5583
|
+
try {
|
|
5584
|
+
return api.verify(value);
|
|
5585
|
+
} catch {
|
|
5586
|
+
risk();
|
|
5587
|
+
return false;
|
|
5588
|
+
}
|
|
5589
|
+
},
|
|
5590
|
+
rollback(value) {
|
|
5591
|
+
try {
|
|
5592
|
+
return api.rollback(value);
|
|
5593
|
+
} catch {
|
|
5594
|
+
risk();
|
|
5595
|
+
return false;
|
|
5596
|
+
}
|
|
5597
|
+
},
|
|
5598
|
+
release(value) {
|
|
5599
|
+
try {
|
|
5600
|
+
api.release(value);
|
|
5601
|
+
} catch {}
|
|
5602
|
+
}
|
|
5603
|
+
},
|
|
5604
|
+
observe(sessionId, input) {
|
|
5605
|
+
if (disposed) return () => void 0;
|
|
5606
|
+
const key = {}, entry = {
|
|
5607
|
+
sessionId,
|
|
5608
|
+
input
|
|
5609
|
+
};
|
|
5610
|
+
observed.set(key, entry);
|
|
5611
|
+
invalidate();
|
|
5612
|
+
const changed = () => {
|
|
5613
|
+
invalidate();
|
|
5614
|
+
try {
|
|
5615
|
+
if (!plain(input.state.getSnapshot())) risky = true;
|
|
5616
|
+
} catch {
|
|
5617
|
+
risky = true;
|
|
5618
|
+
}
|
|
5619
|
+
};
|
|
5620
|
+
changed();
|
|
5621
|
+
const unsubscribe = input.state.subscribe(changed);
|
|
5622
|
+
const remove = () => {
|
|
5623
|
+
if (!subscriptions.delete(remove)) return;
|
|
5624
|
+
unsubscribe();
|
|
5625
|
+
observed.delete(key);
|
|
5626
|
+
invalidate();
|
|
5627
|
+
};
|
|
5628
|
+
subscriptions.add(remove);
|
|
5629
|
+
return remove;
|
|
5630
|
+
},
|
|
5631
|
+
dispose() {
|
|
5632
|
+
if (disposed) return;
|
|
5633
|
+
disposed = true;
|
|
5634
|
+
voiceObserver.disconnect();
|
|
5635
|
+
page.removeEventListener("click", onClick, true);
|
|
5636
|
+
clearLease();
|
|
5637
|
+
clearImport();
|
|
5638
|
+
for (const remove of subscriptions) remove();
|
|
5639
|
+
subscriptions.clear();
|
|
5640
|
+
observed.clear();
|
|
5641
|
+
for (const name of events) page.removeEventListener(name, onActivity, true);
|
|
5642
|
+
}
|
|
5643
|
+
};
|
|
5644
|
+
}
|
|
5645
|
+
/** Install only on the authenticated Mobile frontend; identity comes from the
|
|
5646
|
+
* real public session-scoped composer slot, state/writes from the public facade. */
|
|
5647
|
+
function installDraftTransfer(ctx, view, page) {
|
|
5648
|
+
const controller = createDraftTransfer(view, page);
|
|
5649
|
+
const previous = Object.getOwnPropertyDescriptor(view, "__dshMobileDraftTransfer");
|
|
5650
|
+
const mounted = /* @__PURE__ */ new Map();
|
|
5651
|
+
let active = true;
|
|
5652
|
+
let requestedSession;
|
|
5653
|
+
let importedToken;
|
|
5654
|
+
const api = {
|
|
5655
|
+
...controller.api,
|
|
5656
|
+
restore(value) {
|
|
5657
|
+
if (!active || !snapshotShape(value)) return false;
|
|
5658
|
+
if (importedToken !== void 0) return controller.api.restore(value);
|
|
5659
|
+
if (mounted.size === 0 || mounted.size === 1 && ![...mounted.values()].includes(value.sessionId)) {
|
|
5660
|
+
if (ctx.get("sessions")?.list?.getSnapshot?.().byId[value.sessionId] === void 0) return false;
|
|
5661
|
+
if (requestedSession !== value.sessionId) {
|
|
5662
|
+
const navigation = ctx.get("uiWorkspace");
|
|
5663
|
+
if (typeof navigation?.openSession !== "function") return false;
|
|
5664
|
+
try {
|
|
5665
|
+
navigation.openSession(value.sessionId);
|
|
5666
|
+
} catch {
|
|
5667
|
+
return false;
|
|
5668
|
+
}
|
|
5669
|
+
requestedSession = value.sessionId;
|
|
5670
|
+
}
|
|
5671
|
+
return false;
|
|
5672
|
+
}
|
|
5673
|
+
const restored = controller.api.restore(value);
|
|
5674
|
+
if (restored) importedToken = value.token;
|
|
5675
|
+
return restored;
|
|
5676
|
+
},
|
|
5677
|
+
release(value) {
|
|
5678
|
+
controller.api.release(value);
|
|
5679
|
+
if (snapshotShape(value) && value.token === importedToken) importedToken = void 0;
|
|
5680
|
+
}
|
|
5681
|
+
};
|
|
5682
|
+
Object.defineProperty(view, "__dshMobileDraftTransfer", {
|
|
5683
|
+
configurable: true,
|
|
5684
|
+
value: api
|
|
5685
|
+
});
|
|
5686
|
+
function Observer({ sessionId }) {
|
|
5687
|
+
(0, react.useEffect)(() => {
|
|
5688
|
+
if (!active) return;
|
|
5689
|
+
const sessions = ctx.get("sessions");
|
|
5690
|
+
const conversation = ctx.get("conversation");
|
|
5691
|
+
if (typeof sessions?.scope !== "function" || typeof conversation?.input?.for !== "function") return;
|
|
5692
|
+
const scope = sessions.scope(sessionId);
|
|
5693
|
+
if (scope === void 0) return;
|
|
5694
|
+
const key = {};
|
|
5695
|
+
mounted.set(key, sessionId);
|
|
5696
|
+
const remove = controller.observe(sessionId, conversation.input.for(scope));
|
|
5697
|
+
return () => {
|
|
5698
|
+
mounted.delete(key);
|
|
5699
|
+
remove();
|
|
5700
|
+
};
|
|
5701
|
+
}, [sessionId]);
|
|
5702
|
+
return (0, react.createElement)("span", {
|
|
5703
|
+
hidden: true,
|
|
5704
|
+
"data-dsh-mobile-draft-observer": sessionId
|
|
5705
|
+
});
|
|
5706
|
+
}
|
|
5707
|
+
const removeSlot = ctx.slots.inject("conversation.input.overlay", () => ctx.slots.register({
|
|
5708
|
+
name: "conversation.input.overlay",
|
|
5709
|
+
id: "dsh-mobile-draft-transfer"
|
|
5710
|
+
}, Observer));
|
|
5711
|
+
return () => {
|
|
5712
|
+
if (!active) return;
|
|
5713
|
+
active = false;
|
|
5714
|
+
removeSlot();
|
|
5715
|
+
mounted.clear();
|
|
5716
|
+
controller.dispose();
|
|
5717
|
+
if (view.__dshMobileDraftTransfer === api) {
|
|
5718
|
+
if (previous === void 0) delete view.__dshMobileDraftTransfer;
|
|
5719
|
+
else Object.defineProperty(view, "__dshMobileDraftTransfer", previous);
|
|
5720
|
+
}
|
|
5721
|
+
};
|
|
5722
|
+
}
|
|
5187
5723
|
//#endregion
|
|
5188
5724
|
//#region src/mobile-font-capabilities.ts
|
|
5189
5725
|
const NO_ADVANCED_MOBILE_TYPOGRAPHY = Object.freeze({
|
|
@@ -7199,6 +7735,161 @@ window.__ModuleLoader__.load({
|
|
|
7199
7735
|
assetDepth: 8
|
|
7200
7736
|
});
|
|
7201
7737
|
//#endregion
|
|
7738
|
+
//#region src/client-recovery.ts
|
|
7739
|
+
/** Only a visible, disconnected public transport needs an immediate resume nudge. */
|
|
7740
|
+
function shouldNudgeReconnect(visibility, connection) {
|
|
7741
|
+
return visibility === "visible" && connection !== "connected";
|
|
7742
|
+
}
|
|
7743
|
+
/** A persistent, known disconnection can request one native renewal. */
|
|
7744
|
+
function shouldRenewSession(connection, downForMs) {
|
|
7745
|
+
return isDisconnected(connection) && downForMs >= 5e3;
|
|
7746
|
+
}
|
|
7747
|
+
function isDisconnected(connection) {
|
|
7748
|
+
return connection === "connecting" || connection === "disconnected";
|
|
7749
|
+
}
|
|
7750
|
+
/**
|
|
7751
|
+
* Own resume nudges and one native renewal per continuous disconnection.
|
|
7752
|
+
* @param getConnection - Resolve the current service to reject a replaced owner.
|
|
7753
|
+
* @param page - The document's window and advertised native adapter.
|
|
7754
|
+
* @param document - The mounted document; page replacement withdraws its work.
|
|
7755
|
+
* @returns An awaited disposer, or nothing when no public connection is present.
|
|
7756
|
+
*/
|
|
7757
|
+
function installMobileConnectionRecovery(getConnection, page, document) {
|
|
7758
|
+
const connection = getConnection();
|
|
7759
|
+
const state = connection?.state;
|
|
7760
|
+
if (connection === void 0 || state === void 0 || typeof connection.reconnect !== "function") return;
|
|
7761
|
+
const origin = page.location.origin;
|
|
7762
|
+
let disposed = false;
|
|
7763
|
+
let generation = 0;
|
|
7764
|
+
let currentState = state.getSnapshot();
|
|
7765
|
+
let downSince = isDisconnected(currentState) ? Date.now() : void 0;
|
|
7766
|
+
let attempted = false;
|
|
7767
|
+
let bridge = page.__DSH_MOBILE_NATIVE__;
|
|
7768
|
+
let pending;
|
|
7769
|
+
let pendingAbort;
|
|
7770
|
+
let cleanup;
|
|
7771
|
+
const current = () => !disposed && page.document === document && page.location.origin === origin && getConnection() === connection && connection.state === state;
|
|
7772
|
+
const cancelPending = () => {
|
|
7773
|
+
generation += 1;
|
|
7774
|
+
pendingAbort?.abort(new DOMException("mobile recovery owner ended", "AbortError"));
|
|
7775
|
+
};
|
|
7776
|
+
const updateState = () => {
|
|
7777
|
+
const next = state.getSnapshot();
|
|
7778
|
+
if (!isDisconnected(next)) {
|
|
7779
|
+
downSince = void 0;
|
|
7780
|
+
attempted = false;
|
|
7781
|
+
if (currentState !== next) cancelPending();
|
|
7782
|
+
} else if (downSince === void 0) {
|
|
7783
|
+
downSince = Date.now();
|
|
7784
|
+
attempted = false;
|
|
7785
|
+
}
|
|
7786
|
+
currentState = next;
|
|
7787
|
+
};
|
|
7788
|
+
const updateBridge = () => {
|
|
7789
|
+
const next = page.__DSH_MOBILE_NATIVE__;
|
|
7790
|
+
if (bridge === next) return;
|
|
7791
|
+
cancelPending();
|
|
7792
|
+
bridge = next;
|
|
7793
|
+
attempted = false;
|
|
7794
|
+
};
|
|
7795
|
+
const renew = () => {
|
|
7796
|
+
if (!current()) {
|
|
7797
|
+
dispose();
|
|
7798
|
+
return;
|
|
7799
|
+
}
|
|
7800
|
+
updateState();
|
|
7801
|
+
updateBridge();
|
|
7802
|
+
const adapter = bridge;
|
|
7803
|
+
if (document.visibilityState !== "visible" || adapter === void 0 || pending !== void 0 || attempted || downSince === void 0 || !shouldRenewSession(currentState, Date.now() - downSince)) return;
|
|
7804
|
+
attempted = true;
|
|
7805
|
+
const owner = generation;
|
|
7806
|
+
const abort = new AbortController();
|
|
7807
|
+
pendingAbort = abort;
|
|
7808
|
+
const owns = () => current() && !abort.signal.aborted && generation === owner && page.__DSH_MOBILE_NATIVE__ === adapter && isDisconnected(state.getSnapshot());
|
|
7809
|
+
const task = Promise.resolve().then(async () => {
|
|
7810
|
+
if (!owns()) return;
|
|
7811
|
+
const capabilities = await adapter.capabilities();
|
|
7812
|
+
if (!owns() || !capabilities.includes("auth.renew")) return;
|
|
7813
|
+
const result = await adapter.invoke("auth.renew", {}, { signal: abort.signal });
|
|
7814
|
+
if (!owns() || typeof result !== "object" || result === null || !("renewed" in result) || result.renewed !== true) return;
|
|
7815
|
+
connection.reconnect?.();
|
|
7816
|
+
}).catch((_error) => {}).finally(() => {
|
|
7817
|
+
if (pending !== task) return;
|
|
7818
|
+
pending = void 0;
|
|
7819
|
+
pendingAbort = void 0;
|
|
7820
|
+
});
|
|
7821
|
+
pending = task;
|
|
7822
|
+
};
|
|
7823
|
+
const resume = () => {
|
|
7824
|
+
if (!current()) {
|
|
7825
|
+
dispose();
|
|
7826
|
+
return;
|
|
7827
|
+
}
|
|
7828
|
+
updateState();
|
|
7829
|
+
updateBridge();
|
|
7830
|
+
if (shouldNudgeReconnect(document.visibilityState, currentState)) connection.reconnect?.();
|
|
7831
|
+
renew();
|
|
7832
|
+
};
|
|
7833
|
+
const stopState = state.subscribe(updateState);
|
|
7834
|
+
const timer = setInterval(renew, 2e3);
|
|
7835
|
+
const pagehide = () => {
|
|
7836
|
+
dispose();
|
|
7837
|
+
};
|
|
7838
|
+
const dispose = () => {
|
|
7839
|
+
if (cleanup !== void 0) return cleanup;
|
|
7840
|
+
disposed = true;
|
|
7841
|
+
cancelPending();
|
|
7842
|
+
clearInterval(timer);
|
|
7843
|
+
stopState();
|
|
7844
|
+
document.removeEventListener("visibilitychange", resume);
|
|
7845
|
+
page.removeEventListener("dsh-mobile-native-ready", updateBridge);
|
|
7846
|
+
page.removeEventListener("pagehide", pagehide);
|
|
7847
|
+
cleanup = Promise.resolve(pending);
|
|
7848
|
+
return cleanup;
|
|
7849
|
+
};
|
|
7850
|
+
document.addEventListener("visibilitychange", resume);
|
|
7851
|
+
page.addEventListener("dsh-mobile-native-ready", updateBridge);
|
|
7852
|
+
page.addEventListener("pagehide", pagehide);
|
|
7853
|
+
return dispose;
|
|
7854
|
+
}
|
|
7855
|
+
//#endregion
|
|
7856
|
+
//#region src/browser-ui-locale.ts
|
|
7857
|
+
const UI_LOCALE_COOKIE = "dsh_ma_ui_locale";
|
|
7858
|
+
/** Host-only, non-secret preference, independent of the short authentication session. */
|
|
7859
|
+
function browserUiLocaleCookie(locale, secure) {
|
|
7860
|
+
return `${UI_LOCALE_COOKIE}=${locale}; Path=/mobile-access; Max-Age=31536000; SameSite=Lax${secure ? "; Secure" : ""}`;
|
|
7861
|
+
}
|
|
7862
|
+
//#endregion
|
|
7863
|
+
//#region src/mobile-ui-locale.ts
|
|
7864
|
+
/** Remember the DSH display language only on authenticated Mobile Gateway documents. */
|
|
7865
|
+
function installMobileUiLocalePersistence(page, view) {
|
|
7866
|
+
if (page.querySelector("meta[name=\"dsh-mobile-gateway\"][content=\"authenticated\"]") === null) return () => {};
|
|
7867
|
+
let disposed = false;
|
|
7868
|
+
let previous;
|
|
7869
|
+
const sync = () => {
|
|
7870
|
+
if (disposed) return;
|
|
7871
|
+
const language = page.documentElement.lang.trim().toLowerCase();
|
|
7872
|
+
if (language === "") return;
|
|
7873
|
+
const base = language.split(/[-_]/u)[0];
|
|
7874
|
+
const locale = base === "zh" || base === "it" ? base : "en";
|
|
7875
|
+
if (locale === previous) return;
|
|
7876
|
+
try {
|
|
7877
|
+
page.cookie = browserUiLocaleCookie(locale, view.location.protocol === "https:");
|
|
7878
|
+
previous = locale;
|
|
7879
|
+
} catch {}
|
|
7880
|
+
};
|
|
7881
|
+
const observer = new view.MutationObserver(sync);
|
|
7882
|
+
observer.observe(page.documentElement, {
|
|
7883
|
+
attributes: true,
|
|
7884
|
+
attributeFilter: ["lang"]
|
|
7885
|
+
});
|
|
7886
|
+
sync();
|
|
7887
|
+
return () => {
|
|
7888
|
+
disposed = true;
|
|
7889
|
+
observer.disconnect();
|
|
7890
|
+
};
|
|
7891
|
+
}
|
|
7892
|
+
//#endregion
|
|
7202
7893
|
//#region src/client.ts
|
|
7203
7894
|
/** Map a host-reported provider onto the UI union, defaulting to the built-in one. */
|
|
7204
7895
|
function parseMobileRemoteProvider(value) {
|
|
@@ -7747,6 +8438,10 @@ window.__ModuleLoader__.load({
|
|
|
7747
8438
|
frp_discovery_invalid: "frpDiscoveryInvalid",
|
|
7748
8439
|
frp_stopped: "frpStopped",
|
|
7749
8440
|
frp_exited: "frpExited",
|
|
8441
|
+
frp_server_address_invalid: "frpServerAddressInvalid",
|
|
8442
|
+
frp_server_port_invalid: "frpServerPortInvalid",
|
|
8443
|
+
frp_token_invalid: "frpTokenInvalid",
|
|
8444
|
+
frp_public_origin_invalid: "frpPublicOriginInvalid",
|
|
7750
8445
|
frp_attach_mode_requires_vhost_port: "frpAttachModeRequiresVhostPort",
|
|
7751
8446
|
frp_attach_cert_unknown: "frpAttachCertUnknownError",
|
|
7752
8447
|
frp_entry_tls_invalid: "frpEntryTlsInvalid",
|
|
@@ -8480,6 +9175,52 @@ window.__ModuleLoader__.load({
|
|
|
8480
9175
|
frpPortLabel.append(frpPort);
|
|
8481
9176
|
frpTokenLabel.append(frpToken);
|
|
8482
9177
|
frpOriginLabel.append(frpOrigin);
|
|
9178
|
+
const frpInputs = {
|
|
9179
|
+
serverAddress: frpServer,
|
|
9180
|
+
serverPort: frpPort,
|
|
9181
|
+
token: frpToken,
|
|
9182
|
+
publicOrigin: frpOrigin,
|
|
9183
|
+
mode: frpMode,
|
|
9184
|
+
entryTls: frpEntryTls,
|
|
9185
|
+
vhostHttpPort: frpVhostPort,
|
|
9186
|
+
publicPort: frpPublicPort
|
|
9187
|
+
};
|
|
9188
|
+
const frpInputLabels = {
|
|
9189
|
+
serverAddress: frpServerLabel,
|
|
9190
|
+
serverPort: frpPortLabel,
|
|
9191
|
+
token: frpTokenLabel,
|
|
9192
|
+
publicOrigin: frpOriginLabel,
|
|
9193
|
+
mode: frpModeLabel,
|
|
9194
|
+
entryTls: frpEntryTlsLabel,
|
|
9195
|
+
vhostHttpPort: frpVhostLabel,
|
|
9196
|
+
publicPort: frpPublicPortLabel
|
|
9197
|
+
};
|
|
9198
|
+
const frpLabelKeys = {
|
|
9199
|
+
serverAddress: "frpServerAddress",
|
|
9200
|
+
serverPort: "frpServerPort",
|
|
9201
|
+
token: "frpToken",
|
|
9202
|
+
publicOrigin: "frpPublicOrigin",
|
|
9203
|
+
mode: "frpMode",
|
|
9204
|
+
entryTls: "frpEntryTls",
|
|
9205
|
+
vhostHttpPort: "frpVhostHttpPort",
|
|
9206
|
+
publicPort: "frpPublicPort"
|
|
9207
|
+
};
|
|
9208
|
+
const frpErrors = /* @__PURE__ */ new Map();
|
|
9209
|
+
for (const [name, input] of Object.entries(frpInputs)) {
|
|
9210
|
+
input.id = `dsh-mobile-frp-${name}`;
|
|
9211
|
+
input.setAttribute("aria-label", t(frpLabelKeys[name]));
|
|
9212
|
+
const error = element("p", "dsh-mobile-control__origin-feedback is-error");
|
|
9213
|
+
error.id = `${input.id}-error`;
|
|
9214
|
+
error.hidden = true;
|
|
9215
|
+
input.setAttribute("aria-describedby", [input.getAttribute("aria-describedby"), error.id].filter(Boolean).join(" "));
|
|
9216
|
+
frpInputLabels[name].append(error);
|
|
9217
|
+
frpErrors.set(name, error);
|
|
9218
|
+
}
|
|
9219
|
+
const frpOriginHint = element("p", "dsh-mobile-control__frp-hint");
|
|
9220
|
+
frpOriginHint.id = "dsh-mobile-frp-origin-hint";
|
|
9221
|
+
frpOriginHint.textContent = t("frpPublicOriginHint");
|
|
9222
|
+
frpOriginLabel.append(frpOriginHint);
|
|
9223
|
+
frpOrigin.setAttribute("aria-describedby", `${frpOriginHint.id} ${frpOrigin.getAttribute("aria-describedby") ?? ""}`);
|
|
8483
9224
|
frpFields.append(frpServerLabel, frpPortLabel, frpTokenLabel, frpOriginLabel, frpModeLabel, frpEntryTlsLabel, frpVhostLabel, frpPublicPortLabel);
|
|
8484
9225
|
frpStep1.append(frpStep1Title, frpStep1Text, frpFields);
|
|
8485
9226
|
const frpStep2 = element("section", "dsh-mobile-control__frp-step");
|
|
@@ -8581,10 +9322,18 @@ window.__ModuleLoader__.load({
|
|
|
8581
9322
|
frpAppRequirement.textContent = t("frpAppRequirement");
|
|
8582
9323
|
const frpConfigurationStatus = element("p", "dsh-mobile-control__component-status");
|
|
8583
9324
|
frpConfigurationStatus.textContent = t("frpConfigurationMissing");
|
|
9325
|
+
const frpFeedback = element("p", "dsh-mobile-control__origin-feedback is-error");
|
|
9326
|
+
frpFeedback.setAttribute("role", "status");
|
|
9327
|
+
frpFeedback.setAttribute("aria-live", "polite");
|
|
9328
|
+
frpFeedback.hidden = true;
|
|
9329
|
+
const frpActionHint = element("p", "dsh-mobile-control__frp-hint");
|
|
9330
|
+
frpActionHint.id = "dsh-mobile-frp-action-hint";
|
|
9331
|
+
frpActionHint.hidden = true;
|
|
8584
9332
|
const frpConfigure = element("button", "dsh-mobile-control__primary dsh-mobile-control__frp-action");
|
|
8585
9333
|
frpConfigure.type = "button";
|
|
8586
9334
|
frpConfigure.textContent = t("frpSaveConnect");
|
|
8587
|
-
|
|
9335
|
+
frpConfigure.setAttribute("aria-describedby", frpActionHint.id);
|
|
9336
|
+
frpStep4.append(frpStep4Title, frpStep4Text, frpAppRequirement, frpConfigurationStatus, frpFeedback, frpActionHint, frpConfigure);
|
|
8588
9337
|
const frpDetails = element("details", "dsh-mobile-control__details");
|
|
8589
9338
|
const frpDetailsSummary = element("summary");
|
|
8590
9339
|
frpDetailsSummary.textContent = t("frpComponentDetails");
|
|
@@ -9660,6 +10409,8 @@ window.__ModuleLoader__.load({
|
|
|
9660
10409
|
frpInstall.textContent = frpDownloadBytes > 0 ? t("installWithSize", { size: formatMegabytes(frpDownloadBytes) }) : t("installFrpc");
|
|
9661
10410
|
frpInstall.disabled = remoteProviderBusy;
|
|
9662
10411
|
frpConfigure.disabled = remoteProviderBusy || !frpInstalled;
|
|
10412
|
+
frpActionHint.hidden = !frpConfigure.disabled;
|
|
10413
|
+
frpActionHint.textContent = t(remoteProviderBusy ? "frpActionBusy" : !frpSupported ? "frpUnsupported" : "frpInstallBeforeConnect");
|
|
9663
10414
|
vpsDeploy.disabled = remoteProviderBusy || !validVpsDeploymentForm();
|
|
9664
10415
|
vpsCopyUninstall.disabled = vpsDeploy.disabled;
|
|
9665
10416
|
vpsUninstall.disabled = vpsDeploy.disabled;
|
|
@@ -10097,7 +10848,6 @@ window.__ModuleLoader__.load({
|
|
|
10097
10848
|
loadRemote();
|
|
10098
10849
|
});
|
|
10099
10850
|
});
|
|
10100
|
-
const validFrpServer = (value) => value === value.trim() && value.length > 0 && value.length <= 253 && !/[\s\u0000-\u001f\u007f/\\@?#]/u.test(value);
|
|
10101
10851
|
const frpForm = () => {
|
|
10102
10852
|
const vhostHttpPort = Number(frpVhostPort.value);
|
|
10103
10853
|
const publicPort = Number(frpPublicPort.value);
|
|
@@ -10109,8 +10859,8 @@ window.__ModuleLoader__.load({
|
|
|
10109
10859
|
publicOrigin: String(frpOrigin.value ?? "").trim(),
|
|
10110
10860
|
mode,
|
|
10111
10861
|
entryTls: frpEntryTls.value === "self-signed" ? "self-signed" : "public-ip-cert",
|
|
10112
|
-
...
|
|
10113
|
-
...
|
|
10862
|
+
...frpEntryTls.value === "self-signed" || frpVhostPort.value.trim() === "" ? {} : { vhostHttpPort },
|
|
10863
|
+
...frpEntryTls.value === "self-signed" ? { publicPort } : {}
|
|
10114
10864
|
};
|
|
10115
10865
|
};
|
|
10116
10866
|
/**
|
|
@@ -10127,20 +10877,39 @@ window.__ModuleLoader__.load({
|
|
|
10127
10877
|
...form.publicPort === void 0 ? {} : { publicPort: form.publicPort }
|
|
10128
10878
|
});
|
|
10129
10879
|
const validFrpForm = (form) => {
|
|
10130
|
-
|
|
10131
|
-
|
|
10132
|
-
|
|
10133
|
-
|
|
10134
|
-
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
|
|
10880
|
+
return validateFrpForm(form).length === 0;
|
|
10881
|
+
};
|
|
10882
|
+
let frpValidationShown = false;
|
|
10883
|
+
let frpValidationAllowsSavedToken = false;
|
|
10884
|
+
const renderFrpIssues = (issues, focus = false) => {
|
|
10885
|
+
for (const [name, input] of Object.entries(frpInputs)) {
|
|
10886
|
+
const issue = issues.find((item) => item.field === name);
|
|
10887
|
+
const feedback = frpErrors.get(name);
|
|
10888
|
+
if (issue === void 0) input.removeAttribute("aria-invalid");
|
|
10889
|
+
else input.setAttribute("aria-invalid", "true");
|
|
10890
|
+
feedback.hidden = issue === void 0;
|
|
10891
|
+
feedback.textContent = issue === void 0 ? "" : t(issue.messageKey);
|
|
10892
|
+
}
|
|
10893
|
+
frpFeedback.hidden = issues.length === 0;
|
|
10894
|
+
frpFeedback.textContent = issues.map((issue) => t(issue.messageKey)).join(" ");
|
|
10895
|
+
if (issues.length !== 0 && focus) {
|
|
10896
|
+
frpConnectionGroup.open = true;
|
|
10897
|
+
frpInputs[issues[0].field].focus();
|
|
10898
|
+
}
|
|
10899
|
+
};
|
|
10900
|
+
const checkFrpForm = (form, allowSavedToken = false) => {
|
|
10901
|
+
const issues = validateFrpForm(form, allowSavedToken);
|
|
10902
|
+
frpValidationShown = true;
|
|
10903
|
+
frpValidationAllowsSavedToken = allowSavedToken;
|
|
10904
|
+
renderFrpIssues(issues, true);
|
|
10905
|
+
return issues.length === 0;
|
|
10906
|
+
};
|
|
10907
|
+
for (const [name, input] of Object.entries(frpInputs)) input.addEventListener(name === "mode" || name === "entryTls" ? "change" : "input", () => {
|
|
10908
|
+
if (frpValidationShown) renderFrpIssues(validateFrpForm(frpForm(), frpValidationAllowsSavedToken));
|
|
10909
|
+
});
|
|
10138
10910
|
frpCopyTemplate.addEventListener("click", () => {
|
|
10139
10911
|
const form = frpForm();
|
|
10140
|
-
if (!
|
|
10141
|
-
remoteStatus.textContent = t("frpInputInvalid");
|
|
10142
|
-
return;
|
|
10143
|
-
}
|
|
10912
|
+
if (!checkFrpForm(form)) return;
|
|
10144
10913
|
navigator.clipboard.writeText(createFrpServerTemplateForClipboard(form.serverPort, form.token, form.publicOrigin)).then(() => {
|
|
10145
10914
|
remoteStatus.textContent = t("templateCopied");
|
|
10146
10915
|
}, () => {
|
|
@@ -10150,10 +10919,13 @@ window.__ModuleLoader__.load({
|
|
|
10150
10919
|
const frpAttachErrorText = (code) => t(REMOTE_ERROR_MESSAGE_KEYS[code] ?? "frpInputInvalid");
|
|
10151
10920
|
const showFrpAttachError = (code) => {
|
|
10152
10921
|
const message = frpAttachErrorText(code);
|
|
10153
|
-
|
|
10154
|
-
|
|
10155
|
-
|
|
10156
|
-
|
|
10922
|
+
const issue = FRP_FORM_ERRORS[code];
|
|
10923
|
+
if (issue !== void 0) {
|
|
10924
|
+
frpValidationShown = true;
|
|
10925
|
+
renderFrpIssues([issue], true);
|
|
10926
|
+
} else {
|
|
10927
|
+
frpFeedback.textContent = message;
|
|
10928
|
+
frpFeedback.hidden = false;
|
|
10157
10929
|
}
|
|
10158
10930
|
remoteStatus.textContent = message;
|
|
10159
10931
|
};
|
|
@@ -10166,10 +10938,7 @@ window.__ModuleLoader__.load({
|
|
|
10166
10938
|
};
|
|
10167
10939
|
frpCopyAttachPlan.addEventListener("click", () => {
|
|
10168
10940
|
const form = frpForm();
|
|
10169
|
-
if (!
|
|
10170
|
-
remoteStatus.textContent = t("frpInputInvalid");
|
|
10171
|
-
return;
|
|
10172
|
-
}
|
|
10941
|
+
if (!checkFrpForm(form, frpConfigured)) return;
|
|
10173
10942
|
if (form.token !== "") {
|
|
10174
10943
|
const attachForm = attachClipboardForm(form);
|
|
10175
10944
|
const code = frpAttachFormErrorCode(attachForm);
|
|
@@ -10202,7 +10971,7 @@ window.__ModuleLoader__.load({
|
|
|
10202
10971
|
remoteStatus.textContent = t("frpAttachPlanCopied");
|
|
10203
10972
|
});
|
|
10204
10973
|
}, (error) => {
|
|
10205
|
-
|
|
10974
|
+
showFrpAttachError(error instanceof Error ? error.message : String(error));
|
|
10206
10975
|
}).catch((error) => {
|
|
10207
10976
|
remoteStatus.textContent = t("frpAttachPlanFailed", { error: String(error) });
|
|
10208
10977
|
}).finally(() => {
|
|
@@ -10213,10 +10982,7 @@ window.__ModuleLoader__.load({
|
|
|
10213
10982
|
/** Copy a token-bearing frpc.toml only from the token the user just typed. */
|
|
10214
10983
|
frpCopyAttachToken.addEventListener("click", () => {
|
|
10215
10984
|
const form = frpForm();
|
|
10216
|
-
if (!
|
|
10217
|
-
remoteStatus.textContent = t("frpInputInvalid");
|
|
10218
|
-
return;
|
|
10219
|
-
}
|
|
10985
|
+
if (!checkFrpForm(form, frpConfigured)) return;
|
|
10220
10986
|
if (form.token !== "") {
|
|
10221
10987
|
const attachForm = attachClipboardForm(form);
|
|
10222
10988
|
const code = frpAttachFormErrorCode(attachForm);
|
|
@@ -10563,15 +11329,13 @@ window.__ModuleLoader__.load({
|
|
|
10563
11329
|
const form = frpForm();
|
|
10564
11330
|
const unchanged = frpConfigured && form.token === "" && form.serverAddress === configuredFrpServer && form.serverPort === configuredFrpPort && form.publicOrigin === configuredFrpOrigin && form.mode === (frpConfiguredMode === "attach" ? "attach" : "deploy") && form.entryTls === (frpConfiguredEntryTls === "self-signed" ? "self-signed" : "public-ip-cert") && (form.vhostHttpPort ?? configuredFrpVhostPort) === configuredFrpVhostPort && (form.publicPort ?? configuredFrpPublicPort) === configuredFrpPublicPort;
|
|
10565
11331
|
if (!unchanged) {
|
|
10566
|
-
|
|
10567
|
-
if (code !== void 0) {
|
|
10568
|
-
if (form.mode === "attach") showFrpAttachError(code);
|
|
10569
|
-
else remoteStatus.textContent = t("frpInputInvalid");
|
|
10570
|
-
return;
|
|
10571
|
-
}
|
|
11332
|
+
if (!checkFrpForm(form)) return;
|
|
10572
11333
|
}
|
|
11334
|
+
renderFrpIssues([]);
|
|
10573
11335
|
remoteProviderBusy = true;
|
|
10574
11336
|
frpConfigure.disabled = true;
|
|
11337
|
+
frpActionHint.hidden = false;
|
|
11338
|
+
frpActionHint.textContent = t("frpActionBusy");
|
|
10575
11339
|
frpConfigure.setAttribute("aria-busy", "true");
|
|
10576
11340
|
frpConfigure.textContent = t("saving");
|
|
10577
11341
|
(unchanged ? Promise.resolve({}) : controlRequestJson("/api/mobile-access/remote/frp/configure", {
|
|
@@ -10585,8 +11349,12 @@ window.__ModuleLoader__.load({
|
|
|
10585
11349
|
body: JSON.stringify({ running: true })
|
|
10586
11350
|
});
|
|
10587
11351
|
}).then(renderRemote, (error) => {
|
|
10588
|
-
|
|
10589
|
-
|
|
11352
|
+
const code = error instanceof Error ? error.message : String(error);
|
|
11353
|
+
if (FRP_FORM_ERRORS[code] !== void 0) showFrpAttachError(code);
|
|
11354
|
+
else {
|
|
11355
|
+
remoteFailureText = t("configureFailed", { error: String(error) });
|
|
11356
|
+
remoteStatus.textContent = remoteFailureText;
|
|
11357
|
+
}
|
|
10590
11358
|
}).finally(() => {
|
|
10591
11359
|
remoteProviderBusy = false;
|
|
10592
11360
|
frpConfigure.setAttribute("aria-busy", "false");
|
|
@@ -12460,6 +13228,8 @@ window.__ModuleLoader__.load({
|
|
|
12460
13228
|
.dsh-mobile-control__switcher{display:grid;grid-template-columns:repeat(2,1fr);gap:4px;margin:0 0 14px;padding:4px;border-radius:12px;background:var(--dsw-alias-bg-layer-1,#f3f5f8)}.dsh-mobile-control__switcher[hidden]{display:none}.dsh-mobile-control__tab{min-height:36px;border:0;border-radius:9px;background:transparent;color:var(--dsw-alias-label-secondary,#606873);font:600 13px/1 system-ui;cursor:pointer}.dsh-mobile-control__tab.is-active{background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);box-shadow:0 1px 3px rgb(15 23 42 / 10%)}.dsh-mobile-control__view[hidden]{display:none}.dsh-mobile-control__intro{margin:0 0 12px;color:var(--dsw-alias-label-secondary,#606873);font-size:12px;line-height:1.55}.dsh-mobile-control__http-frame-warning{box-sizing:border-box;margin:-4px 0 12px;padding:8px 10px;border:1px solid #d6a83f;border-radius:9px;background:var(--dsw-alias-bg-layer-1,#fff7df);color:var(--dsw-alias-label-primary,#694b00);font-size:11px;line-height:1.5;overflow-wrap:anywhere}.dsh-mobile-control__http-frame-warning[hidden]{display:none}.dsh-mobile-control__view.is-remote .dsh-mobile-control__actions{display:grid;grid-template-columns:1fr 1fr;gap:8px}.dsh-mobile-control__view.is-remote .dsh-mobile-control__actions button[hidden]{display:none}
|
|
12461
13229
|
.dsh-mobile-control__provider-section{position:relative;margin:0 0 14px}.dsh-mobile-control__section-title{margin:0 0 8px;color:var(--dsw-alias-label-primary,#16181d);font:650 13px/1.4 system-ui}.dsh-mobile-control__provider-section>.dsh-mobile-control__section-title{padding-right:42px}.dsh-mobile-control__provider-choices{display:grid;grid-template-columns:1fr;gap:8px}.dsh-mobile-control__provider{display:flex;min-width:0;flex-direction:column;gap:6px;min-height:94px;padding:10px 11px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);text-align:left;cursor:pointer;touch-action:manipulation;transition:border-color 160ms ease,background-color 160ms ease,box-shadow 160ms ease}.dsh-mobile-control__provider:hover{border-color:#6f96db;background:var(--dsw-alias-interactive-bg-hover-solid,var(--dsw-alias-bg-layer-1,#f8fbff))}.dsh-mobile-control__provider.is-selected{border-color:var(--dsw-alias-label-primary-bluish,#2563eb);background:var(--dsw-alias-interactive-bg-active,var(--dsw-alias-bg-layer-1,#f5f8ff));box-shadow:0 0 0 1px var(--dsw-alias-label-primary-bluish,#2563eb) inset}.dsh-mobile-control__provider:disabled{cursor:wait;opacity:.62}.dsh-mobile-control__provider-top{display:flex;min-width:0;align-items:flex-start;justify-content:space-between;gap:5px}.dsh-mobile-control__provider-top strong{min-width:0;font-size:12px;line-height:1.3}.dsh-mobile-control__provider-badge{flex:none;padding:2px 5px;border-radius:999px;background:var(--dsw-alias-state-business-tertiary,#e8f0ff);color:var(--dsw-alias-label-primary,#1d4ed8);font:650 11px/1.25 system-ui}.dsh-mobile-control__provider-badge.is-cpolar{background:var(--dsw-alias-state-success-tertiary,#eaf8f2);color:var(--dsw-alias-label-primary,#087454)}.dsh-mobile-control__provider-description{color:var(--dsw-alias-label-secondary,#606873);font-size:12px;line-height:1.45}.dsh-mobile-control__provider-info{position:absolute;z-index:5;top:-13px;right:-8px}.dsh-mobile-control__provider-info-button{display:flex;align-items:center;justify-content:center;width:44px;height:44px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--dsw-alias-label-secondary,#475569);cursor:pointer;touch-action:manipulation}.dsh-mobile-control__provider-info-button:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f5f9);color:var(--dsw-alias-label-primary-bluish,#2563eb)}.dsh-mobile-control__provider-info-glyph{display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:18px;height:18px;border:1.5px solid currentColor;border-radius:50%;font:700 12px/1 system-ui}.dsh-mobile-control__provider-info-popover{position:absolute;z-index:6;top:38px;right:4px;box-sizing:border-box;width:min(292px,calc(100vw - 72px));padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:12px;background:var(--dsw-alias-bg-layer-2,#fff);box-shadow:0 10px 28px rgb(15 23 42 / 16%)}.dsh-mobile-control__provider-info-popover[hidden]{display:none}.dsh-mobile-control__provider-info-popover strong,.dsh-mobile-control__provider-info-popover span{display:block}.dsh-mobile-control__provider-info-popover strong{margin-bottom:3px;font-size:12px}.dsh-mobile-control__provider-info-popover span{color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.55}
|
|
12462
13230
|
.dsh-mobile-control__self-hosted{margin:8px 0 0;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:12px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__self-hosted-summary{display:flex;box-sizing:border-box;min-height:48px;align-items:center;justify-content:space-between;gap:10px;padding:8px 11px;cursor:pointer;list-style-position:inside}.dsh-mobile-control__self-hosted-summary>span:first-child{display:flex;min-width:0;flex-direction:column;gap:1px}.dsh-mobile-control__self-hosted-summary strong{font-size:11px}.dsh-mobile-control__self-hosted-summary span span{color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.35}.dsh-mobile-control__provider-badge.is-frp{background:var(--dsw-alias-bg-layer-1,#eef0f3);color:var(--dsw-alias-label-secondary,#475569)}.dsh-mobile-control__self-hosted-body{padding:0 8px 8px}.dsh-mobile-control__provider.is-frp{width:100%;min-height:64px;background:var(--dsw-alias-bg-layer-2,#fff)}
|
|
13231
|
+
.dsh-mobile-control__frp-setup input[aria-invalid=true],.dsh-mobile-control__frp-setup select[aria-invalid=true]{border-color:var(--dsw-alias-state-error-primary,#bc3030)}
|
|
13232
|
+
.dsh-mobile-control__frp-setup .dsh-mobile-control__field>.dsh-mobile-control__origin-feedback{margin:0}
|
|
12463
13233
|
/* The cloudflared setup section deliberately reuses this card, so
|
|
12464
13234
|
.dsh-mobile-control__cloudflared-setup is a DOM hook for targeting rather than a
|
|
12465
13235
|
style rule of its own. */
|
|
@@ -12495,10 +13265,16 @@ window.__ModuleLoader__.load({
|
|
|
12495
13265
|
` + EXTENSION_RECOVERY_STYLES;
|
|
12496
13266
|
/** Mount the desktop control or mobile feature enhancements. */
|
|
12497
13267
|
function apply(ctx) {
|
|
13268
|
+
ctx.effect(() => installMobileUiLocalePersistence(document, window), "dsh-mobile: browser UI locale persistence");
|
|
12498
13269
|
ctx.effect(() => {
|
|
12499
13270
|
if (window.__DSH_MOBILE_FRONTEND__ !== "dedicated") return;
|
|
12500
|
-
|
|
13271
|
+
const connection = ctx.get("connection");
|
|
13272
|
+
if (connection !== void 0) return trustAuthenticatedGatewayConnection(connection);
|
|
12501
13273
|
}, "dsh-mobile: authenticated gateway client trust");
|
|
13274
|
+
ctx.effect(() => {
|
|
13275
|
+
if (!ownsMobileCustomization(document.querySelector("meta[name=\"dsh-mobile-gateway\"][content=\"authenticated\"]") !== null, window.__DSH_MOBILE_FRONTEND__)) return;
|
|
13276
|
+
return installMobileConnectionRecovery(() => ctx.get("connection"), window, document);
|
|
13277
|
+
}, "dsh-mobile: connection recovery");
|
|
12502
13278
|
ctx.effect(() => {
|
|
12503
13279
|
const desktopAdmin = isDesktopAdminSurface(location.hostname, location.search, window.__DSH_MOBILE_FRONTEND__, location.protocol);
|
|
12504
13280
|
const gatewayCustomization = ownsMobileCustomization(document.querySelector("meta[name=\"dsh-mobile-gateway\"][content=\"authenticated\"]") !== null, window.__DSH_MOBILE_FRONTEND__);
|
|
@@ -12509,6 +13285,7 @@ window.__ModuleLoader__.load({
|
|
|
12509
13285
|
document.head.append(style);
|
|
12510
13286
|
const removeCustom = !desktopAdmin || gatewayCustomization ? installCustomAssets() : () => void 0;
|
|
12511
13287
|
const removeVoiceSession = installVoiceSession(!desktopAdmin);
|
|
13288
|
+
const removeDraftTransfer = !desktopAdmin && document.querySelector("meta[name=\"dsh-mobile-gateway\"][content=\"authenticated\"]") !== null ? installDraftTransfer(ctx, window, document) : () => void 0;
|
|
12512
13289
|
const removeModuleSettings = ctx.slots.inject("settings.general.item", () => ctx.slots.register({
|
|
12513
13290
|
name: "settings.general.item",
|
|
12514
13291
|
id: "dsh-mobile-client-modules",
|
|
@@ -12562,6 +13339,7 @@ window.__ModuleLoader__.load({
|
|
|
12562
13339
|
removeCustom();
|
|
12563
13340
|
removeSurface();
|
|
12564
13341
|
removeVoiceSession();
|
|
13342
|
+
removeDraftTransfer();
|
|
12565
13343
|
localFont.dispose();
|
|
12566
13344
|
style.remove();
|
|
12567
13345
|
};
|
|
@@ -12612,6 +13390,7 @@ window.__ModuleLoader__.load({
|
|
|
12612
13390
|
removeControl();
|
|
12613
13391
|
removeCustom();
|
|
12614
13392
|
removeVoiceSession();
|
|
13393
|
+
removeDraftTransfer();
|
|
12615
13394
|
style.remove();
|
|
12616
13395
|
};
|
|
12617
13396
|
}, "dsh-mobile: stock mobile adaptation and local control");
|
|
@@ -12660,6 +13439,8 @@ window.__ModuleLoader__.load({
|
|
|
12660
13439
|
exports.selectMobileControlLocale = selectMobileControlLocale;
|
|
12661
13440
|
exports.selectedMobileControlLocale = selectedMobileControlLocale;
|
|
12662
13441
|
exports.setMobileSurfaceOpen = setMobileSurfaceOpen;
|
|
13442
|
+
exports.shouldNudgeReconnect = shouldNudgeReconnect;
|
|
13443
|
+
exports.shouldRenewSession = shouldRenewSession;
|
|
12663
13444
|
exports.startExtensionChangeStream = startExtensionChangeStream;
|
|
12664
13445
|
exports.startLifecycleRefreshScheduler = startLifecycleRefreshScheduler;
|
|
12665
13446
|
exports.trustAuthenticatedGatewayConnection = trustAuthenticatedGatewayConnection;
|