dsh-mobile 0.6.14 → 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 +32 -0
- package/CONTRIBUTORS.md +22 -2
- package/README.en.md +8 -6
- package/README.md +8 -6
- package/SECURITY.md +5 -1
- package/THIRD_PARTY_NOTICES.md +30 -2
- package/apps/mobile/README.md +35 -5
- package/apps/mobile/README.zh-CN.md +35 -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/docs/SLOW_CONNECTIONS.en.md +11 -1
- package/docs/SLOW_CONNECTIONS.md +11 -1
- package/lib/client.js +846 -60
- package/lib/client.js.map +1 -1
- package/lib/extension-worker-runtime.mjs.map +1 -1
- package/lib/index.d.mts +28 -2
- package/lib/index.mjs +499 -50
- package/lib/index.mjs.map +1 -1
- package/lib/mobile-compat.js +1 -1
- package/lib/mobile-compat.js.map +1 -1
- package/package.json +9 -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({
|
|
@@ -6001,12 +6537,17 @@ window.__ModuleLoader__.load({
|
|
|
6001
6537
|
/** Browser-safe desktop-admin Host checks. Do not import Node APIs here. */
|
|
6002
6538
|
const DESKTOP_ADMIN_HEADER = "x-dsh-mobile-desktop-admin";
|
|
6003
6539
|
const DESKTOP_ADMIN_MARKER = "dsh-app://app";
|
|
6004
|
-
/**
|
|
6540
|
+
/** Non-simple source marker for this plugin's same-origin JSON management actions. */
|
|
6541
|
+
const WEB_ADMIN_HEADER = "x-dsh-mobile-web-admin";
|
|
6542
|
+
const WEB_ADMIN_MARKER = "same-origin-json";
|
|
6543
|
+
/** Attach separate Desktop/Web source markers only to matching mutating admin requests. */
|
|
6005
6544
|
function localAdminRequestHeaders(init, location) {
|
|
6006
6545
|
const headers = new Headers(init?.headers);
|
|
6007
6546
|
if (!headers.has("content-type")) headers.set("content-type", "application/json");
|
|
6008
6547
|
headers.delete(DESKTOP_ADMIN_HEADER);
|
|
6548
|
+
headers.delete(WEB_ADMIN_HEADER);
|
|
6009
6549
|
if (init?.method?.toUpperCase() === "POST" && location.protocol === "dsh-app:" && location.hostname === "app") headers.set(DESKTOP_ADMIN_HEADER, DESKTOP_ADMIN_MARKER);
|
|
6550
|
+
else if (init?.method?.toUpperCase() === "POST" && (location.protocol === "http:" || location.protocol === "https:") && isLocalAdminHostname(location.hostname) && headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() === "application/json") headers.set(WEB_ADMIN_HEADER, WEB_ADMIN_MARKER);
|
|
6010
6551
|
return headers;
|
|
6011
6552
|
}
|
|
6012
6553
|
function unwrapBrackets(hostname) {
|
|
@@ -7194,6 +7735,161 @@ window.__ModuleLoader__.load({
|
|
|
7194
7735
|
assetDepth: 8
|
|
7195
7736
|
});
|
|
7196
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
|
|
7197
7893
|
//#region src/client.ts
|
|
7198
7894
|
/** Map a host-reported provider onto the UI union, defaulting to the built-in one. */
|
|
7199
7895
|
function parseMobileRemoteProvider(value) {
|
|
@@ -7742,6 +8438,10 @@ window.__ModuleLoader__.load({
|
|
|
7742
8438
|
frp_discovery_invalid: "frpDiscoveryInvalid",
|
|
7743
8439
|
frp_stopped: "frpStopped",
|
|
7744
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",
|
|
7745
8445
|
frp_attach_mode_requires_vhost_port: "frpAttachModeRequiresVhostPort",
|
|
7746
8446
|
frp_attach_cert_unknown: "frpAttachCertUnknownError",
|
|
7747
8447
|
frp_entry_tls_invalid: "frpEntryTlsInvalid",
|
|
@@ -8475,6 +9175,52 @@ window.__ModuleLoader__.load({
|
|
|
8475
9175
|
frpPortLabel.append(frpPort);
|
|
8476
9176
|
frpTokenLabel.append(frpToken);
|
|
8477
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") ?? ""}`);
|
|
8478
9224
|
frpFields.append(frpServerLabel, frpPortLabel, frpTokenLabel, frpOriginLabel, frpModeLabel, frpEntryTlsLabel, frpVhostLabel, frpPublicPortLabel);
|
|
8479
9225
|
frpStep1.append(frpStep1Title, frpStep1Text, frpFields);
|
|
8480
9226
|
const frpStep2 = element("section", "dsh-mobile-control__frp-step");
|
|
@@ -8576,10 +9322,18 @@ window.__ModuleLoader__.load({
|
|
|
8576
9322
|
frpAppRequirement.textContent = t("frpAppRequirement");
|
|
8577
9323
|
const frpConfigurationStatus = element("p", "dsh-mobile-control__component-status");
|
|
8578
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;
|
|
8579
9332
|
const frpConfigure = element("button", "dsh-mobile-control__primary dsh-mobile-control__frp-action");
|
|
8580
9333
|
frpConfigure.type = "button";
|
|
8581
9334
|
frpConfigure.textContent = t("frpSaveConnect");
|
|
8582
|
-
|
|
9335
|
+
frpConfigure.setAttribute("aria-describedby", frpActionHint.id);
|
|
9336
|
+
frpStep4.append(frpStep4Title, frpStep4Text, frpAppRequirement, frpConfigurationStatus, frpFeedback, frpActionHint, frpConfigure);
|
|
8583
9337
|
const frpDetails = element("details", "dsh-mobile-control__details");
|
|
8584
9338
|
const frpDetailsSummary = element("summary");
|
|
8585
9339
|
frpDetailsSummary.textContent = t("frpComponentDetails");
|
|
@@ -9655,6 +10409,8 @@ window.__ModuleLoader__.load({
|
|
|
9655
10409
|
frpInstall.textContent = frpDownloadBytes > 0 ? t("installWithSize", { size: formatMegabytes(frpDownloadBytes) }) : t("installFrpc");
|
|
9656
10410
|
frpInstall.disabled = remoteProviderBusy;
|
|
9657
10411
|
frpConfigure.disabled = remoteProviderBusy || !frpInstalled;
|
|
10412
|
+
frpActionHint.hidden = !frpConfigure.disabled;
|
|
10413
|
+
frpActionHint.textContent = t(remoteProviderBusy ? "frpActionBusy" : !frpSupported ? "frpUnsupported" : "frpInstallBeforeConnect");
|
|
9658
10414
|
vpsDeploy.disabled = remoteProviderBusy || !validVpsDeploymentForm();
|
|
9659
10415
|
vpsCopyUninstall.disabled = vpsDeploy.disabled;
|
|
9660
10416
|
vpsUninstall.disabled = vpsDeploy.disabled;
|
|
@@ -10092,7 +10848,6 @@ window.__ModuleLoader__.load({
|
|
|
10092
10848
|
loadRemote();
|
|
10093
10849
|
});
|
|
10094
10850
|
});
|
|
10095
|
-
const validFrpServer = (value) => value === value.trim() && value.length > 0 && value.length <= 253 && !/[\s\u0000-\u001f\u007f/\\@?#]/u.test(value);
|
|
10096
10851
|
const frpForm = () => {
|
|
10097
10852
|
const vhostHttpPort = Number(frpVhostPort.value);
|
|
10098
10853
|
const publicPort = Number(frpPublicPort.value);
|
|
@@ -10104,8 +10859,8 @@ window.__ModuleLoader__.load({
|
|
|
10104
10859
|
publicOrigin: String(frpOrigin.value ?? "").trim(),
|
|
10105
10860
|
mode,
|
|
10106
10861
|
entryTls: frpEntryTls.value === "self-signed" ? "self-signed" : "public-ip-cert",
|
|
10107
|
-
...
|
|
10108
|
-
...
|
|
10862
|
+
...frpEntryTls.value === "self-signed" || frpVhostPort.value.trim() === "" ? {} : { vhostHttpPort },
|
|
10863
|
+
...frpEntryTls.value === "self-signed" ? { publicPort } : {}
|
|
10109
10864
|
};
|
|
10110
10865
|
};
|
|
10111
10866
|
/**
|
|
@@ -10122,20 +10877,39 @@ window.__ModuleLoader__.load({
|
|
|
10122
10877
|
...form.publicPort === void 0 ? {} : { publicPort: form.publicPort }
|
|
10123
10878
|
});
|
|
10124
10879
|
const validFrpForm = (form) => {
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
|
|
10129
|
-
|
|
10130
|
-
|
|
10131
|
-
|
|
10132
|
-
|
|
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
|
+
});
|
|
10133
10910
|
frpCopyTemplate.addEventListener("click", () => {
|
|
10134
10911
|
const form = frpForm();
|
|
10135
|
-
if (!
|
|
10136
|
-
remoteStatus.textContent = t("frpInputInvalid");
|
|
10137
|
-
return;
|
|
10138
|
-
}
|
|
10912
|
+
if (!checkFrpForm(form)) return;
|
|
10139
10913
|
navigator.clipboard.writeText(createFrpServerTemplateForClipboard(form.serverPort, form.token, form.publicOrigin)).then(() => {
|
|
10140
10914
|
remoteStatus.textContent = t("templateCopied");
|
|
10141
10915
|
}, () => {
|
|
@@ -10145,10 +10919,13 @@ window.__ModuleLoader__.load({
|
|
|
10145
10919
|
const frpAttachErrorText = (code) => t(REMOTE_ERROR_MESSAGE_KEYS[code] ?? "frpInputInvalid");
|
|
10146
10920
|
const showFrpAttachError = (code) => {
|
|
10147
10921
|
const message = frpAttachErrorText(code);
|
|
10148
|
-
|
|
10149
|
-
|
|
10150
|
-
|
|
10151
|
-
|
|
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;
|
|
10152
10929
|
}
|
|
10153
10930
|
remoteStatus.textContent = message;
|
|
10154
10931
|
};
|
|
@@ -10161,10 +10938,7 @@ window.__ModuleLoader__.load({
|
|
|
10161
10938
|
};
|
|
10162
10939
|
frpCopyAttachPlan.addEventListener("click", () => {
|
|
10163
10940
|
const form = frpForm();
|
|
10164
|
-
if (!
|
|
10165
|
-
remoteStatus.textContent = t("frpInputInvalid");
|
|
10166
|
-
return;
|
|
10167
|
-
}
|
|
10941
|
+
if (!checkFrpForm(form, frpConfigured)) return;
|
|
10168
10942
|
if (form.token !== "") {
|
|
10169
10943
|
const attachForm = attachClipboardForm(form);
|
|
10170
10944
|
const code = frpAttachFormErrorCode(attachForm);
|
|
@@ -10197,7 +10971,7 @@ window.__ModuleLoader__.load({
|
|
|
10197
10971
|
remoteStatus.textContent = t("frpAttachPlanCopied");
|
|
10198
10972
|
});
|
|
10199
10973
|
}, (error) => {
|
|
10200
|
-
|
|
10974
|
+
showFrpAttachError(error instanceof Error ? error.message : String(error));
|
|
10201
10975
|
}).catch((error) => {
|
|
10202
10976
|
remoteStatus.textContent = t("frpAttachPlanFailed", { error: String(error) });
|
|
10203
10977
|
}).finally(() => {
|
|
@@ -10208,10 +10982,7 @@ window.__ModuleLoader__.load({
|
|
|
10208
10982
|
/** Copy a token-bearing frpc.toml only from the token the user just typed. */
|
|
10209
10983
|
frpCopyAttachToken.addEventListener("click", () => {
|
|
10210
10984
|
const form = frpForm();
|
|
10211
|
-
if (!
|
|
10212
|
-
remoteStatus.textContent = t("frpInputInvalid");
|
|
10213
|
-
return;
|
|
10214
|
-
}
|
|
10985
|
+
if (!checkFrpForm(form, frpConfigured)) return;
|
|
10215
10986
|
if (form.token !== "") {
|
|
10216
10987
|
const attachForm = attachClipboardForm(form);
|
|
10217
10988
|
const code = frpAttachFormErrorCode(attachForm);
|
|
@@ -10558,15 +11329,13 @@ window.__ModuleLoader__.load({
|
|
|
10558
11329
|
const form = frpForm();
|
|
10559
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;
|
|
10560
11331
|
if (!unchanged) {
|
|
10561
|
-
|
|
10562
|
-
if (code !== void 0) {
|
|
10563
|
-
if (form.mode === "attach") showFrpAttachError(code);
|
|
10564
|
-
else remoteStatus.textContent = t("frpInputInvalid");
|
|
10565
|
-
return;
|
|
10566
|
-
}
|
|
11332
|
+
if (!checkFrpForm(form)) return;
|
|
10567
11333
|
}
|
|
11334
|
+
renderFrpIssues([]);
|
|
10568
11335
|
remoteProviderBusy = true;
|
|
10569
11336
|
frpConfigure.disabled = true;
|
|
11337
|
+
frpActionHint.hidden = false;
|
|
11338
|
+
frpActionHint.textContent = t("frpActionBusy");
|
|
10570
11339
|
frpConfigure.setAttribute("aria-busy", "true");
|
|
10571
11340
|
frpConfigure.textContent = t("saving");
|
|
10572
11341
|
(unchanged ? Promise.resolve({}) : controlRequestJson("/api/mobile-access/remote/frp/configure", {
|
|
@@ -10580,8 +11349,12 @@ window.__ModuleLoader__.load({
|
|
|
10580
11349
|
body: JSON.stringify({ running: true })
|
|
10581
11350
|
});
|
|
10582
11351
|
}).then(renderRemote, (error) => {
|
|
10583
|
-
|
|
10584
|
-
|
|
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
|
+
}
|
|
10585
11358
|
}).finally(() => {
|
|
10586
11359
|
remoteProviderBusy = false;
|
|
10587
11360
|
frpConfigure.setAttribute("aria-busy", "false");
|
|
@@ -12455,6 +13228,8 @@ window.__ModuleLoader__.load({
|
|
|
12455
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}
|
|
12456
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}
|
|
12457
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}
|
|
12458
13233
|
/* The cloudflared setup section deliberately reuses this card, so
|
|
12459
13234
|
.dsh-mobile-control__cloudflared-setup is a DOM hook for targeting rather than a
|
|
12460
13235
|
style rule of its own. */
|
|
@@ -12490,10 +13265,16 @@ window.__ModuleLoader__.load({
|
|
|
12490
13265
|
` + EXTENSION_RECOVERY_STYLES;
|
|
12491
13266
|
/** Mount the desktop control or mobile feature enhancements. */
|
|
12492
13267
|
function apply(ctx) {
|
|
13268
|
+
ctx.effect(() => installMobileUiLocalePersistence(document, window), "dsh-mobile: browser UI locale persistence");
|
|
12493
13269
|
ctx.effect(() => {
|
|
12494
13270
|
if (window.__DSH_MOBILE_FRONTEND__ !== "dedicated") return;
|
|
12495
|
-
|
|
13271
|
+
const connection = ctx.get("connection");
|
|
13272
|
+
if (connection !== void 0) return trustAuthenticatedGatewayConnection(connection);
|
|
12496
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");
|
|
12497
13278
|
ctx.effect(() => {
|
|
12498
13279
|
const desktopAdmin = isDesktopAdminSurface(location.hostname, location.search, window.__DSH_MOBILE_FRONTEND__, location.protocol);
|
|
12499
13280
|
const gatewayCustomization = ownsMobileCustomization(document.querySelector("meta[name=\"dsh-mobile-gateway\"][content=\"authenticated\"]") !== null, window.__DSH_MOBILE_FRONTEND__);
|
|
@@ -12504,6 +13285,7 @@ window.__ModuleLoader__.load({
|
|
|
12504
13285
|
document.head.append(style);
|
|
12505
13286
|
const removeCustom = !desktopAdmin || gatewayCustomization ? installCustomAssets() : () => void 0;
|
|
12506
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;
|
|
12507
13289
|
const removeModuleSettings = ctx.slots.inject("settings.general.item", () => ctx.slots.register({
|
|
12508
13290
|
name: "settings.general.item",
|
|
12509
13291
|
id: "dsh-mobile-client-modules",
|
|
@@ -12557,6 +13339,7 @@ window.__ModuleLoader__.load({
|
|
|
12557
13339
|
removeCustom();
|
|
12558
13340
|
removeSurface();
|
|
12559
13341
|
removeVoiceSession();
|
|
13342
|
+
removeDraftTransfer();
|
|
12560
13343
|
localFont.dispose();
|
|
12561
13344
|
style.remove();
|
|
12562
13345
|
};
|
|
@@ -12607,6 +13390,7 @@ window.__ModuleLoader__.load({
|
|
|
12607
13390
|
removeControl();
|
|
12608
13391
|
removeCustom();
|
|
12609
13392
|
removeVoiceSession();
|
|
13393
|
+
removeDraftTransfer();
|
|
12610
13394
|
style.remove();
|
|
12611
13395
|
};
|
|
12612
13396
|
}, "dsh-mobile: stock mobile adaptation and local control");
|
|
@@ -12655,6 +13439,8 @@ window.__ModuleLoader__.load({
|
|
|
12655
13439
|
exports.selectMobileControlLocale = selectMobileControlLocale;
|
|
12656
13440
|
exports.selectedMobileControlLocale = selectedMobileControlLocale;
|
|
12657
13441
|
exports.setMobileSurfaceOpen = setMobileSurfaceOpen;
|
|
13442
|
+
exports.shouldNudgeReconnect = shouldNudgeReconnect;
|
|
13443
|
+
exports.shouldRenewSession = shouldRenewSession;
|
|
12658
13444
|
exports.startExtensionChangeStream = startExtensionChangeStream;
|
|
12659
13445
|
exports.startLifecycleRefreshScheduler = startLifecycleRefreshScheduler;
|
|
12660
13446
|
exports.trustAuthenticatedGatewayConnection = trustAuthenticatedGatewayConnection;
|