dsh-mobile 0.4.1 → 0.4.2

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/lib/client.js CHANGED
@@ -20,7 +20,6 @@ window.__ModuleLoader__.load({
20
20
  pluginUpdatedRestart: "Updated to v{version}. Restart DSH to apply it.",
21
21
  pluginUpdateFailed: "Update failed: {error}",
22
22
  updateTo: "Update to v{version}",
23
- updateNotesHeading: "What’s new",
24
23
  updateNotice: "Restart DSH after installing. Existing apps and paired devices need no re-pairing; unsure about the desktop version? Check the README compatibility table.",
25
24
  updateNotesEmpty: "No release notes fetched — updating is still fine; full details on the GitHub Release page.",
26
25
  updateNow: "Update now",
@@ -189,7 +188,6 @@ window.__ModuleLoader__.load({
189
188
  pluginUpdatedRestart: "Aggiornato alla v{version}. Riavvia DSH per applicarlo.",
190
189
  pluginUpdateFailed: "Aggiornamento non riuscito: {error}",
191
190
  updateTo: "Aggiorna alla v{version}",
192
- updateNotesHeading: "Novità",
193
191
  updateNotice: "Riavvia DSH dopo l’installazione. Le app esistenti e i dispositivi già associati non richiedono ri-associazione; in caso di dubbi sulla versione desktop, consulta la tabella di compatibilità nel README.",
194
192
  updateNotesEmpty: "Note di rilascio non recuperate — l’aggiornamento resta possibile; dettagli nella pagina Release su GitHub.",
195
193
  updateNow: "Aggiorna ora",
@@ -360,7 +358,6 @@ window.__ModuleLoader__.load({
360
358
  pluginUpdatedRestart: "插件已更新至 v{version},重启 DSH 后生效。",
361
359
  pluginUpdateFailed: "更新失败:{error}",
362
360
  updateTo: "更新到 v{version}",
363
- updateNotesHeading: "更新内容",
364
361
  updateNotice: "安装后需重启 DSH。现有 App 与已配对设备无需重新配对;电脑端版本不确定时,先对照 README 兼容表。",
365
362
  updateNotesEmpty: "未取到最新版更新说明,仍可继续更新;详情见 GitHub Release 页。",
366
363
  updateNow: "立即更新",
@@ -563,7 +560,6 @@ window.__ModuleLoader__.load({
563
560
  });
564
561
  Object.assign(MOBILE_CONTROL_MESSAGES.en, {
565
562
  selfHostedConnections: "Self-hosted connection",
566
- selfHostedDescription: "For users with a VPS and domain. It does not depend on public tunnel bandwidth.",
567
563
  advanced: "Advanced",
568
564
  frpName: "Self-hosted FRP",
569
565
  frpDescription: "A restricted FRP channel for this DSH gateway only. You manage the VPS and public domain.",
@@ -651,7 +647,6 @@ window.__ModuleLoader__.load({
651
647
  });
652
648
  Object.assign(MOBILE_CONTROL_MESSAGES.it, {
653
649
  selfHostedConnections: "Connessione autogestita",
654
- selfHostedDescription: "Per chi dispone di VPS e dominio. Non dipende dalla banda dei tunnel pubblici.",
655
650
  advanced: "Avanzato",
656
651
  frpName: "FRP autogestito",
657
652
  frpDescription: "Canale FRP limitato al solo gateway DSH. VPS e dominio pubblico restano sotto il tuo controllo.",
@@ -739,7 +734,6 @@ window.__ModuleLoader__.load({
739
734
  });
740
735
  Object.assign(MOBILE_CONTROL_MESSAGES.zh, {
741
736
  selfHostedConnections: "自建连接",
742
- selfHostedDescription: "适合已有 VPS 的用户,可使用域名或公网 IPv4,不受公共服务带宽限制。",
743
737
  advanced: "高级",
744
738
  frpName: "自建 FRP",
745
739
  frpDescription: "只为当前 DSH 网关建立单用途 FRP 通道,VPS 和公开入口由你掌控。",
@@ -1040,6 +1034,336 @@ window.__ModuleLoader__.load({
1040
1034
  generated: "生成时间"
1041
1035
  }
1042
1036
  };
1037
+ Object.assign(MOBILE_CONTROL_MESSAGES.en, {
1038
+ selfHostedDescription: "Use your own VPS or HTTPS reverse proxy without a public tunnel service.",
1039
+ originName: "Own reverse proxy",
1040
+ originDescription: "You manage HTTPS. DSH Mobile provides a private, authenticated HTTP backend; no tunnel component is installed.",
1041
+ prepareOrigin: "Connect your HTTPS reverse proxy",
1042
+ originPublicOrigin: "Public HTTPS address",
1043
+ originListenHost: "Private listen IPv4",
1044
+ originListenPort: "HTTP backend port",
1045
+ originAllowedCidrs: "Allowed proxy source CIDRs",
1046
+ originHelp: "Same computer: use 127.0.0.1. For a proxy on another LAN device, use this computer’s private IPv4 and allow only the proxy’s direct source IPv4/32. Separate CIDRs with spaces or commas.",
1047
+ originWarning: "Keep this HTTP backend private. Never expose or port-forward it to the Internet. Your proxy must terminate HTTPS and preserve the external Host, including its port, and WebSocket upgrades.",
1048
+ originSaveStart: "Save and start backend",
1049
+ originBackendSaved: "Saved HTTP backend (not running)",
1050
+ originBackendListening: "Listening HTTP backend",
1051
+ originCopyBackend: "Copy backend address",
1052
+ originBackendCopied: "HTTP backend address copied.",
1053
+ originPurge: "Clear proxy settings",
1054
+ originPurgeConfirm: "Stop this private HTTP backend and delete only its reverse-proxy settings? Paired remote devices, LAN access, and other providers are kept.",
1055
+ originPurged: "The backend is stopped and its proxy settings are cleared. Paired devices are kept.",
1056
+ originSaving: "Saving settings and starting the private backend…",
1057
+ originPurging: "Stopping the backend and clearing its settings…",
1058
+ originBackendReady: "Backend listening",
1059
+ originReady: "The private HTTP backend is listening. Public HTTPS, its certificate, and WebSocket connectivity have NOT been verified. Test the public address from your phone before relying on it.",
1060
+ originUnavailable: "Configure the public HTTPS address and private backend first.",
1061
+ originStarting: "Starting the private HTTP backend…",
1062
+ switchingOrigin: "Switching to your own reverse proxy…",
1063
+ reconnectingOrigin: "Restarting the private HTTP backend…",
1064
+ resetOriginConfirm: "Stop this backend and reset remote pairing? All paired remote devices must pair again. Reverse-proxy settings and LAN devices are kept.",
1065
+ originPublicOriginInvalid: "Enter a public HTTPS origin only, such as https://phone.example.com:8815; no path, query, credentials, or private address.",
1066
+ originListenHostInvalid: "Use an explicit loopback or private IPv4 address on this computer, not 0.0.0.0 or a public address.",
1067
+ originListenPortInvalid: "Use a whole-number port from 1024 to 65535; lower ports need privileges the plugin does not have.",
1068
+ originListenPortReserved: "Port 3443 belongs to the LAN gateway and port 3080 to DSH itself, so both are always taken. Choose another port, such as 3444 or 3445.",
1069
+ originAllowedCidrsInvalid: "Allow 1–16 valid loopback/private IPv4 CIDRs with no host bits. For a LAN listener, include the actual proxy source IPv4/32; public ranges are not accepted.",
1070
+ originSettingsInvalid: "The reverse-proxy settings are invalid. Check the four fields and save again.",
1071
+ originConfigMissing: "Save the reverse-proxy settings before starting the backend.",
1072
+ originConfigInvalid: "The saved settings are invalid. Correct and save them again, or clear only these settings.",
1073
+ originPortInUse: "The backend port is already in use. Stop the conflicting listener or choose another port. Note that 3443, 3080, and a port already used by a cloudflared tunnel are never free.",
1074
+ originAddressUnavailable: "The listen address is not available on this computer. Choose its current private IPv4.",
1075
+ originStartFailed: "The private backend could not start. Check its address, port, permissions, and local logs.",
1076
+ originConfigTargetInvalid: "The saved proxy settings file is not a regular file. Remove it and save the settings again.",
1077
+ originControllerUnavailable: "The reverse-proxy provider is not available in this session. Restart DSH and try again."
1078
+ });
1079
+ Object.assign(MOBILE_CONTROL_MESSAGES.it, {
1080
+ selfHostedDescription: "Usa il tuo VPS o reverse proxy HTTPS senza un servizio di tunnel pubblico.",
1081
+ originName: "Reverse proxy proprio",
1082
+ originDescription: "Gestisci tu HTTPS. DSH Mobile offre un backend HTTP privato con autenticazione; non installa componenti tunnel.",
1083
+ prepareOrigin: "Collega il tuo reverse proxy HTTPS",
1084
+ originPublicOrigin: "Indirizzo HTTPS pubblico",
1085
+ originListenHost: "IPv4 privato di ascolto",
1086
+ originListenPort: "Porta backend HTTP",
1087
+ originAllowedCidrs: "CIDR sorgente del proxy consentiti",
1088
+ originHelp: "Stesso computer: usa 127.0.0.1. Per un proxy su un altro dispositivo LAN, usa l’IPv4 privato di questo computer e consenti solo l’IPv4 sorgente diretta del proxy con /32. Separa i CIDR con spazi o virgole.",
1089
+ originWarning: "Mantieni privato il backend HTTP. Non esporlo né inoltrarne la porta su Internet. Il proxy deve terminare HTTPS e conservare l’Host esterno, inclusa la porta, e gli upgrade WebSocket.",
1090
+ originSaveStart: "Salva e avvia backend",
1091
+ originBackendSaved: "Backend HTTP salvato (non attivo)",
1092
+ originBackendListening: "Backend HTTP in ascolto",
1093
+ originCopyBackend: "Copia indirizzo backend",
1094
+ originBackendCopied: "Indirizzo backend HTTP copiato.",
1095
+ originPurge: "Cancella impostazioni proxy",
1096
+ originPurgeConfirm: "Fermare questo backend HTTP privato ed eliminare solo le sue impostazioni reverse proxy? Dispositivi remoti abbinati, accesso LAN e altri provider vengono conservati.",
1097
+ originPurged: "Backend fermato e impostazioni proxy cancellate. I dispositivi abbinati sono conservati.",
1098
+ originSaving: "Salvataggio impostazioni e avvio del backend privato…",
1099
+ originPurging: "Arresto del backend e cancellazione impostazioni…",
1100
+ originBackendReady: "Backend in ascolto",
1101
+ originReady: "Il backend HTTP privato è in ascolto. HTTPS pubblico, certificato e connettività WebSocket NON sono stati verificati. Prova l’indirizzo pubblico dal telefono prima di usarlo.",
1102
+ originUnavailable: "Configura prima l’indirizzo HTTPS pubblico e il backend privato.",
1103
+ originStarting: "Avvio del backend HTTP privato…",
1104
+ switchingOrigin: "Passaggio al reverse proxy proprio…",
1105
+ reconnectingOrigin: "Riavvio del backend HTTP privato…",
1106
+ resetOriginConfirm: "Fermare il backend e reimpostare gli abbinamenti remoti? Tutti i dispositivi remoti dovranno essere abbinati di nuovo. Le impostazioni proxy e i dispositivi LAN vengono conservati.",
1107
+ originPublicOriginInvalid: "Inserisci solo un’origine HTTPS pubblica, ad esempio https://phone.example.com:8815, senza percorso, query, credenziali o indirizzo privato.",
1108
+ originListenHostInvalid: "Usa un IPv4 loopback o privato esplicito di questo computer, non 0.0.0.0 né un indirizzo pubblico.",
1109
+ originListenPortInvalid: "Usa una porta intera da 1024 a 65535; le porte più basse richiedono privilegi che il plugin non ha.",
1110
+ originListenPortReserved: "La porta 3443 appartiene al gateway LAN e la 3080 a DSH stesso, quindi sono sempre occupate. Scegli un’altra porta, ad esempio 3444 o 3445.",
1111
+ originAllowedCidrsInvalid: "Consenti 1–16 CIDR IPv4 loopback/privati validi senza bit host. Per l’ascolto LAN includi l’IPv4 sorgente reale del proxy con /32; le reti pubbliche non sono ammesse.",
1112
+ originSettingsInvalid: "Impostazioni reverse proxy non valide. Controlla i quattro campi e salva di nuovo.",
1113
+ originConfigMissing: "Salva le impostazioni reverse proxy prima di avviare il backend.",
1114
+ originConfigInvalid: "Impostazioni salvate non valide. Correggile e salvale di nuovo, oppure cancella solo queste impostazioni.",
1115
+ originPortInUse: "La porta backend è già occupata. Ferma il processo in conflitto o scegli un’altra porta. 3443, 3080 e una porta già usata da un tunnel cloudflared non sono mai libere.",
1116
+ originAddressUnavailable: "L’indirizzo di ascolto non è disponibile su questo computer. Scegli il suo IPv4 privato attuale.",
1117
+ originStartFailed: "Impossibile avviare il backend privato. Controlla indirizzo, porta, permessi e log locali.",
1118
+ originConfigTargetInvalid: "Il file delle impostazioni proxy salvato non è un file regolare. Rimuovilo e salva di nuovo le impostazioni.",
1119
+ originControllerUnavailable: "Il provider reverse proxy non è disponibile in questa sessione. Riavvia DSH e riprova."
1120
+ });
1121
+ Object.assign(MOBILE_CONTROL_MESSAGES.zh, {
1122
+ selfHostedDescription: "使用自己的 VPS 或 HTTPS 反向代理,不依赖公共隧道服务。",
1123
+ originName: "自有反向代理",
1124
+ originDescription: "HTTPS 由你管理,插件提供带认证的私有 HTTP 后端,无需安装隧道组件。",
1125
+ prepareOrigin: "接入自有 HTTPS 反向代理",
1126
+ originPublicOrigin: "公网 HTTPS 地址",
1127
+ originListenHost: "私有监听 IPv4",
1128
+ originListenPort: "HTTP 后端端口",
1129
+ originAllowedCidrs: "允许的代理来源 CIDR",
1130
+ originHelp: "代理与 DSH 同机时用 127.0.0.1;代理在另一台局域网设备上时,填写本机私有 IPv4,并只允许代理实际直连来源 IPv4/32。多个 CIDR 用空格或逗号分隔。",
1131
+ originWarning: "HTTP 后端必须留在私网,绝不能直接暴露公网或做公网端口映射。反向代理负责 HTTPS,并须保留外部 Host(含端口)及 WebSocket 升级。",
1132
+ originSaveStart: "保存并启动后端",
1133
+ originBackendSaved: "已保存的 HTTP 后端(未运行)",
1134
+ originBackendListening: "正在监听的 HTTP 后端",
1135
+ originCopyBackend: "复制后端地址",
1136
+ originBackendCopied: "已复制 HTTP 后端地址。",
1137
+ originPurge: "清除代理配置",
1138
+ originPurgeConfirm: "停止此私有 HTTP 后端,并且只删除它的反向代理配置?已配对的远程设备、局域网访问和其他提供方均保留。",
1139
+ originPurged: "已停止后端并清除代理配置,已配对设备仍保留。",
1140
+ originSaving: "正在保存配置并启动私有后端…",
1141
+ originPurging: "正在停止后端并清除配置…",
1142
+ originBackendReady: "后端已监听",
1143
+ originReady: "私有 HTTP 后端已监听,但尚未验证公网 HTTPS、证书及 WebSocket 连通性。请先用手机测试公网地址,不代表公网连接已成功。",
1144
+ originUnavailable: "请先配置公网 HTTPS 地址和私有后端。",
1145
+ originStarting: "正在启动私有 HTTP 后端…",
1146
+ switchingOrigin: "正在切换到自有反向代理…",
1147
+ reconnectingOrigin: "正在重启私有 HTTP 后端…",
1148
+ resetOriginConfirm: "停止此后端并重置远程配对?所有已配对的远程设备都需要重新配对;反向代理配置和局域网设备保留。",
1149
+ originPublicOriginInvalid: "只填写公网 HTTPS 源地址,例如 https://phone.example.com:8815;不能含路径、查询参数、账号密码或私有地址。",
1150
+ originListenHostInvalid: "填写本机明确的回环或私有 IPv4,不能使用 0.0.0.0 或公网地址。",
1151
+ originListenPortInvalid: "端口必须是 1024–65535 的整数;更低的端口需要插件没有的特权。",
1152
+ originListenPortReserved: "3443 属于局域网网关、3080 属于 DSH 自身,两者始终被占用。请换一个端口,例如 3444 或 3445。",
1153
+ originAllowedCidrsInvalid: "填写 1–16 个规范的回环/私有 IPv4 CIDR,不能有主机位;局域网监听时须包含代理实际来源 IPv4/32,不接受公网网段。",
1154
+ originSettingsInvalid: "反向代理配置无效,请检查四个字段后重新保存。",
1155
+ originConfigMissing: "请先保存反向代理配置,再启动后端。",
1156
+ originConfigInvalid: "已保存的配置无效,请修正并重新保存,或仅清除这项配置。",
1157
+ originPortInUse: "后端端口已被占用,请停止冲突进程或改用其他端口。3443、3080 以及已被 cloudflared 隧道占用的端口永远不会空闲。",
1158
+ originAddressUnavailable: "本机没有这个监听地址,请选择当前本机的私有 IPv4。",
1159
+ originStartFailed: "私有后端启动失败,请检查地址、端口、权限和本地日志。",
1160
+ originConfigTargetInvalid: "已保存的代理配置文件不是普通文件。请移除后重新保存设置。",
1161
+ originControllerUnavailable: "当前会话里反向代理提供方不可用。请重启 DSH 后重试。"
1162
+ });
1163
+ Object.assign(MOBILE_CONTROL_MESSAGES.en, {
1164
+ cloudflaredBadge: "Account optional",
1165
+ cloudflaredDescription: "Installs the official cloudflared client on demand. A quick tunnel needs no account and gives a temporary public address; a named tunnel uses a Cloudflare account token and keeps one hostname that survives restarts.",
1166
+ prepareCloudflared: "Prepare cloudflared",
1167
+ cloudflaredUnsupported: "Only Windows x64 is currently supported. You can still use the built-in Tailscale Funnel.",
1168
+ cloudflaredNotInstalled: "Not installed. A fixed version is downloaded from the official cloudflared release page only after you click the button below.",
1169
+ cloudflaredReady: "Official component {version} is ready.",
1170
+ cloudflaredQuickNote: "Quick tunnel: no Cloudflare account is needed, but Cloudflare positions quick tunnels for testing — the address is random, rate-limited, and carries no uptime guarantee. Do not rely on one for access that must stay reachable.",
1171
+ cloudflaredNamedNote: "Named tunnel: the hostname above stays the same across restarts and Cloudflare routes it to the local forward port. The connector token is stored only in the DSH Mobile private directory, is handed to cloudflared through its environment rather than the command line, and is never shown again once saved.",
1172
+ cloudflaredComponentNote: "Downloaded from the official cloudflared release page and verified at a fixed version only after you choose Install. The client is started with automatic updates disabled. It does not add a system service, startup item, registry entry, or PATH entry.",
1173
+ installConfirmCloudflared: "Download and verify cloudflared {size} from the official release page and store it only in the DSH Mobile private directory? No system service, PATH/registry entry, or startup item is added.",
1174
+ installingCloudflared: "Installing the official cloudflared component. Keep DSH running until it completes.",
1175
+ purgeCloudflared: "Completely remove the cloudflared component",
1176
+ purgeCloudflaredConfirm: "Completely remove the cloudflared component and its runtime logs from the DSH Mobile private directory? Local network access, DSH data, and other system programs are unaffected.",
1177
+ purgingCloudflared: "Stopping the channel and removing cloudflared files managed by DSH Mobile…",
1178
+ switchingCloudflared: "Switching to cloudflared…",
1179
+ reconnectingCloudflared: "Reconnecting cloudflared…",
1180
+ remoteUnavailableCloudflared: "cloudflared is not installed.",
1181
+ remoteStartingCloudflared: "Starting the cloudflared connector…",
1182
+ remoteConnectingCloudflared: "Starting the DSH authentication gateway…",
1183
+ resetCloudflaredConfirm: "Disable the cloudflared remote channel and remove all remote paired devices? The installed component and the local network configuration are unchanged.",
1184
+ cloudflaredMissing: "The official cloudflared component is not installed. Complete the preparation steps above.",
1185
+ cloudflaredInvalid: "cloudflared component verification failed. Remove it completely and reinstall.",
1186
+ cloudflaredPortUnavailable: "Could not allocate a local remote-gateway port. Try again.",
1187
+ cloudflaredLaunchFailed: "The cloudflared client failed to start.",
1188
+ cloudflaredTimeout: "Timed out waiting for the tunnel to become reachable. Reconnect.",
1189
+ cloudflaredStopped: "The cloudflared connection stopped.",
1190
+ cloudflaredExited: "The cloudflared connection exited unexpectedly. Reconnect.",
1191
+ cloudflaredOutputInvalid: "cloudflared returned an unrecognized status.",
1192
+ cloudflaredOriginInvalid: "The public address returned by cloudflared failed validation.",
1193
+ cloudflaredComponentUnsupported: "This build cannot run the cloudflared component.",
1194
+ cloudflaredDownloadHashMismatch: "The downloaded cloudflared failed its SHA-256 verification. Retry the install.",
1195
+ cloudflaredDownloadSizeMismatch: "The downloaded cloudflared is not the pinned size. Retry the install.",
1196
+ cloudflaredDownloadRedirectMissing: "The download page stopped redirecting to the release asset. Retry later, or install from the official page manually.",
1197
+ cloudflaredDownloadRedirectInvalid: "The download redirect did not point at a GitHub release asset, so it was refused.",
1198
+ cloudflaredDownloadRedirectRejected: "The download redirect was refused by policy. Retry later, or install from the official page manually.",
1199
+ cloudflaredExecutableHashMismatch: "The installed cloudflared no longer matches the verified build. Remove it completely and install again.",
1200
+ cloudflaredPortReservationFailed: "Could not reserve a local port for the tunnel. Try again.",
1201
+ cloudflaredTunnelTargetInvalid: "The saved tunnel configuration is not a regular file. Remove it and save the settings again.",
1202
+ cloudflaredTunnelSaving: "Saving the tunnel settings and reconnecting cloudflared…",
1203
+ cloudflaredTunnelForgetting: "Removing the saved tunnel token…",
1204
+ cloudflaredTunnelTitle: "Tunnel type",
1205
+ cloudflaredTunnelQuick: "Quick",
1206
+ cloudflaredTunnelNamed: "Named",
1207
+ cloudflaredTunnelQuickHint: "No account: cloudflared allocates a random address on every start.",
1208
+ cloudflaredTunnelNamedHint: "Uses a Cloudflare account token and keeps one address that survives restarts.",
1209
+ cloudflaredTunnelHostname: "Public hostname",
1210
+ cloudflaredTunnelHostnamePlaceholder: "dsh.example.com",
1211
+ cloudflaredTunnelPort: "Local forward port",
1212
+ cloudflaredTunnelToken: "Connector token",
1213
+ cloudflaredTunnelTokenPlaceholder: "Paste the tunnel token from the Cloudflare dashboard",
1214
+ cloudflaredTunnelTokenSaved: "A token is saved. Leave blank to keep it.",
1215
+ cloudflaredTunnelSave: "Save and connect",
1216
+ cloudflaredTunnelSaveQuick: "Switch to a quick tunnel",
1217
+ cloudflaredTunnelConfigured: "Named tunnel configured: {hostname} reaches this computer on 127.0.0.1:{port}.",
1218
+ cloudflaredTunnelMissing: "No named tunnel configured yet.",
1219
+ cloudflaredTunnelHint: "In the Cloudflare dashboard create a tunnel, then set its public hostname to http://127.0.0.1:{port}. The token is stored only in the DSH Mobile private directory and is never shown again after saving.",
1220
+ cloudflaredTunnelForget: "Remove the saved token",
1221
+ cloudflaredTunnelForgetConfirm: "Remove the saved tunnel token, stop the cloudflared channel, and go back to a quick tunnel? Nothing reconnects on its own: switch the channel back on afterwards. The tunnel still exists in your Cloudflare account, and its public hostname is unchanged.",
1222
+ cloudflaredTunnelInvalid: "The saved named-tunnel configuration is unreadable; the provider is using a quick tunnel. Save the settings again.",
1223
+ cloudflaredTunnelPortUnavailable: "The configured local forward port is unavailable. Cloudflare routes the public hostname to that exact port, so free it or choose another one.",
1224
+ cloudflaredTunnelHostnameInvalid: "Enter a public hostname under a domain on this Cloudflare account, for example dsh.example.com.",
1225
+ cloudflaredTunnelPortInvalid: "Enter a forward port between 1024 and 65535.",
1226
+ cloudflaredTunnelPortReserved: "Port 3443 is reserved for the DSH Mobile LAN gateway and is always occupied. Choose another port, such as 3444 or 3445.",
1227
+ cloudflaredTunnelTokenInvalid: "That does not look like a tunnel token. Copy the whole token from the Cloudflare dashboard.",
1228
+ cloudflaredTunnelSettingsInvalid: "The tunnel settings were rejected.",
1229
+ cloudflaredTunnelConfigMissing: "Enter the token, hostname, and port to use a named tunnel."
1230
+ });
1231
+ Object.assign(MOBILE_CONTROL_MESSAGES.it, {
1232
+ cloudflaredBadge: "Account facoltativo",
1233
+ cloudflaredDescription: "Installa su richiesta il client cloudflared ufficiale. Il tunnel rapido non richiede account e fornisce un indirizzo pubblico temporaneo; il tunnel con nome usa il token di un account Cloudflare e mantiene un hostname stabile tra i riavvii.",
1234
+ prepareCloudflared: "Prepara cloudflared",
1235
+ cloudflaredUnsupported: "Attualmente è supportato solo Windows x64. Puoi comunque usare Tailscale Funnel integrato.",
1236
+ cloudflaredNotInstalled: "Non installato. Una versione fissa viene scaricata dalla pagina ufficiale di cloudflared solo dopo aver premuto il pulsante.",
1237
+ cloudflaredReady: "Componente ufficiale {version} pronto.",
1238
+ cloudflaredQuickNote: "Tunnel rapido: non serve un account Cloudflare, ma Cloudflare li considera pensati per i test — l’indirizzo è casuale, soggetto a limiti di frequenza e senza garanzia di disponibilità. Non usarlo per un accesso che deve restare raggiungibile.",
1239
+ cloudflaredNamedNote: "Tunnel con nome: l’hostname qui sopra resta lo stesso tra i riavvii e Cloudflare lo instrada verso la porta di inoltro locale. Il token del connettore è salvato solo nella directory privata DSH Mobile, viene passato a cloudflared tramite l’ambiente e non tramite la riga di comando, e dopo il salvataggio non viene più mostrato.",
1240
+ cloudflaredComponentNote: "Viene scaricato dalla pagina ufficiale di cloudflared e verificato a una versione fissa solo dopo aver scelto Installa. Il client viene avviato con gli aggiornamenti automatici disattivati. Non aggiunge servizi di sistema, avvio automatico, voci di registro o PATH.",
1241
+ installConfirmCloudflared: "Scaricare e verificare cloudflared {size} dalla pagina ufficiale e salvarlo solo nella directory privata DSH Mobile? Non vengono aggiunti servizi, PATH/registro o avvio automatico.",
1242
+ installingCloudflared: "Installazione del componente cloudflared ufficiale. Mantieni DSH in esecuzione fino al termine.",
1243
+ purgeCloudflared: "Rimuovi completamente il componente cloudflared",
1244
+ purgeCloudflaredConfirm: "Rimuovere completamente il componente cloudflared e i log runtime dalla directory privata DSH Mobile? Rete locale, dati DSH e altri programmi non sono interessati.",
1245
+ purgingCloudflared: "Arresto del canale e rimozione dei file cloudflared gestiti da DSH Mobile…",
1246
+ switchingCloudflared: "Passaggio a cloudflared…",
1247
+ reconnectingCloudflared: "Riconnessione di cloudflared…",
1248
+ remoteUnavailableCloudflared: "cloudflared non è installato.",
1249
+ remoteStartingCloudflared: "Avvio del connettore cloudflared…",
1250
+ remoteConnectingCloudflared: "Avvio del gateway di autenticazione DSH…",
1251
+ resetCloudflaredConfirm: "Disattivare il canale remoto cloudflared e rimuovere tutti i dispositivi remoti? Il componente installato e la configurazione della rete locale restano invariati.",
1252
+ cloudflaredMissing: "Il componente cloudflared ufficiale non è installato. Completa i passaggi sopra.",
1253
+ cloudflaredInvalid: "Verifica del componente cloudflared non riuscita. Rimuovilo completamente e reinstallalo.",
1254
+ cloudflaredPortUnavailable: "Impossibile assegnare una porta locale al gateway remoto. Riprova.",
1255
+ cloudflaredLaunchFailed: "Il client cloudflared non è stato avviato.",
1256
+ cloudflaredTimeout: "Timeout in attesa che il tunnel diventi raggiungibile. Riconnettiti.",
1257
+ cloudflaredStopped: "La connessione cloudflared è stata arrestata.",
1258
+ cloudflaredExited: "La connessione cloudflared è terminata in modo imprevisto. Riconnettiti.",
1259
+ cloudflaredOutputInvalid: "cloudflared ha restituito uno stato non riconosciuto.",
1260
+ cloudflaredOriginInvalid: "L’indirizzo pubblico restituito da cloudflared non ha superato la verifica.",
1261
+ cloudflaredComponentUnsupported: "Questa build non può eseguire il componente cloudflared.",
1262
+ cloudflaredDownloadHashMismatch: "Il cloudflared scaricato non ha superato la verifica SHA-256. Riprova l’installazione.",
1263
+ cloudflaredDownloadSizeMismatch: "Il cloudflared scaricato non ha la dimensione prevista. Riprova l’installazione.",
1264
+ cloudflaredDownloadRedirectMissing: "La pagina di download non reindirizza più all’asset della release. Riprova più tardi o installa manualmente dalla pagina ufficiale.",
1265
+ cloudflaredDownloadRedirectInvalid: "Il reindirizzamento di download non puntava a un asset di release GitHub ed è stato rifiutato.",
1266
+ cloudflaredDownloadRedirectRejected: "Il reindirizzamento di download è stato rifiutato dalle regole. Riprova più tardi o installa manualmente dalla pagina ufficiale.",
1267
+ cloudflaredExecutableHashMismatch: "Il cloudflared installato non corrisponde più alla build verificata. Rimuovilo completamente e reinstalla.",
1268
+ cloudflaredPortReservationFailed: "Impossibile riservare una porta locale per il tunnel. Riprova.",
1269
+ cloudflaredTunnelTargetInvalid: "La configurazione salvata del tunnel non è un file regolare. Rimuovila e salva di nuovo le impostazioni.",
1270
+ cloudflaredTunnelSaving: "Salvataggio delle impostazioni e riconnessione di cloudflared…",
1271
+ cloudflaredTunnelForgetting: "Rimozione del token del tunnel salvato…",
1272
+ cloudflaredTunnelTitle: "Tipo di tunnel",
1273
+ cloudflaredTunnelQuick: "Rapido",
1274
+ cloudflaredTunnelNamed: "Con nome",
1275
+ cloudflaredTunnelQuickHint: "Senza account: cloudflared assegna un indirizzo casuale a ogni avvio.",
1276
+ cloudflaredTunnelNamedHint: "Usa il token di un account Cloudflare e mantiene un indirizzo stabile tra i riavvii.",
1277
+ cloudflaredTunnelHostname: "Hostname pubblico",
1278
+ cloudflaredTunnelHostnamePlaceholder: "dsh.example.com",
1279
+ cloudflaredTunnelPort: "Porta di inoltro locale",
1280
+ cloudflaredTunnelToken: "Token del connettore",
1281
+ cloudflaredTunnelTokenPlaceholder: "Incolla il token del tunnel dalla dashboard Cloudflare",
1282
+ cloudflaredTunnelTokenSaved: "Un token è salvato. Lascia vuoto per mantenerlo.",
1283
+ cloudflaredTunnelSave: "Salva e connetti",
1284
+ cloudflaredTunnelSaveQuick: "Passa a un tunnel rapido",
1285
+ cloudflaredTunnelConfigured: "Tunnel con nome configurato: {hostname} raggiunge questo computer su 127.0.0.1:{port}.",
1286
+ cloudflaredTunnelMissing: "Nessun tunnel con nome configurato.",
1287
+ cloudflaredTunnelHint: "Nella dashboard Cloudflare crea un tunnel e imposta il suo hostname pubblico su http://127.0.0.1:{port}. Il token è salvato solo nella directory privata DSH Mobile e non viene più mostrato dopo il salvataggio.",
1288
+ cloudflaredTunnelForget: "Rimuovi il token salvato",
1289
+ cloudflaredTunnelForgetConfirm: "Rimuovere il token salvato, arrestare il canale cloudflared e tornare a un tunnel rapido? Nulla si riconnette da solo: riattiva il canale in seguito. Il tunnel resta nel tuo account Cloudflare e il suo hostname pubblico non cambia.",
1290
+ cloudflaredTunnelInvalid: "La configurazione salvata del tunnel con nome non è leggibile; il provider usa un tunnel rapido. Salva di nuovo le impostazioni.",
1291
+ cloudflaredTunnelPortUnavailable: "La porta di inoltro locale configurata non è disponibile. Cloudflare instrada l’hostname pubblico esattamente su quella porta: liberala o scegline un’altra.",
1292
+ cloudflaredTunnelHostnameInvalid: "Inserisci un hostname pubblico sotto un dominio di questo account Cloudflare, ad esempio dsh.example.com.",
1293
+ cloudflaredTunnelPortInvalid: "Inserisci una porta di inoltro tra 1024 e 65535.",
1294
+ cloudflaredTunnelPortReserved: "La porta 3443 è riservata al gateway LAN di DSH Mobile ed è sempre occupata. Scegli un’altra porta, ad esempio 3444 o 3445.",
1295
+ cloudflaredTunnelTokenInvalid: "Non sembra un token di tunnel. Copia il token completo dalla dashboard Cloudflare.",
1296
+ cloudflaredTunnelSettingsInvalid: "Le impostazioni del tunnel sono state rifiutate.",
1297
+ cloudflaredTunnelConfigMissing: "Inserisci token, hostname e porta per usare un tunnel con nome."
1298
+ });
1299
+ Object.assign(MOBILE_CONTROL_MESSAGES.zh, {
1300
+ cloudflaredBadge: "账号可选",
1301
+ cloudflaredDescription: "按需安装官方 cloudflared 客户端。快速隧道无需账号,提供临时公网地址;命名隧道使用 Cloudflare 账号令牌,地址固定、重启不变。",
1302
+ prepareCloudflared: "准备 cloudflared",
1303
+ cloudflaredUnsupported: "当前仅支持 Windows x64。你仍可选择内置的 Tailscale Funnel。",
1304
+ cloudflaredNotInstalled: "尚未安装。只有点击下方按钮后,才会从 cloudflared 官方发布页下载固定版本。",
1305
+ cloudflaredReady: "官方组件 {version} 已就绪。",
1306
+ cloudflaredQuickNote: "快速隧道:无需 Cloudflare 账号,但官方定位是测试用途 —— 地址随机、有限流、无可用性保证。请勿用于必须长期可达的访问。",
1307
+ cloudflaredNamedNote: "命名隧道:上面的公网域名重启后不变,由 Cloudflare 转发到本机转发端口。连接器令牌只保存在 DSH Mobile 私有目录,经环境变量而非命令行传给 cloudflared,保存后不再显示。",
1308
+ cloudflaredComponentNote: "仅在你点击安装后从 cloudflared 官方发布页下载并校验固定版本,且启动时关闭自动更新。不会写入系统服务、开机启动、注册表或 PATH。",
1309
+ installConfirmCloudflared: "将从 cloudflared 官方发布页下载并校验固定版本({size}),仅保存到 DSH Mobile 私有目录。不会安装系统服务、写入 PATH/注册表或设置开机启动。是否继续?",
1310
+ installingCloudflared: "正在安装 cloudflared 官方组件。完成前请保持 DSH 运行。",
1311
+ purgeCloudflared: "彻底移除 cloudflared 组件",
1312
+ purgeCloudflaredConfirm: "彻底移除 DSH Mobile 私有目录中的 cloudflared 组件和运行日志?不会影响局域网、DSH 数据或系统中的其他程序。",
1313
+ purgingCloudflared: "正在关闭通道并清理 DSH Mobile 管理的 cloudflared 文件…",
1314
+ switchingCloudflared: "正在切换到 cloudflared…",
1315
+ reconnectingCloudflared: "正在重连 cloudflared…",
1316
+ remoteUnavailableCloudflared: "cloudflared 尚未安装。",
1317
+ remoteStartingCloudflared: "正在启动 cloudflared 连接器…",
1318
+ remoteConnectingCloudflared: "正在启动 DSH 认证网关…",
1319
+ resetCloudflaredConfirm: "关闭 cloudflared 远程通道并移除所有远程配对设备?已安装组件与局域网配置保持不变。",
1320
+ cloudflaredMissing: "cloudflared 官方组件尚未安装。请先完成上方准备步骤。",
1321
+ cloudflaredInvalid: "cloudflared 组件校验失败。请彻底移除后重新安装。",
1322
+ cloudflaredPortUnavailable: "无法分配本机远程网关端口,请重试。",
1323
+ cloudflaredLaunchFailed: "cloudflared 客户端未能启动。",
1324
+ cloudflaredTimeout: "等待隧道可用超时,请重新连接。",
1325
+ cloudflaredStopped: "cloudflared 连接已停止。",
1326
+ cloudflaredExited: "cloudflared 连接意外退出,请重新连接。",
1327
+ cloudflaredOutputInvalid: "cloudflared 返回了无法识别的状态。",
1328
+ cloudflaredOriginInvalid: "cloudflared 返回的公网地址未通过校验。",
1329
+ cloudflaredComponentUnsupported: "当前构建无法运行 cloudflared 组件。",
1330
+ cloudflaredDownloadHashMismatch: "下载的 cloudflared 未通过 SHA-256 校验。请重新安装。",
1331
+ cloudflaredDownloadSizeMismatch: "下载的 cloudflared 体积与固定版本不符。请重新安装。",
1332
+ cloudflaredDownloadRedirectMissing: "下载页不再跳转到发布资源。请稍后重试,或按官方页面手动安装。",
1333
+ cloudflaredDownloadRedirectInvalid: "下载跳转的目标不是 GitHub 发布资源,已被拒绝。",
1334
+ cloudflaredDownloadRedirectRejected: "下载跳转未通过校验规则。请稍后重试,或按官方页面手动安装。",
1335
+ cloudflaredExecutableHashMismatch: "已安装的 cloudflared 与校验过的版本不一致。请彻底移除后重新安装。",
1336
+ cloudflaredPortReservationFailed: "无法为隧道预留本机端口,请重试。",
1337
+ cloudflaredTunnelTargetInvalid: "已保存的隧道配置不是普通文件。请移除后重新保存设置。",
1338
+ cloudflaredTunnelSaving: "正在保存隧道设置并重新连接 cloudflared…",
1339
+ cloudflaredTunnelForgetting: "正在移除已保存的隧道令牌…",
1340
+ cloudflaredTunnelTitle: "隧道类型",
1341
+ cloudflaredTunnelQuick: "快速隧道",
1342
+ cloudflaredTunnelNamed: "命名隧道",
1343
+ cloudflaredTunnelQuickHint: "无需账号:每次启动由 cloudflared 随机分配地址。",
1344
+ cloudflaredTunnelNamedHint: "使用 Cloudflare 账号令牌,地址固定,重启后仍然有效。",
1345
+ cloudflaredTunnelHostname: "公网域名",
1346
+ cloudflaredTunnelHostnamePlaceholder: "dsh.example.com",
1347
+ cloudflaredTunnelPort: "本机转发端口",
1348
+ cloudflaredTunnelToken: "连接器令牌",
1349
+ cloudflaredTunnelTokenPlaceholder: "粘贴 Cloudflare 控制台里的隧道令牌",
1350
+ cloudflaredTunnelTokenSaved: "已保存令牌,留空表示不更改。",
1351
+ cloudflaredTunnelSave: "保存并连接",
1352
+ cloudflaredTunnelSaveQuick: "切换到快速隧道",
1353
+ cloudflaredTunnelConfigured: "命名隧道已配置:{hostname} 将转发到本机 127.0.0.1:{port}。",
1354
+ cloudflaredTunnelMissing: "尚未配置命名隧道。",
1355
+ cloudflaredTunnelHint: "先在 Cloudflare 控制台创建隧道,并把它的公开主机名指向 http://127.0.0.1:{port}。令牌只保存在 DSH Mobile 私有目录,保存后不会再显示。",
1356
+ cloudflaredTunnelForget: "移除已保存的令牌",
1357
+ cloudflaredTunnelForgetConfirm: "移除已保存的隧道令牌、停止 cloudflared 通道并改回快速隧道?不会自动重连,之后需要手动重新打开开关。Cloudflare 账号里的隧道本身仍在,公开主机名也不会改变。",
1358
+ cloudflaredTunnelInvalid: "已保存的命名隧道配置无法读取,当前按快速隧道运行。请重新保存设置。",
1359
+ cloudflaredTunnelPortUnavailable: "配置的本机转发端口不可用。Cloudflare 把公开主机名固定转发到该端口,请释放它或换一个端口。",
1360
+ cloudflaredTunnelHostnameInvalid: "请填写本 Cloudflare 账号下某个域名的公开主机名,例如 dsh.example.com。",
1361
+ cloudflaredTunnelPortInvalid: "请填写 1024 到 65535 之间的转发端口。",
1362
+ cloudflaredTunnelPortReserved: "3443 已留给 DSH Mobile 局域网网关,且始终被占用。请换一个端口,例如 3444 或 3445。",
1363
+ cloudflaredTunnelTokenInvalid: "这不像是一个隧道令牌。请从 Cloudflare 控制台完整复制令牌。",
1364
+ cloudflaredTunnelSettingsInvalid: "隧道设置未通过校验。",
1365
+ cloudflaredTunnelConfigMissing: "使用命名隧道需要同时填写令牌、公网域名和端口。"
1366
+ });
1043
1367
  const DIAGNOSTIC_REASON_MESSAGES = {
1044
1368
  en: {
1045
1369
  "lan-setup-required": ["Local network setup has not been completed.", "Return to Local network and choose a network to finish setup."],
@@ -1053,6 +1377,7 @@ window.__ModuleLoader__.load({
1053
1377
  "firewall-missing": ["The complete local network firewall rules were not found.", "Run dsh-mobile setup as administrator and check the firewall rules."],
1054
1378
  "firewall-unknown": ["The system did not allow the plugin to read firewall status.", "If the phone cannot find this computer, run setup as administrator."],
1055
1379
  "remote-off": ["Remote access through {provider} is currently off.", ""],
1380
+ "remote-origin-ready": ["The private HTTP backend is listening; public HTTPS is not verified.", "Verify your reverse proxy, public certificate, external Host including port, and WebSocket access from your phone. Keep the HTTP backend private."],
1056
1381
  "remote-ready": ["{provider} endpoint {endpointSuffix} is reachable in about {latencyMs} ms.", ""],
1057
1382
  "remote-rate-limited": ["{provider} endpoint {endpointSuffix} is reachable, but this check observed rate limiting.", "Try again later; older sessions load on demand to reduce traffic."],
1058
1383
  "remote-fake-ip": ["The Tailscale address is intercepted by the current VPN or DNS proxy, so TLS was not established.", "Switch VPN node or proxy mode; if it still fails, use cpolar."],
@@ -1074,6 +1399,7 @@ window.__ModuleLoader__.load({
1074
1399
  "firewall-missing": ["Le regole firewall complete per la rete locale non sono state trovate.", "Esegui dsh-mobile setup come amministratore e controlla le regole firewall."],
1075
1400
  "firewall-unknown": ["Il sistema non ha consentito al plugin di leggere lo stato del firewall.", "Se il telefono non trova il computer, esegui setup come amministratore."],
1076
1401
  "remote-off": ["L’accesso remoto tramite {provider} è disattivato.", ""],
1402
+ "remote-origin-ready": ["Il backend HTTP privato è in ascolto; HTTPS pubblico non è verificato.", "Verifica dal telefono reverse proxy, certificato pubblico, Host esterno inclusa la porta e accesso WebSocket. Mantieni privato il backend HTTP."],
1077
1403
  "remote-ready": ["L’endpoint {provider} {endpointSuffix} è raggiungibile in circa {latencyMs} ms.", ""],
1078
1404
  "remote-rate-limited": ["L’endpoint {provider} {endpointSuffix} è raggiungibile, ma il controllo ha rilevato una limitazione temporanea.", "Riprova più tardi; le sessioni precedenti vengono caricate su richiesta per ridurre il traffico."],
1079
1405
  "remote-fake-ip": ["L’indirizzo Tailscale è intercettato dalla VPN o dal proxy DNS corrente e TLS non è stato stabilito.", "Cambia nodo VPN o modalità proxy; se il problema continua, usa cpolar."],
@@ -1095,6 +1421,7 @@ window.__ModuleLoader__.load({
1095
1421
  "firewall-missing": ["未找到完整的局域网防火墙规则。", "以管理员身份运行 dsh-mobile setup 并检查防火墙规则。"],
1096
1422
  "firewall-unknown": ["系统未允许插件读取防火墙状态。", "若手机找不到电脑,请以管理员身份运行 setup。"],
1097
1423
  "remote-off": ["{provider} 远程访问当前未启用。", ""],
1424
+ "remote-origin-ready": ["私有 HTTP 后端已监听;尚未验证公网 HTTPS。", "请用手机核对反向代理、公网证书、含端口的外部 Host 及 WebSocket 访问。HTTP 后端必须留在私网。"],
1098
1425
  "remote-ready": ["{provider} 端点 {endpointSuffix} 可达,往返约 {latencyMs} ms。", ""],
1099
1426
  "remote-rate-limited": ["{provider} 端点 {endpointSuffix} 可达,但本次检查观察到服务限流。", "稍后重试;旧会话会按需加载以减少流量。"],
1100
1427
  "remote-fake-ip": ["Tailscale 地址被当前 VPN 或 DNS 代理接管,TLS 链路未建立。", "切换 VPN 节点或代理模式;仍失败时改用 cpolar。"],
@@ -1579,11 +1906,11 @@ window.__ModuleLoader__.load({
1579
1906
  .dsh-native-mobile-backdrop { display:block; position:fixed; z-index:235; inset:env(safe-area-inset-top) 0 0; border:0; background:rgb(15 23 42 / 32%); }
1580
1907
  .dsh-native-mobile-backdrop:not([hidden]) { animation:dsh-mobile-fade-in var(--dsh-mobile-motion-duration) ease-out; }
1581
1908
  .dsh-native-mobile-backdrop[hidden] { display:none; }
1582
- [data-dsh-mobile-details] { position:fixed !important; z-index:250 !important; inset:0 0 0 auto !important; width:min(94vw,460px) !important; max-width:none !important; transform:translateX(100%); transition:transform var(--dsh-mobile-motion-duration) var(--dsh-mobile-motion-ease); background:var(--dsw-bg, #fff); box-shadow:-18px 0 46px rgb(15 23 42 / 18%); }
1909
+ [data-dsh-mobile-details] { position:fixed !important; z-index:250 !important; inset:0 0 0 auto !important; width:min(94vw,460px) !important; max-width:none !important; transform:translateX(100%); transition:transform var(--dsh-mobile-motion-duration) var(--dsh-mobile-motion-ease); background:var(--dsw-alias-bg-layer-1, #fff); box-shadow:-18px 0 46px rgb(15 23 42 / 18%); }
1583
1910
  [data-dsh-mobile-details][data-open="true"] { transform:translateX(0); }
1584
1911
  [data-dsh-mobile-handle] { display:none !important; }
1585
1912
  [data-dsh-mobile-settings] { flex-direction:column !important; width:100vw !important; height:100dvh !important; max-width:none !important; border-radius:0 !important; animation:dsh-mobile-panel-in var(--dsh-mobile-motion-duration) var(--dsh-mobile-motion-ease); }
1586
- [data-dsh-mobile-settings-nav] { flex:none !important; width:100% !important; padding:max(14px,env(safe-area-inset-top)) 12px 8px !important; gap:10px !important; border-bottom:1px solid var(--dsw-alias-border-subtle,#e8ebef); }
1913
+ [data-dsh-mobile-settings-nav] { flex:none !important; width:100% !important; padding:max(14px,env(safe-area-inset-top)) 12px 8px !important; gap:10px !important; border-bottom:1px solid var(--dsw-alias-border-l2,#e8ebef); }
1587
1914
  [data-dsh-mobile-settings-nav] [class*="_navTitle"] { padding:0 8px !important; font-size:18px !important; line-height:28px !important; }
1588
1915
  [data-dsh-mobile-settings-list] { flex-direction:row !important; gap:4px !important; overflow-x:auto !important; scrollbar-width:none; }
1589
1916
  [data-dsh-mobile-settings-list]::-webkit-scrollbar { display:none; }
@@ -1667,7 +1994,7 @@ window.__ModuleLoader__.load({
1667
1994
  camera action on the same row metrics and tokens as the native menu. */
1668
1995
  .dsh-mobile-media-action { box-sizing:border-box; display:flex; align-items:center; justify-content:flex-start; gap:8px; width:100%; min-width:0; min-height:40px; padding:8px 10px; border:0; border-radius:10px; background:transparent; color:var(--dsw-alias-label-primary,inherit); cursor:pointer; font:inherit; font-size:14px; line-height:22px; text-align:left; touch-action:manipulation; }
1669
1996
  .dsh-mobile-media-action:active { opacity:.72; }
1670
- .dsh-mobile-media-action:focus-visible { outline:2px solid var(--dsw-alias-interactive-border-focus,#4c82f7); outline-offset:1px; }
1997
+ .dsh-mobile-media-action:focus-visible { outline:2px solid var(--dsw-alias-state-business-primary,#4c82f7); outline-offset:1px; }
1671
1998
  .dsh-mobile-media-action:disabled { cursor:default; opacity:.38; }
1672
1999
  .dsh-mobile-media-action svg { flex:none; width:16px; height:16px; color:var(--dsw-alias-label-tertiary,currentColor); }
1673
2000
  .dsh-mobile-media-action span { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
@@ -2380,6 +2707,10 @@ window.__ModuleLoader__.load({
2380
2707
  }
2381
2708
  //#endregion
2382
2709
  //#region src/client.ts
2710
+ /** Map a host-reported provider onto the UI union, defaulting to the built-in one. */
2711
+ function parseMobileRemoteProvider(value) {
2712
+ return value === "cpolar" || value === "cloudflared" || value === "frp" || value === "origin" ? value : "tailscale";
2713
+ }
2383
2714
  const queuedDefinitions = [];
2384
2715
  let queuedLegacyMount;
2385
2716
  if (typeof window !== "undefined" && window.dshMobile === void 0) window.dshMobile = {
@@ -2642,7 +2973,103 @@ window.__ModuleLoader__.load({
2642
2973
  function createFrpServerTemplateForClipboard(serverPort, token, publicOrigin) {
2643
2974
  return createRestrictedFrpServerTemplate(serverPort, token, publicOrigin);
2644
2975
  }
2976
+ const ORIGIN_ERROR_MESSAGE_KEYS = {
2977
+ origin_public_origin_invalid: "originPublicOriginInvalid",
2978
+ origin_listen_host_invalid: "originListenHostInvalid",
2979
+ origin_listen_port_invalid: "originListenPortInvalid",
2980
+ origin_listen_port_reserved: "originListenPortReserved",
2981
+ origin_allowed_cidrs_invalid: "originAllowedCidrsInvalid",
2982
+ origin_settings_invalid: "originSettingsInvalid",
2983
+ origin_config_missing: "originConfigMissing",
2984
+ origin_config_invalid: "originConfigInvalid",
2985
+ origin_config_target_invalid: "originConfigTargetInvalid",
2986
+ origin_controller_unavailable: "originControllerUnavailable",
2987
+ origin_listen_port_in_use: "originPortInUse",
2988
+ origin_listen_address_unavailable: "originAddressUnavailable",
2989
+ origin_gateway_start_failed: "originStartFailed"
2990
+ };
2645
2991
  function installControl() {
2992
+ /**
2993
+ * Provider failure codes to message keys.
2994
+ *
2995
+ * Kept as keys rather than translated text so the same table can serve both the
2996
+ * status repaint and a failed request: a server rejection carries its code in
2997
+ * `error.message`, and showing that raw would print `Error: cloudflared_...` in
2998
+ * every language while the translated sentence sat unreachable.
2999
+ */
3000
+ const REMOTE_ERROR_MESSAGE_KEYS = {
3001
+ funnel_permission_required: "funnelPermission",
3002
+ funnel_https_required: "funnelHttps",
3003
+ funnel_start_failed: "funnelStart",
3004
+ funnel_start_timeout: "funnelTimeout",
3005
+ tailscale_dns_missing: "tailscaleDnsMissing",
3006
+ gateway_start_failed: "gatewayStartFailed",
3007
+ control_channel_failed: "controlChannelFailed",
3008
+ cpolar_component_missing: "cpolarMissing",
3009
+ cpolar_component_invalid: "cpolarInvalid",
3010
+ cpolar_config_missing: "cpolarConfigMissing",
3011
+ cpolar_config_invalid: "cpolarConfigInvalid",
3012
+ cpolar_port_unavailable: "cpolarPortUnavailable",
3013
+ cpolar_launch_failed: "cpolarLaunchFailed",
3014
+ cpolar_start_timeout: "cpolarTimeout",
3015
+ cpolar_stopped: "cpolarStopped",
3016
+ cpolar_exited: "cpolarExited",
3017
+ cpolar_invalid_output: "cpolarOutputInvalid",
3018
+ cpolar_invalid_origin: "cpolarOriginInvalid",
3019
+ cloudflared_component_missing: "cloudflaredMissing",
3020
+ cloudflared_component_invalid: "cloudflaredInvalid",
3021
+ cloudflared_port_unavailable: "cloudflaredPortUnavailable",
3022
+ cloudflared_launch_failed: "cloudflaredLaunchFailed",
3023
+ cloudflared_start_timeout: "cloudflaredTimeout",
3024
+ cloudflared_stopped: "cloudflaredStopped",
3025
+ cloudflared_exited: "cloudflaredExited",
3026
+ cloudflared_invalid_output: "cloudflaredOutputInvalid",
3027
+ cloudflared_invalid_origin: "cloudflaredOriginInvalid",
3028
+ cloudflared_tunnel_port_unavailable: "cloudflaredTunnelPortUnavailable",
3029
+ cloudflared_tunnel_hostname_invalid: "cloudflaredTunnelHostnameInvalid",
3030
+ cloudflared_tunnel_port_invalid: "cloudflaredTunnelPortInvalid",
3031
+ cloudflared_tunnel_port_reserved: "cloudflaredTunnelPortReserved",
3032
+ cloudflared_tunnel_token_invalid: "cloudflaredTunnelTokenInvalid",
3033
+ cloudflared_tunnel_settings_invalid: "cloudflaredTunnelSettingsInvalid",
3034
+ cloudflared_tunnel_config_missing: "cloudflaredTunnelConfigMissing",
3035
+ cloudflared_tunnel_config_invalid: "cloudflaredTunnelInvalid",
3036
+ cloudflared_tunnel_target_invalid: "cloudflaredTunnelTargetInvalid",
3037
+ cloudflared_component_unsupported: "cloudflaredComponentUnsupported",
3038
+ cloudflared_download_hash_mismatch: "cloudflaredDownloadHashMismatch",
3039
+ cloudflared_download_size_mismatch: "cloudflaredDownloadSizeMismatch",
3040
+ cloudflared_executable_hash_mismatch: "cloudflaredExecutableHashMismatch",
3041
+ cloudflared_download_redirect_missing: "cloudflaredDownloadRedirectMissing",
3042
+ cloudflared_download_redirect_invalid: "cloudflaredDownloadRedirectInvalid",
3043
+ cloudflared_download_redirect_rejected: "cloudflaredDownloadRedirectRejected",
3044
+ cloudflared_port_reservation_failed: "cloudflaredPortReservationFailed",
3045
+ frp_component_missing: "frpMissing",
3046
+ frp_component_invalid: "frpInvalid",
3047
+ frp_config_missing: "frpConfigMissing",
3048
+ frp_config_verify_failed: "frpConfigVerifyFailed",
3049
+ frp_vhost_publicly_reachable: "frpVhostPublic",
3050
+ frp_vhost_probe_failed: "frpVhostProbeFailed",
3051
+ frp_launch_failed: "frpLaunchFailed",
3052
+ frp_start_timeout: "frpTimeout",
3053
+ frp_discovery_mismatch: "frpDiscoveryMismatch",
3054
+ frp_discovery_invalid: "frpDiscoveryInvalid",
3055
+ frp_stopped: "frpStopped",
3056
+ frp_exited: "frpExited",
3057
+ ...ORIGIN_ERROR_MESSAGE_KEYS
3058
+ };
3059
+ /** The table above, translated, for the status repaint. */
3060
+ const translatedRemoteErrors = () => Object.fromEntries(Object.entries(REMOTE_ERROR_MESSAGE_KEYS).map(([code, key]) => [code, t(key)]));
3061
+ /**
3062
+ * Text for a failed control request.
3063
+ *
3064
+ * A rejection carries its code in `error.message`, so a known code renders its
3065
+ * translated sentence; only an unrecognized one falls back to the generic message,
3066
+ * and it shows the bare message rather than a stringified Error.
3067
+ */
3068
+ const remoteFailureTextFor = (error, fallbackKey) => {
3069
+ const message = error instanceof Error ? error.message : String(error);
3070
+ const key = REMOTE_ERROR_MESSAGE_KEYS[message];
3071
+ return key === void 0 ? t(fallbackKey, { error: message }) : t(key);
3072
+ };
2646
3073
  const locale = selectedMobileControlLocale();
2647
3074
  const localeTag = locale === "it" ? "it-IT" : locale === "zh" ? "zh-CN" : "en-US";
2648
3075
  const t = controlTranslator(locale);
@@ -2834,7 +3261,35 @@ window.__ModuleLoader__.load({
2834
3261
  cpolarChoiceDescription.textContent = t("cpolarDescription");
2835
3262
  cpolarChoiceTop.append(cpolarChoiceName, cpolarChoiceBadge);
2836
3263
  cpolarChoice.append(cpolarChoiceTop, cpolarChoiceDescription);
2837
- providerChoices.append(cpolarChoice, tailscaleChoice);
3264
+ const cloudflaredChoice = element("button", "dsh-mobile-control__provider");
3265
+ cloudflaredChoice.type = "button";
3266
+ cloudflaredChoice.setAttribute("role", "radio");
3267
+ cloudflaredChoice.setAttribute("aria-checked", "false");
3268
+ const cloudflaredChoiceTop = element("span", "dsh-mobile-control__provider-top");
3269
+ const cloudflaredChoiceName = element("strong");
3270
+ cloudflaredChoiceName.textContent = "cloudflared";
3271
+ const cloudflaredChoiceBadge = element("span", "dsh-mobile-control__provider-badge");
3272
+ cloudflaredChoiceBadge.textContent = t("cloudflaredBadge");
3273
+ const cloudflaredChoiceDescription = element("span", "dsh-mobile-control__provider-description");
3274
+ cloudflaredChoiceDescription.textContent = t("cloudflaredDescription");
3275
+ cloudflaredChoiceTop.append(cloudflaredChoiceName, cloudflaredChoiceBadge);
3276
+ cloudflaredChoice.append(cloudflaredChoiceTop, cloudflaredChoiceDescription);
3277
+ providerChoices.append(cpolarChoice, cloudflaredChoice, tailscaleChoice);
3278
+ const providerCardOrder = [
3279
+ cpolarChoice,
3280
+ cloudflaredChoice,
3281
+ tailscaleChoice
3282
+ ];
3283
+ providerChoices.addEventListener("keydown", (event) => {
3284
+ const step = event.key === "ArrowRight" || event.key === "ArrowDown" ? 1 : event.key === "ArrowLeft" || event.key === "ArrowUp" ? -1 : 0;
3285
+ const jump = event.key === "Home" ? 0 : event.key === "End" ? providerCardOrder.length - 1 : void 0;
3286
+ if (step === 0 && jump === void 0) return;
3287
+ const current = providerCardOrder.indexOf(document.activeElement);
3288
+ if (current < 0) return;
3289
+ const next = jump ?? (current + step + providerCardOrder.length) % providerCardOrder.length;
3290
+ event.preventDefault();
3291
+ providerCardOrder[next]?.focus();
3292
+ });
2838
3293
  const cpolarSetup = element("section", "dsh-mobile-control__cpolar-setup");
2839
3294
  cpolarSetup.hidden = true;
2840
3295
  const cpolarSetupTitle = element("h3", "dsh-mobile-control__section-title");
@@ -2896,6 +3351,96 @@ window.__ModuleLoader__.load({
2896
3351
  cpolarDetailsBody.append(cpolarDetailsText, cpolarStorage, cpolarOfficial, cpolarTerms, cpolarPurge);
2897
3352
  cpolarDetails.append(cpolarDetailsSummary, cpolarDetailsBody);
2898
3353
  cpolarSetup.append(cpolarSetupTitle, cpolarComponentStatus, cpolarInstall, cpolarAccount, cpolarDetails);
3354
+ const cloudflaredSetup = element("section", "dsh-mobile-control__cpolar-setup dsh-mobile-control__cloudflared-setup");
3355
+ cloudflaredSetup.hidden = true;
3356
+ const cloudflaredSetupTitle = element("h3", "dsh-mobile-control__section-title");
3357
+ cloudflaredSetupTitle.textContent = t("prepareCloudflared");
3358
+ const cloudflaredTunnelTitle = element("h3", "dsh-mobile-control__section-title");
3359
+ cloudflaredTunnelTitle.textContent = t("cloudflaredTunnelTitle");
3360
+ const cloudflaredMode = element("div", "dsh-mobile-control__switcher");
3361
+ const cloudflaredModeQuick = element("button", "dsh-mobile-control__tab is-active");
3362
+ cloudflaredModeQuick.type = "button";
3363
+ cloudflaredModeQuick.textContent = t("cloudflaredTunnelQuick");
3364
+ const cloudflaredModeNamed = element("button", "dsh-mobile-control__tab");
3365
+ cloudflaredModeNamed.type = "button";
3366
+ cloudflaredModeNamed.textContent = t("cloudflaredTunnelNamed");
3367
+ cloudflaredMode.append(cloudflaredModeQuick, cloudflaredModeNamed);
3368
+ const cloudflaredModeHint = element("p", "dsh-mobile-control__component-status");
3369
+ cloudflaredModeHint.textContent = t("cloudflaredTunnelQuickHint");
3370
+ const cloudflaredTunnel = element("div", "dsh-mobile-control__tunnel");
3371
+ cloudflaredTunnel.hidden = true;
3372
+ const cloudflaredTunnelFields = element("div", "dsh-mobile-control__frp-fields");
3373
+ const cloudflaredHostnameLabel = element("label", "dsh-mobile-control__field");
3374
+ cloudflaredHostnameLabel.textContent = t("cloudflaredTunnelHostname");
3375
+ const cloudflaredHostname = element("input");
3376
+ cloudflaredHostname.type = "text";
3377
+ cloudflaredHostname.autocomplete = "off";
3378
+ cloudflaredHostname.spellcheck = false;
3379
+ cloudflaredHostname.placeholder = t("cloudflaredTunnelHostnamePlaceholder");
3380
+ const cloudflaredPortLabel = element("label", "dsh-mobile-control__field");
3381
+ cloudflaredPortLabel.textContent = t("cloudflaredTunnelPort");
3382
+ const cloudflaredPort = element("input");
3383
+ cloudflaredPort.type = "number";
3384
+ cloudflaredPort.inputMode = "numeric";
3385
+ cloudflaredPort.min = "1024";
3386
+ cloudflaredPort.max = "65535";
3387
+ cloudflaredPort.value = "3444";
3388
+ const cloudflaredTokenLabel = element("label", "dsh-mobile-control__field");
3389
+ cloudflaredTokenLabel.textContent = t("cloudflaredTunnelToken");
3390
+ const cloudflaredToken = element("input");
3391
+ cloudflaredToken.type = "password";
3392
+ cloudflaredToken.autocomplete = "off";
3393
+ cloudflaredToken.spellcheck = false;
3394
+ cloudflaredToken.placeholder = t("cloudflaredTunnelTokenPlaceholder");
3395
+ cloudflaredHostnameLabel.append(cloudflaredHostname);
3396
+ cloudflaredPortLabel.append(cloudflaredPort);
3397
+ cloudflaredTokenLabel.append(cloudflaredToken);
3398
+ cloudflaredTunnelFields.append(cloudflaredHostnameLabel, cloudflaredPortLabel, cloudflaredTokenLabel);
3399
+ const cloudflaredTunnelStatus = element("p", "dsh-mobile-control__component-status");
3400
+ cloudflaredTunnelStatus.textContent = t("cloudflaredTunnelMissing");
3401
+ const cloudflaredTunnelHint = element("p", "dsh-mobile-control__component-status");
3402
+ cloudflaredTunnelHint.textContent = t("cloudflaredTunnelHint", { port: "3444" });
3403
+ const cloudflaredTunnelSave = element("button", "dsh-mobile-control__primary dsh-mobile-control__frp-action");
3404
+ cloudflaredTunnelSave.type = "button";
3405
+ cloudflaredTunnelSave.textContent = t("cloudflaredTunnelSave");
3406
+ /** The button commits the shown mode, so quick mode must not promise to save settings. */
3407
+ const cloudflaredSaveLabel = () => (cloudflaredModeDraft ?? cloudflaredTunnelMode) === "named" ? t("cloudflaredTunnelSave") : t("cloudflaredTunnelSaveQuick");
3408
+ const cloudflaredTunnelForget = element("button", "dsh-mobile-control__danger dsh-mobile-control__frp-action");
3409
+ cloudflaredTunnelForget.type = "button";
3410
+ cloudflaredTunnelForget.textContent = t("cloudflaredTunnelForget");
3411
+ cloudflaredTunnelForget.hidden = true;
3412
+ cloudflaredTunnel.append(cloudflaredTunnelFields, cloudflaredTunnelStatus, cloudflaredTunnelHint);
3413
+ const cloudflaredComponentStatus = element("p", "dsh-mobile-control__component-status");
3414
+ cloudflaredComponentStatus.textContent = t("checkingComponent");
3415
+ const cloudflaredInstall = element("button", "dsh-mobile-control__primary");
3416
+ cloudflaredInstall.type = "button";
3417
+ cloudflaredInstall.textContent = t("installOfficial");
3418
+ const cloudflaredNote = element("p", "dsh-mobile-control__origin-warning");
3419
+ cloudflaredNote.textContent = t("cloudflaredQuickNote");
3420
+ const cloudflaredDetails = element("details", "dsh-mobile-control__details");
3421
+ const cloudflaredDetailsSummary = element("summary");
3422
+ cloudflaredDetailsSummary.textContent = t("componentDetails");
3423
+ const cloudflaredDetailsBody = element("div", "dsh-mobile-control__details-body");
3424
+ const cloudflaredDetailsText = element("p");
3425
+ cloudflaredDetailsText.textContent = t("cloudflaredComponentNote");
3426
+ const cloudflaredStorage = element("code", "dsh-mobile-control__storage");
3427
+ cloudflaredStorage.textContent = t("pluginPrivateDirectory");
3428
+ const cloudflaredOfficial = element("a", "dsh-mobile-control__text-link");
3429
+ cloudflaredOfficial.href = "https://github.com/cloudflare/cloudflared/releases";
3430
+ cloudflaredOfficial.target = "_blank";
3431
+ cloudflaredOfficial.rel = "noopener noreferrer";
3432
+ cloudflaredOfficial.textContent = t("officialDownload");
3433
+ const cloudflaredTerms = element("a", "dsh-mobile-control__text-link");
3434
+ cloudflaredTerms.href = "https://www.cloudflare.com/website-terms/";
3435
+ cloudflaredTerms.target = "_blank";
3436
+ cloudflaredTerms.rel = "noopener noreferrer";
3437
+ cloudflaredTerms.textContent = t("terms");
3438
+ const cloudflaredPurge = element("button", "dsh-mobile-control__danger");
3439
+ cloudflaredPurge.type = "button";
3440
+ cloudflaredPurge.textContent = t("purgeCloudflared");
3441
+ cloudflaredDetailsBody.append(cloudflaredDetailsText, cloudflaredStorage, cloudflaredOfficial, cloudflaredTerms, cloudflaredPurge);
3442
+ cloudflaredDetails.append(cloudflaredDetailsSummary, cloudflaredDetailsBody);
3443
+ cloudflaredSetup.append(cloudflaredSetupTitle, cloudflaredTunnelTitle, cloudflaredMode, cloudflaredModeHint, cloudflaredTunnel, cloudflaredTunnelSave, cloudflaredTunnelForget, cloudflaredComponentStatus, cloudflaredInstall, cloudflaredNote, cloudflaredDetails);
2899
3444
  const selfHosted = element("details", "dsh-mobile-control__self-hosted");
2900
3445
  const selfHostedSummary = element("summary", "dsh-mobile-control__self-hosted-summary");
2901
3446
  const selfHostedSummaryText = element("span");
@@ -2918,7 +3463,17 @@ window.__ModuleLoader__.load({
2918
3463
  frpChoiceDescription.textContent = t("frpDescription");
2919
3464
  frpChoiceTop.append(frpChoiceName);
2920
3465
  frpChoice.append(frpChoiceTop, frpChoiceDescription);
2921
- selfHostedBody.append(frpChoice);
3466
+ const originChoice = element("button", "dsh-mobile-control__provider is-origin");
3467
+ originChoice.type = "button";
3468
+ originChoice.setAttribute("aria-pressed", "false");
3469
+ const originChoiceTop = element("span", "dsh-mobile-control__provider-top");
3470
+ const originChoiceName = element("strong");
3471
+ originChoiceName.textContent = t("originName");
3472
+ const originChoiceDescription = element("span", "dsh-mobile-control__provider-description");
3473
+ originChoiceDescription.textContent = t("originDescription");
3474
+ originChoiceTop.append(originChoiceName);
3475
+ originChoice.append(originChoiceTop, originChoiceDescription);
3476
+ selfHostedBody.append(frpChoice, originChoice);
2922
3477
  selfHosted.append(selfHostedSummary, selfHostedBody);
2923
3478
  providerSection.append(providerHeading, providerInfo, providerChoices, selfHosted);
2924
3479
  const frpSetup = element("section", "dsh-mobile-control__frp-setup");
@@ -3099,6 +3654,82 @@ window.__ModuleLoader__.load({
3099
3654
  frpVerifySummary.textContent = t("frpStep4Title");
3100
3655
  frpVerifyGroup.append(frpVerifySummary, frpStep4);
3101
3656
  frpSetup.append(frpSetupTitle, frpOverview, frpConnectionGroup, frpVpsGroup, frpComponentGroup, frpVerifyGroup, frpDetails);
3657
+ const originSetup = element("section", "dsh-mobile-control__origin-setup");
3658
+ originSetup.hidden = true;
3659
+ const originSetupTitle = element("h3", "dsh-mobile-control__section-title");
3660
+ originSetupTitle.textContent = t("prepareOrigin");
3661
+ const originHelp = element("p", "dsh-mobile-control__component-note");
3662
+ originHelp.id = "dsh-mobile-origin-help";
3663
+ originHelp.textContent = t("originHelp");
3664
+ const originWarning = element("p", "dsh-mobile-control__origin-warning");
3665
+ originWarning.textContent = t("originWarning");
3666
+ const originFields = element("div", "dsh-mobile-control__origin-fields");
3667
+ const originPublicLabel = element("label", "dsh-mobile-control__field");
3668
+ originPublicLabel.textContent = t("originPublicOrigin");
3669
+ const originPublic = element("input");
3670
+ originPublic.type = "url";
3671
+ originPublic.placeholder = "https://phone.example.com:8815";
3672
+ originPublic.maxLength = 512;
3673
+ const originHostLabel = element("label", "dsh-mobile-control__field");
3674
+ originHostLabel.textContent = t("originListenHost");
3675
+ const originHost = element("input");
3676
+ originHost.type = "text";
3677
+ originHost.value = "127.0.0.1";
3678
+ originHost.maxLength = 15;
3679
+ const originPortLabel = element("label", "dsh-mobile-control__field");
3680
+ originPortLabel.textContent = t("originListenPort");
3681
+ const originPort = element("input");
3682
+ originPort.type = "number";
3683
+ originPort.inputMode = "numeric";
3684
+ originPort.min = "1024";
3685
+ originPort.max = "65535";
3686
+ originPort.step = "1";
3687
+ originPort.value = "3444";
3688
+ const originCidrsLabel = element("label", "dsh-mobile-control__field");
3689
+ originCidrsLabel.textContent = t("originAllowedCidrs");
3690
+ const originCidrs = element("input");
3691
+ originCidrs.type = "text";
3692
+ originCidrs.value = "127.0.0.0/8";
3693
+ originCidrs.maxLength = 1040;
3694
+ for (const input of [
3695
+ originPublic,
3696
+ originHost,
3697
+ originPort,
3698
+ originCidrs
3699
+ ]) {
3700
+ input.required = true;
3701
+ input.autocomplete = "off";
3702
+ input.spellcheck = false;
3703
+ input.setAttribute("aria-describedby", "dsh-mobile-origin-help dsh-mobile-origin-feedback");
3704
+ }
3705
+ originPublicLabel.append(originPublic);
3706
+ originHostLabel.append(originHost);
3707
+ originPortLabel.append(originPort);
3708
+ originCidrsLabel.append(originCidrs);
3709
+ originFields.append(originPublicLabel, originHostLabel, originPortLabel, originCidrsLabel);
3710
+ const originFeedback = element("p", "dsh-mobile-control__origin-feedback");
3711
+ originFeedback.id = "dsh-mobile-origin-feedback";
3712
+ originFeedback.setAttribute("role", "status");
3713
+ originFeedback.setAttribute("aria-live", "polite");
3714
+ originFeedback.hidden = true;
3715
+ const originConfigure = element("button", "dsh-mobile-control__primary");
3716
+ originConfigure.type = "button";
3717
+ originConfigure.textContent = t("originSaveStart");
3718
+ const originPurge = element("button", "dsh-mobile-control__danger");
3719
+ originPurge.type = "button";
3720
+ originPurge.textContent = t("originPurge");
3721
+ originPurge.hidden = true;
3722
+ const originFormActions = element("div", "dsh-mobile-control__actions");
3723
+ originFormActions.append(originConfigure, originPurge);
3724
+ const originBackend = element("div", "dsh-mobile-control__origin-backend");
3725
+ originBackend.hidden = true;
3726
+ const originBackendLabel = element("span", "dsh-mobile-control__access-label");
3727
+ const originBackendAddress = element("code", "dsh-mobile-control__storage");
3728
+ const originCopyBackend = element("button", "dsh-mobile-control__secondary");
3729
+ originCopyBackend.type = "button";
3730
+ originCopyBackend.textContent = t("originCopyBackend");
3731
+ originBackend.append(originBackendLabel, originBackendAddress, originCopyBackend);
3732
+ originSetup.append(originSetupTitle, originHelp, originWarning, originFields, originFeedback, originFormActions, originBackend);
3102
3733
  const tailscaleInfo = element("details", "dsh-mobile-control__details");
3103
3734
  const tailscaleInfoSummary = element("summary");
3104
3735
  tailscaleInfoSummary.textContent = t("tailscaleHelp");
@@ -3118,7 +3749,7 @@ window.__ModuleLoader__.load({
3118
3749
  providerSetupMeta.append(providerSetupName, remoteStateBadge);
3119
3750
  providerSetupHeader.append(providerSetupHeading, providerSetupMeta);
3120
3751
  const providerSetupBody = element("div", "dsh-mobile-control__provider-setup-body");
3121
- providerSetupBody.append(cpolarSetup, frpSetup, tailscaleInfo);
3752
+ providerSetupBody.append(cpolarSetup, cloudflaredSetup, frpSetup, originSetup, tailscaleInfo);
3122
3753
  const remoteAccess = element("div", "dsh-mobile-control__access");
3123
3754
  remoteAccess.hidden = true;
3124
3755
  const remoteAccessLabel = element("span", "dsh-mobile-control__access-label");
@@ -3444,8 +4075,23 @@ window.__ModuleLoader__.load({
3444
4075
  let remoteSetupOpenedAt = 0;
3445
4076
  let remoteReconnectBusy = false;
3446
4077
  let remoteProviderBusy = false;
4078
+ let remoteFailureText;
3447
4079
  let cpolarInstalled = false;
3448
4080
  let cpolarConfigured = false;
4081
+ let cloudflaredInstalled = false;
4082
+ let cloudflaredDownloadSize = "";
4083
+ let cloudflaredTunnelMode = "quick";
4084
+ let cloudflaredModeDraft = null;
4085
+ let cloudflaredTunnelConfigured = false;
4086
+ let configuredCloudflaredHostname = "";
4087
+ let configuredCloudflaredPort = 3444;
4088
+ let cloudflaredHostnameDirty = false;
4089
+ let cloudflaredPortDirty = false;
4090
+ let originConfigured = false;
4091
+ let originFormDirty = false;
4092
+ let originFormBusy = false;
4093
+ let remoteSnapshotEpoch = 0;
4094
+ let latestRemoteState = {};
3449
4095
  let frpInstalled = false;
3450
4096
  let frpConfigured = false;
3451
4097
  let frpLayoutInitialized = false;
@@ -3750,24 +4396,38 @@ window.__ModuleLoader__.load({
3750
4396
  });
3751
4397
  });
3752
4398
  const renderRemote = (data) => {
4399
+ latestRemoteState = data;
3753
4400
  remoteRunning = data.running === true;
3754
- remoteProvider = data.provider === "cpolar" ? "cpolar" : data.provider === "frp" ? "frp" : "tailscale";
4401
+ remoteProvider = parseMobileRemoteProvider(data.provider);
3755
4402
  const cpolar = remoteProvider === "cpolar";
4403
+ const cloudflared = remoteProvider === "cloudflared";
3756
4404
  const frp = remoteProvider === "frp";
4405
+ const origin = remoteProvider === "origin";
3757
4406
  const tailscale = remoteProvider === "tailscale";
3758
4407
  tailscaleChoice.classList.toggle("is-selected", tailscale);
3759
4408
  cpolarChoice.classList.toggle("is-selected", cpolar);
4409
+ cloudflaredChoice.classList.toggle("is-selected", cloudflared);
3760
4410
  frpChoice.classList.toggle("is-selected", frp);
4411
+ originChoice.classList.toggle("is-selected", origin);
3761
4412
  tailscaleChoice.setAttribute("aria-checked", String(tailscale));
3762
4413
  cpolarChoice.setAttribute("aria-checked", String(cpolar));
4414
+ cloudflaredChoice.setAttribute("aria-checked", String(cloudflared));
4415
+ tailscaleChoice.tabIndex = tailscale ? 0 : -1;
4416
+ cpolarChoice.tabIndex = cpolar ? 0 : -1;
4417
+ cloudflaredChoice.tabIndex = cloudflared ? 0 : -1;
3763
4418
  frpChoice.setAttribute("aria-pressed", String(frp));
3764
- providerSetupName.textContent = cpolar ? "cpolar" : frp ? t("frpName") : "Tailscale Funnel";
4419
+ originChoice.setAttribute("aria-pressed", String(origin));
4420
+ providerSetupName.textContent = cpolar ? "cpolar" : cloudflared ? "cloudflared" : frp ? t("frpName") : origin ? t("originName") : "Tailscale Funnel";
3765
4421
  tailscaleChoice.disabled = remoteProviderBusy;
3766
4422
  cpolarChoice.disabled = remoteProviderBusy;
4423
+ cloudflaredChoice.disabled = remoteProviderBusy;
3767
4424
  frpChoice.disabled = remoteProviderBusy;
4425
+ originChoice.disabled = remoteProviderBusy;
3768
4426
  cpolarSetup.hidden = !cpolar;
4427
+ cloudflaredSetup.hidden = !cloudflared;
3769
4428
  frpSetup.hidden = !frp;
3770
- if (frp) selfHosted.open = true;
4429
+ originSetup.hidden = !origin;
4430
+ if (frp || origin) selfHosted.open = true;
3771
4431
  tailscaleInfo.hidden = !tailscale;
3772
4432
  remoteReset.textContent = tailscale ? t("resetRemoteLogin") : t("resetRemoteDevices");
3773
4433
  const providers = data.providers !== null && typeof data.providers === "object" ? data.providers : {};
@@ -3789,6 +4449,52 @@ window.__ModuleLoader__.load({
3789
4449
  cpolarConfigure.disabled = remoteProviderBusy;
3790
4450
  cpolarPurge.hidden = !cpolarInstalled && !cpolarConfigured;
3791
4451
  cpolarComponentStatus.textContent = !cpolarSupported ? t("cpolarUnsupported") : !cpolarInstalled ? t("cpolarNotInstalled") : !cpolarConfigured ? t("cpolarNeedsToken", { version: componentVersion }) : t("cpolarReady", { version: componentVersion });
4452
+ const cloudflaredProvider = providers.cloudflared !== null && typeof providers.cloudflared === "object" ? providers.cloudflared : {};
4453
+ const cloudflaredComponent = cloudflaredProvider.component !== null && typeof cloudflaredProvider.component === "object" ? cloudflaredProvider.component : {};
4454
+ cloudflaredInstalled = cloudflaredComponent.installed === true;
4455
+ const cloudflaredSupported = cloudflaredComponent.supported !== false;
4456
+ const cloudflaredComponentError = typeof cloudflaredComponent.errorCode === "string" ? cloudflaredComponent.errorCode : "";
4457
+ const cloudflaredVersion = typeof cloudflaredComponent.version === "string" ? cloudflaredComponent.version : "";
4458
+ const cloudflaredDownloadBytes = typeof cloudflaredComponent.downloadBytes === "number" ? cloudflaredComponent.downloadBytes : 0;
4459
+ if (cloudflaredDownloadBytes > 0) cloudflaredDownloadSize = formatMegabytes(cloudflaredDownloadBytes);
4460
+ const cloudflaredStoragePath = typeof cloudflaredComponent.storagePath === "string" ? cloudflaredComponent.storagePath : `DSH Mobile ${t("pluginPrivateDirectory")}`;
4461
+ cloudflaredStorage.textContent = cloudflaredStoragePath;
4462
+ cloudflaredStorage.title = cloudflaredStoragePath;
4463
+ cloudflaredChoiceBadge.textContent = cloudflaredInstalled ? t("ready") : t("cloudflaredBadge");
4464
+ cloudflaredInstall.hidden = cloudflaredInstalled || !cloudflaredSupported;
4465
+ cloudflaredInstall.textContent = cloudflaredDownloadBytes > 0 ? t("installWithSize", { size: formatMegabytes(cloudflaredDownloadBytes) }) : t("installOfficial");
4466
+ cloudflaredInstall.disabled = remoteProviderBusy;
4467
+ cloudflaredPurge.hidden = !cloudflaredInstalled;
4468
+ cloudflaredComponentStatus.textContent = !cloudflaredSupported ? t("cloudflaredUnsupported") : !cloudflaredInstalled ? cloudflaredComponentError === "cloudflared_component_invalid" ? t("cloudflaredInvalid") : t("cloudflaredNotInstalled") : t("cloudflaredReady", { version: cloudflaredVersion });
4469
+ const cloudflaredTunnelConfiguration = cloudflaredProvider.configuration !== null && typeof cloudflaredProvider.configuration === "object" ? cloudflaredProvider.configuration : {};
4470
+ cloudflaredTunnelMode = cloudflaredTunnelConfiguration.mode === "named" ? "named" : "quick";
4471
+ cloudflaredTunnelConfigured = cloudflaredTunnelConfiguration.configured === true;
4472
+ configuredCloudflaredHostname = typeof cloudflaredTunnelConfiguration.hostname === "string" ? cloudflaredTunnelConfiguration.hostname : "";
4473
+ configuredCloudflaredPort = typeof cloudflaredTunnelConfiguration.port === "number" ? cloudflaredTunnelConfiguration.port : 3444;
4474
+ const cloudflaredTunnelError = typeof cloudflaredTunnelConfiguration.errorCode === "string" ? cloudflaredTunnelConfiguration.errorCode : "";
4475
+ if (cloudflaredModeDraft !== null && cloudflaredModeDraft === cloudflaredTunnelMode) cloudflaredModeDraft = null;
4476
+ const cloudflaredShownMode = cloudflaredModeDraft ?? cloudflaredTunnelMode;
4477
+ cloudflaredModeQuick.classList.toggle("is-active", cloudflaredShownMode === "quick");
4478
+ cloudflaredModeNamed.classList.toggle("is-active", cloudflaredShownMode === "named");
4479
+ cloudflaredModeQuick.setAttribute("aria-pressed", String(cloudflaredShownMode === "quick"));
4480
+ cloudflaredModeNamed.setAttribute("aria-pressed", String(cloudflaredShownMode === "named"));
4481
+ cloudflaredModeQuick.disabled = remoteProviderBusy;
4482
+ cloudflaredModeNamed.disabled = remoteProviderBusy;
4483
+ cloudflaredModeHint.textContent = cloudflaredShownMode === "named" ? t("cloudflaredTunnelNamedHint") : t("cloudflaredTunnelQuickHint");
4484
+ cloudflaredNote.textContent = cloudflaredShownMode === "named" ? t("cloudflaredNamedNote") : t("cloudflaredQuickNote");
4485
+ cloudflaredTunnel.hidden = cloudflaredShownMode !== "named";
4486
+ cloudflaredTunnelSave.textContent = cloudflaredSaveLabel();
4487
+ if (!cloudflaredHostnameDirty) cloudflaredHostname.value = configuredCloudflaredHostname;
4488
+ if (!cloudflaredPortDirty) cloudflaredPort.value = String(configuredCloudflaredPort);
4489
+ cloudflaredToken.placeholder = cloudflaredTunnelConfigured ? t("cloudflaredTunnelTokenSaved") : t("cloudflaredTunnelTokenPlaceholder");
4490
+ const cloudflaredFieldPort = String(cloudflaredPort.value ?? "").trim();
4491
+ cloudflaredTunnelHint.textContent = t("cloudflaredTunnelHint", { port: cloudflaredFieldPort === "" ? String(configuredCloudflaredPort) : cloudflaredFieldPort });
4492
+ cloudflaredTunnelStatus.textContent = cloudflaredTunnelError !== "" ? t("cloudflaredTunnelInvalid") : cloudflaredTunnelConfigured ? t("cloudflaredTunnelConfigured", {
4493
+ hostname: configuredCloudflaredHostname,
4494
+ port: String(configuredCloudflaredPort)
4495
+ }) : t("cloudflaredTunnelMissing");
4496
+ cloudflaredTunnelSave.disabled = remoteProviderBusy || !cloudflaredInstalled;
4497
+ cloudflaredTunnelForget.hidden = !cloudflaredTunnelConfigured;
3792
4498
  const frpProvider = providers.frp !== null && typeof providers.frp === "object" ? providers.frp : {};
3793
4499
  const frpComponent = frpProvider.component !== null && typeof frpProvider.component === "object" ? frpProvider.component : {};
3794
4500
  const frpConfiguration = frpProvider.configuration !== null && typeof frpProvider.configuration === "object" ? frpProvider.configuration : {};
@@ -3829,7 +4535,34 @@ window.__ModuleLoader__.load({
3829
4535
  frpVpsGroup.open = !frpConfigured;
3830
4536
  frpLayoutInitialized = true;
3831
4537
  }
3832
- selfHostedBadge.textContent = frpConfigured && frpInstalled ? t("ready") : t("advanced");
4538
+ const originProvider = providers.origin !== null && typeof providers.origin === "object" ? providers.origin : {};
4539
+ const originConfiguration = originProvider.configuration !== null && typeof originProvider.configuration === "object" ? originProvider.configuration : {};
4540
+ originConfigured = originConfiguration.configured === true;
4541
+ if (!originFormDirty && !originFormBusy) {
4542
+ originPublic.value = typeof originConfiguration.publicOrigin === "string" ? originConfiguration.publicOrigin : "";
4543
+ originHost.value = typeof originConfiguration.listenHost === "string" ? originConfiguration.listenHost : "127.0.0.1";
4544
+ originPort.value = typeof originConfiguration.listenPort === "number" ? String(originConfiguration.listenPort) : "3444";
4545
+ originCidrs.value = Array.isArray(originConfiguration.allowedCidrs) ? originConfiguration.allowedCidrs.filter((value) => typeof value === "string").join(", ") : "127.0.0.0/8";
4546
+ }
4547
+ originSetup.setAttribute("aria-busy", String(originFormBusy));
4548
+ originConfigure.setAttribute("aria-busy", String(originFormBusy));
4549
+ originConfigure.textContent = originFormBusy ? t("saving") : t("originSaveStart");
4550
+ for (const input of [
4551
+ originPublic,
4552
+ originHost,
4553
+ originPort,
4554
+ originCidrs
4555
+ ]) input.disabled = remoteProviderBusy;
4556
+ originConfigure.disabled = remoteProviderBusy;
4557
+ originPurge.disabled = remoteProviderBusy;
4558
+ originPurge.hidden = !originConfigured && originConfiguration.errorCode !== "origin_config_invalid" && originProvider.running !== true;
4559
+ const originListening = origin && originProvider.state === "ready" && originProvider.running === true;
4560
+ const backendOrigin = originListening && typeof data.backendOrigin === "string" ? data.backendOrigin : typeof originConfiguration.backendOrigin === "string" ? originConfiguration.backendOrigin : "";
4561
+ originBackend.hidden = backendOrigin === "";
4562
+ originBackendLabel.textContent = originListening ? t("originBackendListening") : t("originBackendSaved");
4563
+ originBackendAddress.textContent = backendOrigin;
4564
+ originCopyBackend.disabled = remoteProviderBusy || backendOrigin === "";
4565
+ selfHostedBadge.textContent = origin ? originListening ? t("originBackendReady") : t("advanced") : frpConfigured && frpInstalled ? t("ready") : t("advanced");
3833
4566
  const state = typeof data.state === "string" ? data.state : "error";
3834
4567
  const errorCode = typeof data.errorCode === "string" ? data.errorCode : "";
3835
4568
  const remoteOrigin = typeof data.origin === "string" ? data.origin : "";
@@ -3845,7 +4578,7 @@ window.__ModuleLoader__.load({
3845
4578
  remoteStateBadge.classList.toggle("is-ready", remoteReady);
3846
4579
  remoteStateBadge.classList.toggle("is-busy", state === "starting" || state === "connecting" || state === "needs-login");
3847
4580
  remoteStateBadge.classList.toggle("is-attention", state === "error" || state === "unavailable");
3848
- remoteStateBadge.textContent = remoteReady ? t("ready") : state === "starting" || state === "connecting" || state === "needs-login" ? t("remoteStateConnecting") : state === "error" || state === "unavailable" ? t("remoteStateAttention") : t("remoteStateOff");
4581
+ remoteStateBadge.textContent = remoteReady ? origin ? t("originBackendReady") : t("ready") : state === "starting" || state === "connecting" || state === "needs-login" ? t("remoteStateConnecting") : state === "error" || state === "unavailable" ? t("remoteStateAttention") : t("remoteStateOff");
3849
4582
  remoteAccess.hidden = !remoteReady;
3850
4583
  remoteAccessLink.href = remoteOrigin;
3851
4584
  remoteAccessLink.textContent = remoteOrigin;
@@ -3853,58 +4586,35 @@ window.__ModuleLoader__.load({
3853
4586
  remoteStatus.classList.toggle("is-running", remoteReady);
3854
4587
  const labels = {
3855
4588
  off: t("remoteOff"),
3856
- unavailable: cpolar ? t("remoteUnavailableCpolar") : frp ? t("remoteUnavailableFrp") : t("remoteUnavailableTailscale"),
3857
- starting: cpolar ? t("remoteStartingCpolar") : frp ? t("remoteStartingFrp") : t("remoteStartingTailscale"),
4589
+ unavailable: cpolar ? t("remoteUnavailableCpolar") : cloudflared ? t("remoteUnavailableCloudflared") : frp ? t("remoteUnavailableFrp") : origin ? t("originUnavailable") : t("remoteUnavailableTailscale"),
4590
+ starting: cpolar ? t("remoteStartingCpolar") : cloudflared ? t("remoteStartingCloudflared") : frp ? t("remoteStartingFrp") : origin ? t("originStarting") : t("remoteStartingTailscale"),
3858
4591
  "needs-login": t("remoteNeedsLogin"),
3859
- connecting: cpolar ? t("remoteConnectingCpolar") : frp ? t("remoteConnectingFrp") : t("remoteConnectingTailscale"),
3860
- ready: t("remoteReady"),
4592
+ connecting: cpolar ? t("remoteConnectingCpolar") : cloudflared ? t("remoteConnectingCloudflared") : frp ? t("remoteConnectingFrp") : t("remoteConnectingTailscale"),
4593
+ ready: origin ? t("originReady") : t("remoteReady"),
3861
4594
  error: t("remoteError")
3862
4595
  };
3863
- const errorLabels = {
3864
- funnel_permission_required: t("funnelPermission"),
3865
- funnel_https_required: t("funnelHttps"),
3866
- funnel_start_failed: t("funnelStart"),
3867
- funnel_start_timeout: t("funnelTimeout"),
3868
- tailscale_dns_missing: t("tailscaleDnsMissing"),
3869
- gateway_start_failed: t("gatewayStartFailed"),
3870
- control_channel_failed: t("controlChannelFailed"),
3871
- cpolar_component_missing: t("cpolarMissing"),
3872
- cpolar_component_invalid: t("cpolarInvalid"),
3873
- cpolar_config_missing: t("cpolarConfigMissing"),
3874
- cpolar_config_invalid: t("cpolarConfigInvalid"),
3875
- cpolar_port_unavailable: t("cpolarPortUnavailable"),
3876
- cpolar_launch_failed: t("cpolarLaunchFailed"),
3877
- cpolar_start_timeout: t("cpolarTimeout"),
3878
- cpolar_stopped: t("cpolarStopped"),
3879
- cpolar_exited: t("cpolarExited"),
3880
- cpolar_invalid_output: t("cpolarOutputInvalid"),
3881
- cpolar_invalid_origin: t("cpolarOriginInvalid"),
3882
- frp_component_missing: t("frpMissing"),
3883
- frp_component_invalid: t("frpInvalid"),
3884
- frp_config_missing: t("frpConfigMissing"),
3885
- frp_config_verify_failed: t("frpConfigVerifyFailed"),
3886
- frp_vhost_publicly_reachable: t("frpVhostPublic"),
3887
- frp_vhost_probe_failed: t("frpVhostProbeFailed"),
3888
- frp_launch_failed: t("frpLaunchFailed"),
3889
- frp_start_timeout: t("frpTimeout"),
3890
- frp_discovery_mismatch: t("frpDiscoveryMismatch"),
3891
- frp_discovery_invalid: t("frpDiscoveryInvalid"),
3892
- frp_stopped: t("frpStopped"),
3893
- frp_exited: t("frpExited")
3894
- };
4596
+ const errorLabels = translatedRemoteErrors();
3895
4597
  remoteStatus.textContent = remoteSetupPending && needsFunnelSetup ? t("setupOpened") : state === "error" ? errorLabels[errorCode] ?? labels.error : labels[state] ?? labels.error;
4598
+ if (remoteFailureText !== void 0) {
4599
+ remoteStatus.textContent = remoteFailureText;
4600
+ remoteFailureText = void 0;
4601
+ }
3896
4602
  remoteGuide.hidden = !needsFunnelSetup;
3897
4603
  remoteSetup.disabled = remoteSetupUrl === "" || remoteReconnectBusy;
3898
4604
  remoteSetupRetry.disabled = remoteReconnectBusy;
3899
4605
  remoteToggle.textContent = remoteRunning ? t("disableRemote") : t("enableRemote");
3900
- const providerPrepared = cpolar ? cpolarInstalled && cpolarConfigured : frp ? frpInstalled && frpConfigured : true;
3901
- remoteToggle.disabled = remoteProviderBusy || !providerPrepared;
4606
+ const providerPrepared = cpolar ? cpolarInstalled && cpolarConfigured : cloudflared ? cloudflaredInstalled : frp ? frpInstalled && frpConfigured : origin ? originConfigured : true;
4607
+ remoteToggle.disabled = remoteProviderBusy || !providerPrepared && !(origin && remoteRunning);
3902
4608
  remoteLogin.hidden = !tailscale || state !== "needs-login" || remoteLoginUrl === "";
3903
4609
  remoteReconnect.hidden = needsFunnelSetup || state !== "error" && state !== "unavailable" || !providerPrepared;
3904
- remoteActions.hidden = !providerPrepared;
3905
- remotePair.disabled = !remoteReady;
3906
- remoteCopyLink.disabled = !remoteReady;
3907
- remoteDevices.disabled = !remoteReady;
4610
+ remoteActions.hidden = !providerPrepared && !(origin && remoteRunning);
4611
+ remotePair.disabled = !remoteReady || originFormBusy;
4612
+ remoteCopyLink.disabled = !remoteReady || originFormBusy;
4613
+ remoteDevices.disabled = !remoteReady || originFormBusy;
4614
+ if (origin) {
4615
+ remoteReconnect.disabled = remoteProviderBusy || remoteReconnectBusy;
4616
+ remoteReset.disabled = remoteProviderBusy;
4617
+ } else remoteReset.disabled = false;
3908
4618
  if (!remoteReady) {
3909
4619
  remoteQr.hidden = true;
3910
4620
  remotePairLink.hidden = true;
@@ -3915,10 +4625,13 @@ window.__ModuleLoader__.load({
3915
4625
  };
3916
4626
  let remoteLoadInFlight = false;
3917
4627
  const loadRemote = () => {
3918
- if (remoteLoadInFlight) return;
4628
+ if (remoteLoadInFlight || originFormBusy) return;
4629
+ const epoch = remoteSnapshotEpoch;
3919
4630
  remoteLoadInFlight = true;
3920
- controlRequestJson("/api/mobile-access/remote/control").then(renderRemote, (error) => {
3921
- remoteStatus.textContent = t("requestFailed", { error: String(error) });
4631
+ controlRequestJson("/api/mobile-access/remote/control").then((data) => {
4632
+ if (epoch === remoteSnapshotEpoch) renderRemote(data);
4633
+ }, (error) => {
4634
+ if (epoch === remoteSnapshotEpoch) remoteStatus.textContent = t("requestFailed", { error: String(error) });
3922
4635
  }).finally(() => {
3923
4636
  remoteLoadInFlight = false;
3924
4637
  });
@@ -3929,13 +4642,16 @@ window.__ModuleLoader__.load({
3929
4642
  remoteProviderBusy = true;
3930
4643
  tailscaleChoice.disabled = true;
3931
4644
  cpolarChoice.disabled = true;
4645
+ cloudflaredChoice.disabled = true;
3932
4646
  frpChoice.disabled = true;
3933
- remoteStatus.textContent = provider === "cpolar" ? t("switchingCpolar") : provider === "frp" ? t("switchingFrp") : t("switchingTailscale");
4647
+ originChoice.disabled = true;
4648
+ remoteStatus.textContent = provider === "cpolar" ? t("switchingCpolar") : provider === "cloudflared" ? t("switchingCloudflared") : provider === "frp" ? t("switchingFrp") : provider === "origin" ? t("switchingOrigin") : t("switchingTailscale");
3934
4649
  controlRequestJson("/api/mobile-access/remote/provider", {
3935
4650
  method: "POST",
3936
4651
  body: JSON.stringify({ provider })
3937
4652
  }).then(renderRemote, (error) => {
3938
- remoteStatus.textContent = t("requestFailed", { error: String(error) });
4653
+ remoteFailureText = remoteFailureTextFor(error, "requestFailed");
4654
+ remoteStatus.textContent = remoteFailureText;
3939
4655
  }).finally(() => {
3940
4656
  remoteProviderBusy = false;
3941
4657
  loadRemote();
@@ -3947,9 +4663,159 @@ window.__ModuleLoader__.load({
3947
4663
  cpolarChoice.addEventListener("click", () => {
3948
4664
  chooseRemoteProvider("cpolar");
3949
4665
  });
4666
+ cloudflaredChoice.addEventListener("click", () => {
4667
+ chooseRemoteProvider("cloudflared");
4668
+ });
3950
4669
  frpChoice.addEventListener("click", () => {
3951
4670
  chooseRemoteProvider("frp");
3952
4671
  });
4672
+ originChoice.addEventListener("click", () => {
4673
+ chooseRemoteProvider("origin");
4674
+ });
4675
+ cloudflaredInstall.addEventListener("click", () => {
4676
+ if (remoteProviderBusy) return;
4677
+ if (!window.confirm(t("installConfirmCloudflared", { size: cloudflaredDownloadSize }))) return;
4678
+ remoteProviderBusy = true;
4679
+ cloudflaredInstall.disabled = true;
4680
+ cloudflaredInstall.textContent = t("downloading");
4681
+ remoteStatus.textContent = t("installingCloudflared");
4682
+ controlRequestJson("/api/mobile-access/remote/cloudflared/component/install", {
4683
+ method: "POST",
4684
+ body: JSON.stringify({ confirm: true })
4685
+ }, LONG_CONTROL_REQUEST_TIMEOUT_MS).then(renderRemote, (error) => {
4686
+ remoteFailureText = remoteFailureTextFor(error, "installFailed");
4687
+ remoteStatus.textContent = remoteFailureText;
4688
+ }).finally(() => {
4689
+ remoteProviderBusy = false;
4690
+ loadRemote();
4691
+ });
4692
+ });
4693
+ cloudflaredPurge.addEventListener("click", () => {
4694
+ if (remoteProviderBusy) return;
4695
+ if (!window.confirm(t("purgeCloudflaredConfirm"))) return;
4696
+ remoteProviderBusy = true;
4697
+ cloudflaredPurge.disabled = true;
4698
+ remoteStatus.textContent = t("purgingCloudflared");
4699
+ controlRequestJson("/api/mobile-access/remote/cloudflared/component/purge", {
4700
+ method: "POST",
4701
+ body: JSON.stringify({ confirm: true })
4702
+ }, LONG_CONTROL_REQUEST_TIMEOUT_MS).then(renderRemote, (error) => {
4703
+ remoteFailureText = t("purgeFailed", { error: String(error) });
4704
+ remoteStatus.textContent = remoteFailureText;
4705
+ }).finally(() => {
4706
+ remoteProviderBusy = false;
4707
+ cloudflaredPurge.disabled = false;
4708
+ loadRemote();
4709
+ });
4710
+ });
4711
+ const showCloudflaredMode = (mode) => {
4712
+ cloudflaredModeDraft = mode;
4713
+ cloudflaredModeQuick.classList.toggle("is-active", mode === "quick");
4714
+ cloudflaredModeNamed.classList.toggle("is-active", mode === "named");
4715
+ cloudflaredModeQuick.setAttribute("aria-pressed", String(mode === "quick"));
4716
+ cloudflaredModeNamed.setAttribute("aria-pressed", String(mode === "named"));
4717
+ cloudflaredModeHint.textContent = mode === "named" ? t("cloudflaredTunnelNamedHint") : t("cloudflaredTunnelQuickHint");
4718
+ cloudflaredNote.textContent = mode === "named" ? t("cloudflaredNamedNote") : t("cloudflaredQuickNote");
4719
+ cloudflaredTunnel.hidden = mode !== "named";
4720
+ cloudflaredTunnelSave.textContent = cloudflaredSaveLabel();
4721
+ };
4722
+ cloudflaredModeQuick.addEventListener("click", () => {
4723
+ if (!remoteProviderBusy) showCloudflaredMode("quick");
4724
+ });
4725
+ cloudflaredModeNamed.addEventListener("click", () => {
4726
+ if (!remoteProviderBusy) showCloudflaredMode("named");
4727
+ });
4728
+ cloudflaredHostname.addEventListener("input", () => {
4729
+ cloudflaredHostnameDirty = true;
4730
+ });
4731
+ cloudflaredPort.addEventListener("input", () => {
4732
+ cloudflaredPortDirty = true;
4733
+ });
4734
+ cloudflaredTunnelSave.addEventListener("click", () => {
4735
+ if (remoteProviderBusy) return;
4736
+ const mode = cloudflaredModeDraft ?? cloudflaredTunnelMode;
4737
+ const hostname = String(cloudflaredHostname.value ?? "").trim();
4738
+ const portValue = String(cloudflaredPort.value ?? "").trim();
4739
+ const port = Number(portValue);
4740
+ const token = String(cloudflaredToken.value ?? "");
4741
+ if (mode === "named") {
4742
+ if (hostname === "") {
4743
+ remoteStatus.textContent = t("cloudflaredTunnelHostnameInvalid");
4744
+ return;
4745
+ }
4746
+ if (portValue === "" || !Number.isSafeInteger(port) || port < 1024 || port > 65535) {
4747
+ remoteStatus.textContent = t("cloudflaredTunnelPortInvalid");
4748
+ return;
4749
+ }
4750
+ if (port === 3443) {
4751
+ remoteStatus.textContent = t("cloudflaredTunnelPortReserved");
4752
+ return;
4753
+ }
4754
+ if (token === "" && !cloudflaredTunnelConfigured) {
4755
+ remoteStatus.textContent = t("cloudflaredTunnelConfigMissing");
4756
+ return;
4757
+ }
4758
+ if (token !== "" && (token.length < 32 || !/^[A-Za-z0-9_=+/-]+$/u.test(token))) {
4759
+ remoteStatus.textContent = t("cloudflaredTunnelTokenInvalid");
4760
+ return;
4761
+ }
4762
+ }
4763
+ remoteProviderBusy = true;
4764
+ cloudflaredTunnelSave.disabled = true;
4765
+ cloudflaredTunnelSave.textContent = t("saving");
4766
+ remoteStatus.textContent = mode === "named" ? t("cloudflaredTunnelSaving") : t("switchingCloudflared");
4767
+ controlRequestJson("/api/mobile-access/remote/cloudflared/tunnel", {
4768
+ method: "POST",
4769
+ body: JSON.stringify(mode === "named" ? {
4770
+ mode,
4771
+ hostname,
4772
+ port,
4773
+ token
4774
+ } : { mode: "quick" })
4775
+ }).then((data) => {
4776
+ cloudflaredToken.value = "";
4777
+ cloudflaredModeDraft = null;
4778
+ cloudflaredHostnameDirty = false;
4779
+ cloudflaredPortDirty = false;
4780
+ remoteFailureText = "";
4781
+ renderRemote(data);
4782
+ }, (error) => {
4783
+ remoteFailureText = remoteFailureTextFor(error, "requestFailed");
4784
+ remoteStatus.textContent = remoteFailureText;
4785
+ }).finally(() => {
4786
+ remoteProviderBusy = false;
4787
+ cloudflaredTunnelSave.disabled = false;
4788
+ cloudflaredTunnelSave.textContent = cloudflaredSaveLabel();
4789
+ loadRemote();
4790
+ });
4791
+ });
4792
+ cloudflaredTunnelForget.addEventListener("click", () => {
4793
+ if (remoteProviderBusy) return;
4794
+ if (!window.confirm(t("cloudflaredTunnelForgetConfirm"))) return;
4795
+ remoteProviderBusy = true;
4796
+ cloudflaredTunnelForget.disabled = true;
4797
+ remoteStatus.textContent = t("cloudflaredTunnelForgetting");
4798
+ controlRequestJson("/api/mobile-access/remote/cloudflared/tunnel/purge", {
4799
+ method: "POST",
4800
+ body: JSON.stringify({ confirm: true })
4801
+ }).then((data) => {
4802
+ cloudflaredToken.value = "";
4803
+ cloudflaredHostname.value = "";
4804
+ cloudflaredPort.value = "3444";
4805
+ cloudflaredModeDraft = null;
4806
+ cloudflaredHostnameDirty = false;
4807
+ cloudflaredPortDirty = false;
4808
+ remoteFailureText = "";
4809
+ renderRemote(data);
4810
+ }, (error) => {
4811
+ remoteFailureText = remoteFailureTextFor(error, "requestFailed");
4812
+ remoteStatus.textContent = remoteFailureText;
4813
+ }).finally(() => {
4814
+ remoteProviderBusy = false;
4815
+ cloudflaredTunnelForget.disabled = false;
4816
+ loadRemote();
4817
+ });
4818
+ });
3953
4819
  cpolarInstall.addEventListener("click", () => {
3954
4820
  if (remoteProviderBusy) return;
3955
4821
  if (!window.confirm(t("installConfirm"))) return;
@@ -3961,7 +4827,8 @@ window.__ModuleLoader__.load({
3961
4827
  method: "POST",
3962
4828
  body: JSON.stringify({ confirm: true })
3963
4829
  }, LONG_CONTROL_REQUEST_TIMEOUT_MS).then(renderRemote, (error) => {
3964
- remoteStatus.textContent = t("installFailed", { error: String(error) });
4830
+ remoteFailureText = remoteFailureTextFor(error, "installFailed");
4831
+ remoteStatus.textContent = remoteFailureText;
3965
4832
  }).finally(() => {
3966
4833
  remoteProviderBusy = false;
3967
4834
  loadRemote();
@@ -3990,7 +4857,8 @@ window.__ModuleLoader__.load({
3990
4857
  body: JSON.stringify({ running: true })
3991
4858
  });
3992
4859
  }).then(renderRemote, (error) => {
3993
- remoteStatus.textContent = t("configureFailed", { error: String(error) });
4860
+ remoteFailureText = t("configureFailed", { error: String(error) });
4861
+ remoteStatus.textContent = remoteFailureText;
3994
4862
  }).finally(() => {
3995
4863
  remoteProviderBusy = false;
3996
4864
  cpolarConfigure.setAttribute("aria-busy", "false");
@@ -4008,7 +4876,8 @@ window.__ModuleLoader__.load({
4008
4876
  method: "POST",
4009
4877
  body: JSON.stringify({ confirm: true })
4010
4878
  }, LONG_CONTROL_REQUEST_TIMEOUT_MS).then(renderRemote, (error) => {
4011
- remoteStatus.textContent = t("purgeFailed", { error: String(error) });
4879
+ remoteFailureText = t("purgeFailed", { error: String(error) });
4880
+ remoteStatus.textContent = remoteFailureText;
4012
4881
  }).finally(() => {
4013
4882
  remoteProviderBusy = false;
4014
4883
  cpolarPurge.disabled = false;
@@ -4308,7 +5177,8 @@ window.__ModuleLoader__.load({
4308
5177
  method: "POST",
4309
5178
  body: JSON.stringify({ confirm: true })
4310
5179
  }, LONG_CONTROL_REQUEST_TIMEOUT_MS).then(renderRemote, (error) => {
4311
- remoteStatus.textContent = t("installFailed", { error: String(error) });
5180
+ remoteFailureText = remoteFailureTextFor(error, "installFailed");
5181
+ remoteStatus.textContent = remoteFailureText;
4312
5182
  }).finally(() => {
4313
5183
  remoteProviderBusy = false;
4314
5184
  loadRemote();
@@ -4337,7 +5207,8 @@ window.__ModuleLoader__.load({
4337
5207
  body: JSON.stringify({ running: true })
4338
5208
  });
4339
5209
  }).then(renderRemote, (error) => {
4340
- remoteStatus.textContent = t("configureFailed", { error: String(error) });
5210
+ remoteFailureText = t("configureFailed", { error: String(error) });
5211
+ remoteStatus.textContent = remoteFailureText;
4341
5212
  }).finally(() => {
4342
5213
  remoteProviderBusy = false;
4343
5214
  frpConfigure.setAttribute("aria-busy", "false");
@@ -4354,20 +5225,132 @@ window.__ModuleLoader__.load({
4354
5225
  method: "POST",
4355
5226
  body: JSON.stringify({ confirm: true })
4356
5227
  }, LONG_CONTROL_REQUEST_TIMEOUT_MS).then(renderRemote, (error) => {
4357
- remoteStatus.textContent = t("purgeFailed", { error: String(error) });
5228
+ remoteFailureText = t("purgeFailed", { error: String(error) });
5229
+ remoteStatus.textContent = remoteFailureText;
4358
5230
  }).finally(() => {
4359
5231
  remoteProviderBusy = false;
4360
5232
  frpPurge.disabled = false;
4361
5233
  loadRemote();
4362
5234
  });
4363
5235
  });
5236
+ const clearOriginValidation = () => {
5237
+ for (const input of [
5238
+ originPublic,
5239
+ originHost,
5240
+ originPort,
5241
+ originCidrs
5242
+ ]) input.removeAttribute("aria-invalid");
5243
+ };
5244
+ const showOriginFeedback = (message, error = false) => {
5245
+ originFeedback.textContent = message;
5246
+ originFeedback.hidden = false;
5247
+ originFeedback.classList.toggle("is-error", error);
5248
+ };
5249
+ const showOriginError = (error) => {
5250
+ const code = error instanceof Error ? error.message : String(error);
5251
+ const key = ORIGIN_ERROR_MESSAGE_KEYS[code];
5252
+ const fields = {
5253
+ origin_public_origin_invalid: originPublic,
5254
+ origin_listen_host_invalid: originHost,
5255
+ origin_listen_address_unavailable: originHost,
5256
+ origin_listen_port_invalid: originPort,
5257
+ origin_listen_port_reserved: originPort,
5258
+ origin_listen_port_in_use: originPort,
5259
+ origin_allowed_cidrs_invalid: originCidrs
5260
+ };
5261
+ clearOriginValidation();
5262
+ fields[code]?.setAttribute("aria-invalid", "true");
5263
+ showOriginFeedback(key === void 0 ? t("configureFailed", { error: String(error) }) : t(key), true);
5264
+ };
5265
+ const setOriginFormBusy = (busy) => {
5266
+ originFormBusy = busy;
5267
+ remoteProviderBusy = busy;
5268
+ remoteSnapshotEpoch += 1;
5269
+ renderRemote(latestRemoteState);
5270
+ };
5271
+ for (const input of [
5272
+ originPublic,
5273
+ originHost,
5274
+ originPort,
5275
+ originCidrs
5276
+ ]) input.addEventListener("input", () => {
5277
+ originFormDirty = true;
5278
+ clearOriginValidation();
5279
+ originFeedback.hidden = true;
5280
+ });
5281
+ originConfigure.addEventListener("click", () => {
5282
+ if (remoteProviderBusy || remoteProvider !== "origin") return;
5283
+ clearOriginValidation();
5284
+ for (const input of [
5285
+ originPublic,
5286
+ originHost,
5287
+ originPort,
5288
+ originCidrs
5289
+ ]) if (!input.reportValidity()) return;
5290
+ const form = {
5291
+ publicOrigin: originPublic.value.trim(),
5292
+ listenHost: originHost.value.trim(),
5293
+ listenPort: Number(originPort.value),
5294
+ allowedCidrs: originCidrs.value.trim().split(/[\s,]+/u).filter(Boolean)
5295
+ };
5296
+ setOriginFormBusy(true);
5297
+ showOriginFeedback(t("originSaving"));
5298
+ controlRequestJson("/api/mobile-access/remote/origin/configure", {
5299
+ method: "POST",
5300
+ body: JSON.stringify(form)
5301
+ }).then(() => controlRequestJson("/api/mobile-access/remote/control", {
5302
+ method: "POST",
5303
+ body: JSON.stringify({ running: true })
5304
+ })).then((data) => {
5305
+ originFormDirty = false;
5306
+ renderRemote(data);
5307
+ if (data.state === "error") showOriginError(new Error(typeof data.errorCode === "string" ? data.errorCode : "origin_gateway_start_failed"));
5308
+ else originFeedback.hidden = true;
5309
+ }, showOriginError).finally(() => {
5310
+ setOriginFormBusy(false);
5311
+ [
5312
+ originPublic,
5313
+ originHost,
5314
+ originPort,
5315
+ originCidrs
5316
+ ].find((input) => input.getAttribute("aria-invalid") === "true")?.focus();
5317
+ loadRemote();
5318
+ });
5319
+ });
5320
+ originPurge.addEventListener("click", () => {
5321
+ if (remoteProviderBusy || !window.confirm(t("originPurgeConfirm"))) return;
5322
+ setOriginFormBusy(true);
5323
+ showOriginFeedback(t("originPurging"));
5324
+ controlRequestJson("/api/mobile-access/remote/origin/purge", {
5325
+ method: "POST",
5326
+ body: JSON.stringify({ confirm: true })
5327
+ }).then((data) => {
5328
+ originFormDirty = false;
5329
+ clearOriginValidation();
5330
+ renderRemote(data);
5331
+ showOriginFeedback(t("originPurged"));
5332
+ }, showOriginError).finally(() => {
5333
+ setOriginFormBusy(false);
5334
+ loadRemote();
5335
+ });
5336
+ });
5337
+ originCopyBackend.addEventListener("click", () => {
5338
+ const address = originBackendAddress.textContent ?? "";
5339
+ if (address === "" || remoteProviderBusy) return;
5340
+ Promise.resolve().then(() => navigator.clipboard.writeText(address)).then(() => {
5341
+ showOriginFeedback(t("originBackendCopied"));
5342
+ }, (error) => {
5343
+ showOriginFeedback(t("requestFailed", { error: String(error) }), true);
5344
+ });
5345
+ });
4364
5346
  remoteToggle.addEventListener("click", () => {
4365
5347
  remoteToggle.disabled = true;
4366
5348
  controlRequestJson("/api/mobile-access/remote/control", {
4367
5349
  method: "POST",
4368
5350
  body: JSON.stringify({ running: !remoteRunning })
4369
5351
  }).then(renderRemote, (error) => {
4370
- remoteStatus.textContent = t("requestFailed", { error: String(error) });
5352
+ remoteFailureText = remoteFailureTextFor(error, "requestFailed");
5353
+ remoteStatus.textContent = remoteFailureText;
4371
5354
  }).finally(loadRemote);
4372
5355
  });
4373
5356
  remoteLogin.addEventListener("click", () => {
@@ -4379,12 +5362,13 @@ window.__ModuleLoader__.load({
4379
5362
  remoteReconnect.disabled = true;
4380
5363
  remoteSetup.disabled = true;
4381
5364
  remoteSetupRetry.disabled = true;
4382
- remoteStatus.textContent = remoteProvider === "cpolar" ? t("reconnectingCpolar") : remoteProvider === "frp" ? t("reconnectingFrp") : t("reconnectingTailscale");
5365
+ remoteStatus.textContent = remoteProvider === "cpolar" ? t("reconnectingCpolar") : remoteProvider === "cloudflared" ? t("reconnectingCloudflared") : remoteProvider === "frp" ? t("reconnectingFrp") : remoteProvider === "origin" ? t("reconnectingOrigin") : t("reconnectingTailscale");
4383
5366
  controlRequestJson("/api/mobile-access/remote/reconnect", {
4384
5367
  method: "POST",
4385
5368
  body: "{}"
4386
5369
  }).then(renderRemote, (error) => {
4387
- remoteStatus.textContent = t("requestFailed", { error: String(error) });
5370
+ remoteFailureText = remoteFailureTextFor(error, "requestFailed");
5371
+ remoteStatus.textContent = remoteFailureText;
4388
5372
  }).finally(() => {
4389
5373
  remoteReconnectBusy = false;
4390
5374
  remoteReconnect.disabled = false;
@@ -4440,7 +5424,8 @@ window.__ModuleLoader__.load({
4440
5424
  remotePair.addEventListener("click", () => {
4441
5425
  remotePair.disabled = true;
4442
5426
  openRemotePairing().catch((error) => {
4443
- remoteStatus.textContent = t("requestFailed", { error: String(error) });
5427
+ remoteFailureText = remoteFailureTextFor(error, "requestFailed");
5428
+ remoteStatus.textContent = remoteFailureText;
4444
5429
  }).finally(() => {
4445
5430
  remotePair.disabled = !remoteReady;
4446
5431
  });
@@ -4448,7 +5433,8 @@ window.__ModuleLoader__.load({
4448
5433
  remoteCopyLink.addEventListener("click", () => {
4449
5434
  remoteCopyLink.disabled = true;
4450
5435
  (activeRemotePairUrl !== "" && activeRemotePairExpiresAt > Date.now() ? copyRemotePairUrl() : openRemotePairing()).catch((error) => {
4451
- remoteStatus.textContent = t("requestFailed", { error: String(error) });
5436
+ remoteFailureText = remoteFailureTextFor(error, "requestFailed");
5437
+ remoteStatus.textContent = remoteFailureText;
4452
5438
  }).finally(() => {
4453
5439
  remoteCopyLink.disabled = !remoteReady;
4454
5440
  });
@@ -4477,7 +5463,8 @@ window.__ModuleLoader__.load({
4477
5463
  method: "POST",
4478
5464
  body: JSON.stringify({ deviceId: id })
4479
5465
  }).then(loadRemoteDevices, (error) => {
4480
- remoteStatus.textContent = t("requestFailed", { error: String(error) });
5466
+ remoteFailureText = remoteFailureTextFor(error, "requestFailed");
5467
+ remoteStatus.textContent = remoteFailureText;
4481
5468
  });
4482
5469
  });
4483
5470
  row.append(label, meta, revoke);
@@ -4486,7 +5473,8 @@ window.__ModuleLoader__.load({
4486
5473
  };
4487
5474
  const loadRemoteDevices = () => {
4488
5475
  controlRequestJson("/api/mobile-access/remote/devices").then(renderRemoteDevices, (error) => {
4489
- remoteStatus.textContent = t("requestFailed", { error: String(error) });
5476
+ remoteFailureText = remoteFailureTextFor(error, "requestFailed");
5477
+ remoteStatus.textContent = remoteFailureText;
4490
5478
  });
4491
5479
  };
4492
5480
  remoteDevices.addEventListener("click", () => {
@@ -4495,13 +5483,14 @@ window.__ModuleLoader__.load({
4495
5483
  if (show) loadRemoteDevices();
4496
5484
  });
4497
5485
  remoteReset.addEventListener("click", () => {
4498
- const prompt = remoteProvider === "cpolar" ? t("resetCpolarConfirm") : remoteProvider === "frp" ? t("resetFrpConfirm") : t("resetTailscaleConfirm");
5486
+ const prompt = remoteProvider === "cpolar" ? t("resetCpolarConfirm") : remoteProvider === "cloudflared" ? t("resetCloudflaredConfirm") : remoteProvider === "frp" ? t("resetFrpConfirm") : remoteProvider === "origin" ? t("resetOriginConfirm") : t("resetTailscaleConfirm");
4499
5487
  if (!window.confirm(prompt)) return;
4500
5488
  controlRequestJson("/api/mobile-access/remote/reset", {
4501
5489
  method: "POST",
4502
5490
  body: JSON.stringify({ confirm: true })
4503
5491
  }).then(renderRemote, (error) => {
4504
- remoteStatus.textContent = t("requestFailed", { error: String(error) });
5492
+ remoteFailureText = remoteFailureTextFor(error, "requestFailed");
5493
+ remoteStatus.textContent = remoteFailureText;
4505
5494
  });
4506
5495
  });
4507
5496
  const renderDiagnostics = (data) => {
@@ -4532,12 +5521,21 @@ window.__ModuleLoader__.load({
4532
5521
  const templates = DIAGNOSTIC_REASON_MESSAGES[locale][reason];
4533
5522
  if (templates === void 0) return serverCopy;
4534
5523
  const facts = entry.facts !== null && typeof entry.facts === "object" ? entry.facts : {};
5524
+ const providerNames = {
5525
+ tailscale: "Tailscale Funnel",
5526
+ cpolar: "cpolar",
5527
+ cloudflared: "cloudflared",
5528
+ frp: t("frpName"),
5529
+ origin: t("originName")
5530
+ };
5531
+ const providerId = typeof facts.provider === "string" ? facts.provider : "";
5532
+ const controllerKey = typeof facts.controllerCode === "string" ? REMOTE_ERROR_MESSAGE_KEYS[facts.controllerCode] : void 0;
4535
5533
  const values = {
4536
- provider: facts.provider === "tailscale" || facts.provider === "cpolar" || facts.provider === "frp" ? facts.provider : "",
5534
+ provider: providerNames[providerId] ?? "",
4537
5535
  latencyMs: typeof facts.latencyMs === "number" && Number.isFinite(facts.latencyMs) ? new Intl.NumberFormat(localeTag).format(facts.latencyMs) : "",
4538
5536
  interfaceName: typeof facts.interfaceName === "string" ? facts.interfaceName : "",
4539
5537
  endpointSuffix: typeof facts.endpointSuffix === "string" ? facts.endpointSuffix : "",
4540
- controllerCode: typeof facts.controllerCode === "string" ? facts.controllerCode : ""
5538
+ controllerCode: controllerKey === void 0 ? "" : t(controllerKey)
4541
5539
  };
4542
5540
  const interpolate = (template) => template.replace(/\{(\w+)\}/gu, (_match, key) => values[key] ?? "");
4543
5541
  let detail = interpolate(templates[0]);
@@ -4552,6 +5550,7 @@ window.__ModuleLoader__.load({
4552
5550
  }
4553
5551
  if (reason === "remote-controller-error") {
4554
5552
  const actionKey = {
5553
+ ...ORIGIN_ERROR_MESSAGE_KEYS,
4555
5554
  component_missing: "remoteUnavailableTailscale",
4556
5555
  funnel_permission_required: "funnelPermission",
4557
5556
  funnel_https_required: "funnelHttps",
@@ -4569,6 +5568,22 @@ window.__ModuleLoader__.load({
4569
5568
  cpolar_start_timeout: "cpolarTimeout",
4570
5569
  cpolar_stopped: "cpolarStopped",
4571
5570
  cpolar_exited: "cpolarExited",
5571
+ cloudflared_component_missing: "cloudflaredMissing",
5572
+ cloudflared_component_invalid: "cloudflaredInvalid",
5573
+ cloudflared_component_unsupported: "cloudflaredComponentUnsupported",
5574
+ cloudflared_port_unavailable: "cloudflaredPortUnavailable",
5575
+ cloudflared_port_reservation_failed: "cloudflaredPortReservationFailed",
5576
+ cloudflared_launch_failed: "cloudflaredLaunchFailed",
5577
+ cloudflared_start_timeout: "cloudflaredTimeout",
5578
+ cloudflared_stopped: "cloudflaredStopped",
5579
+ cloudflared_exited: "cloudflaredExited",
5580
+ cloudflared_invalid_origin: "cloudflaredOriginInvalid",
5581
+ cloudflared_tunnel_port_unavailable: "cloudflaredTunnelPortUnavailable",
5582
+ cloudflared_tunnel_config_invalid: "cloudflaredTunnelInvalid",
5583
+ cloudflared_tunnel_target_invalid: "cloudflaredTunnelTargetInvalid",
5584
+ cloudflared_download_hash_mismatch: "cloudflaredDownloadHashMismatch",
5585
+ cloudflared_download_size_mismatch: "cloudflaredDownloadSizeMismatch",
5586
+ cloudflared_executable_hash_mismatch: "cloudflaredExecutableHashMismatch",
4572
5587
  frp_component_missing: "frpMissing",
4573
5588
  frp_component_invalid: "frpInvalid",
4574
5589
  frp_config_missing: "frpConfigMissing",
@@ -6007,31 +7022,38 @@ window.__ModuleLoader__.load({
6007
7022
  /** Theme-aware desktop Mobile Access panel styles. */
6008
7023
  const CONTROL_STYLES = `
6009
7024
  .dsh-mobile-control{position:fixed;z-index:1000;left:16px;bottom:112px;font:14px/1.45 system-ui;color:var(--dsw-alias-label-primary,#16181d)}
6010
- .dsh-mobile-control__panel{box-sizing:border-box;width:min(380px,calc(100vw - 32px));max-height:calc(100vh - 140px);overflow-y:auto;padding:16px;border:1px solid var(--dsw-alias-border-subtle,#e1e5eb);border-radius:18px;background:var(--dsw-alias-bg-layer-2,#fff);box-shadow:0 18px 50px rgb(15 23 42 / 18%)}
6011
- .dsh-mobile-control__header{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:10px}.dsh-mobile-control__panel h2{margin:0;font-size:17px;line-height:24px}.dsh-mobile-control__header-actions{display:flex;align-items:center;gap:2px}.dsh-mobile-control__update-plugin,.dsh-mobile-control__diagnostic-entry,.dsh-mobile-control__close{display:inline-flex;align-items:center;justify-content:center;min-width:44px;height:44px;padding:0;border:0;border-radius:10px;background:transparent;color:inherit;cursor:pointer}.dsh-mobile-control__update-plugin,.dsh-mobile-control__diagnostic-entry{padding:0 8px;color:#2563eb;font:650 12px/1 system-ui;white-space:nowrap}.dsh-mobile-control__update-plugin[hidden]{display:none}.dsh-mobile-control__update-plugin:disabled{cursor:wait;opacity:.55}.dsh-mobile-control__close{font-size:24px;line-height:1}.dsh-mobile-control__update-plugin:hover:not(:disabled),.dsh-mobile-control__diagnostic-entry:hover,.dsh-mobile-control__close:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f3f6)}
6012
- .dsh-mobile-control__release-notice{margin:-2px 0 10px;padding:8px 10px;border-radius:9px;background:var(--dsw-alias-bg-layer-1,#eff6ff);color:var(--dsw-alias-label-primary,#1d4ed8);font-size:11px;line-height:1.45}.dsh-mobile-control__release-notice.is-error{color:#dc2626}.dsh-mobile-control__release-notice[hidden]{display:none}.dsh-mobile-control__update-card{margin:0 0 10px;padding:11px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:12px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__update-card[hidden]{display:none}.dsh-mobile-control__update-card-title{margin:0 0 7px;color:var(--dsw-alias-label-primary,#16181d);font:650 13px/1.4 system-ui}.dsh-mobile-control__update-notes{display:block;box-sizing:border-box;max-height:150px;overflow-y:auto;margin:0 0 8px;padding:8px 10px;border:1px solid var(--dsw-alias-border-subtle,#e1e5eb);border-radius:9px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-secondary,#475569);white-space:pre-wrap;word-break:break-word;font:11px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.dsh-mobile-control__update-notes[hidden]{display:none}.dsh-mobile-control__update-notice{margin:0 0 9px;padding:7px 9px;border-radius:8px;background:#fff4d6;color:#92400e;font-size:11px;line-height:1.5}.dsh-mobile-control__update-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px}.dsh-mobile-control__update-actions .dsh-mobile-control__primary,.dsh-mobile-control__update-actions .dsh-mobile-control__secondary{min-height:34px;padding:0 12px}.dsh-mobile-control__app-download{display:flex;align-items:center;justify-content:space-between;box-sizing:border-box;min-height:38px;margin:0 0 10px;padding:8px 11px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-1,#f7f8fa);color:var(--dsw-alias-label-primary,#16181d);font:600 12px/1.3 system-ui;text-decoration:none}.dsh-mobile-control__app-download[hidden]{display:none}.dsh-mobile-control__app-download::after{color:#2563eb;font-size:14px;content:"↗"}.dsh-mobile-control__app-download:hover{border-color:#9fb9e8;background:var(--dsw-alias-interactive-bg-hover-solid,var(--dsw-alias-bg-layer-2,#f5f8ff));color:var(--dsw-alias-label-primary,#1d4ed8)}
7025
+ .dsh-mobile-control__panel{box-sizing:border-box;width:min(380px,calc(100vw - 32px));max-height:calc(100vh - 140px);overflow-y:auto;padding:16px;border:1px solid var(--dsw-alias-border-l2,#e1e5eb);border-radius:18px;background:var(--dsw-alias-bg-layer-2,#fff);box-shadow:0 18px 50px rgb(15 23 42 / 18%)}
7026
+ .dsh-mobile-control__header{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:10px}.dsh-mobile-control__panel h2{margin:0;font-size:17px;line-height:24px}.dsh-mobile-control__header-actions{display:flex;align-items:center;gap:2px}.dsh-mobile-control__update-plugin,.dsh-mobile-control__diagnostic-entry,.dsh-mobile-control__close{display:inline-flex;align-items:center;justify-content:center;min-width:44px;height:44px;padding:0;border:0;border-radius:10px;background:transparent;color:inherit;cursor:pointer}.dsh-mobile-control__update-plugin,.dsh-mobile-control__diagnostic-entry{padding:0 8px;color:var(--dsw-alias-label-primary-bluish,#2563eb);font:650 12px/1 system-ui;white-space:nowrap}.dsh-mobile-control__update-plugin[hidden]{display:none}.dsh-mobile-control__update-plugin:disabled{cursor:wait;opacity:.55}.dsh-mobile-control__close{font-size:24px;line-height:1}.dsh-mobile-control__update-plugin:hover:not(:disabled),.dsh-mobile-control__diagnostic-entry:hover,.dsh-mobile-control__close:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f3f6)}
7027
+ .dsh-mobile-control__release-notice{margin:-2px 0 10px;padding:8px 10px;border-radius:9px;background:var(--dsw-alias-bg-layer-1,#eff6ff);color:var(--dsw-alias-label-primary,#1d4ed8);font-size:11px;line-height:1.45}.dsh-mobile-control__release-notice.is-error{color:var(--dsw-alias-label-primary,#dc2626);border-left:3px solid var(--dsw-alias-state-error-primary,#dc2626)}.dsh-mobile-control__release-notice[hidden]{display:none}.dsh-mobile-control__update-card{margin:0 0 10px;padding:11px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:12px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__update-card[hidden]{display:none}.dsh-mobile-control__update-card-title{margin:0 0 7px;color:var(--dsw-alias-label-primary,#16181d);font:650 13px/1.4 system-ui}.dsh-mobile-control__update-notes{display:block;box-sizing:border-box;max-height:150px;overflow-y:auto;margin:0 0 8px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#e1e5eb);border-radius:9px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-secondary,#475569);white-space:pre-wrap;word-break:break-word;font:11px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.dsh-mobile-control__update-notes[hidden]{display:none}.dsh-mobile-control__update-notice{margin:0 0 9px;padding:7px 9px;border-radius:8px;background:var(--dsw-alias-state-warn-tertiary,#fff4d6);color:var(--dsw-alias-state-warn-label,#92400e);font-size:11px;line-height:1.5}.dsh-mobile-control__update-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px}.dsh-mobile-control__update-actions .dsh-mobile-control__primary,.dsh-mobile-control__update-actions .dsh-mobile-control__secondary{min-height:36px;padding:0 12px}.dsh-mobile-control__app-download{display:flex;align-items:center;justify-content:space-between;box-sizing:border-box;min-height:44px;margin:0 0 10px;padding:8px 11px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-1,#f7f8fa);color:var(--dsw-alias-label-primary,#16181d);font:600 12px/1.3 system-ui;text-decoration:none}.dsh-mobile-control__app-download[hidden]{display:none}.dsh-mobile-control__app-download::after{color:var(--dsw-alias-label-primary-bluish,#2563eb);font-size:14px;content:"↗"}.dsh-mobile-control__app-download:hover{border-color:#9fb9e8;background:var(--dsw-alias-interactive-bg-hover-solid,var(--dsw-alias-bg-layer-2,#f5f8ff));color:var(--dsw-alias-label-primary,#1d4ed8)}
6013
7028
  .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}
6014
- .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:repeat(2,minmax(0,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-subtle,#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:#2563eb;background:var(--dsw-alias-interactive-bg-active,var(--dsw-alias-bg-layer-1,#f5f8ff));box-shadow:0 0 0 1px #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:#e8f0ff;color:#1d4ed8;font:650 9px/1.25 system-ui}.dsh-mobile-control__provider-badge.is-cpolar{background:#eaf8f2;color:#087454}.dsh-mobile-control__provider-description{color:var(--dsw-alias-label-secondary,#606873);font-size:10px;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:#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-subtle,#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}
6015
- .dsh-mobile-control__self-hosted{margin:8px 0 0;border:1px solid var(--dsw-alias-border-subtle,#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:9px;line-height:1.35}.dsh-mobile-control__provider-badge.is-frp{background:#eef0f3;color:#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)}
6016
- .dsh-mobile-control__cpolar-setup{margin:0 0 12px;padding:12px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__cpolar-setup[hidden],.dsh-mobile-control__cpolar-account[hidden],.dsh-mobile-control__details[hidden],.dsh-mobile-control__view.is-remote .dsh-mobile-control__actions[hidden],.dsh-mobile-control__danger[hidden]{display:none}.dsh-mobile-control__component-status,.dsh-mobile-control__component-note{margin:0 0 10px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.55}.dsh-mobile-control__cpolar-setup>.dsh-mobile-control__primary{width:100%;min-height:44px;padding:9px 12px;border-radius:10px;font:600 12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__cpolar-account{margin-top:10px}.dsh-mobile-control__link-row{display:flex;flex-wrap:wrap;gap:6px 12px;margin:0 0 10px}.dsh-mobile-control__text-link{color:#2563eb;font-size:11px;text-decoration:none}.dsh-mobile-control__text-link:hover{text-decoration:underline}.dsh-mobile-control__token-label{display:flex;flex-direction:column;gap:5px;margin:0 0 8px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px}.dsh-mobile-control__token{box-sizing:border-box;width:100%;min-height:44px;padding:9px 10px;border:1px solid var(--dsw-alias-border-normal,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-bg-layer-2,#fff));color:var(--dsw-alias-label-primary,#16181d);font:16px/1.4 system-ui}.dsh-mobile-control__cpolar-connect{display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:100%;min-height:44px;padding:10px 14px;border-radius:12px;font:650 13px/1.2 system-ui;cursor:pointer;transition:background-color 160ms ease,border-color 160ms ease,opacity 160ms ease}.dsh-mobile-control__cpolar-connect:hover:not(:disabled){border-color:#1d4ed8;background:#1d4ed8}.dsh-mobile-control__cpolar-connect:active:not(:disabled){border-color:#1e40af;background:#1e40af}.dsh-mobile-control__cpolar-connect:disabled{cursor:wait;opacity:.55}.dsh-mobile-control__details{margin:10px 0 0;border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb);padding-top:9px}.dsh-mobile-control__details>summary{min-height:30px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:30px;cursor:pointer}.dsh-mobile-control__details-body{display:flex;flex-wrap:wrap;align-items:center;gap:7px 12px;padding:4px 0}.dsh-mobile-control__details-body p{flex:1 0 100%;margin:0;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__storage{display:block;flex:1 0 100%;max-width:100%;overflow:hidden;padding:7px 8px;border-radius:8px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-secondary,#475569);font:10px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__danger{flex:1 0 100%;min-height:38px;margin-top:3px;padding:7px 10px;border:1px solid #dc2626;border-radius:9px;background:transparent;color:#dc2626;font:12px/1.3 system-ui;cursor:pointer}
6017
- .dsh-mobile-control__frp-setup{margin:0;padding:12px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__frp-setup[hidden]{display:none}.dsh-mobile-control__frp-step{padding:11px 0}.dsh-mobile-control__frp-step + .dsh-mobile-control__frp-step{border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}.dsh-mobile-control__frp-step>strong{display:block;margin-bottom:3px;font-size:12px;line-height:1.4}.dsh-mobile-control__frp-step>p{margin:0 0 9px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__frp-changes{margin:0 0 9px;padding:0;list-style:none;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__frp-changes li{position:relative;margin:0;padding-left:12px}.dsh-mobile-control__frp-changes li+li{margin-top:5px}.dsh-mobile-control__frp-changes li::before{content:"•";position:absolute;left:2px;color:var(--dsw-alias-label-tertiary,#98a1ad)}.dsh-mobile-control__frp-step>.dsh-mobile-control__frp-requirement{padding:8px 9px;border-radius:9px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-primary,#384152);font-size:11px}.dsh-mobile-control__frp-fields{display:grid;grid-template-columns:minmax(0,1fr) 96px;gap:8px}.dsh-mobile-control__field{display:flex;min-width:0;flex-direction:column;gap:5px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px}.dsh-mobile-control__field:nth-child(3),.dsh-mobile-control__field:nth-child(4){grid-column:1/-1}.dsh-mobile-control__field input{box-sizing:border-box;width:100%;min-height:44px;padding:9px 10px;border:1px solid var(--dsw-alias-border-normal,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);font:16px/1.4 system-ui}.dsh-mobile-control__frp-action{box-sizing:border-box;width:100%;min-height:44px;padding:9px 12px;border-radius:10px;font:650 12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__frp-action:disabled{cursor:not-allowed;opacity:.5}.dsh-mobile-control__remote-workspace{margin:0;padding:12px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:15px;background:var(--dsw-alias-bg-layer-1,#f7f8fa)}.dsh-mobile-control__stage-header{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:9px}.dsh-mobile-control__stage-header .dsh-mobile-control__section-title{margin:0}.dsh-mobile-control__stage-meta{display:flex;min-width:0;align-items:center;justify-content:flex-end;gap:5px}.dsh-mobile-control__stage-value{max-width:115px;overflow:hidden;color:var(--dsw-alias-label-primary,#16181d);font:650 10px/1.3 system-ui;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__state-badge{flex:none;padding:3px 7px;border-radius:999px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-secondary,#606873);font:650 9px/1.25 system-ui}.dsh-mobile-control__state-badge.is-ready{background:#e6f7f0;color:#087454}.dsh-mobile-control__state-badge.is-busy{background:#e8f0ff;color:#1d4ed8}.dsh-mobile-control__state-badge.is-attention{background:#fff4dc;color:#935100}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__status{box-sizing:border-box;margin:0 0 10px;padding:9px 10px;border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);font-size:11px;line-height:1.45}.dsh-mobile-control__provider-setup-body{margin:0 0 10px}.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__cpolar-setup{margin:0}.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__cpolar-setup>.dsh-mobile-control__section-title,.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__frp-setup>.dsh-mobile-control__section-title{display:none}.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__details{margin:0;padding:9px 10px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__actions{margin-top:2px}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__qr{margin:10px 0 0}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__manage-row{margin-top:10px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}
6018
- .dsh-mobile-control__frp-overview{display:grid;grid-template-columns:32px minmax(0,1fr);align-items:center;gap:10px;margin:0 0 10px;padding:10px;border:1px solid #a9dfc9;border-radius:11px;background:#edf9f4}.dsh-mobile-control__frp-overview[hidden]{display:none}.dsh-mobile-control__frp-overview-mark{display:grid;width:32px;height:32px;place-items:center;border-radius:50%;background:#087454;color:#fff;font:700 15px/1 system-ui}.dsh-mobile-control__frp-overview-body{display:flex;min-width:0;flex-direction:column;gap:2px}.dsh-mobile-control__frp-overview-body strong{color:#075d46;font-size:12px;line-height:1.35}.dsh-mobile-control__frp-overview-body span{overflow:hidden;color:#357061;font:10px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__frp-group{margin:8px 0 0;overflow:hidden;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__frp-group>summary{box-sizing:border-box;min-height:44px;padding:12px 34px 10px 12px;color:var(--dsw-alias-label-primary,#16181d);font:650 12px/1.4 system-ui;cursor:pointer}.dsh-mobile-control__frp-group[open]>summary{border-bottom:1px solid var(--dsw-alias-border-subtle,#e1e5eb);background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__frp-group>.dsh-mobile-control__frp-step{padding:12px}.dsh-mobile-control__frp-group>.dsh-mobile-control__frp-step>strong:first-child{display:none}.dsh-mobile-control__frp-group .dsh-mobile-control__frp-step{border-top:0}.dsh-mobile-control__frp-setup>.dsh-mobile-control__frp-step{margin-bottom:8px;padding:10px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}
6019
- .dsh-mobile-control__lan-setup{box-sizing:border-box;margin:0 0 12px;padding:14px;border:1px solid #6f96db;border-radius:15px;background:var(--dsw-alias-bg-layer-1,#f5f8ff)}.dsh-mobile-control__lan-setup[hidden],.dsh-mobile-control__lan-setup-form[hidden]{display:none}.dsh-mobile-control__lan-setup>.dsh-mobile-control__section-title{margin-bottom:5px}.dsh-mobile-control__lan-setup>.dsh-mobile-control__intro{margin-bottom:12px}.dsh-mobile-control__lan-setup-form{display:grid;gap:9px}.dsh-mobile-control__lan-setup .dsh-mobile-control__field select{box-sizing:border-box;width:100%;min-height:44px;padding:9px 32px 9px 10px;border:1px solid var(--dsw-alias-border-normal,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);font:14px/1.4 system-ui}.dsh-mobile-control__lan-setup .dsh-mobile-control__component-note{margin:0}.dsh-mobile-control__lan-setup-actions{display:grid;grid-template-columns:1fr 1.35fr;gap:8px}.dsh-mobile-control__lan-setup-actions button{box-sizing:border-box;min-width:0;min-height:44px;padding:9px 10px;border-radius:10px;font:650 12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__lan-setup-actions button:disabled{cursor:wait;opacity:.55}.dsh-mobile-control__lan-setup>.dsh-mobile-control__status{margin:10px 0 0;line-height:1.5}.dsh-mobile-control__lan-setup>.dsh-mobile-control__status:empty{display:none}
6020
- .dsh-mobile-control__access{display:flex;align-items:baseline;gap:6px;min-width:0;margin:0 0 12px}.dsh-mobile-control__access[hidden]{display:none}.dsh-mobile-control__access-label{flex:none;color:var(--dsw-alias-label-secondary,#606873);white-space:nowrap}.dsh-mobile-control__access-label::after{content:":"}.dsh-mobile-control__access-link{min-width:0;overflow:hidden;color:#2563eb;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__access-link:hover{text-decoration:underline}.dsh-mobile-control__qr{display:flex;justify-content:center;margin:0 0 12px}.dsh-mobile-control__qr[hidden]{display:none}.dsh-mobile-control__qr img{border-radius:12px;background:#fff;padding:8px}
6021
- .dsh-mobile-control__status{margin:0 0 14px;overflow-wrap:anywhere;color:var(--dsw-alias-label-secondary,#606873)}.dsh-mobile-control__status::before{display:inline-block;width:8px;height:8px;margin-right:7px;border-radius:50%;background:#98a1ad;content:""}.dsh-mobile-control__status.is-running::before{background:#16a36a}.dsh-mobile-control__status.is-key{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:12px;word-break:break-all}
7029
+ .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}
7030
+ .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)}
7031
+ /* The cloudflared setup section deliberately reuses this card, so
7032
+ .dsh-mobile-control__cloudflared-setup is a DOM hook for targeting rather than a
7033
+ style rule of its own. */
7034
+ .dsh-mobile-control__cpolar-setup{margin:0 0 12px;padding:12px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__cpolar-setup[hidden],.dsh-mobile-control__cpolar-account[hidden],.dsh-mobile-control__details[hidden],.dsh-mobile-control__view.is-remote .dsh-mobile-control__actions[hidden],.dsh-mobile-control__danger[hidden]{display:none}.dsh-mobile-control__component-status,.dsh-mobile-control__component-note{margin:0 0 10px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.55}.dsh-mobile-control__cpolar-setup>.dsh-mobile-control__primary{width:100%;min-height:44px;padding:9px 12px;border-radius:10px;font:600 12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__cpolar-account{margin-top:10px}.dsh-mobile-control__link-row{display:flex;flex-wrap:wrap;gap:6px 12px;margin:0 0 10px}.dsh-mobile-control__text-link{color:var(--dsw-alias-label-primary-bluish,#2563eb);font-size:11px;text-decoration:none}.dsh-mobile-control__text-link:hover{text-decoration:underline}.dsh-mobile-control__token-label{display:flex;flex-direction:column;gap:5px;margin:0 0 8px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px}.dsh-mobile-control__token{box-sizing:border-box;width:100%;min-height:44px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-bg-layer-2,#fff));color:var(--dsw-alias-label-primary,#16181d);font:16px/1.4 system-ui}.dsh-mobile-control__cpolar-connect{display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:100%;min-height:44px;padding:10px 14px;border-radius:12px;font:650 13px/1.2 system-ui;cursor:pointer;transition:background-color 160ms ease,border-color 160ms ease,opacity 160ms ease}.dsh-mobile-control__cpolar-connect:hover:not(:disabled){border-color:#1d4ed8;background:#1d4ed8}.dsh-mobile-control__cpolar-connect:active:not(:disabled){border-color:#1e40af;background:#1e40af}.dsh-mobile-control__cpolar-connect:disabled{cursor:wait;opacity:.55}.dsh-mobile-control__details{margin:10px 0 0;border-top:1px solid var(--dsw-alias-border-l2,#e1e5eb);padding-top:9px}.dsh-mobile-control__details>summary{min-height:36px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:30px;cursor:pointer}.dsh-mobile-control__details-body{display:flex;flex-wrap:wrap;align-items:center;gap:7px 12px;padding:4px 0}.dsh-mobile-control__details-body p{flex:1 0 100%;margin:0;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__storage{display:block;flex:1 0 100%;max-width:100%;overflow:hidden;padding:7px 8px;border-radius:8px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-secondary,#475569);font:11px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__danger{flex:1 0 100%;min-height:44px;margin-top:3px;padding:7px 10px;border:1px solid var(--dsw-alias-state-error-primary,#dc2626);border-radius:9px;background:transparent;color:var(--dsw-alias-label-primary,#dc2626);font:12px/1.3 system-ui;cursor:pointer}
7035
+ .dsh-mobile-control__danger:hover:not(:disabled),.dsh-mobile-control__device-revoke:hover{background:var(--dsw-alias-interactive-bg-hover-danger,rgb(220 38 38 / 8%))}
7036
+ .dsh-mobile-control__frp-setup{margin:0;padding:12px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__frp-setup[hidden]{display:none}.dsh-mobile-control__frp-step{padding:11px 0}.dsh-mobile-control__frp-step + .dsh-mobile-control__frp-step{border-top:1px solid var(--dsw-alias-border-l2,#e1e5eb)}.dsh-mobile-control__frp-step>strong{display:block;margin-bottom:3px;font-size:12px;line-height:1.4}.dsh-mobile-control__frp-step>p{margin:0 0 9px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__frp-changes{margin:0 0 9px;padding:0;list-style:none;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__frp-changes li{position:relative;margin:0;padding-left:12px}.dsh-mobile-control__frp-changes li+li{margin-top:5px}.dsh-mobile-control__frp-changes li::before{content:"•";position:absolute;left:2px;color:var(--dsw-alias-label-tertiary,#98a1ad)}.dsh-mobile-control__frp-step>.dsh-mobile-control__frp-requirement{padding:8px 9px;border-radius:9px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-primary,#384152);font-size:11px}.dsh-mobile-control__frp-fields{display:grid;grid-template-columns:minmax(0,1fr) 96px;gap:8px}.dsh-mobile-control__field{display:flex;min-width:0;flex-direction:column;gap:5px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px}.dsh-mobile-control__field:nth-child(3),.dsh-mobile-control__field:nth-child(4){grid-column:1/-1}.dsh-mobile-control__field input{box-sizing:border-box;width:100%;min-height:44px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);font:16px/1.4 system-ui}.dsh-mobile-control__frp-action{box-sizing:border-box;width:100%;min-height:44px;padding:9px 12px;border-radius:10px;font:650 12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__frp-action:disabled{cursor:not-allowed;opacity:.5}.dsh-mobile-control__remote-workspace{margin:0;padding:12px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:15px;background:var(--dsw-alias-bg-layer-1,#f7f8fa)}.dsh-mobile-control__stage-header{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:9px}.dsh-mobile-control__stage-header .dsh-mobile-control__section-title{margin:0}.dsh-mobile-control__stage-meta{display:flex;min-width:0;align-items:center;justify-content:flex-end;gap:5px}.dsh-mobile-control__stage-value{max-width:115px;overflow:hidden;color:var(--dsw-alias-label-primary,#16181d);font:650 11px/1.3 system-ui;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__state-badge{flex:none;padding:3px 7px;border-radius:999px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-secondary,#606873);font:650 11px/1.25 system-ui}.dsh-mobile-control__state-badge.is-ready{background:var(--dsw-alias-state-success-tertiary,#e6f7f0);color:var(--dsw-alias-label-primary,#087454)}.dsh-mobile-control__state-badge.is-busy{background:var(--dsw-alias-state-business-tertiary,#e8f0ff);color:var(--dsw-alias-label-primary,#1d4ed8)}.dsh-mobile-control__state-badge.is-attention{background:var(--dsw-alias-state-warn-tertiary,#fff4dc);color:var(--dsw-alias-label-primary,#935100)}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__status{box-sizing:border-box;margin:0 0 10px;padding:9px 10px;border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);font-size:11px;line-height:1.45}.dsh-mobile-control__provider-setup-body{margin:0 0 10px}.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__cpolar-setup{margin:0}.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__cpolar-setup>.dsh-mobile-control__section-title,.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__frp-setup>.dsh-mobile-control__section-title{display:none}.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__details{margin:0;padding:9px 10px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__actions{margin-top:2px}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__qr{margin:10px 0 0}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__manage-row{margin-top:10px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l2,#e1e5eb)}
7037
+ .dsh-mobile-control__frp-overview{display:grid;grid-template-columns:32px minmax(0,1fr);align-items:center;gap:10px;margin:0 0 10px;padding:10px;border:1px solid var(--dsw-alias-state-success-primary,#a9dfc9);border-radius:11px;background:var(--dsw-alias-state-success-tertiary,#edf9f4)}.dsh-mobile-control__frp-overview[hidden]{display:none}.dsh-mobile-control__frp-overview-mark{display:grid;width:32px;height:32px;place-items:center;border-radius:50%;background:#087454;color:#fff;font:700 15px/1 system-ui}.dsh-mobile-control__frp-overview-body{display:flex;min-width:0;flex-direction:column;gap:2px}.dsh-mobile-control__frp-overview-body strong{color:var(--dsw-alias-label-primary,#075d46);font-size:12px;line-height:1.35}.dsh-mobile-control__frp-overview-body span{overflow:hidden;color:var(--dsw-alias-label-secondary,#357061);font:11px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__frp-group{margin:8px 0 0;overflow:hidden;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__frp-group>summary{box-sizing:border-box;min-height:44px;padding:12px 34px 10px 12px;color:var(--dsw-alias-label-primary,#16181d);font:650 12px/1.4 system-ui;cursor:pointer}.dsh-mobile-control__frp-group[open]>summary{border-bottom:1px solid var(--dsw-alias-border-l2,#e1e5eb);background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__frp-group>.dsh-mobile-control__frp-step{padding:12px}.dsh-mobile-control__frp-group>.dsh-mobile-control__frp-step>strong:first-child{display:none}.dsh-mobile-control__frp-group .dsh-mobile-control__frp-step{border-top:0}.dsh-mobile-control__frp-setup>.dsh-mobile-control__frp-step{margin-bottom:8px;padding:10px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__tunnel{margin:0 0 10px}.dsh-mobile-control__tunnel[hidden]{display:none}
7038
+ .dsh-mobile-control__lan-setup{box-sizing:border-box;margin:0 0 12px;padding:14px;border:1px solid #6f96db;border-radius:15px;background:var(--dsw-alias-bg-layer-1,#f5f8ff)}.dsh-mobile-control__lan-setup[hidden],.dsh-mobile-control__lan-setup-form[hidden]{display:none}.dsh-mobile-control__lan-setup>.dsh-mobile-control__section-title{margin-bottom:5px}.dsh-mobile-control__lan-setup>.dsh-mobile-control__intro{margin-bottom:12px}.dsh-mobile-control__lan-setup-form{display:grid;gap:9px}.dsh-mobile-control__lan-setup .dsh-mobile-control__field select{box-sizing:border-box;width:100%;min-height:44px;padding:9px 32px 9px 10px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);font:14px/1.4 system-ui}.dsh-mobile-control__lan-setup .dsh-mobile-control__component-note{margin:0}.dsh-mobile-control__lan-setup-actions{display:grid;grid-template-columns:1fr 1.35fr;gap:8px}.dsh-mobile-control__lan-setup-actions button{box-sizing:border-box;min-width:0;min-height:44px;padding:9px 10px;border-radius:10px;font:650 12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__lan-setup-actions button:disabled{cursor:wait;opacity:.55}.dsh-mobile-control__lan-setup>.dsh-mobile-control__status{margin:10px 0 0;line-height:1.5}.dsh-mobile-control__lan-setup>.dsh-mobile-control__status:empty{display:none}
7039
+ .dsh-mobile-control__access{display:flex;align-items:baseline;gap:6px;min-width:0;margin:0 0 12px}.dsh-mobile-control__access[hidden]{display:none}.dsh-mobile-control__access-label{flex:none;color:var(--dsw-alias-label-secondary,#606873);white-space:nowrap}.dsh-mobile-control__access-label::after{content:":"}.dsh-mobile-control__access-link{min-width:0;overflow:hidden;color:var(--dsw-alias-label-primary-bluish,#2563eb);text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__access-link:hover{text-decoration:underline}.dsh-mobile-control__qr{display:flex;justify-content:center;margin:0 0 12px}.dsh-mobile-control__qr[hidden]{display:none}.dsh-mobile-control__qr img{border-radius:12px;background:#fff;padding:8px}
7040
+ .dsh-mobile-control__status{margin:0 0 14px;overflow-wrap:anywhere;color:var(--dsw-alias-label-secondary,#606873)}.dsh-mobile-control__status::before{display:inline-block;width:8px;height:8px;margin-right:7px;border-radius:50%;background:var(--dsw-alias-label-tertiary,#98a1ad);content:""}.dsh-mobile-control__status.is-running::before{background:var(--dsw-alias-state-success-primary,#16a36a)}.dsh-mobile-control__status.is-key{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:12px;word-break:break-all}
6022
7041
  .dsh-mobile-control__guide{margin:0 0 14px;padding:12px;border:1px solid #6f96db;border-radius:12px;background:var(--dsw-alias-bg-layer-1,#eff6ff)}.dsh-mobile-control__guide[hidden]{display:none}.dsh-mobile-control__guide-title{margin:0;color:var(--dsw-alias-label-primary,#172554);font:650 13px/1.45 system-ui}.dsh-mobile-control__guide-summary,.dsh-mobile-control__guide-note{margin:4px 0 0;color:var(--dsw-alias-label-secondary,#475569);font-size:12px;line-height:1.5}.dsh-mobile-control__guide-steps{margin:8px 0 0;padding-left:20px;color:var(--dsw-alias-label-primary,#1e293b);font-size:12px;line-height:1.6}.dsh-mobile-control__guide-note{color:var(--dsw-alias-label-secondary,#64748b)}.dsh-mobile-control__guide-actions{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px}.dsh-mobile-control__guide-actions button{min-width:0;min-height:44px;padding:8px;border-radius:10px;font:12px/1.25 system-ui;cursor:pointer}.dsh-mobile-control__guide-actions button:disabled{cursor:not-allowed;opacity:.45}
6023
7042
  .dsh-mobile-control__extensions{margin:0 0 12px;color:var(--dsw-alias-label-secondary,#606873);font-size:12px}
6024
- .dsh-mobile-control__view.is-diagnostics{--dsh-diagnostic-ok:#087454;--dsh-diagnostic-warning:#a35b00;--dsh-diagnostic-error:#c62828;--dsh-diagnostic-info:#526071}.dsh-mobile-control__diagnostic-summary{box-sizing:border-box;margin:0;padding:13px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:16px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__diagnostic-summary-main{display:grid;grid-template-columns:36px minmax(0,1fr);align-items:center;gap:11px}.dsh-mobile-control__diagnostic-summary-icon{position:relative;display:block;width:36px;height:36px;border-radius:50%;background:#e8edf3;color:var(--dsh-diagnostic-info)}.dsh-mobile-control__diagnostic-summary-icon::before,.dsh-mobile-control__diagnostic-summary-icon::after{position:absolute;content:""}.dsh-mobile-control__diagnostic-summary-body{display:flex;min-width:0;flex-direction:column;gap:2px}.dsh-mobile-control__diagnostic-summary-body strong{font-size:13px;line-height:1.35}.dsh-mobile-control__diagnostic-summary-body span{color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__diagnostic-summary-meta{display:block;margin-top:11px;padding-top:9px;border-top:1px solid var(--dsw-alias-border-subtle,#dbe1e8);color:var(--dsw-alias-label-secondary,#606873);font-size:10px;line-height:1.45}.dsh-mobile-control__diagnostic-summary.is-ok .dsh-mobile-control__diagnostic-summary-icon{background:#e6f7f0;color:var(--dsh-diagnostic-ok)}.dsh-mobile-control__diagnostic-summary.is-ok .dsh-mobile-control__diagnostic-summary-icon::before{top:10px;left:10px;width:13px;height:7px;border-bottom:2px solid currentColor;border-left:2px solid currentColor;transform:rotate(-45deg)}.dsh-mobile-control__diagnostic-summary.is-attention .dsh-mobile-control__diagnostic-summary-icon{background:#fff4dc;color:var(--dsh-diagnostic-warning)}.dsh-mobile-control__diagnostic-summary.is-error .dsh-mobile-control__diagnostic-summary-icon{background:#fdecec;color:var(--dsh-diagnostic-error)}.dsh-mobile-control__diagnostic-summary.is-attention .dsh-mobile-control__diagnostic-summary-icon::before,.dsh-mobile-control__diagnostic-summary.is-error .dsh-mobile-control__diagnostic-summary-icon::before{top:8px;left:17px;width:2px;height:13px;border-radius:2px;background:currentColor}.dsh-mobile-control__diagnostic-summary.is-attention .dsh-mobile-control__diagnostic-summary-icon::after,.dsh-mobile-control__diagnostic-summary.is-error .dsh-mobile-control__diagnostic-summary-icon::after{bottom:8px;left:17px;width:2px;height:2px;border-radius:50%;background:currentColor}.dsh-mobile-control__diagnostic-summary.is-running .dsh-mobile-control__diagnostic-summary-icon{background:#e8f0ff;color:#2563eb}.dsh-mobile-control__diagnostic-summary.is-running .dsh-mobile-control__diagnostic-summary-icon::before{inset:9px;border:2px solid rgb(37 99 235 / 24%);border-top-color:currentColor;border-radius:50%;animation:dsh-diagnostic-spin .8s linear infinite}
7043
+ .dsh-mobile-control__view.is-diagnostics{--dsh-diagnostic-ok:var(--dsw-alias-state-success-primary,#087454);--dsh-diagnostic-warning:var(--dsw-alias-state-warn-primary,#a35b00);--dsh-diagnostic-error:var(--dsw-alias-state-error-primary,#c62828);--dsh-diagnostic-info:var(--dsw-alias-label-secondary,#526071)}.dsh-mobile-control__diagnostic-summary{box-sizing:border-box;margin:0;padding:13px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:16px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__diagnostic-summary-main{display:grid;grid-template-columns:36px minmax(0,1fr);align-items:center;gap:11px}.dsh-mobile-control__diagnostic-summary-icon{position:relative;display:block;width:36px;height:36px;border-radius:50%;background:var(--dsw-alias-bg-layer-1,#e8edf3);color:var(--dsh-diagnostic-info)}.dsh-mobile-control__diagnostic-summary-icon::before,.dsh-mobile-control__diagnostic-summary-icon::after{position:absolute;content:""}.dsh-mobile-control__diagnostic-summary-body{display:flex;min-width:0;flex-direction:column;gap:2px}.dsh-mobile-control__diagnostic-summary-body strong{font-size:13px;line-height:1.35}.dsh-mobile-control__diagnostic-summary-body span{color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__diagnostic-summary-meta{display:block;margin-top:11px;padding-top:9px;border-top:1px solid var(--dsw-alias-border-l2,#dbe1e8);color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.45}.dsh-mobile-control__diagnostic-summary.is-ok .dsh-mobile-control__diagnostic-summary-icon{background:var(--dsw-alias-state-success-tertiary,#e6f7f0);color:var(--dsh-diagnostic-ok)}.dsh-mobile-control__diagnostic-summary.is-ok .dsh-mobile-control__diagnostic-summary-icon::before{top:10px;left:10px;width:13px;height:7px;border-bottom:2px solid currentColor;border-left:2px solid currentColor;transform:rotate(-45deg)}.dsh-mobile-control__diagnostic-summary.is-attention .dsh-mobile-control__diagnostic-summary-icon{background:var(--dsw-alias-state-warn-tertiary,#fff4dc);color:var(--dsh-diagnostic-warning)}.dsh-mobile-control__diagnostic-summary.is-error .dsh-mobile-control__diagnostic-summary-icon{background:var(--dsw-alias-state-error-secondary,#fdecec);color:var(--dsh-diagnostic-error)}.dsh-mobile-control__diagnostic-summary.is-attention .dsh-mobile-control__diagnostic-summary-icon::before,.dsh-mobile-control__diagnostic-summary.is-error .dsh-mobile-control__diagnostic-summary-icon::before{top:8px;left:17px;width:2px;height:13px;border-radius:2px;background:currentColor}.dsh-mobile-control__diagnostic-summary.is-attention .dsh-mobile-control__diagnostic-summary-icon::after,.dsh-mobile-control__diagnostic-summary.is-error .dsh-mobile-control__diagnostic-summary-icon::after{bottom:8px;left:17px;width:2px;height:2px;border-radius:50%;background:currentColor}.dsh-mobile-control__diagnostic-summary.is-running .dsh-mobile-control__diagnostic-summary-icon{background:var(--dsw-alias-state-business-tertiary,#e8f0ff);color:var(--dsw-alias-label-primary-bluish,#2563eb)}.dsh-mobile-control__diagnostic-summary.is-running .dsh-mobile-control__diagnostic-summary-icon::before{inset:9px;border:2px solid rgb(37 99 235 / 24%);border-top-color:currentColor;border-radius:50%;animation:dsh-diagnostic-spin .8s linear infinite}
6025
7044
  .dsh-mobile-control__diagnostic-summary.is-idle .dsh-mobile-control__diagnostic-summary-icon::before{top:8px;left:17px;width:2px;height:2px;border-radius:50%;background:currentColor}.dsh-mobile-control__diagnostic-summary.is-idle .dsh-mobile-control__diagnostic-summary-icon::after{top:13px;left:17px;width:2px;height:11px;border-radius:2px;background:currentColor}
6026
- .dsh-mobile-control__diagnostic-toolbar{display:grid;grid-template-columns:1fr;gap:8px;margin-top:10px}.dsh-mobile-control__diagnostic-toolbar.has-report{grid-template-columns:1fr 1fr}.dsh-mobile-control__diagnostic-run,.dsh-mobile-control__diagnostic-copy{box-sizing:border-box;width:100%;min-height:44px;padding:9px 10px;border-radius:11px;font:650 12px/1.3 system-ui;cursor:pointer;touch-action:manipulation}.dsh-mobile-control__diagnostic-copy[hidden]{display:none}.dsh-mobile-control__diagnostic-run:disabled{cursor:wait;opacity:.58}.dsh-mobile-control__diagnostic-feedback{margin:8px 0 0;padding:8px 10px;border-radius:9px;background:#eff6ff;color:#1d4ed8;font-size:11px;line-height:1.45}.dsh-mobile-control__diagnostic-feedback[hidden]{display:none}
6027
- .dsh-mobile-control__diagnostic-checks{display:grid;gap:12px;margin-top:12px;animation:dsh-diagnostic-reveal 160ms ease-out both}.dsh-mobile-control__diagnostic-checks[hidden]{display:none}.dsh-mobile-control__diagnostic-group{overflow:hidden;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__diagnostic-group-header{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px 11px;border-bottom:1px solid var(--dsw-alias-border-subtle,#e1e5eb);background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__diagnostic-group-header h3{margin:0;font:650 11px/1.4 system-ui}.dsh-mobile-control__diagnostic-group-header span{color:var(--dsw-alias-label-secondary,#606873);font-size:10px}.dsh-mobile-control__diagnostic-list{display:flex;flex-direction:column}.dsh-mobile-control__diagnostic-check{display:grid;grid-template-columns:26px minmax(0,1fr);gap:9px;padding:11px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__diagnostic-check + .dsh-mobile-control__diagnostic-check{border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}.dsh-mobile-control__diagnostic-marker{position:relative;width:26px;height:26px;border-radius:50%;background:#edf1f5;color:var(--dsh-diagnostic-info)}.dsh-mobile-control__diagnostic-marker::before,.dsh-mobile-control__diagnostic-marker::after{position:absolute;content:""}.dsh-mobile-control__diagnostic-check.is-ok .dsh-mobile-control__diagnostic-marker{background:#e6f7f0;color:var(--dsh-diagnostic-ok)}.dsh-mobile-control__diagnostic-check.is-ok .dsh-mobile-control__diagnostic-marker::before{top:7px;left:7px;width:9px;height:5px;border-bottom:1.8px solid currentColor;border-left:1.8px solid currentColor;transform:rotate(-45deg)}.dsh-mobile-control__diagnostic-check.is-warning .dsh-mobile-control__diagnostic-marker{background:#fff4dc;color:var(--dsh-diagnostic-warning)}.dsh-mobile-control__diagnostic-check.is-error .dsh-mobile-control__diagnostic-marker{background:#fdecec;color:var(--dsh-diagnostic-error)}.dsh-mobile-control__diagnostic-check.is-warning .dsh-mobile-control__diagnostic-marker::before,.dsh-mobile-control__diagnostic-check.is-error .dsh-mobile-control__diagnostic-marker::before{top:6px;left:12px;width:2px;height:9px;border-radius:2px;background:currentColor}.dsh-mobile-control__diagnostic-check.is-warning .dsh-mobile-control__diagnostic-marker::after,.dsh-mobile-control__diagnostic-check.is-error .dsh-mobile-control__diagnostic-marker::after{bottom:6px;left:12px;width:2px;height:2px;border-radius:50%;background:currentColor}.dsh-mobile-control__diagnostic-check.is-info .dsh-mobile-control__diagnostic-marker::before{top:6px;left:12px;width:2px;height:2px;border-radius:50%;background:currentColor}.dsh-mobile-control__diagnostic-check.is-info .dsh-mobile-control__diagnostic-marker::after{top:10px;left:12px;width:2px;height:9px;border-radius:2px;background:currentColor}.dsh-mobile-control__diagnostic-check-body{min-width:0}.dsh-mobile-control__diagnostic-check-header{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}.dsh-mobile-control__diagnostic-check-header strong{min-width:0;font-size:12px;line-height:1.4}.dsh-mobile-control__diagnostic-badge{flex:none;padding:2px 6px;border-radius:999px;background:#edf1f5;color:var(--dsh-diagnostic-info);font:650 10px/1.3 system-ui}.dsh-mobile-control__diagnostic-check.is-ok .dsh-mobile-control__diagnostic-badge{background:#e6f7f0;color:var(--dsh-diagnostic-ok)}.dsh-mobile-control__diagnostic-check.is-warning .dsh-mobile-control__diagnostic-badge{background:#fff4dc;color:var(--dsh-diagnostic-warning)}.dsh-mobile-control__diagnostic-check.is-error .dsh-mobile-control__diagnostic-badge{background:#fdecec;color:var(--dsh-diagnostic-error)}.dsh-mobile-control__diagnostic-check p{margin:4px 0 0;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5;overflow-wrap:anywhere}.dsh-mobile-control__diagnostic-check .dsh-mobile-control__diagnostic-action{margin-top:7px;padding:7px 8px;border-radius:8px;background:var(--dsw-alias-bg-layer-1,#f8fafc);color:var(--dsw-alias-label-primary,#16181d)}.dsh-mobile-control__diagnostic-action span{display:inline-block;margin-right:6px;color:#2563eb;font-weight:700}.dsh-mobile-control__diagnostic-details{margin-top:12px}.dsh-mobile-control__diagnostic-details[hidden]{display:none}.dsh-mobile-control__diagnostic-details>summary{box-sizing:border-box;min-height:44px;line-height:44px}.dsh-mobile-control__diagnostic-report{box-sizing:border-box;max-height:220px;margin:4px 0 0;overflow:auto;padding:10px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:10px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-secondary,#606873);font:10px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere}
7045
+ .dsh-mobile-control__diagnostic-toolbar{display:grid;grid-template-columns:1fr;gap:8px;margin-top:10px}.dsh-mobile-control__diagnostic-toolbar.has-report{grid-template-columns:1fr 1fr}.dsh-mobile-control__diagnostic-run,.dsh-mobile-control__diagnostic-copy{box-sizing:border-box;width:100%;min-height:44px;padding:9px 10px;border-radius:11px;font:650 12px/1.3 system-ui;cursor:pointer;touch-action:manipulation}.dsh-mobile-control__diagnostic-copy[hidden]{display:none}.dsh-mobile-control__diagnostic-run:disabled{cursor:wait;opacity:.58}.dsh-mobile-control__diagnostic-feedback{margin:8px 0 0;padding:8px 10px;border-radius:9px;background:var(--dsw-alias-state-business-tertiary,#eff6ff);color:var(--dsw-alias-label-primary,#1d4ed8);font-size:11px;line-height:1.45}.dsh-mobile-control__diagnostic-feedback[hidden]{display:none}
7046
+ .dsh-mobile-control__diagnostic-checks{display:grid;gap:12px;margin-top:12px;animation:dsh-diagnostic-reveal 160ms ease-out both}.dsh-mobile-control__diagnostic-checks[hidden]{display:none}.dsh-mobile-control__diagnostic-group{overflow:hidden;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__diagnostic-group-header{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px 11px;border-bottom:1px solid var(--dsw-alias-border-l2,#e1e5eb);background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__diagnostic-group-header h3{margin:0;font:650 11px/1.4 system-ui}.dsh-mobile-control__diagnostic-group-header span{color:var(--dsw-alias-label-secondary,#606873);font-size:11px}.dsh-mobile-control__diagnostic-list{display:flex;flex-direction:column}.dsh-mobile-control__diagnostic-check{display:grid;grid-template-columns:26px minmax(0,1fr);gap:9px;padding:11px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__diagnostic-check + .dsh-mobile-control__diagnostic-check{border-top:1px solid var(--dsw-alias-border-l2,#e1e5eb)}.dsh-mobile-control__diagnostic-marker{position:relative;width:26px;height:26px;border-radius:50%;background:var(--dsw-alias-bg-layer-1,#edf1f5);color:var(--dsh-diagnostic-info)}.dsh-mobile-control__diagnostic-marker::before,.dsh-mobile-control__diagnostic-marker::after{position:absolute;content:""}.dsh-mobile-control__diagnostic-check.is-ok .dsh-mobile-control__diagnostic-marker{background:var(--dsw-alias-state-success-tertiary,#e6f7f0);color:var(--dsh-diagnostic-ok)}.dsh-mobile-control__diagnostic-check.is-ok .dsh-mobile-control__diagnostic-marker::before{top:7px;left:7px;width:9px;height:5px;border-bottom:1.8px solid currentColor;border-left:1.8px solid currentColor;transform:rotate(-45deg)}.dsh-mobile-control__diagnostic-check.is-warning .dsh-mobile-control__diagnostic-marker{background:var(--dsw-alias-state-warn-tertiary,#fff4dc);color:var(--dsh-diagnostic-warning)}.dsh-mobile-control__diagnostic-check.is-error .dsh-mobile-control__diagnostic-marker{background:var(--dsw-alias-state-error-secondary,#fdecec);color:var(--dsh-diagnostic-error)}.dsh-mobile-control__diagnostic-check.is-warning .dsh-mobile-control__diagnostic-marker::before,.dsh-mobile-control__diagnostic-check.is-error .dsh-mobile-control__diagnostic-marker::before{top:6px;left:12px;width:2px;height:9px;border-radius:2px;background:currentColor}.dsh-mobile-control__diagnostic-check.is-warning .dsh-mobile-control__diagnostic-marker::after,.dsh-mobile-control__diagnostic-check.is-error .dsh-mobile-control__diagnostic-marker::after{bottom:6px;left:12px;width:2px;height:2px;border-radius:50%;background:currentColor}.dsh-mobile-control__diagnostic-check.is-info .dsh-mobile-control__diagnostic-marker::before{top:6px;left:12px;width:2px;height:2px;border-radius:50%;background:currentColor}.dsh-mobile-control__diagnostic-check.is-info .dsh-mobile-control__diagnostic-marker::after{top:10px;left:12px;width:2px;height:9px;border-radius:2px;background:currentColor}.dsh-mobile-control__diagnostic-check-body{min-width:0}.dsh-mobile-control__diagnostic-check-header{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}.dsh-mobile-control__diagnostic-check-header strong{min-width:0;font-size:12px;line-height:1.4}.dsh-mobile-control__diagnostic-badge{flex:none;padding:2px 6px;border-radius:999px;background:var(--dsw-alias-bg-layer-1,#edf1f5);color:var(--dsh-diagnostic-info);font:650 11px/1.3 system-ui}.dsh-mobile-control__diagnostic-check.is-ok .dsh-mobile-control__diagnostic-badge{background:var(--dsw-alias-state-success-tertiary,#e6f7f0);color:var(--dsh-diagnostic-ok)}.dsh-mobile-control__diagnostic-check.is-warning .dsh-mobile-control__diagnostic-badge{background:var(--dsw-alias-state-warn-tertiary,#fff4dc);color:var(--dsh-diagnostic-warning)}.dsh-mobile-control__diagnostic-check.is-error .dsh-mobile-control__diagnostic-badge{background:var(--dsw-alias-state-error-secondary,#fdecec);color:var(--dsh-diagnostic-error)}.dsh-mobile-control__diagnostic-check p{margin:4px 0 0;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5;overflow-wrap:anywhere}.dsh-mobile-control__diagnostic-check .dsh-mobile-control__diagnostic-action{margin-top:7px;padding:7px 8px;border-radius:8px;background:var(--dsw-alias-bg-layer-1,#f8fafc);color:var(--dsw-alias-label-primary,#16181d)}.dsh-mobile-control__diagnostic-action span{display:inline-block;margin-right:6px;color:var(--dsw-alias-label-primary-bluish,#2563eb);font-weight:700}.dsh-mobile-control__diagnostic-details{margin-top:12px}.dsh-mobile-control__diagnostic-details[hidden]{display:none}.dsh-mobile-control__diagnostic-details>summary{box-sizing:border-box;min-height:44px;line-height:44px}.dsh-mobile-control__diagnostic-report{box-sizing:border-box;max-height:220px;margin:4px 0 0;overflow:auto;padding:10px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:10px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-secondary,#606873);font:11px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere}
6028
7047
  .dsh-mobile-control__diagnostic-checks{transition:opacity 150ms ease}.dsh-mobile-control__diagnostic-checks.is-refreshing{opacity:.52}
6029
7048
  @keyframes dsh-diagnostic-spin{to{transform:rotate(360deg)}}@keyframes dsh-diagnostic-reveal{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}
6030
- .dsh-mobile-control__actions{display:flex;flex-wrap:nowrap;gap:6px}.dsh-mobile-control__actions[hidden],.dsh-mobile-control__manage-row[hidden]{display:none}.dsh-mobile-control__actions button{flex:1 1 0;min-width:0;min-height:40px;padding:8px 4px;border-radius:10px;font:12px/1.2 system-ui;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dsh-mobile-control__secondary{border:1px solid var(--dsw-alias-border-normal,#cfd5dd);background:transparent;color:inherit}.dsh-mobile-control__primary{border:1px solid #2563eb;background:#2563eb;color:#fff}.dsh-mobile-control__actions button:disabled{cursor:not-allowed;opacity:.45}
7049
+ .dsh-mobile-control__actions{display:flex;flex-wrap:nowrap;gap:6px}.dsh-mobile-control__actions[hidden],.dsh-mobile-control__manage-row[hidden]{display:none}.dsh-mobile-control__actions button{flex:1 1 0;min-width:0;min-height:44px;padding:8px 4px;border-radius:10px;font:12px/1.2 system-ui;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dsh-mobile-control__secondary{border:1px solid var(--dsw-alias-border-l3,#cfd5dd);background:transparent;color:inherit}.dsh-mobile-control__primary{border:1px solid #2563eb;background:#2563eb;color:#fff}.dsh-mobile-control__actions button:disabled{cursor:not-allowed;opacity:.45}
6031
7050
  .dsh-mobile-control button:focus-visible,.dsh-mobile-control a:focus-visible,.dsh-mobile-control input:focus-visible,.dsh-mobile-control select:focus-visible,.dsh-mobile-control summary:focus-visible{outline:3px solid rgb(37 99 235 / 28%);outline-offset:2px}
7051
+ /* 42px matches the DSH sidebar footer row; it is not one of the panel target tiers. */
6032
7052
  .dsh-mobile-control__trigger{box-sizing:border-box;flex:1 1 auto;display:flex;align-items:center;gap:8px;width:100%;height:42px;margin:4px 0;padding:0 10px 0 8px;border:0;border-radius:12px;background:transparent;color:var(--dsw-alias-label-primary,#16181d);font-family:inherit;font-size:14px;line-height:22px;cursor:pointer;overflow:hidden}.dsh-mobile-control__trigger:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f3f6)}.dsh-mobile-control__trigger:active,.dsh-mobile-control__trigger[aria-expanded="true"]{background:var(--dsw-alias-interactive-bg-active,#e8ebf0)}.dsh-mobile-control__trigger:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,currentColor);outline-offset:2px}.dsh-mobile-control__trigger.is-rail{flex:0 0 auto;width:36px;height:36px;margin:8px 0 10px;padding:0;justify-content:center;gap:0;border-radius:50%}.dsh-mobile-control__trigger-icon{display:block;flex:none}.dsh-mobile-control__trigger-label{min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
6033
- .dsh-mobile-control__manage-row{display:flex;justify-content:space-between;gap:8px;margin-top:10px}.dsh-mobile-control__manage{flex:1 1 0;min-width:0;min-height:34px;padding:6px 8px;border:1px solid var(--dsw-alias-border-normal,#cfd5dd);border-radius:10px;background:transparent;color:inherit;font:12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__devices{margin-top:10px;border:1px solid var(--dsw-alias-border-subtle,#e1e5eb);border-radius:10px;padding:8px;max-height:220px;overflow-y:auto}.dsh-mobile-control__device-empty{color:var(--dsw-alias-label-secondary,#606873);font-size:12px;margin:0}.dsh-mobile-control__device{display:flex;align-items:center;gap:8px;padding:6px 2px}.dsh-mobile-control__device + .dsh-mobile-control__device{border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}.dsh-mobile-control__device-label{flex:1 1 0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px}.dsh-mobile-control__device-meta{flex:none;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;white-space:nowrap}.dsh-mobile-control__device-revoke{flex:none;min-height:28px;padding:4px 8px;border:1px solid #dc2626;border-radius:8px;background:transparent;color:#dc2626;font:12px/1.2 system-ui;cursor:pointer}.dsh-mobile-control__ws-paths{margin:10px 0 0;padding:12px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__ws-paths-list{margin:0 0 8px;padding:0;list-style:none}.dsh-mobile-control__ws-paths-empty{color:var(--dsw-alias-label-secondary,#606873);font-size:12px}.dsh-mobile-control__ws-paths-item{display:flex;align-items:center;gap:8px;padding:6px 0}.dsh-mobile-control__ws-paths-item + .dsh-mobile-control__ws-paths-item{border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}.dsh-mobile-control__ws-paths-item code{flex:1 1 0;min-width:0;overflow:hidden;font:11px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__ws-paths-remove{flex:none;min-height:28px;padding:4px 8px;border:1px solid var(--dsw-alias-border-normal,#cfd5dd);border-radius:8px;background:transparent;color:inherit;font:12px/1.2 system-ui;cursor:pointer}.dsh-mobile-control__trigger,.dsh-mobile-control__diagnostic-entry{position:relative}.dsh-mobile-control__ws-dot{position:absolute;top:2px;right:2px;z-index:1;min-width:16px;height:16px;padding:0 4px;border-radius:999px;background:#dc2626;color:#fff;font:650 10px/16px system-ui;text-align:center;cursor:pointer}.dsh-mobile-control__ws-dot::after{content:'';position:absolute;inset:-5px}.dsh-mobile-control__ws-sr-status{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.dsh-mobile-control__ws-paths-row{display:flex;gap:8px}.dsh-mobile-control__ws-paths-row .dsh-mobile-control__primary{width:auto;flex:none;padding:9px 14px;border-radius:10px}.dsh-mobile-control__ws-paths-group{padding:6px 0}.dsh-mobile-control__ws-paths-group + .dsh-mobile-control__ws-paths-group{border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}.dsh-mobile-control__ws-paths-group-head{display:flex;align-items:center;gap:8px}.dsh-mobile-control__ws-count{flex:none;padding:1px 7px;border-radius:999px;background:#e8f0ff;color:#1d4ed8;font:650 10px/1.6 system-ui}.dsh-mobile-control__ws-paths-item.is-child{padding-left:12px}.dsh-mobile-control__ws-paths-group-head .dsh-mobile-control__primary,.dsh-mobile-control__ws-paths-item .dsh-mobile-control__primary{width:auto;flex:none;min-height:34px;padding:6px 12px;border-radius:10px}.dsh-mobile-control__ws-paths-row input{flex:1 1 0;min-width:0;box-sizing:border-box;min-height:44px;padding:9px 10px;border:1px solid var(--dsw-alias-border-normal,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);font:16px/1.4 system-ui}.dsh-mobile-control__ws-paths-row button{flex:none}.dsh-mobile-control__ws-paths-detected-title{margin:0 0 2px;font:600 12px/1.4 system-ui}.dsh-mobile-control__ws-paths-advanced{margin-top:8px}.dsh-mobile-control__ws-paths-advanced summary{cursor:pointer;color:var(--dsw-alias-label-secondary,#606873);font:12px/1.4 system-ui;user-select:none}.dsh-mobile-control__ws-paths-advanced summary:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,currentColor);outline-offset:2px;border-radius:4px}.dsh-mobile-control__ws-paths-advanced[open] summary{margin-bottom:6px}.dsh-mobile-control__ws-paths-advanced .dsh-mobile-control__status{margin:6px 0 0}
6034
- @media (max-width:359px){.dsh-mobile-control__provider-choices{grid-template-columns:1fr}.dsh-mobile-control__provider{min-height:68px}}@media (prefers-reduced-motion:reduce){.dsh-mobile-control__provider,.dsh-mobile-control__cpolar-connect{transition:none}.dsh-mobile-control__diagnostic-summary.is-running .dsh-mobile-control__diagnostic-summary-icon::before,.dsh-mobile-control__diagnostic-checks{animation:none}}
7053
+ .dsh-mobile-control__manage-row{display:flex;justify-content:space-between;gap:8px;margin-top:10px}.dsh-mobile-control__manage{flex:1 1 0;min-width:0;min-height:36px;padding:6px 8px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:10px;background:transparent;color:inherit;font:12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__devices{margin-top:10px;border:1px solid var(--dsw-alias-border-l2,#e1e5eb);border-radius:10px;padding:8px;max-height:220px;overflow-y:auto}.dsh-mobile-control__device-empty{color:var(--dsw-alias-label-secondary,#606873);font-size:12px;margin:0}.dsh-mobile-control__device{display:flex;align-items:center;gap:8px;padding:6px 2px}.dsh-mobile-control__device + .dsh-mobile-control__device{border-top:1px solid var(--dsw-alias-border-l2,#e1e5eb)}.dsh-mobile-control__device-label{flex:1 1 0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px}.dsh-mobile-control__device-meta{flex:none;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;white-space:nowrap}.dsh-mobile-control__device-revoke{flex:none;min-height:36px;padding:4px 8px;border:1px solid var(--dsw-alias-state-error-primary,#dc2626);border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary,#dc2626);font:12px/1.2 system-ui;cursor:pointer}.dsh-mobile-control__ws-paths{margin:10px 0 0;padding:12px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__ws-paths-list{margin:0 0 8px;padding:0;list-style:none}.dsh-mobile-control__ws-paths-empty{color:var(--dsw-alias-label-secondary,#606873);font-size:12px}.dsh-mobile-control__ws-paths-item{display:flex;align-items:center;gap:8px;padding:6px 0}.dsh-mobile-control__ws-paths-item + .dsh-mobile-control__ws-paths-item{border-top:1px solid var(--dsw-alias-border-l2,#e1e5eb)}.dsh-mobile-control__ws-paths-item code{flex:1 1 0;min-width:0;overflow:hidden;font:11px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__ws-paths-remove{flex:none;min-height:36px;padding:4px 8px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:8px;background:transparent;color:inherit;font:12px/1.2 system-ui;cursor:pointer}.dsh-mobile-control__trigger,.dsh-mobile-control__diagnostic-entry{position:relative}.dsh-mobile-control__ws-dot{position:absolute;top:2px;right:2px;z-index:1;min-width:16px;height:16px;padding:0 4px;border-radius:999px;background:var(--dsw-alias-state-error-primary,#dc2626);color:#fff;font:650 11px/16px system-ui;text-align:center;cursor:pointer}.dsh-mobile-control__ws-dot::after{content:'';position:absolute;inset:-5px}.dsh-mobile-control__ws-sr-status{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.dsh-mobile-control__ws-paths-row{display:flex;gap:8px}.dsh-mobile-control__ws-paths-row .dsh-mobile-control__primary{width:auto;flex:none;padding:9px 14px;border-radius:10px}.dsh-mobile-control__ws-paths-group{padding:6px 0}.dsh-mobile-control__ws-paths-group + .dsh-mobile-control__ws-paths-group{border-top:1px solid var(--dsw-alias-border-l2,#e1e5eb)}.dsh-mobile-control__ws-paths-group-head{display:flex;align-items:center;gap:8px}.dsh-mobile-control__ws-count{flex:none;padding:1px 7px;border-radius:999px;background:var(--dsw-alias-state-business-tertiary,#e8f0ff);color:var(--dsw-alias-label-primary,#1d4ed8);font:650 11px/1.6 system-ui}.dsh-mobile-control__ws-paths-item.is-child{padding-left:12px}.dsh-mobile-control__ws-paths-group-head .dsh-mobile-control__primary,.dsh-mobile-control__ws-paths-item .dsh-mobile-control__primary{width:auto;flex:none;min-height:36px;padding:6px 12px;border-radius:10px}.dsh-mobile-control__ws-paths-row input{flex:1 1 0;min-width:0;box-sizing:border-box;min-height:44px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);font:16px/1.4 system-ui}.dsh-mobile-control__ws-paths-row button{flex:none}.dsh-mobile-control__ws-paths-detected-title{margin:0 0 2px;font:600 12px/1.4 system-ui}.dsh-mobile-control__ws-paths-advanced{margin-top:8px}.dsh-mobile-control__ws-paths-advanced summary{cursor:pointer;color:var(--dsw-alias-label-secondary,#606873);font:12px/1.4 system-ui;user-select:none}.dsh-mobile-control__ws-paths-advanced summary:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,currentColor);outline-offset:2px;border-radius:4px}.dsh-mobile-control__ws-paths-advanced[open] summary{margin-bottom:6px}.dsh-mobile-control__ws-paths-advanced .dsh-mobile-control__status{margin:6px 0 0}
7054
+ @media (max-width:359px){.dsh-mobile-control__provider{min-height:68px}}@media (prefers-reduced-motion:reduce){.dsh-mobile-control__provider,.dsh-mobile-control__cpolar-connect{transition:none}.dsh-mobile-control__diagnostic-summary.is-running .dsh-mobile-control__diagnostic-summary-icon::before,.dsh-mobile-control__diagnostic-checks{animation:none}}
7055
+
7056
+ .dsh-mobile-control__origin-setup{margin:0;padding:12px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__origin-setup[hidden],.dsh-mobile-control__origin-backend[hidden],.dsh-mobile-control__origin-feedback[hidden]{display:none}.dsh-mobile-control__origin-fields{display:grid;grid-template-columns:minmax(0,1fr) 110px;gap:12px}.dsh-mobile-control__origin-fields>.dsh-mobile-control__field{font-size:12px}.dsh-mobile-control__origin-fields>.dsh-mobile-control__field:nth-child(1),.dsh-mobile-control__origin-fields>.dsh-mobile-control__field:nth-child(4){grid-column:1/-1}.dsh-mobile-control__origin-fields>.dsh-mobile-control__field:nth-child(2){grid-column:1}.dsh-mobile-control__origin-fields>.dsh-mobile-control__field:nth-child(3){grid-column:2}.dsh-mobile-control__origin-warning{margin:12px 0;padding:10px 12px;border-left:3px solid var(--dsw-alias-state-warn-primary,#a56810);border-radius:4px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-primary,#384152);font-size:12px;line-height:1.6}.dsh-mobile-control__origin-feedback{font-size:12px;line-height:1.6;overflow-wrap:anywhere}.dsh-mobile-control__origin-feedback.is-error{color:var(--dsw-alias-state-error-primary,#bc3030)}.dsh-mobile-control__origin-fields input[aria-invalid=true]{border-color:var(--dsw-alias-state-error-primary,#bc3030)}.dsh-mobile-control__origin-backend{display:flex;min-width:0;flex-direction:column;align-items:stretch;gap:8px;margin-top:12px;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l2,#dbe1e8)}.dsh-mobile-control__origin-backend button{align-self:flex-start;min-height:44px}.dsh-mobile-control__self-hosted-body .is-origin{margin-top:8px}
6035
7057
  `;
6036
7058
  /** Mount the desktop control or mobile feature enhancements. */
6037
7059
  function apply(ctx) {
@@ -6134,6 +7156,7 @@ window.__ModuleLoader__.load({
6134
7156
  exports.normalizeDiagnosticOverall = normalizeDiagnosticOverall;
6135
7157
  exports.normalizeDiagnosticStatus = normalizeDiagnosticStatus;
6136
7158
  exports.parseMobileExtensionManifest = parseMobileExtensionManifest;
7159
+ exports.parseMobileRemoteProvider = parseMobileRemoteProvider;
6137
7160
  exports.publishAuthoritativeExtensionIds = publishAuthoritativeExtensionIds;
6138
7161
  exports.reconcileRemovedExtensions = reconcileRemovedExtensions;
6139
7162
  exports.registerUniqueDisposable = registerUniqueDisposable;