dsh-mobile 0.5.1 → 0.5.3
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 +17 -0
- package/CONTRIBUTORS.md +5 -4
- package/README.en.md +21 -8
- package/README.md +21 -8
- package/cordis.patch.yml +6 -0
- package/docs/ATTACH_EXISTING_FRPS.en.md +3 -1
- package/docs/ATTACH_EXISTING_FRPS.md +3 -1
- package/docs/SELF_HOSTED_FRP.en.md +3 -1
- package/docs/SELF_HOSTED_FRP.md +3 -1
- package/lib/client.js +196 -13
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +170 -120
- package/lib/index.mjs +141 -9
- package/lib/index.mjs.map +1 -1
- package/node_modules/dsh-mobile-question-fixes/lib/client.js +78 -0
- package/node_modules/dsh-mobile-question-fixes/lib/client.js.map +1 -0
- package/node_modules/dsh-mobile-question-fixes/lib/index.mjs +14 -0
- package/node_modules/dsh-mobile-question-fixes/lib/index.mjs.map +1 -0
- package/node_modules/dsh-mobile-question-fixes/package.json +25 -0
- package/package.json +16 -7
- package/packages/question-fixes/lib/client.js +78 -0
- package/packages/question-fixes/lib/client.js.map +1 -0
- package/packages/question-fixes/lib/index.mjs +14 -0
- package/packages/question-fixes/lib/index.mjs.map +1 -0
- package/packages/question-fixes/package.json +25 -0
package/lib/client.js
CHANGED
|
@@ -664,6 +664,9 @@ window.__ModuleLoader__.load({
|
|
|
664
664
|
frpVhostProbeFailed: "The VPS address could not be checked. Confirm DNS and retry.",
|
|
665
665
|
frpLaunchFailed: "frpc failed to start.",
|
|
666
666
|
frpTimeout: "The public endpoint did not become ready. Check frps, Caddy, DNS, and firewall.",
|
|
667
|
+
frpProxyNameInUse: "The frps already has a proxy with this installation name in use — usually another DSH computer, or a session that has not expired yet. Disable self-hosted FRP on that machine, or remove that proxy on the frps, then retry.",
|
|
668
|
+
frpRemotePortInUse: "Another proxy on the frps already owns this public entry port. Choose a different entry port and retry.",
|
|
669
|
+
frpProxyStartFailed: "The frps refused frpc's proxy registration. Check its allowPorts, the ports already in use, and its log.",
|
|
667
670
|
frpDiscoveryMismatch: "The public domain points to another DSH computer. Check the Caddy and frps mappings.",
|
|
668
671
|
frpDiscoveryInvalid: "The public domain did not return a valid DSH Mobile discovery response.",
|
|
669
672
|
frpStopped: "The FRP connection stopped.",
|
|
@@ -751,6 +754,9 @@ window.__ModuleLoader__.load({
|
|
|
751
754
|
frpVhostProbeFailed: "Impossibile controllare il VPS. Verifica il DNS e riprova.",
|
|
752
755
|
frpLaunchFailed: "Avvio di frpc non riuscito.",
|
|
753
756
|
frpTimeout: "Endpoint pubblico non pronto. Controlla frps, Caddy, DNS e firewall.",
|
|
757
|
+
frpProxyNameInUse: "Sul frps è già in uso un proxy con il nome di questa installazione — di solito un altro computer DSH o una sessione non ancora scaduta. Disattiva l’FRP autogestito su quella macchina, oppure rimuovi quel proxy sul frps, e riprova.",
|
|
758
|
+
frpRemotePortInUse: "Un altro proxy sul frps occupa già questa porta di ingresso pubblica. Scegli un’altra porta di ingresso e riprova.",
|
|
759
|
+
frpProxyStartFailed: "Il frps ha rifiutato la registrazione del proxy da parte di frpc. Controlla allowPorts, le porte già in uso e il log del server.",
|
|
754
760
|
frpDiscoveryMismatch: "Il dominio pubblico punta a un altro computer DSH. Controlla Caddy e frps.",
|
|
755
761
|
frpDiscoveryInvalid: "Il dominio pubblico non ha restituito una risposta DSH Mobile valida.",
|
|
756
762
|
frpStopped: "Connessione FRP arrestata.",
|
|
@@ -838,6 +844,9 @@ window.__ModuleLoader__.load({
|
|
|
838
844
|
frpVhostProbeFailed: "无法检查 VPS 地址,请确认 DNS 后重试。",
|
|
839
845
|
frpLaunchFailed: "frpc 未能启动。",
|
|
840
846
|
frpTimeout: "公网端点未能就绪,请检查 frps、Caddy、DNS 和防火墙。",
|
|
847
|
+
frpProxyNameInUse: "该 frps 上已有本机安装名对应的 FRP 代理在使用中——通常来自另一台 DSH 电脑,或尚未过期的旧连接。请在那台机器上关闭自建 FRP,或在 frps 上移除该代理后重试。",
|
|
848
|
+
frpRemotePortInUse: "该公网入口端口已被 frps 上的其他代理占用,请改用其他入口端口后重试。",
|
|
849
|
+
frpProxyStartFailed: "frps 拒绝了 frpc 的代理注册,请检查 frps 的 allowPorts、端口占用与服务器日志。",
|
|
841
850
|
frpDiscoveryMismatch: "公开域名连接到了另一台 DSH 电脑,请检查 Caddy 与 frps 映射。",
|
|
842
851
|
frpDiscoveryInvalid: "公开域名没有返回有效的 DSH Mobile 发现信息。",
|
|
843
852
|
frpStopped: "FRP 连接已停止。",
|
|
@@ -2040,13 +2049,39 @@ window.__ModuleLoader__.load({
|
|
|
2040
2049
|
return JSON.stringify(value);
|
|
2041
2050
|
}
|
|
2042
2051
|
/**
|
|
2052
|
+
* Legacy proxy name, kept only as the default of {@link createFrpcToml}.
|
|
2053
|
+
*
|
|
2054
|
+
* A shared frps accepts one proxy per name across *all* its clients, so a fixed
|
|
2055
|
+
* name let any other DSH installation — or a not yet reaped session of this one —
|
|
2056
|
+
* own `dsh-mobile` first. frpc then logged `start error: proxy name [dsh-mobile]
|
|
2057
|
+
* is already in use`, kept running without publishing the entry port, and the
|
|
2058
|
+
* start-up self-check ended in `frp_start_timeout` ("the public endpoint did not
|
|
2059
|
+
* become ready"). Product callers therefore pass an installation-derived name
|
|
2060
|
+
* from {@link frpProxyName}; this constant only keeps the exported API and the
|
|
2061
|
+
* byte-for-byte compatibility test working for legacy callers.
|
|
2062
|
+
*/
|
|
2063
|
+
const FRP_DEFAULT_PROXY_NAME = "dsh-mobile";
|
|
2064
|
+
/**
|
|
2065
|
+
* Validate a proxy name for the generated frpc configuration.
|
|
2066
|
+
*
|
|
2067
|
+
* frps accepts one proxy per name on the whole server, so the name must be a
|
|
2068
|
+
* single safe token: no whitespace, no quotes, no path or bracket characters.
|
|
2069
|
+
*/
|
|
2070
|
+
function validateFrpProxyName(value) {
|
|
2071
|
+
if (typeof value !== "string" || value.length < 1 || value.length > 128 || !/^[A-Za-z0-9._-]+$/u.test(value)) throw new Error("frp_proxy_name_invalid");
|
|
2072
|
+
return value;
|
|
2073
|
+
}
|
|
2074
|
+
/**
|
|
2043
2075
|
* Build the single-purpose frpc configuration for the current loopback gateway.
|
|
2044
2076
|
*
|
|
2045
2077
|
* The default (public-CA) entry is an HTTP vhost behind Caddy and is emitted byte
|
|
2046
2078
|
* for byte as before. The self-signed entry cannot use a vhost at all: frps only
|
|
2047
2079
|
* forwards raw TCP, and the gateway terminates TLS on that connection.
|
|
2080
|
+
*
|
|
2081
|
+
* `proxyName` defaults to the legacy fixed name only for legacy callers; the
|
|
2082
|
+
* provider and the attach preview always pass {@link frpProxyName}.
|
|
2048
2083
|
*/
|
|
2049
|
-
function createFrpcToml(settings, localPort) {
|
|
2084
|
+
function createFrpcToml(settings, localPort, proxyName = FRP_DEFAULT_PROXY_NAME) {
|
|
2050
2085
|
if (!Number.isSafeInteger(localPort) || localPort < 1 || localPort > 65535) throw new Error("frp_local_port_invalid");
|
|
2051
2086
|
const header = [
|
|
2052
2087
|
`serverAddr = ${tomlString(settings.serverAddress)}`,
|
|
@@ -2056,7 +2091,7 @@ window.__ModuleLoader__.load({
|
|
|
2056
2091
|
"transport.tls.enable = true",
|
|
2057
2092
|
"",
|
|
2058
2093
|
"[[proxies]]",
|
|
2059
|
-
|
|
2094
|
+
`name = ${tomlString(validateFrpProxyName(proxyName))}`
|
|
2060
2095
|
];
|
|
2061
2096
|
if (isFrpSelfSignedIngress(settings)) return [
|
|
2062
2097
|
...header,
|
|
@@ -2125,7 +2160,7 @@ window.__ModuleLoader__.load({
|
|
|
2125
2160
|
* the provider writes stays byte-for-byte identical to the upstream output.
|
|
2126
2161
|
*/
|
|
2127
2162
|
function createFrpAttachFrpcToml(settings, options = {}) {
|
|
2128
|
-
const frpcToml = createFrpcToml(settings, attachLocalPort(options));
|
|
2163
|
+
const frpcToml = createFrpcToml(settings, attachLocalPort(options), options.proxyName ?? "dsh-mobile");
|
|
2129
2164
|
return options.revealToken === true ? frpcToml : redactFrpcTomlToken(frpcToml);
|
|
2130
2165
|
}
|
|
2131
2166
|
const DEFAULT_ATTACH_CONFIG_FILE = "<DSH Mobile 私有目录>/remote/frp/config/frpc.toml";
|
|
@@ -2700,7 +2735,9 @@ window.__ModuleLoader__.load({
|
|
|
2700
2735
|
[data-dsh-mobile-history-loader] { position:relative !important; min-height:1px !important; }
|
|
2701
2736
|
[data-dsh-mobile-history-loader] button:not(:disabled) { position:absolute !important; width:1px !important; height:1px !important; margin:-1px !important; padding:0 !important; clip-path:inset(50%) !important; opacity:0 !important; overflow:hidden !important; pointer-events:none !important; }
|
|
2702
2737
|
[data-dsh-mobile-history-loader] button:disabled { min-height:28px !important; padding:4px 12px !important; }
|
|
2703
|
-
|
|
2738
|
+
/* DSH's sibling margins exclude hidden and empty Chat Node Seats. Keep that
|
|
2739
|
+
spacing rule so searchable hidden seats contribute no folded-turn gaps. */
|
|
2740
|
+
[data-dsh-mobile-message-column] { box-sizing:border-box !important; width:100% !important; max-width:none !important; margin:0 !important; padding:0 !important; gap:0 !important; --dsh-chat-flow-gap:10px; }
|
|
2704
2741
|
[data-dsh-mobile-message-column] > * { width:100% !important; max-width:100% !important; }
|
|
2705
2742
|
[data-dsh-mobile-message-column] [data-disclosure-row] { box-sizing:border-box !important; display:grid !important; grid-template-columns:16px minmax(0,1fr) !important; grid-auto-rows:auto !important; align-items:center !important; column-gap:6px !important; width:100% !important; height:auto !important; min-height:40px !important; padding:4px 0 !important; }
|
|
2706
2743
|
[data-dsh-mobile-message-column] [data-disclosure-row] > [class*="_leading"] { grid-column:1 !important; grid-row:1 !important; margin-right:0 !important; }
|
|
@@ -2722,12 +2759,27 @@ window.__ModuleLoader__.load({
|
|
|
2722
2759
|
/* The context ring is a composer-metadata _root too, so the metadata rule
|
|
2723
2760
|
above stretched it across the whole dock. It cannot shrink, which left the
|
|
2724
2761
|
session statistics beside it a few pixels wide, every number clipped to an
|
|
2725
|
-
ellipsis.
|
|
2726
|
-
|
|
2727
|
-
a wide screen keeps the ring beside them on one line. */
|
|
2762
|
+
ellipsis. Keep the ring at its own size and let it hold the row the numbers
|
|
2763
|
+
are on. */
|
|
2728
2764
|
[data-dsh-mobile-center] [data-composer-card] ~ * [class*="_root"]:has(> [class*="_trigger"][aria-haspopup="dialog"]) { width:auto !important; max-width:none !important; flex:0 0 auto !important; }
|
|
2729
|
-
|
|
2730
|
-
|
|
2765
|
+
/* Slot entries participate in this flex layout through display:contents.
|
|
2766
|
+
Keep core statistics and the meter together; extra entries use their own
|
|
2767
|
+
rows instead of taking the statistics' remaining width on phones. */
|
|
2768
|
+
[data-dsh-mobile-center] [class*="_dock"]:has([data-composer-stats]) { display:flex !important; flex-wrap:wrap !important; justify-content:flex-start !important; align-items:center !important; column-gap:12px !important; row-gap:2px !important; width:100% !important; max-width:100% !important; }
|
|
2769
|
+
/* Dock row gaps replace the single-row metadata's negative margin. */
|
|
2770
|
+
[data-dsh-mobile-center] [class*="_dock"]:has([data-composer-stats]) > *,
|
|
2771
|
+
[data-dsh-mobile-center] [class*="_dock"]:has([data-composer-stats]) [data-slot="conversation.composer.dock"] > * { margin-bottom:0 !important; }
|
|
2772
|
+
[data-dsh-mobile-center] [class*="_dock"]:has([data-composer-stats]) [data-slot="conversation.composer.dock"] > :not([data-composer-stats]) { flex:0 1 100% !important; min-width:0 !important; max-width:100% !important; }
|
|
2773
|
+
[data-dsh-mobile-center] [class*="_dock"]:has([data-composer-stats]) [data-composer-stats] { order:-2 !important; }
|
|
2774
|
+
[data-dsh-mobile-center] [class*="_dock"]:has([data-composer-stats]) > [class*="_root"]:has(> [class*="_trigger"][aria-haspopup="dialog"]) { order:-1 !important; }
|
|
2775
|
+
/* Interactive pills expose complete values in their labels and dialogs.
|
|
2776
|
+
Static compact or untimed pills have no disclosure, so they may wrap. */
|
|
2777
|
+
[data-dsh-mobile-center] [data-composer-card] ~ * [class*="_root"][data-composer-stats] { box-sizing:border-box !important; width:auto !important; max-width:none !important; flex:1 1 0 !important; flex-wrap:nowrap !important; justify-content:flex-start !important; overflow:hidden !important; row-gap:2px !important; }
|
|
2778
|
+
[data-dsh-mobile-center] [data-composer-stats] > * { min-width:0 !important; }
|
|
2779
|
+
[data-dsh-mobile-center] [data-composer-stats] [class*="_pill"] { display:block !important; min-width:0 !important; white-space:nowrap !important; overflow:hidden !important; text-overflow:ellipsis !important; }
|
|
2780
|
+
[data-dsh-mobile-center] [data-composer-stats] [class*="_pill"] svg { display:inline-block !important; vertical-align:-2px !important; margin-right:6px !important; }
|
|
2781
|
+
[data-dsh-mobile-center] [data-composer-stats]:has(span[class*="_pill"]) { flex-wrap:wrap !important; overflow:visible !important; }
|
|
2782
|
+
[data-dsh-mobile-center] [data-composer-stats] span[class*="_pill"] { flex:0 1 auto !important; max-width:100% !important; white-space:normal !important; overflow:visible !important; overflow-wrap:anywhere !important; text-overflow:clip !important; }
|
|
2731
2783
|
/* Message runtime details are inline on desktop. Give the clock/runtime
|
|
2732
2784
|
label its own wrapping row on narrow screens so TTFT and throughput do
|
|
2733
2785
|
not push the action buttons or clip at the viewport edge. */
|
|
@@ -3762,6 +3814,107 @@ window.__ModuleLoader__.load({
|
|
|
3762
3814
|
};
|
|
3763
3815
|
}
|
|
3764
3816
|
//#endregion
|
|
3817
|
+
//#region src/voice-session.ts
|
|
3818
|
+
/** Screen wake-lock ownership and mobile composer focus during DSH dictation. */
|
|
3819
|
+
/** Voice phases during which the user is dictating. */
|
|
3820
|
+
const DICTATING_PHASES = [
|
|
3821
|
+
"requesting",
|
|
3822
|
+
"recording",
|
|
3823
|
+
"transcribing"
|
|
3824
|
+
];
|
|
3825
|
+
/** Whether DSH's published voice phase requires an active dictation surface. */
|
|
3826
|
+
function phaseIsDictating(phase) {
|
|
3827
|
+
return typeof phase === "string" && DICTATING_PHASES.includes(phase);
|
|
3828
|
+
}
|
|
3829
|
+
/** The wake lock API, or undefined where the browser does not offer it. */
|
|
3830
|
+
function wakeLockApi() {
|
|
3831
|
+
const api = navigator.wakeLock;
|
|
3832
|
+
return api === void 0 ? void 0 : api;
|
|
3833
|
+
}
|
|
3834
|
+
/**
|
|
3835
|
+
* Hold an available screen wake lock while DSH dictation is active and visible.
|
|
3836
|
+
* @param allowKeyboardDismissal Whether a mobile composer may lose text focus when capture starts.
|
|
3837
|
+
* @returns A disposer that releases held and subsequently returned wake locks.
|
|
3838
|
+
*/
|
|
3839
|
+
function installVoiceSession(allowKeyboardDismissal = true) {
|
|
3840
|
+
const api = wakeLockApi();
|
|
3841
|
+
if (api === void 0 && !allowKeyboardDismissal) return () => {};
|
|
3842
|
+
let sentinel = null;
|
|
3843
|
+
let pending = false;
|
|
3844
|
+
let disposed = false;
|
|
3845
|
+
let generation = 0;
|
|
3846
|
+
let lastRow = null;
|
|
3847
|
+
let lastVisible = !document.hidden;
|
|
3848
|
+
const currentRow = () => Array.from(document.querySelectorAll("[data-voice-activity]")).find((row) => phaseIsDictating(row.getAttribute("data-voice-activity"))) ?? null;
|
|
3849
|
+
const releaseLock = (lock) => {
|
|
3850
|
+
if (!lock.released) lock.release().catch((_error) => {});
|
|
3851
|
+
};
|
|
3852
|
+
const release = () => {
|
|
3853
|
+
const held = sentinel;
|
|
3854
|
+
sentinel = null;
|
|
3855
|
+
if (held !== null) releaseLock(held);
|
|
3856
|
+
};
|
|
3857
|
+
const acquireIfWanted = () => {
|
|
3858
|
+
if (api === void 0 || disposed || pending || sentinel !== null || document.hidden) return;
|
|
3859
|
+
const row = currentRow();
|
|
3860
|
+
if (row === null) return;
|
|
3861
|
+
const requestedGeneration = generation;
|
|
3862
|
+
pending = true;
|
|
3863
|
+
api.request("screen").then((lock) => {
|
|
3864
|
+
lock.addEventListener("release", () => {
|
|
3865
|
+
if (sentinel === lock) sentinel = null;
|
|
3866
|
+
});
|
|
3867
|
+
if (disposed || document.hidden || requestedGeneration !== generation || currentRow() !== row) releaseLock(lock);
|
|
3868
|
+
else if (!lock.released) sentinel = lock;
|
|
3869
|
+
}, (_error) => {}).finally(() => {
|
|
3870
|
+
pending = false;
|
|
3871
|
+
if (!disposed && requestedGeneration !== generation) acquireIfWanted();
|
|
3872
|
+
});
|
|
3873
|
+
};
|
|
3874
|
+
const dismissKeyboard = (row) => {
|
|
3875
|
+
if (!allowKeyboardDismissal) return;
|
|
3876
|
+
if (!(window.__DSH_MOBILE_NATIVE__ !== void 0 ? window.__DSH_MOBILE_KEYBOARD_STATE__?.noHardwareKeyboard === true : window.matchMedia("(pointer:coarse) and (hover:none)").matches)) return;
|
|
3877
|
+
const active = document.activeElement;
|
|
3878
|
+
const composer = row.closest("[data-composer-card]");
|
|
3879
|
+
if (active instanceof HTMLElement && composer?.contains(active) && active.closest("[data-composer-input]") !== null && (active.isContentEditable || active instanceof HTMLTextAreaElement || active instanceof HTMLInputElement)) active.blur();
|
|
3880
|
+
};
|
|
3881
|
+
const apply = () => {
|
|
3882
|
+
const row = currentRow();
|
|
3883
|
+
const visible = !document.hidden;
|
|
3884
|
+
const changed = row !== lastRow || visible !== lastVisible;
|
|
3885
|
+
if (changed) {
|
|
3886
|
+
generation++;
|
|
3887
|
+
release();
|
|
3888
|
+
if (row !== null && row !== lastRow && visible) dismissKeyboard(row);
|
|
3889
|
+
}
|
|
3890
|
+
lastRow = row;
|
|
3891
|
+
lastVisible = visible;
|
|
3892
|
+
if (changed) acquireIfWanted();
|
|
3893
|
+
};
|
|
3894
|
+
const containsVoiceRow = (node) => node instanceof Element && (node.matches("[data-voice-activity]") || node.querySelector("[data-voice-activity]") !== null);
|
|
3895
|
+
const observer = new MutationObserver((records) => {
|
|
3896
|
+
if (records.some((record) => record.type === "attributes" || [...record.addedNodes, ...record.removedNodes].some(containsVoiceRow))) apply();
|
|
3897
|
+
});
|
|
3898
|
+
observer.observe(document.documentElement, {
|
|
3899
|
+
subtree: true,
|
|
3900
|
+
childList: true,
|
|
3901
|
+
attributes: true,
|
|
3902
|
+
attributeFilter: ["data-voice-activity"]
|
|
3903
|
+
});
|
|
3904
|
+
const onVisibility = () => {
|
|
3905
|
+
apply();
|
|
3906
|
+
};
|
|
3907
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
3908
|
+
apply();
|
|
3909
|
+
return () => {
|
|
3910
|
+
disposed = true;
|
|
3911
|
+
generation++;
|
|
3912
|
+
observer.disconnect();
|
|
3913
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
3914
|
+
release();
|
|
3915
|
+
};
|
|
3916
|
+
}
|
|
3917
|
+
//#endregion
|
|
3765
3918
|
//#region src/local-admin-host.ts
|
|
3766
3919
|
/** Browser-safe desktop-admin Host checks. Do not import Node APIs here. */
|
|
3767
3920
|
const DESKTOP_ADMIN_HEADER = "x-dsh-mobile-desktop-admin";
|
|
@@ -4365,6 +4518,9 @@ window.__ModuleLoader__.load({
|
|
|
4365
4518
|
frp_vhost_probe_failed: "frpVhostProbeFailed",
|
|
4366
4519
|
frp_launch_failed: "frpLaunchFailed",
|
|
4367
4520
|
frp_start_timeout: "frpTimeout",
|
|
4521
|
+
frp_proxy_name_in_use: "frpProxyNameInUse",
|
|
4522
|
+
frp_remote_port_in_use: "frpRemotePortInUse",
|
|
4523
|
+
frp_proxy_start_failed: "frpProxyStartFailed",
|
|
4368
4524
|
frp_discovery_mismatch: "frpDiscoveryMismatch",
|
|
4369
4525
|
frp_discovery_invalid: "frpDiscoveryInvalid",
|
|
4370
4526
|
frp_stopped: "frpStopped",
|
|
@@ -4426,6 +4582,9 @@ window.__ModuleLoader__.load({
|
|
|
4426
4582
|
"frp_vhost_probe_failed",
|
|
4427
4583
|
"frp_launch_failed",
|
|
4428
4584
|
"frp_start_timeout",
|
|
4585
|
+
"frp_proxy_name_in_use",
|
|
4586
|
+
"frp_remote_port_in_use",
|
|
4587
|
+
"frp_proxy_start_failed",
|
|
4429
4588
|
"frp_discovery_mismatch",
|
|
4430
4589
|
"frp_discovery_invalid",
|
|
4431
4590
|
"frp_stopped",
|
|
@@ -4972,10 +5131,12 @@ window.__ModuleLoader__.load({
|
|
|
4972
5131
|
frpCopyAttachPlan.type = "button";
|
|
4973
5132
|
frpCopyAttachPlan.textContent = t("frpAttachPlan");
|
|
4974
5133
|
frpCopyAttachPlan.hidden = true;
|
|
5134
|
+
frpCopyAttachPlan.disabled = true;
|
|
4975
5135
|
const frpCopyAttachToken = element("button", "dsh-mobile-control__secondary dsh-mobile-control__frp-action");
|
|
4976
5136
|
frpCopyAttachToken.type = "button";
|
|
4977
5137
|
frpCopyAttachToken.textContent = t("frpAttachToken");
|
|
4978
5138
|
frpCopyAttachToken.hidden = true;
|
|
5139
|
+
frpCopyAttachToken.disabled = true;
|
|
4979
5140
|
const frpSelfCheckButton = element("button", "dsh-mobile-control__secondary dsh-mobile-control__frp-action");
|
|
4980
5141
|
frpSelfCheckButton.type = "button";
|
|
4981
5142
|
frpSelfCheckButton.textContent = t("frpAttachSelfCheck");
|
|
@@ -5555,6 +5716,8 @@ window.__ModuleLoader__.load({
|
|
|
5555
5716
|
let configuredFrpOrigin = "";
|
|
5556
5717
|
let configuredFrpVhostPort = 7080;
|
|
5557
5718
|
let configuredFrpPublicPort = 33080;
|
|
5719
|
+
/** Proxy name the host writes into its frpc.toml; the local clipboard copy must match. */
|
|
5720
|
+
let configuredFrpProxyName;
|
|
5558
5721
|
let frpConfiguredMode = "deploy";
|
|
5559
5722
|
let frpConfiguredEntryTls = "public-ip-cert";
|
|
5560
5723
|
let frpModeDraft = false;
|
|
@@ -5985,6 +6148,7 @@ window.__ModuleLoader__.load({
|
|
|
5985
6148
|
if (frpConfigured && !frpEntryTlsDraft) frpEntryTls.value = configuredFrpEntryTls;
|
|
5986
6149
|
if (typeof frpConfiguration.vhostHttpPort === "number") configuredFrpVhostPort = frpConfiguration.vhostHttpPort;
|
|
5987
6150
|
if (typeof frpConfiguration.publicPort === "number") configuredFrpPublicPort = frpConfiguration.publicPort;
|
|
6151
|
+
configuredFrpProxyName = typeof frpConfiguration.proxyName === "string" && frpConfiguration.proxyName !== "" ? frpConfiguration.proxyName : void 0;
|
|
5988
6152
|
if (!frpVhostDraft) {
|
|
5989
6153
|
if (frpMode.value === "attach" && configuredFrpMode === "attach" && configuredFrpEntryTls === "public-ip-cert") {
|
|
5990
6154
|
frpVhostPort.value = String(configuredFrpVhostPort);
|
|
@@ -5995,12 +6159,14 @@ window.__ModuleLoader__.load({
|
|
|
5995
6159
|
const attachSelected = frpMode.value === "attach";
|
|
5996
6160
|
const selfSignedSelected = attachSelected && frpEntryTls.value === "self-signed";
|
|
5997
6161
|
frpStep2Title.textContent = t(attachSelected ? "frpStep2TitleAttach" : "frpStep2Title");
|
|
5998
|
-
frpStep2Text.textContent = t(attachSelected ? "frpStep2TextAttach" : "frpStep2Text");
|
|
6162
|
+
frpStep2Text.textContent = attachSelected && configuredFrpProxyName === void 0 ? t("loadingRemoteStatus") : t(attachSelected ? "frpStep2TextAttach" : "frpStep2Text");
|
|
5999
6163
|
frpStep4Text.textContent = t(attachSelected ? "frpStep4TextAttach" : "frpStep4Text");
|
|
6000
6164
|
frpAppRequirement.textContent = t(selfSignedSelected ? "frpAppRequirementSelfSigned" : "frpAppRequirement");
|
|
6001
6165
|
frpCopyTemplate.hidden = attachSelected;
|
|
6002
6166
|
frpCopyAttachPlan.hidden = !attachSelected;
|
|
6003
6167
|
frpCopyAttachToken.hidden = !attachSelected;
|
|
6168
|
+
frpCopyAttachPlan.disabled = remoteProviderBusy || configuredFrpProxyName === void 0;
|
|
6169
|
+
frpCopyAttachToken.disabled = remoteProviderBusy || configuredFrpProxyName === void 0;
|
|
6004
6170
|
frpSelfCheckButton.hidden = !frpConfigured;
|
|
6005
6171
|
frpVhostPort.disabled = selfSignedSelected;
|
|
6006
6172
|
frpVhostLabel.hidden = selfSignedSelected;
|
|
@@ -6478,6 +6644,13 @@ window.__ModuleLoader__.load({
|
|
|
6478
6644
|
}
|
|
6479
6645
|
remoteStatus.textContent = message;
|
|
6480
6646
|
};
|
|
6647
|
+
const frpClipboardProxyName = () => {
|
|
6648
|
+
if (configuredFrpProxyName === void 0) {
|
|
6649
|
+
remoteStatus.textContent = t("loadingRemoteStatus");
|
|
6650
|
+
loadRemote();
|
|
6651
|
+
}
|
|
6652
|
+
return configuredFrpProxyName;
|
|
6653
|
+
};
|
|
6481
6654
|
frpCopyAttachPlan.addEventListener("click", () => {
|
|
6482
6655
|
const form = frpForm();
|
|
6483
6656
|
if (!validFrpServer(form.serverAddress) || !Number.isSafeInteger(form.serverPort) || form.serverPort < 1 || form.serverPort > 65535 || form.publicOrigin === "") {
|
|
@@ -6491,7 +6664,9 @@ window.__ModuleLoader__.load({
|
|
|
6491
6664
|
showFrpAttachError(code);
|
|
6492
6665
|
return;
|
|
6493
6666
|
}
|
|
6494
|
-
|
|
6667
|
+
const proxyName = frpClipboardProxyName();
|
|
6668
|
+
if (proxyName === void 0) return;
|
|
6669
|
+
navigator.clipboard.writeText(createFrpAttachTemplateForClipboard(attachForm, { proxyName })).then(() => {
|
|
6495
6670
|
remoteStatus.textContent = t("frpAttachPlanCopied");
|
|
6496
6671
|
}, () => {
|
|
6497
6672
|
remoteStatus.textContent = t("frpAttachPlanFailed", { error: t("templateCopyFailed") });
|
|
@@ -6519,7 +6694,7 @@ window.__ModuleLoader__.load({
|
|
|
6519
6694
|
remoteStatus.textContent = t("frpAttachPlanFailed", { error: String(error) });
|
|
6520
6695
|
}).finally(() => {
|
|
6521
6696
|
remoteProviderBusy = false;
|
|
6522
|
-
frpCopyAttachPlan.disabled =
|
|
6697
|
+
frpCopyAttachPlan.disabled = configuredFrpProxyName === void 0;
|
|
6523
6698
|
});
|
|
6524
6699
|
});
|
|
6525
6700
|
/** Copy a token-bearing frpc.toml only from the token the user just typed. */
|
|
@@ -6536,7 +6711,12 @@ window.__ModuleLoader__.load({
|
|
|
6536
6711
|
showFrpAttachError(code);
|
|
6537
6712
|
return;
|
|
6538
6713
|
}
|
|
6539
|
-
|
|
6714
|
+
const proxyName = frpClipboardProxyName();
|
|
6715
|
+
if (proxyName === void 0) return;
|
|
6716
|
+
navigator.clipboard.writeText(createFrpAttachFrpcTomlForClipboard(attachForm, {
|
|
6717
|
+
revealToken: true,
|
|
6718
|
+
proxyName
|
|
6719
|
+
})).then(() => {
|
|
6540
6720
|
remoteStatus.textContent = t("frpAttachTokenCopied");
|
|
6541
6721
|
}, () => {
|
|
6542
6722
|
remoteStatus.textContent = t("frpAttachTokenFailed", { error: t("templateCopyFailed") });
|
|
@@ -8707,6 +8887,7 @@ window.__ModuleLoader__.load({
|
|
|
8707
8887
|
style.dataset.plugin = "dsh-mobile";
|
|
8708
8888
|
style.textContent = desktopAdmin ? CONTROL_STYLES : NATIVE_MOBILE_STYLES;
|
|
8709
8889
|
document.head.append(style);
|
|
8890
|
+
const removeVoiceSession = installVoiceSession(!desktopAdmin);
|
|
8710
8891
|
if (!desktopAdmin) {
|
|
8711
8892
|
const removeSettingsAction = ctx.slots.inject("settings.general.item", () => {
|
|
8712
8893
|
const removeSwitchComputer = ctx.slots.register({
|
|
@@ -8733,6 +8914,7 @@ window.__ModuleLoader__.load({
|
|
|
8733
8914
|
removeSettingsAction();
|
|
8734
8915
|
removeCustom();
|
|
8735
8916
|
removeSurface();
|
|
8917
|
+
removeVoiceSession();
|
|
8736
8918
|
style.remove();
|
|
8737
8919
|
};
|
|
8738
8920
|
}
|
|
@@ -8778,6 +8960,7 @@ window.__ModuleLoader__.load({
|
|
|
8778
8960
|
});
|
|
8779
8961
|
return () => {
|
|
8780
8962
|
removeControl();
|
|
8963
|
+
removeVoiceSession();
|
|
8781
8964
|
style.remove();
|
|
8782
8965
|
};
|
|
8783
8966
|
}, "dsh-mobile: stock mobile adaptation and local control");
|