dsh-mobile 0.6.18 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -1
- package/CONTRIBUTORS.md +4 -1
- package/README.en.md +34 -20
- package/README.md +34 -20
- package/SECURITY.md +7 -0
- package/THIRD_PARTY_NOTICES.md +33 -0
- package/apps/mobile/README.md +28 -3
- package/apps/mobile/README.zh-CN.md +28 -3
- package/apps/mobile/release.json +3 -3
- package/docs/CADDY_MANAGED.en.md +1 -1
- package/docs/CADDY_MANAGED.md +1 -1
- package/docs/CLIENT_MODULES.en.md +2 -0
- package/docs/CLIENT_MODULES.md +2 -0
- package/docs/CLOUDFLARE_TUNNEL.en.md +7 -5
- package/docs/CLOUDFLARE_TUNNEL.md +7 -5
- package/docs/CPOLAR_SUBDOMAIN.en.md +2 -2
- package/docs/CPOLAR_SUBDOMAIN.md +2 -2
- package/docs/IROH.en.md +65 -0
- package/docs/IROH.md +65 -0
- package/docs/MOBILE_PLUGIN_ADAPTATION.en.md +8 -0
- package/docs/MOBILE_PLUGIN_ADAPTATION.md +8 -0
- package/docs/README.md +2 -1
- package/docs/SELF_HOSTED_ORIGIN.en.md +26 -1
- package/docs/SELF_HOSTED_ORIGIN.md +27 -2
- package/docs/SKIN_COMPATIBILITY.en.md +2 -0
- package/docs/SKIN_COMPATIBILITY.md +2 -0
- package/docs/SLOW_CONNECTIONS.en.md +8 -0
- package/docs/SLOW_CONNECTIONS.md +8 -0
- package/lib/cli.js.map +1 -1
- package/lib/client.js +1475 -240
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +270 -2
- package/lib/index.mjs +1934 -251
- package/lib/index.mjs.map +1 -1
- package/lib/mobile-layout.js +17 -6
- package/lib/mobile-layout.js.map +1 -1
- package/native/iroh-host/LICENSE.dsh-mobile-iroh +21 -0
- package/native/iroh-host/LICENSE.dsh-tether +21 -0
- package/native/iroh-host/NOTICE.dsh-mobile-iroh +13 -0
- package/native/iroh-host/README.md +66 -0
- package/package.json +14 -5
- package/packages/question-fixes/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -104,6 +104,11 @@ window.__ModuleLoader__.load({
|
|
|
104
104
|
collapseMobileAccess: "Collapse Mobile access",
|
|
105
105
|
downloadAndroid: "Download latest Android app",
|
|
106
106
|
downloadAndroidVersion: "Download latest Android app · v{version}",
|
|
107
|
+
downloadAndroidIroh: "Download full APK (iroh)",
|
|
108
|
+
downloadAndroidIrohAria: "Download the full Android APK that includes iroh",
|
|
109
|
+
downloadAndroidIrohVersion: "Download full APK (iroh) · v{version}",
|
|
110
|
+
downloadAndroidVersionSize: "Download latest Android app · v{version} · {size} MB",
|
|
111
|
+
downloadAndroidIrohVersionSize: "Download full APK (iroh) · v{version} · {size} MB",
|
|
107
112
|
downloadAndroidAria: "Download the latest Android app from GitHub Releases",
|
|
108
113
|
downloadAndroidVersionAria: "Download Android app v{version} from GitHub Releases",
|
|
109
114
|
updatePlugin: "Update plugin",
|
|
@@ -117,6 +122,7 @@ window.__ModuleLoader__.load({
|
|
|
117
122
|
updateNow: "Update now",
|
|
118
123
|
updateLater: "Not now",
|
|
119
124
|
lan: "Local network",
|
|
125
|
+
lanChoiceDescription: "Same Wi-Fi or hotspot. Fastest path, no internet or account needed.",
|
|
120
126
|
remote: "Remote",
|
|
121
127
|
lanAccess: "Local network access",
|
|
122
128
|
remoteAccess: "Remote access",
|
|
@@ -124,7 +130,7 @@ window.__ModuleLoader__.load({
|
|
|
124
130
|
remoteAddress: "Remote address",
|
|
125
131
|
loadingStatus: "Loading status…",
|
|
126
132
|
loadingRemoteStatus: "Loading remote status…",
|
|
127
|
-
generateCopyKey: "Generate
|
|
133
|
+
generateCopyKey: "Generate pairing QR code",
|
|
128
134
|
copyPairLink: "Copy pairing link",
|
|
129
135
|
managePairedDevices: "Manage paired devices",
|
|
130
136
|
clearAllDevices: "Clear all devices",
|
|
@@ -139,11 +145,11 @@ window.__ModuleLoader__.load({
|
|
|
139
145
|
providerInfoAria: "View remote connection security and network information",
|
|
140
146
|
providerGroupAria: "Remote connection method",
|
|
141
147
|
providerSafeTitle: "You remain protected",
|
|
142
|
-
providerSafeText: "Only paired devices can access DSH.
|
|
148
|
+
providerSafeText: "Only paired devices can access DSH. On mainland China networks prefer iroh (peer-to-peer, no domain or account needed) or cpolar; Tailscale may be slow or unavailable there. Every on-demand component can be removed completely.",
|
|
143
149
|
builtIn: "Built in",
|
|
144
150
|
mainlandPreferred: "Preferred in mainland China",
|
|
145
|
-
tailscaleDescription: "
|
|
146
|
-
cpolarDescription: "Installs the official component on demand and
|
|
151
|
+
tailscaleDescription: "Built in, no install. Broad coverage; sign in to Tailscale and enable Funnel once. Mainland China networks may be unstable.",
|
|
152
|
+
cpolarDescription: "Works well on mainland China networks. Installs the official component on demand and gives you a temporary public address.",
|
|
147
153
|
prepareCpolar: "Prepare cpolar",
|
|
148
154
|
checkingComponent: "Checking component…",
|
|
149
155
|
installOfficial: "Install official component",
|
|
@@ -174,13 +180,35 @@ window.__ModuleLoader__.load({
|
|
|
174
180
|
disableRemote: "Disable remote access",
|
|
175
181
|
continueLogin: "Continue login",
|
|
176
182
|
reconnect: "Reconnect",
|
|
177
|
-
generateRemoteQr: "Generate
|
|
183
|
+
generateRemoteQr: "Generate pairing QR code",
|
|
178
184
|
copyRemoteLink: "Copy remote pairing link",
|
|
179
185
|
manageRemoteDevices: "Manage remote devices",
|
|
180
186
|
resetRemoteLogin: "Sign out and clear remote login",
|
|
181
187
|
resetRemoteDevices: "Disable and clear remote devices",
|
|
182
188
|
lanOn: "Local network access is on.",
|
|
183
189
|
lanOff: "Local network access is off.",
|
|
190
|
+
lanBadgeOn: "On",
|
|
191
|
+
lanBadgeOff: "Off",
|
|
192
|
+
qrGenerated: "LAN QR code generated. Scan it from the app.",
|
|
193
|
+
qrPlaceholder: "Tap “Generate pairing QR code” to show the pairing QR.",
|
|
194
|
+
irohQrPlaceholder: "Tap “Generate iroh pairing QR code” to show the QR.",
|
|
195
|
+
irohLinkCopied: "Pairing link copied. Paste or open it in the full Android app; browsers cannot use iroh.",
|
|
196
|
+
remoteQrPlaceholder: "Tap “Generate pairing QR code” to show the QR.",
|
|
197
|
+
remoteQrPlaceholderIdle: "Turn on remote access to generate the pairing QR.",
|
|
198
|
+
pairingExpires: "Pairing QR valid until {time}. Regenerating invalidates the old code.",
|
|
199
|
+
pairingExpired: "The pairing QR has expired. Generate a new one.",
|
|
200
|
+
cloudflaredEnableNeedsConfig: "Save the named-tunnel settings with “Save and connect” before turning on remote access.",
|
|
201
|
+
cloudflaredShowConfig: "Show settings",
|
|
202
|
+
cloudflaredHideConfig: "Hide settings",
|
|
203
|
+
cloudflaredEditConfig: "Edit",
|
|
204
|
+
cloudflaredSwitchModeDisconnect: "Switching tunnel type closes the current remote connection first. Continue?",
|
|
205
|
+
cloudflaredTunnelSaved: "Settings saved. Check the connection status above.",
|
|
206
|
+
purgeIroh: "Remove the downloaded iroh component",
|
|
207
|
+
irohPurgeConfirm: "Remove the iroh component downloaded by this plugin? Saved identity, pairings, companion packages and other programs are kept.",
|
|
208
|
+
irohComponentRunning: "Turn off iroh remote access before removing its component.",
|
|
209
|
+
purgingIroh: "Removing the iroh component…",
|
|
210
|
+
devicesLanTitle: "LAN devices",
|
|
211
|
+
devicesRemoteTitle: "Remote devices",
|
|
184
212
|
enableLan: "Enable local network access",
|
|
185
213
|
disableLan: "Disable local network access",
|
|
186
214
|
extensionsLoaded: "Extensions: {loaded} loaded",
|
|
@@ -273,6 +301,11 @@ window.__ModuleLoader__.load({
|
|
|
273
301
|
collapseMobileAccess: "Riduci Accesso mobile",
|
|
274
302
|
downloadAndroid: "Scarica l’ultima app Android",
|
|
275
303
|
downloadAndroidVersion: "Scarica l’ultima app Android · v{version}",
|
|
304
|
+
downloadAndroidIroh: "Scarica APK completa (iroh)",
|
|
305
|
+
downloadAndroidIrohAria: "Scarica l’APK Android completo con iroh",
|
|
306
|
+
downloadAndroidIrohVersion: "Scarica APK completa (iroh) · v{version}",
|
|
307
|
+
downloadAndroidVersionSize: "Scarica l’ultima app Android · v{version} · {size} MB",
|
|
308
|
+
downloadAndroidIrohVersionSize: "Scarica APK completa (iroh) · v{version} · {size} MB",
|
|
276
309
|
downloadAndroidAria: "Scarica l’ultima app Android da GitHub Releases",
|
|
277
310
|
downloadAndroidVersionAria: "Scarica l’app Android v{version} da GitHub Releases",
|
|
278
311
|
updatePlugin: "Aggiorna plugin",
|
|
@@ -286,6 +319,7 @@ window.__ModuleLoader__.load({
|
|
|
286
319
|
updateNow: "Aggiorna ora",
|
|
287
320
|
updateLater: "Non ora",
|
|
288
321
|
lan: "Rete locale",
|
|
322
|
+
lanChoiceDescription: "Stessa Wi-Fi o hotspot. Percorso più veloce, senza internet né account.",
|
|
289
323
|
remote: "Remoto",
|
|
290
324
|
lanAccess: "Accesso rete locale",
|
|
291
325
|
remoteAccess: "Accesso remoto",
|
|
@@ -293,7 +327,7 @@ window.__ModuleLoader__.load({
|
|
|
293
327
|
remoteAddress: "Indirizzo remoto",
|
|
294
328
|
loadingStatus: "Lettura stato…",
|
|
295
329
|
loadingRemoteStatus: "Lettura stato remoto…",
|
|
296
|
-
generateCopyKey: "Genera
|
|
330
|
+
generateCopyKey: "Genera codice QR di abbinamento",
|
|
297
331
|
copyPairLink: "Copia link di abbinamento",
|
|
298
332
|
managePairedDevices: "Gestisci dispositivi abbinati",
|
|
299
333
|
clearAllDevices: "Rimuovi tutti i dispositivi",
|
|
@@ -308,11 +342,11 @@ window.__ModuleLoader__.load({
|
|
|
308
342
|
providerInfoAria: "Informazioni su sicurezza e rete della connessione remota",
|
|
309
343
|
providerGroupAria: "Metodo di connessione remota",
|
|
310
344
|
providerSafeTitle: "La protezione resta attiva",
|
|
311
|
-
providerSafeText: "Solo i dispositivi abbinati possono accedere a DSH.
|
|
345
|
+
providerSafeText: "Solo i dispositivi abbinati possono accedere a DSH. Sulle reti della Cina continentale preferisci iroh (peer-to-peer, senza dominio o account) o cpolar; Tailscale può essere lento o non disponibile. Ogni componente installato su richiesta può essere rimosso completamente.",
|
|
312
346
|
builtIn: "Integrato",
|
|
313
347
|
mainlandPreferred: "Preferito in Cina continentale",
|
|
314
|
-
tailscaleDescription: "Copertura
|
|
315
|
-
cpolarDescription: "
|
|
348
|
+
tailscaleDescription: "Integrato, nessuna installazione. Copertura ampia; accedi a Tailscale e abilita Funnel una volta. Le reti della Cina continentale possono essere instabili.",
|
|
349
|
+
cpolarDescription: "Adatto alle reti della Cina continentale. Installa il componente ufficiale su richiesta e fornisce un indirizzo pubblico temporaneo.",
|
|
316
350
|
prepareCpolar: "Prepara cpolar",
|
|
317
351
|
checkingComponent: "Controllo componente…",
|
|
318
352
|
installOfficial: "Installa componente ufficiale",
|
|
@@ -343,13 +377,35 @@ window.__ModuleLoader__.load({
|
|
|
343
377
|
disableRemote: "Disattiva accesso remoto",
|
|
344
378
|
continueLogin: "Continua accesso",
|
|
345
379
|
reconnect: "Riconnetti",
|
|
346
|
-
generateRemoteQr: "Genera QR di abbinamento
|
|
380
|
+
generateRemoteQr: "Genera codice QR di abbinamento",
|
|
347
381
|
copyRemoteLink: "Copia link di abbinamento remoto",
|
|
348
382
|
manageRemoteDevices: "Gestisci dispositivi remoti",
|
|
349
383
|
resetRemoteLogin: "Esci e rimuovi accesso remoto",
|
|
350
384
|
resetRemoteDevices: "Disattiva e rimuovi dispositivi remoti",
|
|
351
385
|
lanOn: "Accesso dalla rete locale attivo.",
|
|
352
386
|
lanOff: "Accesso dalla rete locale disattivato.",
|
|
387
|
+
lanBadgeOn: "Attivo",
|
|
388
|
+
lanBadgeOff: "Disattivo",
|
|
389
|
+
qrGenerated: "Codice QR LAN generato. Scansionalo dall’app.",
|
|
390
|
+
qrPlaceholder: "Tocca «Genera codice QR di abbinamento» per mostrare il QR.",
|
|
391
|
+
irohQrPlaceholder: "Tocca «Genera codice QR iroh» per mostrare il QR.",
|
|
392
|
+
irohLinkCopied: "Link di abbinamento copiato. Incollalo o aprilo nell’app Android completa; i browser non supportano iroh.",
|
|
393
|
+
remoteQrPlaceholder: "Tocca «Genera codice QR di abbinamento» per mostrare il QR.",
|
|
394
|
+
remoteQrPlaceholderIdle: "Attiva l’accesso remoto per generare il QR di abbinamento.",
|
|
395
|
+
pairingExpires: "QR di abbinamento valido fino a {time}. Rigenerare invalida il vecchio codice.",
|
|
396
|
+
pairingExpired: "Il QR di abbinamento è scaduto. Generane uno nuovo.",
|
|
397
|
+
cloudflaredEnableNeedsConfig: "Salva le impostazioni del tunnel con nome («Salva e connetti») prima di attivare l’accesso remoto.",
|
|
398
|
+
cloudflaredShowConfig: "Mostra impostazioni",
|
|
399
|
+
cloudflaredHideConfig: "Nascondi impostazioni",
|
|
400
|
+
cloudflaredEditConfig: "Modifica",
|
|
401
|
+
cloudflaredSwitchModeDisconnect: "Cambiare tipo di tunnel chiude prima la connessione remota attuale. Continuare?",
|
|
402
|
+
cloudflaredTunnelSaved: "Impostazioni salvate. Controlla lo stato della connessione qui sopra.",
|
|
403
|
+
purgeIroh: "Rimuovi il componente iroh scaricato",
|
|
404
|
+
irohPurgeConfirm: "Rimuovere il componente iroh scaricato da questo plugin? Identità, abbinamenti, pacchetti esterni e altri programmi vengono conservati.",
|
|
405
|
+
irohComponentRunning: "Disattiva l’accesso remoto iroh prima di rimuovere il componente.",
|
|
406
|
+
purgingIroh: "Rimozione del componente iroh…",
|
|
407
|
+
devicesLanTitle: "Dispositivi rete locale",
|
|
408
|
+
devicesRemoteTitle: "Dispositivi remoti",
|
|
353
409
|
enableLan: "Attiva accesso rete locale",
|
|
354
410
|
disableLan: "Disattiva accesso rete locale",
|
|
355
411
|
extensionsLoaded: "Estensioni: {loaded} caricate",
|
|
@@ -453,6 +509,11 @@ window.__ModuleLoader__.load({
|
|
|
453
509
|
collapseMobileAccess: "收起移动访问",
|
|
454
510
|
downloadAndroid: "下载最新 Android App",
|
|
455
511
|
downloadAndroidVersion: "下载最新 Android App · v{version}",
|
|
512
|
+
downloadAndroidIroh: "下载完整版 APK(含 iroh)",
|
|
513
|
+
downloadAndroidIrohAria: "下载包含 iroh 的完整版 Android APK",
|
|
514
|
+
downloadAndroidIrohVersion: "下载完整版(含 iroh)· v{version}",
|
|
515
|
+
downloadAndroidVersionSize: "下载最新 Android App · v{version} · {size} MB",
|
|
516
|
+
downloadAndroidIrohVersionSize: "下载完整版(含 iroh)· v{version} · {size} MB",
|
|
456
517
|
downloadAndroidAria: "从 GitHub Releases 下载最新版 Android App",
|
|
457
518
|
downloadAndroidVersionAria: "从 GitHub Releases 下载 Android App v{version}",
|
|
458
519
|
updatePlugin: "更新插件",
|
|
@@ -466,6 +527,7 @@ window.__ModuleLoader__.load({
|
|
|
466
527
|
updateNow: "立即更新",
|
|
467
528
|
updateLater: "暂不",
|
|
468
529
|
lan: "局域网",
|
|
530
|
+
lanChoiceDescription: "同一 Wi-Fi 或热点下直连,最快,不需要外网或账号。",
|
|
469
531
|
remote: "远程",
|
|
470
532
|
lanAccess: "局域网访问",
|
|
471
533
|
remoteAccess: "远程访问",
|
|
@@ -473,7 +535,7 @@ window.__ModuleLoader__.load({
|
|
|
473
535
|
remoteAddress: "远程地址",
|
|
474
536
|
loadingStatus: "正在读取状态…",
|
|
475
537
|
loadingRemoteStatus: "正在读取远程状态…",
|
|
476
|
-
generateCopyKey: "
|
|
538
|
+
generateCopyKey: "生成配对二维码",
|
|
477
539
|
copyPairLink: "复制配对链接",
|
|
478
540
|
managePairedDevices: "管理配对设备",
|
|
479
541
|
clearAllDevices: "清除所有设备",
|
|
@@ -488,11 +550,11 @@ window.__ModuleLoader__.load({
|
|
|
488
550
|
providerInfoAria: "查看远程连接安全与网络说明",
|
|
489
551
|
providerGroupAria: "远程连接方式",
|
|
490
552
|
providerSafeTitle: "你始终可以放心",
|
|
491
|
-
providerSafeText: "只有已配对设备能进入 DSH
|
|
553
|
+
providerSafeText: "只有已配对设备能进入 DSH。国内网络建议优先尝试 iroh(点对点直连,无需域名或账号)或 cpolar;Tailscale 在中国大陆网络下可能连接缓慢、中断或无法使用。所有按需安装的组件都可彻底清理。",
|
|
492
554
|
builtIn: "内置",
|
|
493
555
|
mainlandPreferred: "国内网络优先",
|
|
494
|
-
tailscaleDescription: "
|
|
495
|
-
cpolarDescription: "
|
|
556
|
+
tailscaleDescription: "内置无需安装。覆盖广;首次需登录 Tailscale 并开启 Funnel。中国大陆网络可能不稳定。",
|
|
557
|
+
cpolarDescription: "国内网络友好。按需安装官方组件,提供临时公网地址。",
|
|
496
558
|
prepareCpolar: "准备 cpolar",
|
|
497
559
|
checkingComponent: "正在检查组件…",
|
|
498
560
|
installOfficial: "安装官方组件",
|
|
@@ -523,13 +585,35 @@ window.__ModuleLoader__.load({
|
|
|
523
585
|
disableRemote: "关闭远程访问",
|
|
524
586
|
continueLogin: "继续登录",
|
|
525
587
|
reconnect: "重新连接",
|
|
526
|
-
generateRemoteQr: "
|
|
588
|
+
generateRemoteQr: "生成配对二维码",
|
|
527
589
|
copyRemoteLink: "复制远程配对链接",
|
|
528
590
|
manageRemoteDevices: "管理远程设备",
|
|
529
591
|
resetRemoteLogin: "退出并清除远程登录",
|
|
530
592
|
resetRemoteDevices: "关闭并清除远程设备",
|
|
531
593
|
lanOn: "局域网访问已开启。",
|
|
532
594
|
lanOff: "局域网访问已关闭。",
|
|
595
|
+
lanBadgeOn: "已开启",
|
|
596
|
+
lanBadgeOff: "已关闭",
|
|
597
|
+
qrGenerated: "局域网二维码已生成,用 App 扫描。",
|
|
598
|
+
qrPlaceholder: "点击「生成配对二维码」显示配对二维码。",
|
|
599
|
+
irohQrPlaceholder: "点击「生成 iroh 配对二维码」显示配对二维码。",
|
|
600
|
+
irohLinkCopied: "配对链接已复制。请在完整版 Android App 中粘贴或打开;浏览器无法使用 iroh。",
|
|
601
|
+
remoteQrPlaceholder: "点击「生成配对二维码」显示配对二维码。",
|
|
602
|
+
remoteQrPlaceholderIdle: "开启远程访问后可生成配对二维码。",
|
|
603
|
+
pairingExpires: "配对二维码有效期至 {time}。重新生成会使旧码失效。",
|
|
604
|
+
pairingExpired: "配对二维码已过期,请重新生成。",
|
|
605
|
+
cloudflaredEnableNeedsConfig: "请先保存命名隧道配置(点「保存并连接」)后再启用远程访问。",
|
|
606
|
+
cloudflaredShowConfig: "展开配置",
|
|
607
|
+
cloudflaredHideConfig: "收起配置",
|
|
608
|
+
cloudflaredEditConfig: "修改",
|
|
609
|
+
cloudflaredSwitchModeDisconnect: "切换快速隧道/命名隧道会先关闭当前远程连接,是否继续?",
|
|
610
|
+
cloudflaredTunnelSaved: "配置已保存,连接结果以上方状态为准。",
|
|
611
|
+
purgeIroh: "移除已下载的 iroh 组件",
|
|
612
|
+
irohPurgeConfirm: "移除本插件下载的 iroh 组件?已保存的身份、配对、外部伴随包和其他程序均会保留。",
|
|
613
|
+
irohComponentRunning: "请先关闭 iroh 远程访问,再移除组件。",
|
|
614
|
+
purgingIroh: "正在移除 iroh 组件…",
|
|
615
|
+
devicesLanTitle: "局域网设备",
|
|
616
|
+
devicesRemoteTitle: "远程设备",
|
|
533
617
|
enableLan: "开启局域网访问",
|
|
534
618
|
disableLan: "关闭局域网访问",
|
|
535
619
|
extensionsLoaded: "扩展:{loaded} 个已加载",
|
|
@@ -755,7 +839,7 @@ window.__ModuleLoader__.load({
|
|
|
755
839
|
selfHostedConnections: "Self-hosted connection",
|
|
756
840
|
advanced: "Advanced",
|
|
757
841
|
frpName: "Self-hosted FRP",
|
|
758
|
-
frpDescription: "
|
|
842
|
+
frpDescription: "Needs a server with a public IP. Opens one dedicated channel for this DSH gateway; you own the VPS and port.",
|
|
759
843
|
prepareFrp: "Configure self-hosted FRP",
|
|
760
844
|
frpStep1Title: "1 · Connection details",
|
|
761
845
|
frpStep1Text: "Enter the VPS address, frps control port, shared token, and public HTTPS origin.",
|
|
@@ -845,7 +929,7 @@ window.__ModuleLoader__.load({
|
|
|
845
929
|
selfHostedConnections: "Connessione autogestita",
|
|
846
930
|
advanced: "Avanzato",
|
|
847
931
|
frpName: "FRP autogestito",
|
|
848
|
-
frpDescription: "
|
|
932
|
+
frpDescription: "Richiede un server con IP pubblico. Apre un canale dedicato per questo gateway DSH; VPS e porta restano tuoi.",
|
|
849
933
|
prepareFrp: "Configura FRP autogestito",
|
|
850
934
|
frpStep1Title: "1 · Dati di connessione",
|
|
851
935
|
frpStep1Text: "Inserisci indirizzo VPS, porta di controllo frps, token condiviso e origine HTTPS pubblica.",
|
|
@@ -935,7 +1019,7 @@ window.__ModuleLoader__.load({
|
|
|
935
1019
|
selfHostedConnections: "自建连接",
|
|
936
1020
|
advanced: "高级",
|
|
937
1021
|
frpName: "自建 FRP",
|
|
938
|
-
frpDescription: "
|
|
1022
|
+
frpDescription: "需要一台有公网 IP 的服务器。只为当前 DSH 网关开一条专用通道,VPS 和端口由你掌控。",
|
|
939
1023
|
prepareFrp: "配置自建 FRP",
|
|
940
1024
|
frpStep1Title: "1 · 连接信息",
|
|
941
1025
|
frpStep1Text: "填写中转服务器地址、frps 控制端口、共享 Token 和手机访问地址。没有域名时,可直接使用公网 IPv4。",
|
|
@@ -1021,8 +1105,111 @@ window.__ModuleLoader__.load({
|
|
|
1021
1105
|
frpStopped: "FRP 连接已停止。",
|
|
1022
1106
|
frpExited: "frpc 意外退出,请检查 VPS 配置后重新连接。"
|
|
1023
1107
|
});
|
|
1108
|
+
Object.assign(MOBILE_CONTROL_MESSAGES.en, {
|
|
1109
|
+
irohName: "iroh (Experimental)",
|
|
1110
|
+
irohExperimental: "Experimental",
|
|
1111
|
+
irohNotReady: "iroh is not running. Start the channel first, then generate a pairing code.",
|
|
1112
|
+
irohPairingFailed: "Could not open an iroh pairing window. Reconnect the channel and retry.",
|
|
1113
|
+
irohPairingTitle: "Phone pairing (experimental)",
|
|
1114
|
+
generateIrohQr: "Generate iroh pairing QR code",
|
|
1115
|
+
enlargeQr: "Enlarge the QR code",
|
|
1116
|
+
closeEnlargedQr: "Close the enlarged QR code",
|
|
1117
|
+
irohPairingReady: "iroh pairing code {code} is live for five minutes. Scan it from the iroh pairing screen; opening another code replaces this one.",
|
|
1118
|
+
irohDescription: "Peer-to-peer encrypted link with relay fallback. No domain, port or account needed; requires the full Android App.",
|
|
1119
|
+
prepareIroh: "Prepare iroh",
|
|
1120
|
+
irohInstallNote: "The Install button downloads the pinned sidecar release (dsh-mobile-iroh 0.1.0-alpha.1, about 15 MB on Windows x64, 24 MB on Linux x64) into the plugin private directory and verifies its SHA-256. An installed companion with a verified copy is reused instead, with no download.",
|
|
1121
|
+
irohComponentNote: "An installed sidecar is re-verified by SHA-256 on every use. It is not bundled with the plugin package; a companion-provided copy can only be removed by removing the companion.",
|
|
1122
|
+
irohPhaseNote: "Experimental connection. Requires the full Android App 0.7.0+ with iroh; the compatibility APK and mobile browsers cannot use it.",
|
|
1123
|
+
irohUnavailable: "iroh is not ready: no sidecar is installed yet. Install the official component below to download and verify it.",
|
|
1124
|
+
irohInstalledNotReady: "iroh is installed but not running. Turn on remote access to start it.",
|
|
1125
|
+
irohStarting: "Resolving the iroh endpoint identity…",
|
|
1126
|
+
irohConnecting: "Sidecar connected; starting the DSH authentication gateway…",
|
|
1127
|
+
irohCompanionMissing: "No verified iroh sidecar is available. Use Install official component below; a separate companion plugin is optional.",
|
|
1128
|
+
irohDataDirectoryInvalid: "Could not prepare the private sidecar directory. Check that DSH can manage its own profile directory, then retry.",
|
|
1129
|
+
irohSidecarInvalid: "The installed sidecar failed checksum verification. Reinstall the component.",
|
|
1130
|
+
irohDownloadFailed: "The sidecar download failed before verification. Check the network and retry.",
|
|
1131
|
+
irohPlatformUnsupported: "iroh sidecars are published for Windows x64 and Linux x64 only.",
|
|
1132
|
+
irohIdentityFailed: "Could not read the iroh endpoint identity from the sidecar.",
|
|
1133
|
+
irohLaunchFailed: "The iroh sidecar failed to start.",
|
|
1134
|
+
irohExited: "The iroh sidecar exited unexpectedly. Reconnect.",
|
|
1135
|
+
irohTimeout: "Timed out waiting for the iroh sidecar. Reconnect.",
|
|
1136
|
+
irohInstallConfirm: "Download and verify the pinned iroh sidecar now (about 15 MB on Windows x64, 24 MB on Linux x64)? Nothing else is installed.",
|
|
1137
|
+
installingIroh: "Downloading and verifying the iroh sidecar…",
|
|
1138
|
+
switchingIroh: "Switching to iroh…",
|
|
1139
|
+
reconnectingIroh: "Reconnecting the iroh transport…",
|
|
1140
|
+
resetIrohConfirm: "Disable the iroh channel? The companion and its pairings stay installed."
|
|
1141
|
+
});
|
|
1142
|
+
Object.assign(MOBILE_CONTROL_MESSAGES.it, {
|
|
1143
|
+
irohName: "iroh (Sperimentale)",
|
|
1144
|
+
irohExperimental: "Sperimentale",
|
|
1145
|
+
irohNotReady: "iroh non è attivo. Avvia il canale e genera di nuovo il codice.",
|
|
1146
|
+
irohPairingFailed: "Impossibile aprire una finestra di abbinamento iroh. Riconnetti il canale e riprova.",
|
|
1147
|
+
irohPairingTitle: "Abbinamento telefono (sperimentale)",
|
|
1148
|
+
generateIrohQr: "Genera QR di abbinamento iroh",
|
|
1149
|
+
enlargeQr: "Ingrandisci il codice QR",
|
|
1150
|
+
closeEnlargedQr: "Chiudi il codice QR ingrandito",
|
|
1151
|
+
irohPairingReady: "Il codice iroh {code} è attivo per cinque minuti. Scansionalo dalla schermata di abbinamento iroh; aprirne un altro sostituisce questo.",
|
|
1152
|
+
irohDescription: "Collegamento cifrato peer-to-peer con ripiego su relay. Nessun dominio, porta o account richiesto; richiede l’App Android completa.",
|
|
1153
|
+
prepareIroh: "Prepara iroh",
|
|
1154
|
+
irohInstallNote: "Il pulsante Installa scarica la release pinnata del sidecar (dsh-mobile-iroh 0.1.0-alpha.1, circa 15 MB su Windows x64, 24 MB su Linux x64) nella directory privata del plugin e ne verifica lo SHA-256. Un complemento installato con una copia verificata viene invece riutilizzato senza scaricare nulla.",
|
|
1155
|
+
irohComponentNote: "Un sidecar installato viene riverificato tramite SHA-256 a ogni uso. Non è incluso nel pacchetto del plugin; una copia fornita dal complemento può essere rimossa solo rimuovendo il complemento.",
|
|
1156
|
+
irohPhaseNote: "Connessione sperimentale. Richiede l’App Android completa 0.7.0+ con iroh; APK compatibile e browser mobili non sono supportati.",
|
|
1157
|
+
irohUnavailable: "iroh non è pronto: nessun sidecar è ancora installato. Installa il componente ufficiale qui sotto per scaricarlo e verificarlo.",
|
|
1158
|
+
irohInstalledNotReady: "iroh è installato ma non in esecuzione. Attiva l’accesso remoto per avviarlo.",
|
|
1159
|
+
irohStarting: "Risoluzione dell’identità endpoint iroh…",
|
|
1160
|
+
irohConnecting: "Sidecar connesso; avvio del gateway di autenticazione DSH…",
|
|
1161
|
+
irohCompanionMissing: "Nessun sidecar iroh verificato disponibile. Usa Installa componente ufficiale qui sotto; il plugin complementare è facoltativo.",
|
|
1162
|
+
irohDataDirectoryInvalid: "Impossibile preparare la directory privata del sidecar. Verifica che DSH possa gestire la propria directory del profilo, poi riprova.",
|
|
1163
|
+
irohSidecarInvalid: "Il sidecar installato non ha superato la verifica del checksum. Reinstalla il componente.",
|
|
1164
|
+
irohDownloadFailed: "Download del sidecar non riuscito prima della verifica. Controlla la rete e riprova.",
|
|
1165
|
+
irohPlatformUnsupported: "I sidecar iroh sono pubblicati solo per Windows x64 e Linux x64.",
|
|
1166
|
+
irohIdentityFailed: "Impossibile leggere l’identità endpoint iroh dal sidecar.",
|
|
1167
|
+
irohLaunchFailed: "Avvio del sidecar iroh non riuscito.",
|
|
1168
|
+
irohExited: "Il sidecar iroh è terminato inaspettatamente. Riconnettiti.",
|
|
1169
|
+
irohTimeout: "Attesa del sidecar iroh scaduta. Riconnettiti.",
|
|
1170
|
+
irohInstallConfirm: "Scaricare e verificare ora il sidecar iroh pinnato (circa 15 MB su Windows x64, 24 MB su Linux x64)? Non viene installato altro.",
|
|
1171
|
+
installingIroh: "Download e verifica del sidecar iroh…",
|
|
1172
|
+
switchingIroh: "Passaggio a iroh…",
|
|
1173
|
+
reconnectingIroh: "Riconnessione del trasporto iroh…",
|
|
1174
|
+
resetIrohConfirm: "Disattivare il canale iroh? Complemento e abbinamenti restano installati."
|
|
1175
|
+
});
|
|
1176
|
+
Object.assign(MOBILE_CONTROL_MESSAGES.zh, {
|
|
1177
|
+
irohName: "iroh(实验性)",
|
|
1178
|
+
irohExperimental: "实验性",
|
|
1179
|
+
irohNotReady: "iroh 通道未运行,请先启动通道再生成配对码。",
|
|
1180
|
+
irohPairingFailed: "无法打开 iroh 配对窗口,请重连通道后重试。",
|
|
1181
|
+
irohPairingTitle: "手机配对(实验性)",
|
|
1182
|
+
generateIrohQr: "生成 iroh 配对二维码",
|
|
1183
|
+
enlargeQr: "放大二维码",
|
|
1184
|
+
closeEnlargedQr: "关闭放大的二维码",
|
|
1185
|
+
irohPairingReady: "iroh 配对码 {code} 已生效,有效期 5 分钟。请在 App 的 iroh 配对页扫码;再生成一个会替换当前这个。",
|
|
1186
|
+
irohDescription: "点对点加密直连,跨网自动经中继。无需域名、端口或账号;需完整版 App。",
|
|
1187
|
+
prepareIroh: "准备 iroh",
|
|
1188
|
+
irohInstallNote: "点击安装会下载固定的 sidecar 发行版(dsh-mobile-iroh 0.1.0-alpha.1,Windows x64 约 15 MB,Linux x64 约 24 MB)到插件私有目录并校验 SHA-256;已安装的配套插件如有校验通过的副本会直接复用,不再下载。",
|
|
1189
|
+
irohComponentNote: "已安装的 sidecar 每次使用都会重新校验 SHA-256。它不随插件包发布;配套提供的副本只能通过移除配套插件来删除。",
|
|
1190
|
+
irohPhaseNote: "实验性连接。需使用含 iroh 的 Android App 0.7.0 及以上完整版;兼容版 APK 和手机浏览器不支持。",
|
|
1191
|
+
irohUnavailable: "iroh 未就绪:尚未安装 sidecar,点击下方「安装官方组件」下载并校验。",
|
|
1192
|
+
irohInstalledNotReady: "iroh 已安装但未运行,启用远程访问即可启动。",
|
|
1193
|
+
irohStarting: "正在解析 iroh 端点身份…",
|
|
1194
|
+
irohConnecting: "Sidecar 已连接,正在启动 DSH 认证网关…",
|
|
1195
|
+
irohCompanionMissing: "尚无校验通过的 iroh 组件,请点击下方「安装官方组件」;无需另装配套插件。",
|
|
1196
|
+
irohDataDirectoryInvalid: "无法准备 sidecar 私有目录。请确认 DSH 能管理自己的配置目录后重试。",
|
|
1197
|
+
irohSidecarInvalid: "已安装的 sidecar 哈希校验失败,请重装组件。",
|
|
1198
|
+
irohDownloadFailed: "下载 sidecar 失败,还没开始校验。请检查网络后重试。",
|
|
1199
|
+
irohPlatformUnsupported: "iroh sidecar 仅发布 Windows x64 与 Linux x64 版本。",
|
|
1200
|
+
irohIdentityFailed: "无法从 sidecar 读取 iroh 端点身份。",
|
|
1201
|
+
irohLaunchFailed: "iroh sidecar 启动失败。",
|
|
1202
|
+
irohExited: "iroh sidecar 意外退出,请重新连接。",
|
|
1203
|
+
irohTimeout: "等待 iroh sidecar 超时,请重新连接。",
|
|
1204
|
+
irohInstallConfirm: "现在下载并校验固定的 iroh sidecar 吗(Windows x64 约 15 MB,Linux x64 约 24 MB)?不会安装其他任何内容。",
|
|
1205
|
+
installingIroh: "正在下载并校验 iroh sidecar…",
|
|
1206
|
+
switchingIroh: "正在切换到 iroh…",
|
|
1207
|
+
reconnectingIroh: "正在重连 iroh 传输…",
|
|
1208
|
+
resetIrohConfirm: "要停用 iroh 通道吗?配套插件与配对不受影响。"
|
|
1209
|
+
});
|
|
1024
1210
|
Object.assign(MOBILE_CONTROL_MESSAGES.en, {
|
|
1025
1211
|
requestTimeout: "The operation timed out. Confirm that DSH is still running, then try again.",
|
|
1212
|
+
invalidResponse: "The server replied with a body that is not valid JSON. Confirm the plugin and DSH versions match, then reload the page.",
|
|
1026
1213
|
diagnostics: "Diagnostics",
|
|
1027
1214
|
openDiagnostics: "Open connection diagnostics",
|
|
1028
1215
|
back: "Back",
|
|
@@ -1071,6 +1258,7 @@ window.__ModuleLoader__.load({
|
|
|
1071
1258
|
});
|
|
1072
1259
|
Object.assign(MOBILE_CONTROL_MESSAGES.it, {
|
|
1073
1260
|
requestTimeout: "Operazione scaduta. Verifica che DSH sia ancora in esecuzione e riprova.",
|
|
1261
|
+
invalidResponse: "Il server ha restituito un contenuto che non è JSON valido. Verifica che le versioni del plugin e di DSH corrispondano, poi ricarica la pagina.",
|
|
1074
1262
|
diagnostics: "Diagnostica",
|
|
1075
1263
|
openDiagnostics: "Apri diagnostica connessione",
|
|
1076
1264
|
back: "Indietro",
|
|
@@ -1119,6 +1307,7 @@ window.__ModuleLoader__.load({
|
|
|
1119
1307
|
});
|
|
1120
1308
|
Object.assign(MOBILE_CONTROL_MESSAGES.zh, {
|
|
1121
1309
|
requestTimeout: "操作超时,请确认 DSH 仍在运行后重试。",
|
|
1310
|
+
invalidResponse: "服务端返回的内容不是有效的 JSON,请确认插件与 DSH 版本匹配后刷新页面。",
|
|
1122
1311
|
diagnostics: "诊断",
|
|
1123
1312
|
openDiagnostics: "打开连接诊断",
|
|
1124
1313
|
back: "返回",
|
|
@@ -1252,11 +1441,11 @@ window.__ModuleLoader__.load({
|
|
|
1252
1441
|
}
|
|
1253
1442
|
};
|
|
1254
1443
|
Object.assign(MOBILE_CONTROL_MESSAGES.en, {
|
|
1255
|
-
selfHostedDescription: "
|
|
1444
|
+
selfHostedDescription: "For your own server or domain. Does not depend on a third-party tunnel service.",
|
|
1256
1445
|
originName: "Own reverse proxy",
|
|
1257
1446
|
caddyName: "Managed Caddy",
|
|
1258
1447
|
caddyModePreviewNote: "HTTPS form preview differs from the current connection.",
|
|
1259
|
-
originDescription: "
|
|
1448
|
+
originDescription: "Already have an HTTPS domain or reverse proxy (Caddy, Nginx)? Point it at the local DSH backend.",
|
|
1260
1449
|
prepareOrigin: "Connect your HTTPS reverse proxy",
|
|
1261
1450
|
originPublicOrigin: "Public HTTPS address",
|
|
1262
1451
|
originListenHost: "Private listen IP (IPv4 / IPv6)",
|
|
@@ -1311,11 +1500,11 @@ window.__ModuleLoader__.load({
|
|
|
1311
1500
|
originControllerUnavailable: "The reverse-proxy provider is not available in this session. Restart DSH and try again."
|
|
1312
1501
|
});
|
|
1313
1502
|
Object.assign(MOBILE_CONTROL_MESSAGES.it, {
|
|
1314
|
-
selfHostedDescription: "
|
|
1503
|
+
selfHostedDescription: "Per un tuo server o dominio. Non dipende da un servizio tunnel di terze parti.",
|
|
1315
1504
|
originName: "Reverse proxy proprio",
|
|
1316
1505
|
caddyName: "Caddy gestito",
|
|
1317
1506
|
caddyModePreviewNote: "L’anteprima del modulo HTTPS differisce dalla connessione attuale.",
|
|
1318
|
-
originDescription: "
|
|
1507
|
+
originDescription: "Hai già un dominio HTTPS o un reverse proxy (Caddy, Nginx)? Puntalo al backend DSH locale.",
|
|
1319
1508
|
prepareOrigin: "Collega il tuo reverse proxy HTTPS",
|
|
1320
1509
|
originPublicOrigin: "Indirizzo HTTPS pubblico",
|
|
1321
1510
|
originListenHost: "IP privato di ascolto (IPv4 / IPv6)",
|
|
@@ -1370,11 +1559,11 @@ window.__ModuleLoader__.load({
|
|
|
1370
1559
|
originControllerUnavailable: "Il provider reverse proxy non è disponibile in questa sessione. Riavvia DSH e riprova."
|
|
1371
1560
|
});
|
|
1372
1561
|
Object.assign(MOBILE_CONTROL_MESSAGES.zh, {
|
|
1373
|
-
selfHostedDescription: "
|
|
1562
|
+
selfHostedDescription: "有自己的服务器或域名时用这里,不依赖第三方隧道服务。",
|
|
1374
1563
|
originName: "自有反向代理",
|
|
1375
1564
|
caddyName: "托管 Caddy",
|
|
1376
1565
|
caddyModePreviewNote: "HTTPS 表单预览与当前连接不同。",
|
|
1377
|
-
originDescription: "
|
|
1566
|
+
originDescription: "已有 HTTPS 域名或反向代理(Caddy、Nginx 等)?直接指向本机 DSH 后端。",
|
|
1378
1567
|
prepareOrigin: "接入自有 HTTPS 反向代理",
|
|
1379
1568
|
originPublicOrigin: "公网 HTTPS 地址",
|
|
1380
1569
|
originListenHost: "私有监听 IP(IPv4 / IPv6)",
|
|
@@ -1430,13 +1619,13 @@ window.__ModuleLoader__.load({
|
|
|
1430
1619
|
});
|
|
1431
1620
|
Object.assign(MOBILE_CONTROL_MESSAGES.en, {
|
|
1432
1621
|
cloudflaredBadge: "Account optional",
|
|
1433
|
-
cloudflaredDescription: "
|
|
1622
|
+
cloudflaredDescription: "Free temporary address without an account; sign in to Cloudflare for a fixed domain. Installs the official client on demand.",
|
|
1434
1623
|
prepareCloudflared: "Prepare cloudflared",
|
|
1435
1624
|
cloudflaredUnsupported: "Windows x64, Linux x64/arm64, and macOS x64/arm64 are supported.",
|
|
1436
1625
|
cloudflaredNotInstalled: "Not installed. A fixed version is downloaded from the official cloudflared release page only after you click the button below.",
|
|
1437
1626
|
cloudflaredReady: "Official component {version} is ready.",
|
|
1438
1627
|
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.",
|
|
1439
|
-
cloudflaredNamedNote: "Named tunnel: the hostname
|
|
1628
|
+
cloudflaredNamedNote: "Named tunnel: the saved hostname 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.",
|
|
1440
1629
|
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.",
|
|
1441
1630
|
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.",
|
|
1442
1631
|
installingCloudflared: "Installing the official cloudflared component. Keep DSH running until it completes.",
|
|
@@ -1498,13 +1687,13 @@ window.__ModuleLoader__.load({
|
|
|
1498
1687
|
});
|
|
1499
1688
|
Object.assign(MOBILE_CONTROL_MESSAGES.it, {
|
|
1500
1689
|
cloudflaredBadge: "Account facoltativo",
|
|
1501
|
-
cloudflaredDescription: "
|
|
1690
|
+
cloudflaredDescription: "Indirizzo temporaneo gratuito senza account; accedi a Cloudflare per un dominio fisso. Installa il client ufficiale su richiesta.",
|
|
1502
1691
|
prepareCloudflared: "Prepara cloudflared",
|
|
1503
1692
|
cloudflaredUnsupported: "Sono supportati Windows x64, Linux x64/arm64 e macOS x64/arm64.",
|
|
1504
1693
|
cloudflaredNotInstalled: "Non installato. Una versione fissa viene scaricata dalla pagina ufficiale di cloudflared solo dopo aver premuto il pulsante.",
|
|
1505
1694
|
cloudflaredReady: "Componente ufficiale {version} pronto.",
|
|
1506
1695
|
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.",
|
|
1507
|
-
cloudflaredNamedNote: "Tunnel con nome: l’hostname
|
|
1696
|
+
cloudflaredNamedNote: "Tunnel con nome: l’hostname salvato 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.",
|
|
1508
1697
|
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.",
|
|
1509
1698
|
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.",
|
|
1510
1699
|
installingCloudflared: "Installazione del componente cloudflared ufficiale. Mantieni DSH in esecuzione fino al termine.",
|
|
@@ -1566,13 +1755,13 @@ window.__ModuleLoader__.load({
|
|
|
1566
1755
|
});
|
|
1567
1756
|
Object.assign(MOBILE_CONTROL_MESSAGES.zh, {
|
|
1568
1757
|
cloudflaredBadge: "账号可选",
|
|
1569
|
-
cloudflaredDescription: "
|
|
1758
|
+
cloudflaredDescription: "免账号获得临时地址;登录 Cloudflare 可换固定域名。按需安装官方客户端。",
|
|
1570
1759
|
prepareCloudflared: "准备 cloudflared",
|
|
1571
1760
|
cloudflaredUnsupported: "支持 Windows x64、Linux x64/arm64 与 macOS x64/arm64。",
|
|
1572
1761
|
cloudflaredNotInstalled: "尚未安装。只有点击下方按钮后,才会从 cloudflared 官方发布页下载固定版本。",
|
|
1573
1762
|
cloudflaredReady: "官方组件 {version} 已就绪。",
|
|
1574
1763
|
cloudflaredQuickNote: "快速隧道:无需 Cloudflare 账号,但官方定位是测试用途 —— 地址随机、有限流、无可用性保证。请勿用于必须长期可达的访问。",
|
|
1575
|
-
cloudflaredNamedNote: "
|
|
1764
|
+
cloudflaredNamedNote: "命名隧道:保存的公网域名重启后不变,由 Cloudflare 转发到本机转发端口。连接器令牌只保存在 DSH Mobile 私有目录,经环境变量而非命令行传给 cloudflared,保存后不再显示。",
|
|
1576
1765
|
cloudflaredComponentNote: "仅在你点击安装后从 cloudflared 官方发布页下载并校验固定版本,且启动时关闭自动更新。不会写入系统服务、开机启动、注册表或 PATH。",
|
|
1577
1766
|
installConfirmCloudflared: "将从 cloudflared 官方发布页下载并校验固定版本({size}),仅保存到 DSH Mobile 私有目录。不会安装系统服务、写入 PATH/注册表或设置开机启动。是否继续?",
|
|
1578
1767
|
installingCloudflared: "正在安装 cloudflared 官方组件。完成前请保持 DSH 运行。",
|
|
@@ -3550,6 +3739,8 @@ window.__ModuleLoader__.load({
|
|
|
3550
3739
|
[data-dsh-mobile-sidebar][data-open="true"]:not(:has([data-dsh-mobile-settings])) [data-dsh-mobile-sidebar-root] > [class*="_regionArea"] { flex:1 0 50% !important; }
|
|
3551
3740
|
[data-dsh-mobile-sidebar][data-open="true"] [data-dsh-mobile-sidebar-root] [class*="_logoRow"] { height:52px !important; padding:4px 0 4px 4px !important; margin-bottom:4px !important; }
|
|
3552
3741
|
[data-dsh-mobile-sidebar][data-open="false"] [data-dsh-mobile-sidebar-root] { width:0 !important; border:0 !important; background:transparent !important; box-shadow:none !important; overflow:visible !important; }
|
|
3742
|
+
[data-dsh-mobile-sidebar][data-open="false"]:not(:has([data-dsh-mobile-settings])) [data-dsh-mobile-sidebar-root] { box-sizing:border-box !important; min-width:0 !important; padding:0 !important; pointer-events:none !important; }
|
|
3743
|
+
[data-dsh-mobile-sidebar][data-open="false"] [data-dsh-mobile-toggle] { pointer-events:auto !important; }
|
|
3553
3744
|
[data-dsh-mobile-sidebar][data-open="false"] [data-dsh-mobile-sidebar-root] > :not(:has([data-dsh-mobile-toggle])) { display:none !important; }
|
|
3554
3745
|
[data-dsh-mobile-sidebar][data-open="false"] [data-dsh-mobile-sidebar-root] > :has([data-dsh-mobile-toggle]) { position:fixed !important; z-index:244 !important; top:env(safe-area-inset-top) !important; left:0 !important; box-sizing:border-box !important; width:50px !important; height:52px !important; padding:4px !important; border:0 !important; background:transparent !important; }
|
|
3555
3746
|
[data-dsh-mobile-sidebar][data-open="false"] [data-dsh-mobile-sidebar-root] > :has([data-dsh-mobile-toggle]) > :not([data-dsh-mobile-toggle]) { display:none !important; }
|
|
@@ -7857,25 +8048,64 @@ window.__ModuleLoader__.load({
|
|
|
7857
8048
|
function ownsMobileCustomization(gateway, frontend) {
|
|
7858
8049
|
return gateway === true || frontend === "dedicated";
|
|
7859
8050
|
}
|
|
7860
|
-
/**
|
|
8051
|
+
/**
|
|
8052
|
+
* Emit each safe failure once until recovery, and ignore work after cancellation
|
|
8053
|
+
* or disposal.
|
|
8054
|
+
*
|
|
8055
|
+
* A failure that only reaches the console is invisible to the user, who sees the
|
|
8056
|
+
* customization disappear and cannot tell it from a third-party plugin bug
|
|
8057
|
+
* (issue #298). The reporter therefore also publishes its current state so the
|
|
8058
|
+
* page can render a visible, localized notice with an explicit recovery action.
|
|
8059
|
+
* @param locale - resolves the active UI locale for the copy.
|
|
8060
|
+
* @param output - console sink for the developer-facing line.
|
|
8061
|
+
* @returns the reporting calls plus a subscribable snapshot.
|
|
8062
|
+
*/
|
|
7861
8063
|
function createMobileAssetLoadReporter(locale, output = console) {
|
|
7862
8064
|
const reported = /* @__PURE__ */ new Set();
|
|
8065
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
7863
8066
|
let disposed = false;
|
|
8067
|
+
const visible = () => {
|
|
8068
|
+
if (reported.has("authentication_required")) return "authentication_required";
|
|
8069
|
+
if (reported.has("gateway_required")) return "gateway_required";
|
|
8070
|
+
return reported.values().next().done === true ? void 0 : reported.values().next().value;
|
|
8071
|
+
};
|
|
8072
|
+
const publish = () => {
|
|
8073
|
+
const state = visible();
|
|
8074
|
+
for (const listener of listeners) try {
|
|
8075
|
+
listener(state);
|
|
8076
|
+
} catch (error) {}
|
|
8077
|
+
};
|
|
7864
8078
|
return {
|
|
7865
8079
|
fail(reason, signal) {
|
|
7866
8080
|
if (disposed || signal?.aborted === true || reported.has(reason)) return;
|
|
7867
8081
|
if (reason === "resource_unavailable" && reported.has("authentication_required")) return;
|
|
7868
8082
|
reported.add(reason);
|
|
7869
8083
|
output.warn(`[DSH Mobile] ${COPY[locale()][reason]}`);
|
|
8084
|
+
publish();
|
|
7870
8085
|
},
|
|
7871
8086
|
recover(signal) {
|
|
7872
8087
|
if (disposed || signal.aborted || reported.size === 0) return;
|
|
7873
8088
|
reported.clear();
|
|
7874
8089
|
output.info(`[DSH Mobile] ${COPY[locale()].recovered}`);
|
|
8090
|
+
publish();
|
|
7875
8091
|
},
|
|
7876
8092
|
dispose() {
|
|
7877
8093
|
disposed = true;
|
|
7878
8094
|
reported.clear();
|
|
8095
|
+
listeners.clear();
|
|
8096
|
+
},
|
|
8097
|
+
getState() {
|
|
8098
|
+
return disposed ? void 0 : visible();
|
|
8099
|
+
},
|
|
8100
|
+
subscribe(listener) {
|
|
8101
|
+
if (disposed) return () => {};
|
|
8102
|
+
listeners.add(listener);
|
|
8103
|
+
return () => {
|
|
8104
|
+
listeners.delete(listener);
|
|
8105
|
+
};
|
|
8106
|
+
},
|
|
8107
|
+
message(reason) {
|
|
8108
|
+
return COPY[locale()][reason];
|
|
7879
8109
|
}
|
|
7880
8110
|
};
|
|
7881
8111
|
}
|
|
@@ -7925,6 +8155,125 @@ window.__ModuleLoader__.load({
|
|
|
7925
8155
|
}
|
|
7926
8156
|
}
|
|
7927
8157
|
//#endregion
|
|
8158
|
+
//#region src/mobile-customization-notice.ts
|
|
8159
|
+
/** Copy for the notice, per locale, kept beside the existing dictionaries. */
|
|
8160
|
+
const NOTICE_COPY = {
|
|
8161
|
+
en: {
|
|
8162
|
+
title: {
|
|
8163
|
+
authentication_required: "Device authentication is unavailable.",
|
|
8164
|
+
gateway_required: "Custom mobile CSS, scripts and extensions need the paired Mobile access entry.",
|
|
8165
|
+
other: "Mobile customization could not be loaded."
|
|
8166
|
+
},
|
|
8167
|
+
action: "Retry loading",
|
|
8168
|
+
dismiss: "Dismiss"
|
|
8169
|
+
},
|
|
8170
|
+
zh: {
|
|
8171
|
+
title: {
|
|
8172
|
+
authentication_required: "设备认证暂不可用。",
|
|
8173
|
+
gateway_required: "自定义移动样式、脚本与扩展需要通过已配对的移动访问入口打开。",
|
|
8174
|
+
other: "移动定制加载失败。"
|
|
8175
|
+
},
|
|
8176
|
+
action: "重试加载",
|
|
8177
|
+
dismiss: "忽略"
|
|
8178
|
+
},
|
|
8179
|
+
it: {
|
|
8180
|
+
title: {
|
|
8181
|
+
authentication_required: "Autenticazione del dispositivo non disponibile.",
|
|
8182
|
+
gateway_required: "CSS, script ed estensioni mobili richiedono l’accesso Mobile abbinato.",
|
|
8183
|
+
other: "Impossibile caricare la personalizzazione mobile."
|
|
8184
|
+
},
|
|
8185
|
+
action: "Riprova il caricamento",
|
|
8186
|
+
dismiss: "Ignora"
|
|
8187
|
+
}
|
|
8188
|
+
};
|
|
8189
|
+
const NOTICE_ATTRIBUTE = "data-dsh-mobile-customization-notice";
|
|
8190
|
+
const NOTICE_STYLES = `
|
|
8191
|
+
[${NOTICE_ATTRIBUTE}]{position:fixed;z-index:1000;left:8px;right:8px;bottom:calc(env(safe-area-inset-bottom) + 8px);display:flex;align-items:flex-start;gap:10px;box-sizing:border-box;padding:10px 12px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:12px;background:var(--dsw-alias-bg-layer-2,#fff);box-shadow:0 8px 24px rgb(15 23 42 / 18%);font:13px/1.45 system-ui;color:var(--dsw-alias-label-primary,#16181d)}
|
|
8192
|
+
[${NOTICE_ATTRIBUTE}][hidden]{display:none}
|
|
8193
|
+
[${NOTICE_ATTRIBUTE}] .dshm-notice-text{flex:1 1 auto;min-width:0}
|
|
8194
|
+
[${NOTICE_ATTRIBUTE}] button{flex:none;box-sizing:border-box;min-height:44px;min-width:44px;padding:0 10px;border:1px solid transparent;border-radius:9px;background:transparent;color:var(--dsw-alias-label-primary,#16181d);font:600 13px/1.2 system-ui;cursor:pointer}
|
|
8195
|
+
[${NOTICE_ATTRIBUTE}] button.dshm-notice-action{background:var(--dsw-alias-state-business-primary,#2563eb);color:#fff;border-color:transparent}
|
|
8196
|
+
[${NOTICE_ATTRIBUTE}] button:hover{opacity:.9}
|
|
8197
|
+
[${NOTICE_ATTRIBUTE}]{max-height:40vh;overflow:auto;left:max(8px,env(safe-area-inset-left));right:max(8px,env(safe-area-inset-right));flex-wrap:wrap}
|
|
8198
|
+
[${NOTICE_ATTRIBUTE}] .dshm-notice-text{margin:0;flex-basis:100%}
|
|
8199
|
+
[${NOTICE_ATTRIBUTE}] button:focus-visible{outline:2px solid currentColor;outline-offset:2px}
|
|
8200
|
+
[${NOTICE_ATTRIBUTE}]{background-color:Canvas;background-image:linear-gradient(var(--dsw-alias-bg-layer-2,Canvas),var(--dsw-alias-bg-layer-2,Canvas));color:var(--dsw-alias-label-primary,CanvasText)}
|
|
8201
|
+
`;
|
|
8202
|
+
/** Copy key for a failure, so the notice and the console never disagree. */
|
|
8203
|
+
function titleKey(reason) {
|
|
8204
|
+
return reason === "authentication_required" || reason === "gateway_required" ? reason : "other";
|
|
8205
|
+
}
|
|
8206
|
+
/**
|
|
8207
|
+
* Mount one visible notice that follows the customization reporter's state.
|
|
8208
|
+
* @param document - the page that owns the notice.
|
|
8209
|
+
* @param options - locale lookup and the reconnect action.
|
|
8210
|
+
* @returns a disposer that removes the notice and its stylesheet.
|
|
8211
|
+
*/
|
|
8212
|
+
function installCustomizationNotice(document, options) {
|
|
8213
|
+
if (!(typeof document.createElement === "function" && typeof document.body?.append === "function" && typeof document.head?.append === "function")) return () => {};
|
|
8214
|
+
const style = document.createElement("style");
|
|
8215
|
+
style.dataset.plugin = "dsh-mobile-notice";
|
|
8216
|
+
style.textContent = NOTICE_STYLES;
|
|
8217
|
+
document.head.append(style);
|
|
8218
|
+
const notice = document.createElement("div");
|
|
8219
|
+
if (typeof notice.setAttribute !== "function" || typeof notice.append !== "function") {
|
|
8220
|
+
style.remove();
|
|
8221
|
+
return () => {};
|
|
8222
|
+
}
|
|
8223
|
+
notice.setAttribute(NOTICE_ATTRIBUTE, "");
|
|
8224
|
+
notice.setAttribute("role", "status");
|
|
8225
|
+
notice.setAttribute("aria-live", "polite");
|
|
8226
|
+
notice.hidden = true;
|
|
8227
|
+
const text = document.createElement("p");
|
|
8228
|
+
text.className = "dshm-notice-text";
|
|
8229
|
+
const action = document.createElement("button");
|
|
8230
|
+
action.type = "button";
|
|
8231
|
+
action.className = "dshm-notice-action";
|
|
8232
|
+
const dismiss = document.createElement("button");
|
|
8233
|
+
dismiss.type = "button";
|
|
8234
|
+
notice.append(text, action, dismiss);
|
|
8235
|
+
document.body.append(notice);
|
|
8236
|
+
let current;
|
|
8237
|
+
let dismissed;
|
|
8238
|
+
const render = () => {
|
|
8239
|
+
const copy = NOTICE_COPY[options.locale()] ?? NOTICE_COPY.en;
|
|
8240
|
+
if (current === void 0 || current === dismissed) {
|
|
8241
|
+
notice.hidden = true;
|
|
8242
|
+
return;
|
|
8243
|
+
}
|
|
8244
|
+
const detail = options.message(current);
|
|
8245
|
+
text.textContent = `${copy.title[titleKey(current)]} ${detail}`;
|
|
8246
|
+
action.textContent = copy.action;
|
|
8247
|
+
dismiss.textContent = copy.dismiss;
|
|
8248
|
+
notice.hidden = false;
|
|
8249
|
+
};
|
|
8250
|
+
let disposed = false;
|
|
8251
|
+
const retry = () => {
|
|
8252
|
+
if (!disposed) options.reconnect();
|
|
8253
|
+
};
|
|
8254
|
+
const close = () => {
|
|
8255
|
+
if (disposed) return;
|
|
8256
|
+
dismissed = current;
|
|
8257
|
+
render();
|
|
8258
|
+
};
|
|
8259
|
+
action.addEventListener("click", retry);
|
|
8260
|
+
dismiss.addEventListener("click", close);
|
|
8261
|
+
const unsubscribe = options.subscribe((reason) => {
|
|
8262
|
+
if (reason !== current) dismissed = void 0;
|
|
8263
|
+
current = reason;
|
|
8264
|
+
render();
|
|
8265
|
+
});
|
|
8266
|
+
render();
|
|
8267
|
+
return () => {
|
|
8268
|
+
disposed = true;
|
|
8269
|
+
action.removeEventListener("click", retry);
|
|
8270
|
+
dismiss.removeEventListener("click", close);
|
|
8271
|
+
unsubscribe();
|
|
8272
|
+
notice.remove();
|
|
8273
|
+
style.remove();
|
|
8274
|
+
};
|
|
8275
|
+
}
|
|
8276
|
+
//#endregion
|
|
7928
8277
|
//#region src/extension-limits.ts
|
|
7929
8278
|
/** Maximum sizes accepted for legacy mobile customization resources. */
|
|
7930
8279
|
const LEGACY_CUSTOM_ASSET_LIMIT = 262144;
|
|
@@ -7948,6 +8297,25 @@ window.__ModuleLoader__.load({
|
|
|
7948
8297
|
function shouldRenewSession(connection, downForMs) {
|
|
7949
8298
|
return isDisconnected(connection) && downForMs >= 5e3;
|
|
7950
8299
|
}
|
|
8300
|
+
/**
|
|
8301
|
+
* Authentication rejection can warrant one renewal even while transport appears
|
|
8302
|
+
* connected. A 403 can also indicate a permission failure, so it is not proof
|
|
8303
|
+
* that the Session expired.
|
|
8304
|
+
*
|
|
8305
|
+
* Issue #298: DSH keeps Sessions in memory, so a Host restart kills every phone
|
|
8306
|
+
* Session while the device credential survives. The live WebSocket can keep
|
|
8307
|
+
* reporting `connected` for a while, so waiting for a disconnect never fires and
|
|
8308
|
+
* the customization channel stays dark. A 401/403 on an authenticated gateway
|
|
8309
|
+
* request is the signal that actually arrives, and it must be able to start the
|
|
8310
|
+
* same renewal the disconnect path uses.
|
|
8311
|
+
* @param status - HTTP status of a rejected gateway request.
|
|
8312
|
+
* @param connection - current public connection state, if known.
|
|
8313
|
+
* @returns whether this rejection should request a renewal.
|
|
8314
|
+
*/
|
|
8315
|
+
function shouldRenewAfterRejection(status, connection) {
|
|
8316
|
+
if (status !== 401 && status !== 403) return false;
|
|
8317
|
+
return !isDisconnected(connection) || connection === void 0;
|
|
8318
|
+
}
|
|
7951
8319
|
function isDisconnected(connection) {
|
|
7952
8320
|
return connection === "connecting" || connection === "disconnected";
|
|
7953
8321
|
}
|
|
@@ -7956,9 +8324,12 @@ window.__ModuleLoader__.load({
|
|
|
7956
8324
|
* @param getConnection - Resolve the current service to reject a replaced owner.
|
|
7957
8325
|
* @param page - The document's window and advertised native adapter.
|
|
7958
8326
|
* @param document - The mounted document; page replacement withdraws its work.
|
|
8327
|
+
* @param onRejected - receives the rejection signal, so a caller that observes a
|
|
8328
|
+
* 401 can ask this same owner for one renewal instead of starting its own.
|
|
8329
|
+
* @param onRenewed - refresh read-only customization after confirmed Cookie installation.
|
|
7959
8330
|
* @returns An awaited disposer, or nothing when no public connection is present.
|
|
7960
8331
|
*/
|
|
7961
|
-
function installMobileConnectionRecovery(getConnection, page, document) {
|
|
8332
|
+
function installMobileConnectionRecovery(getConnection, page, document, onRejected, onRenewed) {
|
|
7962
8333
|
const connection = getConnection();
|
|
7963
8334
|
const state = connection?.state;
|
|
7964
8335
|
if (connection === void 0 || state === void 0 || typeof connection.reconnect !== "function") return;
|
|
@@ -7968,6 +8339,8 @@ window.__ModuleLoader__.load({
|
|
|
7968
8339
|
let currentState = state.getSnapshot();
|
|
7969
8340
|
let downSince = isDisconnected(currentState) ? Date.now() : void 0;
|
|
7970
8341
|
let attempted = false;
|
|
8342
|
+
let credentialsRejected = false;
|
|
8343
|
+
let rejectedSince;
|
|
7971
8344
|
let bridge = page.__DSH_MOBILE_NATIVE__;
|
|
7972
8345
|
let pending;
|
|
7973
8346
|
let pendingAbort;
|
|
@@ -7981,8 +8354,13 @@ window.__ModuleLoader__.load({
|
|
|
7981
8354
|
const next = state.getSnapshot();
|
|
7982
8355
|
if (!isDisconnected(next)) {
|
|
7983
8356
|
downSince = void 0;
|
|
7984
|
-
|
|
7985
|
-
|
|
8357
|
+
if (currentState !== next) {
|
|
8358
|
+
if (next === "connected") {
|
|
8359
|
+
rejectedSince = void 0;
|
|
8360
|
+
attempted = false;
|
|
8361
|
+
}
|
|
8362
|
+
cancelPending();
|
|
8363
|
+
}
|
|
7986
8364
|
} else if (downSince === void 0) {
|
|
7987
8365
|
downSince = Date.now();
|
|
7988
8366
|
attempted = false;
|
|
@@ -7999,30 +8377,63 @@ window.__ModuleLoader__.load({
|
|
|
7999
8377
|
const renew = () => {
|
|
8000
8378
|
if (!current()) {
|
|
8001
8379
|
dispose();
|
|
8002
|
-
return;
|
|
8380
|
+
return false;
|
|
8003
8381
|
}
|
|
8004
8382
|
updateState();
|
|
8005
8383
|
updateBridge();
|
|
8006
8384
|
const adapter = bridge;
|
|
8007
|
-
|
|
8385
|
+
const downLongEnough = downSince !== void 0 && shouldRenewSession(currentState, Date.now() - downSince);
|
|
8386
|
+
const rejectionPending = rejectedSince !== void 0;
|
|
8387
|
+
if (document.visibilityState !== "visible" || adapter === void 0 || pending !== void 0 || attempted || credentialsRejected || !downLongEnough && !rejectionPending) return false;
|
|
8008
8388
|
attempted = true;
|
|
8009
8389
|
const owner = generation;
|
|
8010
8390
|
const abort = new AbortController();
|
|
8011
8391
|
pendingAbort = abort;
|
|
8012
|
-
const owns = () => current() && !abort.signal.aborted && generation === owner && page.__DSH_MOBILE_NATIVE__ === adapter
|
|
8392
|
+
const owns = () => current() && !abort.signal.aborted && generation === owner && page.__DSH_MOBILE_NATIVE__ === adapter;
|
|
8013
8393
|
const task = Promise.resolve().then(async () => {
|
|
8014
8394
|
if (!owns()) return;
|
|
8015
8395
|
const capabilities = await adapter.capabilities();
|
|
8016
8396
|
if (!owns() || !capabilities.includes("auth.renew")) return;
|
|
8017
8397
|
const result = await adapter.invoke("auth.renew", {}, { signal: abort.signal });
|
|
8018
8398
|
if (!owns() || typeof result !== "object" || result === null || !("renewed" in result) || result.renewed !== true) return;
|
|
8399
|
+
rejectedSince = void 0;
|
|
8019
8400
|
connection.reconnect?.();
|
|
8020
|
-
|
|
8401
|
+
if (current()) onRenewed?.();
|
|
8402
|
+
}).catch((error) => {
|
|
8403
|
+
if (!owns() || typeof error !== "object" || error === null || !("code" in error)) return;
|
|
8404
|
+
credentialsRejected = error.code === "device_revoked" || error.code === "device_expired" || error.code === "authentication_failed";
|
|
8405
|
+
}).finally(() => {
|
|
8021
8406
|
if (pending !== task) return;
|
|
8022
8407
|
pending = void 0;
|
|
8023
8408
|
pendingAbort = void 0;
|
|
8024
8409
|
});
|
|
8025
8410
|
pending = task;
|
|
8411
|
+
return true;
|
|
8412
|
+
};
|
|
8413
|
+
const retry = () => {
|
|
8414
|
+
if (!current()) return false;
|
|
8415
|
+
updateState();
|
|
8416
|
+
updateBridge();
|
|
8417
|
+
if (!current() || pending !== void 0 || credentialsRejected || bridge === void 0 || rejectedSince === void 0 && downSince === void 0) return false;
|
|
8418
|
+
attempted = false;
|
|
8419
|
+
return renew();
|
|
8420
|
+
};
|
|
8421
|
+
/**
|
|
8422
|
+
* Record a rejected authenticated request and try the same single renewal.
|
|
8423
|
+
*
|
|
8424
|
+
* Only an owner that is still current and has not already attempted may act, so
|
|
8425
|
+
* a burst of 401s cannot start parallel renewals, and a revoked device cannot
|
|
8426
|
+
* keep retrying: revocation arrives as 401 too, but the native bridge refuses
|
|
8427
|
+
* `auth.renew` and the attempt is not restarted.
|
|
8428
|
+
* @param status - HTTP status the gateway returned.
|
|
8429
|
+
*/
|
|
8430
|
+
const rejected = (status) => {
|
|
8431
|
+
if (!current()) return;
|
|
8432
|
+
updateState();
|
|
8433
|
+
updateBridge();
|
|
8434
|
+
if (!shouldRenewAfterRejection(status, state.getSnapshot())) return;
|
|
8435
|
+
rejectedSince ??= Date.now();
|
|
8436
|
+
renew();
|
|
8026
8437
|
};
|
|
8027
8438
|
const resume = () => {
|
|
8028
8439
|
if (!current()) {
|
|
@@ -8054,6 +8465,7 @@ window.__ModuleLoader__.load({
|
|
|
8054
8465
|
document.addEventListener("visibilitychange", resume);
|
|
8055
8466
|
page.addEventListener("dsh-mobile-native-ready", updateBridge);
|
|
8056
8467
|
page.addEventListener("pagehide", pagehide);
|
|
8468
|
+
onRejected?.(rejected, retry);
|
|
8057
8469
|
return dispose;
|
|
8058
8470
|
}
|
|
8059
8471
|
//#endregion
|
|
@@ -9165,7 +9577,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
9165
9577
|
//#region src/client.ts
|
|
9166
9578
|
/** Map a host-reported provider onto the UI union, defaulting to the built-in one. */
|
|
9167
9579
|
function parseMobileRemoteProvider(value) {
|
|
9168
|
-
return value === "cpolar" || value === "cloudflared" || value === "frp" || value === "origin" ? value : "tailscale";
|
|
9580
|
+
return value === "cpolar" || value === "cloudflared" || value === "frp" || value === "origin" || value === "iroh" ? value : "tailscale";
|
|
9169
9581
|
}
|
|
9170
9582
|
/**
|
|
9171
9583
|
* Set a surface's visibility; opening a page or overlay brings it to the front.
|
|
@@ -9513,11 +9925,15 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
9513
9925
|
const androidVersion = releaseVersion(data.androidVersion);
|
|
9514
9926
|
const expectedAndroidDownloadUrls = androidVersion === void 0 ? [] : ["v", "android-v"].map((prefix) => `${GITHUB_RELEASES_URL}/download/${prefix}${androidVersion}/dsh-mobile-android-v${androidVersion}.apk`);
|
|
9515
9927
|
const androidDownloadUrl = typeof data.androidDownloadUrl === "string" && expectedAndroidDownloadUrls.includes(data.androidDownloadUrl) ? data.androidDownloadUrl : GITHUB_RELEASES_URL;
|
|
9928
|
+
const androidIrohDownloadUrl = androidVersion === void 0 || androidDownloadUrl === GITHUB_RELEASES_URL ? void 0 : `${GITHUB_RELEASES_URL}/download/android-v${androidVersion}/dsh-mobile-android-v${androidVersion}-iroh.apk`;
|
|
9516
9929
|
return {
|
|
9517
9930
|
updateAvailable: data.updateAvailable === true && latestVersion !== void 0,
|
|
9518
9931
|
...latestVersion === void 0 ? {} : { latestVersion },
|
|
9519
9932
|
...androidVersion === void 0 ? {} : { androidVersion },
|
|
9520
9933
|
androidDownloadUrl,
|
|
9934
|
+
...androidIrohDownloadUrl === void 0 ? {} : { androidIrohDownloadUrl },
|
|
9935
|
+
...typeof data.androidCompatBytes === "number" && data.androidCompatBytes > 0 ? { androidCompatBytes: data.androidCompatBytes } : {},
|
|
9936
|
+
...typeof data.androidIrohBytes === "number" && data.androidIrohBytes > 0 ? { androidIrohBytes: data.androidIrohBytes } : {},
|
|
9521
9937
|
...typeof data.releaseNotes === "string" && data.releaseNotes !== "" ? { releaseNotes: data.releaseNotes.slice(0, 4e3) } : {}
|
|
9522
9938
|
};
|
|
9523
9939
|
}
|
|
@@ -9538,7 +9954,18 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
9538
9954
|
signal: controller.signal,
|
|
9539
9955
|
headers: localAdminRequestHeaders(init, location)
|
|
9540
9956
|
});
|
|
9541
|
-
const
|
|
9957
|
+
const text = await response.text();
|
|
9958
|
+
let body = {};
|
|
9959
|
+
if (text.trim() !== "") {
|
|
9960
|
+
let parsed;
|
|
9961
|
+
try {
|
|
9962
|
+
parsed = JSON.parse(text);
|
|
9963
|
+
} catch {
|
|
9964
|
+
throw new Error(!response.ok ? `HTTP ${String(response.status)} (${path})` : `${controlTranslator()("invalidResponse")} (${path})`);
|
|
9965
|
+
}
|
|
9966
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(!response.ok ? `HTTP ${String(response.status)} (${path})` : `${controlTranslator()("invalidResponse")} (${path})`);
|
|
9967
|
+
body = parsed;
|
|
9968
|
+
}
|
|
9542
9969
|
if (!response.ok) throw new Error(typeof body.error === "string" ? body.error : `HTTP ${String(response.status)}`);
|
|
9543
9970
|
return body;
|
|
9544
9971
|
} catch (error) {
|
|
@@ -9711,6 +10138,24 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
9711
10138
|
frp_discovery_invalid: "frpDiscoveryInvalid",
|
|
9712
10139
|
frp_stopped: "frpStopped",
|
|
9713
10140
|
frp_exited: "frpExited",
|
|
10141
|
+
iroh_companion_missing: "irohCompanionMissing",
|
|
10142
|
+
iroh_data_directory_invalid: "irohDataDirectoryInvalid",
|
|
10143
|
+
iroh_sidecar_invalid: "irohSidecarInvalid",
|
|
10144
|
+
iroh_download_failed: "irohDownloadFailed",
|
|
10145
|
+
iroh_download_size_mismatch: "irohDownloadFailed",
|
|
10146
|
+
iroh_download_hash_mismatch: "irohSidecarInvalid",
|
|
10147
|
+
iroh_download_timeout: "irohTimeout",
|
|
10148
|
+
iroh_platform_unsupported: "irohPlatformUnsupported",
|
|
10149
|
+
iroh_identity_failed: "irohIdentityFailed",
|
|
10150
|
+
iroh_identity_timeout: "irohIdentityFailed",
|
|
10151
|
+
iroh_identity_invalid: "irohIdentityFailed",
|
|
10152
|
+
iroh_launch_failed: "irohLaunchFailed",
|
|
10153
|
+
iroh_start_timeout: "irohTimeout",
|
|
10154
|
+
iroh_start_failed: "irohLaunchFailed",
|
|
10155
|
+
iroh_exited: "irohExited",
|
|
10156
|
+
iroh_not_ready: "irohNotReady",
|
|
10157
|
+
iroh_pairing_failed: "irohPairingFailed",
|
|
10158
|
+
iroh_component_running: "irohComponentRunning",
|
|
9714
10159
|
frp_server_address_invalid: "frpServerAddressInvalid",
|
|
9715
10160
|
frp_server_port_invalid: "frpServerPortInvalid",
|
|
9716
10161
|
frp_token_invalid: "frpTokenInvalid",
|
|
@@ -9822,7 +10267,8 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
9822
10267
|
cpolar: "cpolar",
|
|
9823
10268
|
cloudflared: "cloudflared",
|
|
9824
10269
|
frp: t("frpName"),
|
|
9825
|
-
origin: t("originName")
|
|
10270
|
+
origin: t("originName"),
|
|
10271
|
+
iroh: t("irohName")
|
|
9826
10272
|
};
|
|
9827
10273
|
const providerId = typeof facts.provider === "string" ? facts.provider : "";
|
|
9828
10274
|
const controllerCode = typeof facts.controllerCode === "string" ? facts.controllerCode : "";
|
|
@@ -9923,17 +10369,31 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
9923
10369
|
close.type = "button";
|
|
9924
10370
|
close.textContent = "×";
|
|
9925
10371
|
close.setAttribute("aria-label", t("collapseMobileAccess"));
|
|
9926
|
-
|
|
10372
|
+
const devicesEntry = element("button", "dsh-mobile-control__diagnostic-entry");
|
|
10373
|
+
devicesEntry.type = "button";
|
|
10374
|
+
devicesEntry.textContent = t("device");
|
|
10375
|
+
devicesEntry.setAttribute("aria-label", t("managePairedDevices"));
|
|
10376
|
+
devicesEntry.setAttribute("aria-pressed", "false");
|
|
10377
|
+
headerActions.append(updatePlugin, diagnosticsEntry, devicesEntry, close);
|
|
9927
10378
|
const releaseNotice = element("p", "dsh-mobile-control__release-notice");
|
|
9928
10379
|
releaseNotice.hidden = true;
|
|
9929
10380
|
releaseNotice.setAttribute("role", "status");
|
|
9930
10381
|
releaseNotice.setAttribute("aria-live", "polite");
|
|
10382
|
+
const appDownloads = element("div", "dsh-mobile-control__app-downloads");
|
|
9931
10383
|
const appDownload = element("a", "dsh-mobile-control__app-download");
|
|
9932
10384
|
appDownload.href = GITHUB_RELEASES_URL;
|
|
9933
10385
|
appDownload.target = "_blank";
|
|
9934
10386
|
appDownload.rel = "noopener noreferrer";
|
|
9935
10387
|
appDownload.textContent = t("downloadAndroid");
|
|
9936
10388
|
appDownload.setAttribute("aria-label", t("downloadAndroidAria"));
|
|
10389
|
+
const appDownloadIroh = element("a", "dsh-mobile-control__app-download is-iroh");
|
|
10390
|
+
appDownloadIroh.href = GITHUB_RELEASES_URL;
|
|
10391
|
+
appDownloadIroh.target = "_blank";
|
|
10392
|
+
appDownloadIroh.rel = "noopener noreferrer";
|
|
10393
|
+
appDownloadIroh.textContent = t("downloadAndroidIroh");
|
|
10394
|
+
appDownloadIroh.setAttribute("aria-label", t("downloadAndroidIrohAria"));
|
|
10395
|
+
appDownloadIroh.hidden = true;
|
|
10396
|
+
appDownloads.append(appDownload, appDownloadIroh);
|
|
9937
10397
|
const switcher = element("div", "dsh-mobile-control__switcher");
|
|
9938
10398
|
const lanTab = element("button", "dsh-mobile-control__tab is-active");
|
|
9939
10399
|
lanTab.type = "button";
|
|
@@ -10058,7 +10518,12 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
10058
10518
|
accessLink.rel = "noreferrer";
|
|
10059
10519
|
access.append(accessLabel, accessLink);
|
|
10060
10520
|
const qrBox = element("div", "dsh-mobile-control__qr");
|
|
10061
|
-
|
|
10521
|
+
const lanPairingValidity = element("p", "dsh-mobile-control__component-status");
|
|
10522
|
+
lanPairingValidity.hidden = true;
|
|
10523
|
+
let activeLanPairUrl = "";
|
|
10524
|
+
let activeLanPairExpiresAt = 0;
|
|
10525
|
+
let lanPairingBusy = false;
|
|
10526
|
+
let lanPairingGeneration = 0;
|
|
10062
10527
|
const status = element("p", "dsh-mobile-control__status");
|
|
10063
10528
|
status.textContent = t("loadingStatus");
|
|
10064
10529
|
const extensionStatus = element("p", "dsh-mobile-control__extensions");
|
|
@@ -10079,11 +10544,9 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
10079
10544
|
const resetAll = element("button", "dsh-mobile-control__manage");
|
|
10080
10545
|
resetAll.type = "button";
|
|
10081
10546
|
resetAll.textContent = t("clearAllDevices");
|
|
10082
|
-
manageRow.append(manageDevices
|
|
10547
|
+
manageRow.append(manageDevices);
|
|
10083
10548
|
const devicePanel = element("div", "dsh-mobile-control__devices");
|
|
10084
|
-
devicePanel.hidden = true;
|
|
10085
10549
|
const remoteView = element("div", "dsh-mobile-control__view is-remote");
|
|
10086
|
-
remoteView.hidden = true;
|
|
10087
10550
|
const remoteIntro = element("p", "dsh-mobile-control__intro");
|
|
10088
10551
|
remoteIntro.textContent = t("remoteIntro");
|
|
10089
10552
|
const httpFrameWarning = element("p", "dsh-mobile-control__http-frame-warning");
|
|
@@ -10093,27 +10556,6 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
10093
10556
|
const providerSection = element("section", "dsh-mobile-control__provider-section");
|
|
10094
10557
|
const providerHeading = element("h3", "dsh-mobile-control__section-title");
|
|
10095
10558
|
providerHeading.textContent = t("chooseProvider");
|
|
10096
|
-
const providerInfo = element("div", "dsh-mobile-control__provider-info");
|
|
10097
|
-
const providerInfoButton = element("button", "dsh-mobile-control__provider-info-button");
|
|
10098
|
-
providerInfoButton.type = "button";
|
|
10099
|
-
providerInfoButton.setAttribute("aria-label", t("providerInfoAria"));
|
|
10100
|
-
providerInfoButton.setAttribute("aria-expanded", "false");
|
|
10101
|
-
providerInfoButton.setAttribute("aria-controls", "dsh-mobile-provider-info");
|
|
10102
|
-
providerInfoButton.setAttribute("aria-describedby", "dsh-mobile-provider-info");
|
|
10103
|
-
const providerInfoGlyph = element("span", "dsh-mobile-control__provider-info-glyph");
|
|
10104
|
-
providerInfoGlyph.textContent = "i";
|
|
10105
|
-
providerInfoGlyph.setAttribute("aria-hidden", "true");
|
|
10106
|
-
const providerInfoPopover = element("div", "dsh-mobile-control__provider-info-popover");
|
|
10107
|
-
providerInfoPopover.id = "dsh-mobile-provider-info";
|
|
10108
|
-
providerInfoPopover.setAttribute("role", "tooltip");
|
|
10109
|
-
providerInfoPopover.hidden = true;
|
|
10110
|
-
const providerInfoTitle = element("strong");
|
|
10111
|
-
providerInfoTitle.textContent = t("providerSafeTitle");
|
|
10112
|
-
const providerInfoText = element("span");
|
|
10113
|
-
providerInfoText.textContent = t("providerSafeText");
|
|
10114
|
-
providerInfoButton.append(providerInfoGlyph);
|
|
10115
|
-
providerInfoPopover.append(providerInfoTitle, providerInfoText);
|
|
10116
|
-
providerInfo.append(providerInfoButton, providerInfoPopover);
|
|
10117
10559
|
const providerChoices = element("div", "dsh-mobile-control__provider-choices");
|
|
10118
10560
|
providerChoices.setAttribute("role", "radiogroup");
|
|
10119
10561
|
providerChoices.setAttribute("aria-label", t("providerGroupAria"));
|
|
@@ -10181,6 +10623,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
10181
10623
|
const cpolarInstall = element("button", "dsh-mobile-control__primary");
|
|
10182
10624
|
cpolarInstall.type = "button";
|
|
10183
10625
|
cpolarInstall.textContent = t("installOfficial");
|
|
10626
|
+
cpolarInstall.hidden = true;
|
|
10184
10627
|
const cpolarAccount = element("div", "dsh-mobile-control__cpolar-account");
|
|
10185
10628
|
cpolarAccount.hidden = true;
|
|
10186
10629
|
const cpolarAccountText = element("p", "dsh-mobile-control__component-note");
|
|
@@ -10232,7 +10675,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
10232
10675
|
cpolarPurge.textContent = t("purgeCpolar");
|
|
10233
10676
|
cpolarDetailsBody.append(cpolarDetailsText, cpolarStorage, cpolarOfficial, cpolarTerms, cpolarPurge);
|
|
10234
10677
|
cpolarDetails.append(cpolarDetailsSummary, cpolarDetailsBody);
|
|
10235
|
-
cpolarSetup.append(cpolarSetupTitle, cpolarComponentStatus,
|
|
10678
|
+
cpolarSetup.append(cpolarSetupTitle, cpolarComponentStatus, cpolarAccount);
|
|
10236
10679
|
const cloudflaredSetup = element("section", "dsh-mobile-control__cpolar-setup dsh-mobile-control__cloudflared-setup");
|
|
10237
10680
|
cloudflaredSetup.hidden = true;
|
|
10238
10681
|
const cloudflaredSetupTitle = element("h3", "dsh-mobile-control__section-title");
|
|
@@ -10249,6 +10692,41 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
10249
10692
|
cloudflaredMode.append(cloudflaredModeQuick, cloudflaredModeNamed);
|
|
10250
10693
|
const cloudflaredModeHint = element("p", "dsh-mobile-control__component-status");
|
|
10251
10694
|
cloudflaredModeHint.textContent = t("cloudflaredTunnelQuickHint");
|
|
10695
|
+
const cloudflaredNotesToggle = element("button", "dsh-mobile-control__notes-toggle");
|
|
10696
|
+
cloudflaredNotesToggle.type = "button";
|
|
10697
|
+
cloudflaredNotesToggle.textContent = "i";
|
|
10698
|
+
cloudflaredNotesToggle.setAttribute("aria-label", t("providerInfoAria"));
|
|
10699
|
+
cloudflaredNotesToggle.setAttribute("aria-expanded", "false");
|
|
10700
|
+
const cloudflaredNotesPanel = element("div", "dsh-mobile-control__notes-panel");
|
|
10701
|
+
cloudflaredNotesPanel.hidden = true;
|
|
10702
|
+
const cloudflaredNotesQuick = element("p");
|
|
10703
|
+
cloudflaredNotesQuick.textContent = t("cloudflaredQuickNote");
|
|
10704
|
+
const cloudflaredNotesNamed = element("p");
|
|
10705
|
+
cloudflaredNotesNamed.textContent = t("cloudflaredNamedNote");
|
|
10706
|
+
cloudflaredNotesPanel.append(cloudflaredNotesQuick, cloudflaredNotesNamed);
|
|
10707
|
+
const cloudflaredModeRow = element("div", "dsh-mobile-control__mode-row");
|
|
10708
|
+
cloudflaredModeRow.append(cloudflaredMode, cloudflaredNotesToggle, cloudflaredNotesPanel);
|
|
10709
|
+
const setCloudflaredNotesOpen = (open) => {
|
|
10710
|
+
cloudflaredNotesPanel.hidden = !open;
|
|
10711
|
+
cloudflaredNotesToggle.setAttribute("aria-expanded", String(open));
|
|
10712
|
+
};
|
|
10713
|
+
const hoverCapable = typeof window.matchMedia === "function" ? window.matchMedia("(hover: hover)") : void 0;
|
|
10714
|
+
cloudflaredModeRow.addEventListener("pointerenter", () => {
|
|
10715
|
+
if (hoverCapable?.matches) setCloudflaredNotesOpen(true);
|
|
10716
|
+
});
|
|
10717
|
+
cloudflaredModeRow.addEventListener("pointerleave", () => {
|
|
10718
|
+
if (hoverCapable?.matches) setCloudflaredNotesOpen(false);
|
|
10719
|
+
});
|
|
10720
|
+
cloudflaredNotesToggle.addEventListener("click", (event) => {
|
|
10721
|
+
setCloudflaredNotesOpen(hoverCapable?.matches && event.detail !== 0 ? true : Boolean(cloudflaredNotesPanel.hidden));
|
|
10722
|
+
});
|
|
10723
|
+
cloudflaredModeRow.addEventListener("keydown", (event) => {
|
|
10724
|
+
if (event.key !== "Escape" || cloudflaredNotesPanel.hidden) return;
|
|
10725
|
+
event.preventDefault();
|
|
10726
|
+
event.stopPropagation();
|
|
10727
|
+
setCloudflaredNotesOpen(false);
|
|
10728
|
+
cloudflaredNotesToggle.focus();
|
|
10729
|
+
});
|
|
10252
10730
|
const cloudflaredTunnel = element("div", "dsh-mobile-control__tunnel");
|
|
10253
10731
|
cloudflaredTunnel.hidden = true;
|
|
10254
10732
|
const cloudflaredTunnelFields = element("div", "dsh-mobile-control__frp-fields");
|
|
@@ -10291,12 +10769,23 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
10291
10769
|
cloudflaredTunnelForget.type = "button";
|
|
10292
10770
|
cloudflaredTunnelForget.textContent = t("cloudflaredTunnelForget");
|
|
10293
10771
|
cloudflaredTunnelForget.hidden = true;
|
|
10294
|
-
|
|
10772
|
+
const cloudflaredTunnelToggle = element("button", "dsh-mobile-control__tunnel-toggle");
|
|
10773
|
+
cloudflaredTunnelToggle.type = "button";
|
|
10774
|
+
cloudflaredTunnelToggle.textContent = t("cloudflaredShowConfig") + " ▾";
|
|
10775
|
+
cloudflaredTunnelToggle.hidden = true;
|
|
10776
|
+
const cloudflaredEditButton = element("button", "dsh-mobile-control__secondary");
|
|
10777
|
+
cloudflaredEditButton.type = "button";
|
|
10778
|
+
cloudflaredEditButton.textContent = t("cloudflaredEditConfig");
|
|
10779
|
+
cloudflaredEditButton.hidden = true;
|
|
10780
|
+
const cloudflaredTunnelFeedback = element("p", "dsh-mobile-control__component-status");
|
|
10781
|
+
cloudflaredTunnelFeedback.hidden = true;
|
|
10782
|
+
cloudflaredTunnel.append(cloudflaredTunnelFields, cloudflaredTunnelStatus, cloudflaredTunnelHint, cloudflaredEditButton, cloudflaredTunnelSave, cloudflaredTunnelForget, cloudflaredTunnelFeedback);
|
|
10295
10783
|
const cloudflaredComponentStatus = element("p", "dsh-mobile-control__component-status");
|
|
10296
10784
|
cloudflaredComponentStatus.textContent = t("checkingComponent");
|
|
10297
10785
|
const cloudflaredInstall = element("button", "dsh-mobile-control__primary");
|
|
10298
10786
|
cloudflaredInstall.type = "button";
|
|
10299
10787
|
cloudflaredInstall.textContent = t("installOfficial");
|
|
10788
|
+
cloudflaredInstall.hidden = true;
|
|
10300
10789
|
const cloudflaredNote = element("p", "dsh-mobile-control__origin-warning");
|
|
10301
10790
|
cloudflaredNote.textContent = t("cloudflaredQuickNote");
|
|
10302
10791
|
const cloudflaredDetails = element("details", "dsh-mobile-control__details");
|
|
@@ -10322,19 +10811,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
10322
10811
|
cloudflaredPurge.textContent = t("purgeCloudflared");
|
|
10323
10812
|
cloudflaredDetailsBody.append(cloudflaredDetailsText, cloudflaredStorage, cloudflaredOfficial, cloudflaredTerms, cloudflaredPurge);
|
|
10324
10813
|
cloudflaredDetails.append(cloudflaredDetailsSummary, cloudflaredDetailsBody);
|
|
10325
|
-
cloudflaredSetup.append(cloudflaredSetupTitle, cloudflaredTunnelTitle,
|
|
10326
|
-
const selfHosted = element("details", "dsh-mobile-control__self-hosted");
|
|
10327
|
-
const selfHostedSummary = element("summary", "dsh-mobile-control__self-hosted-summary");
|
|
10328
|
-
const selfHostedSummaryText = element("span");
|
|
10329
|
-
const selfHostedSummaryTitle = element("strong");
|
|
10330
|
-
selfHostedSummaryTitle.textContent = t("selfHostedConnections");
|
|
10331
|
-
const selfHostedSummaryDescription = element("span");
|
|
10332
|
-
selfHostedSummaryDescription.textContent = t("selfHostedDescription");
|
|
10333
|
-
const selfHostedBadge = element("span", "dsh-mobile-control__provider-badge is-frp");
|
|
10334
|
-
selfHostedBadge.textContent = t("advanced");
|
|
10335
|
-
selfHostedSummaryText.append(selfHostedSummaryTitle, selfHostedSummaryDescription);
|
|
10336
|
-
selfHostedSummary.append(selfHostedSummaryText, selfHostedBadge);
|
|
10337
|
-
const selfHostedBody = element("div", "dsh-mobile-control__self-hosted-body");
|
|
10814
|
+
cloudflaredSetup.append(cloudflaredSetupTitle, cloudflaredTunnelTitle, cloudflaredModeRow, cloudflaredModeHint, cloudflaredTunnelToggle, cloudflaredTunnel, cloudflaredComponentStatus);
|
|
10338
10815
|
const frpChoice = element("button", "dsh-mobile-control__provider is-frp");
|
|
10339
10816
|
frpChoice.type = "button";
|
|
10340
10817
|
frpChoice.setAttribute("aria-pressed", "false");
|
|
@@ -10343,7 +10820,9 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
10343
10820
|
frpChoiceName.textContent = t("frpName");
|
|
10344
10821
|
const frpChoiceDescription = element("span", "dsh-mobile-control__provider-description");
|
|
10345
10822
|
frpChoiceDescription.textContent = t("frpDescription");
|
|
10346
|
-
|
|
10823
|
+
const frpChoiceBadge = element("span", "dsh-mobile-control__provider-badge is-frp");
|
|
10824
|
+
frpChoiceBadge.textContent = t("advanced");
|
|
10825
|
+
frpChoiceTop.append(frpChoiceName, frpChoiceBadge);
|
|
10347
10826
|
frpChoice.append(frpChoiceTop, frpChoiceDescription);
|
|
10348
10827
|
const originChoice = element("button", "dsh-mobile-control__provider is-origin");
|
|
10349
10828
|
originChoice.type = "button";
|
|
@@ -10353,11 +10832,37 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
10353
10832
|
originChoiceName.textContent = t("originName");
|
|
10354
10833
|
const originChoiceDescription = element("span", "dsh-mobile-control__provider-description");
|
|
10355
10834
|
originChoiceDescription.textContent = t("originDescription");
|
|
10356
|
-
|
|
10835
|
+
const originChoiceBadge = element("span", "dsh-mobile-control__provider-badge");
|
|
10836
|
+
originChoiceBadge.textContent = t("advanced");
|
|
10837
|
+
originChoiceTop.append(originChoiceName, originChoiceBadge);
|
|
10357
10838
|
originChoice.append(originChoiceTop, originChoiceDescription);
|
|
10358
|
-
|
|
10359
|
-
|
|
10360
|
-
|
|
10839
|
+
const irohChoice = element("button", "dsh-mobile-control__provider is-iroh");
|
|
10840
|
+
irohChoice.type = "button";
|
|
10841
|
+
irohChoice.setAttribute("aria-pressed", "false");
|
|
10842
|
+
const irohChoiceTop = element("span", "dsh-mobile-control__provider-top");
|
|
10843
|
+
const irohChoiceName = element("strong");
|
|
10844
|
+
irohChoiceName.textContent = t("irohName");
|
|
10845
|
+
const irohChoiceBadge = element("span", "dsh-mobile-control__provider-badge is-iroh");
|
|
10846
|
+
irohChoiceBadge.textContent = t("irohExperimental");
|
|
10847
|
+
const irohChoiceDescription = element("span", "dsh-mobile-control__provider-description");
|
|
10848
|
+
irohChoiceDescription.textContent = t("irohDescription");
|
|
10849
|
+
irohChoiceTop.append(irohChoiceName, irohChoiceBadge);
|
|
10850
|
+
irohChoice.append(irohChoiceTop, irohChoiceDescription);
|
|
10851
|
+
providerChoices.append(irohChoice, frpChoice, originChoice);
|
|
10852
|
+
const lanChoice = element("button", "dsh-mobile-control__provider is-lan");
|
|
10853
|
+
lanChoice.type = "button";
|
|
10854
|
+
lanChoice.setAttribute("role", "radio");
|
|
10855
|
+
lanChoice.setAttribute("aria-checked", "true");
|
|
10856
|
+
const lanChoiceTop = element("span", "dsh-mobile-control__provider-top");
|
|
10857
|
+
const lanChoiceName = element("strong");
|
|
10858
|
+
lanChoiceName.textContent = t("lan");
|
|
10859
|
+
const lanChoiceBadge = element("span", "dsh-mobile-control__provider-badge");
|
|
10860
|
+
lanChoiceBadge.textContent = t("builtIn");
|
|
10861
|
+
const lanChoiceDescription = element("span", "dsh-mobile-control__provider-description");
|
|
10862
|
+
lanChoiceDescription.textContent = t("lanChoiceDescription");
|
|
10863
|
+
lanChoiceTop.append(lanChoiceName, lanChoiceBadge);
|
|
10864
|
+
lanChoice.append(lanChoiceTop, lanChoiceDescription);
|
|
10865
|
+
providerSection.append(providerHeading, lanChoice, providerChoices);
|
|
10361
10866
|
const frpSetup = element("section", "dsh-mobile-control__frp-setup");
|
|
10362
10867
|
frpSetup.hidden = true;
|
|
10363
10868
|
const frpSetupTitle = element("h3", "dsh-mobile-control__section-title");
|
|
@@ -10653,7 +11158,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
10653
11158
|
const frpVerifySummary = element("summary");
|
|
10654
11159
|
frpVerifySummary.textContent = t("frpStep4Title");
|
|
10655
11160
|
frpVerifyGroup.append(frpVerifySummary, frpStep4);
|
|
10656
|
-
frpSetup.append(frpSetupTitle, frpOverview, frpConnectionGroup, frpVpsGroup, frpComponentGroup, frpVerifyGroup
|
|
11161
|
+
frpSetup.append(frpSetupTitle, frpOverview, frpConnectionGroup, frpVpsGroup, frpComponentGroup, frpVerifyGroup);
|
|
10657
11162
|
const originSetup = element("section", "dsh-mobile-control__origin-setup");
|
|
10658
11163
|
originSetup.hidden = true;
|
|
10659
11164
|
const originSetupTitle = element("h3", "dsh-mobile-control__section-title");
|
|
@@ -10828,18 +11333,82 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
10828
11333
|
tailscaleInfoText.textContent = t("tailscaleHelpText");
|
|
10829
11334
|
tailscaleInfoBody.append(tailscaleInfoText);
|
|
10830
11335
|
tailscaleInfo.append(tailscaleInfoSummary, tailscaleInfoBody);
|
|
10831
|
-
const providerSetupHeader = element("div", "dsh-mobile-control__stage-header");
|
|
10832
|
-
const providerSetupHeading = element("h3", "dsh-mobile-control__section-title");
|
|
10833
|
-
providerSetupHeading.textContent = t("currentProvider");
|
|
10834
|
-
const providerSetupName = element("span", "dsh-mobile-control__stage-value");
|
|
10835
|
-
providerSetupName.textContent = "Tailscale Funnel";
|
|
10836
|
-
const remoteStateBadge = element("span", "dsh-mobile-control__state-badge");
|
|
10837
|
-
remoteStateBadge.textContent = t("remoteStateOff");
|
|
10838
|
-
const providerSetupMeta = element("div", "dsh-mobile-control__stage-meta");
|
|
10839
|
-
providerSetupMeta.append(providerSetupName, remoteStateBadge);
|
|
10840
|
-
providerSetupHeader.append(providerSetupHeading, providerSetupMeta);
|
|
10841
11336
|
const providerSetupBody = element("div", "dsh-mobile-control__provider-setup-body");
|
|
10842
|
-
|
|
11337
|
+
const irohSetup = element("section", "dsh-mobile-control__frp-setup");
|
|
11338
|
+
irohSetup.hidden = true;
|
|
11339
|
+
const irohSetupTitle = element("h3", "dsh-mobile-control__section-title");
|
|
11340
|
+
irohSetupTitle.textContent = t("prepareIroh");
|
|
11341
|
+
const irohPhaseNote = element("p", "dsh-mobile-control__component-note");
|
|
11342
|
+
irohPhaseNote.textContent = t("irohPhaseNote");
|
|
11343
|
+
const irohComponentStatus = element("p", "dsh-mobile-control__component-status");
|
|
11344
|
+
irohComponentStatus.textContent = t("checkingComponent");
|
|
11345
|
+
irohComponentStatus.setAttribute("role", "status");
|
|
11346
|
+
irohComponentStatus.setAttribute("aria-live", "polite");
|
|
11347
|
+
const irohInstall = element("button", "dsh-mobile-control__primary");
|
|
11348
|
+
irohInstall.type = "button";
|
|
11349
|
+
irohInstall.textContent = t("installOfficial");
|
|
11350
|
+
const irohInstallProgress = element("progress", "dsh-mobile-control__progress");
|
|
11351
|
+
irohInstallProgress.hidden = true;
|
|
11352
|
+
irohInstallProgress.setAttribute("aria-hidden", "true");
|
|
11353
|
+
const irohPairing = element("div", "dsh-mobile-control__iroh-pairing");
|
|
11354
|
+
const irohPairingTitle = element("p", "dsh-mobile-control__component-status");
|
|
11355
|
+
irohPairingTitle.textContent = t("irohPairingTitle");
|
|
11356
|
+
const irohPair = element("button", "dsh-mobile-control__primary");
|
|
11357
|
+
irohPair.type = "button";
|
|
11358
|
+
irohPair.textContent = t("generateIrohQr");
|
|
11359
|
+
const irohQr = element("div", "dsh-mobile-control__qr is-expandable");
|
|
11360
|
+
irohQr.hidden = true;
|
|
11361
|
+
const irohPairStatus = element("p", "dsh-mobile-control__component-status");
|
|
11362
|
+
const irohCopyDescriptor = element("button", "dsh-mobile-control__secondary");
|
|
11363
|
+
irohCopyDescriptor.type = "button";
|
|
11364
|
+
irohCopyDescriptor.textContent = t("copyPairLink");
|
|
11365
|
+
irohCopyDescriptor.hidden = true;
|
|
11366
|
+
irohCopyDescriptor.disabled = true;
|
|
11367
|
+
irohPairing.append(irohPairingTitle, irohQr, irohPairStatus);
|
|
11368
|
+
const irohDetails = element("details", "dsh-mobile-control__details");
|
|
11369
|
+
const irohDetailsSummary = element("summary");
|
|
11370
|
+
irohDetailsSummary.textContent = t("componentDetails");
|
|
11371
|
+
const irohDetailsBody = element("div", "dsh-mobile-control__details-body");
|
|
11372
|
+
const irohDetailsText = element("p");
|
|
11373
|
+
irohDetailsText.textContent = t("irohComponentNote");
|
|
11374
|
+
const irohInstallNote = element("p");
|
|
11375
|
+
irohInstallNote.textContent = t("irohInstallNote");
|
|
11376
|
+
const irohStorage = element("code", "dsh-mobile-control__storage");
|
|
11377
|
+
irohStorage.textContent = t("pluginPrivateDirectory");
|
|
11378
|
+
const irohOfficial = element("a", "dsh-mobile-control__text-link");
|
|
11379
|
+
irohOfficial.href = "https://www.npmjs.com/package/dsh-mobile-iroh";
|
|
11380
|
+
irohOfficial.target = "_blank";
|
|
11381
|
+
irohOfficial.rel = "noopener noreferrer";
|
|
11382
|
+
irohOfficial.textContent = t("officialDownload");
|
|
11383
|
+
const irohPurge = element("button", "dsh-mobile-control__danger");
|
|
11384
|
+
irohPurge.type = "button";
|
|
11385
|
+
irohPurge.textContent = t("purgeIroh");
|
|
11386
|
+
irohDetailsBody.append(irohDetailsText, irohInstallNote, irohStorage, irohOfficial, irohPurge);
|
|
11387
|
+
irohDetails.append(irohDetailsSummary, irohDetailsBody);
|
|
11388
|
+
irohSetup.append(irohSetupTitle, irohPhaseNote, irohComponentStatus, irohInstall, irohInstallProgress, irohPairing);
|
|
11389
|
+
providerSetupBody.append(cpolarSetup, cloudflaredSetup, frpSetup, originSetup, irohSetup, tailscaleInfo);
|
|
11390
|
+
const irohQrZoom = element("div", "dsh-mobile-control__qr-zoom");
|
|
11391
|
+
irohQrZoom.hidden = true;
|
|
11392
|
+
const irohQrZoomImage = element("img", "dsh-mobile-control__qr-zoom-image");
|
|
11393
|
+
irohQrZoomImage.alt = t("pairingQr");
|
|
11394
|
+
const irohQrZoomClose = element("button", "dsh-mobile-control__qr-zoom-close");
|
|
11395
|
+
irohQrZoomClose.type = "button";
|
|
11396
|
+
irohQrZoomClose.textContent = "×";
|
|
11397
|
+
irohQrZoomClose.setAttribute("aria-label", t("closeEnlargedQr"));
|
|
11398
|
+
const setIrohQrZoomOpen = (open) => {
|
|
11399
|
+
if (open) {
|
|
11400
|
+
irohQrZoom.setAttribute("role", "dialog");
|
|
11401
|
+
irohQrZoom.setAttribute("aria-modal", "true");
|
|
11402
|
+
irohQrZoom.setAttribute("aria-label", t("enlargeQr"));
|
|
11403
|
+
} else {
|
|
11404
|
+
irohQrZoom.removeAttribute("role");
|
|
11405
|
+
irohQrZoom.removeAttribute("aria-modal");
|
|
11406
|
+
irohQrZoom.removeAttribute("aria-label");
|
|
11407
|
+
}
|
|
11408
|
+
irohQrZoom.hidden = !open;
|
|
11409
|
+
};
|
|
11410
|
+
irohQrZoom.append(irohQrZoomImage, irohQrZoomClose);
|
|
11411
|
+
root.append(irohQrZoom);
|
|
10843
11412
|
const remoteAccess = element("div", "dsh-mobile-control__access");
|
|
10844
11413
|
remoteAccess.hidden = true;
|
|
10845
11414
|
const remoteAccessLabel = element("span", "dsh-mobile-control__access-label");
|
|
@@ -10903,9 +11472,16 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
10903
11472
|
remoteCopyLink.disabled = true;
|
|
10904
11473
|
const remotePairLink = element("p", "dsh-mobile-control__status");
|
|
10905
11474
|
remotePairLink.hidden = true;
|
|
11475
|
+
const pairingValidity = element("p", "dsh-mobile-control__component-status");
|
|
11476
|
+
pairingValidity.hidden = true;
|
|
10906
11477
|
let activeRemotePairUrl = "";
|
|
10907
11478
|
let activeRemotePairExpiresAt = 0;
|
|
10908
|
-
|
|
11479
|
+
let remotePairingBusy = false;
|
|
11480
|
+
let remotePairingGeneration = 0;
|
|
11481
|
+
let activeIrohDescriptor = "";
|
|
11482
|
+
let activeIrohPairExpiresAt = 0;
|
|
11483
|
+
let activeIrohQrSvg = "";
|
|
11484
|
+
remoteActions.append(remoteToggle, irohPair, irohCopyDescriptor, remoteLogin, remoteReconnect, remotePair, remoteCopyLink);
|
|
10909
11485
|
const remoteManageRow = element("div", "dsh-mobile-control__manage-row");
|
|
10910
11486
|
const remoteDevices = element("button", "dsh-mobile-control__manage");
|
|
10911
11487
|
remoteDevices.type = "button";
|
|
@@ -10916,7 +11492,6 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
10916
11492
|
remoteReset.textContent = t("resetRemoteLogin");
|
|
10917
11493
|
remoteManageRow.append(remoteDevices, remoteReset);
|
|
10918
11494
|
const remoteDevicePanel = element("div", "dsh-mobile-control__devices");
|
|
10919
|
-
remoteDevicePanel.hidden = true;
|
|
10920
11495
|
const wsPathsSection = element("section", "dsh-mobile-control__ws-paths");
|
|
10921
11496
|
const wsPathsTitle = element("h3", "dsh-mobile-control__section-title");
|
|
10922
11497
|
wsPathsTitle.textContent = t("wsPathsTitle");
|
|
@@ -11100,10 +11675,53 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11100
11675
|
wsPathsInput.value = "";
|
|
11101
11676
|
saveWsPaths([...wsPathsCurrent, value]);
|
|
11102
11677
|
});
|
|
11103
|
-
const remoteWorkspace = element("
|
|
11104
|
-
|
|
11678
|
+
const remoteWorkspace = element("div", "dsh-mobile-control__remote-workspace");
|
|
11679
|
+
const providerInstallRow = element("div", "dsh-mobile-control__provider-install");
|
|
11680
|
+
providerInstallRow.append(cpolarInstall, cloudflaredInstall);
|
|
11681
|
+
const setupDetailsRow = element("div", "dsh-mobile-control__setup-details");
|
|
11682
|
+
for (const disclosure of [
|
|
11683
|
+
cpolarDetails,
|
|
11684
|
+
cloudflaredDetails,
|
|
11685
|
+
frpDetails,
|
|
11686
|
+
irohDetails
|
|
11687
|
+
]) {
|
|
11688
|
+
disclosure.hidden = true;
|
|
11689
|
+
setupDetailsRow.append(disclosure);
|
|
11690
|
+
}
|
|
11691
|
+
remoteWorkspace.append(remoteStatus, remoteAccess, remoteGuide, providerSetupBody, remoteQr, pairingValidity, providerInstallRow, remoteActions, remotePairLink, setupDetailsRow);
|
|
11105
11692
|
const diagnosticsView = element("div", "dsh-mobile-control__view is-diagnostics");
|
|
11106
11693
|
diagnosticsView.hidden = true;
|
|
11694
|
+
const devicesView = element("div", "dsh-mobile-control__view is-devices");
|
|
11695
|
+
devicesView.hidden = true;
|
|
11696
|
+
const devicesSwitcher = element("div", "dsh-mobile-control__switcher");
|
|
11697
|
+
const devicesLanTab = element("button", "dsh-mobile-control__tab is-active");
|
|
11698
|
+
devicesLanTab.type = "button";
|
|
11699
|
+
devicesLanTab.textContent = t("devicesLanTitle");
|
|
11700
|
+
const devicesRemoteTab = element("button", "dsh-mobile-control__tab");
|
|
11701
|
+
devicesRemoteTab.type = "button";
|
|
11702
|
+
devicesRemoteTab.textContent = t("devicesRemoteTitle");
|
|
11703
|
+
devicesSwitcher.append(devicesLanTab, devicesRemoteTab);
|
|
11704
|
+
const devicesLanGroup = element("div");
|
|
11705
|
+
const devicesLanRow = element("div", "dsh-mobile-control__manage-row");
|
|
11706
|
+
devicesLanRow.append(resetAll);
|
|
11707
|
+
devicesLanGroup.append(devicePanel, devicesLanRow);
|
|
11708
|
+
const devicesRemoteGroup = element("div");
|
|
11709
|
+
devicesRemoteGroup.hidden = true;
|
|
11710
|
+
const devicesRemoteRow = element("div", "dsh-mobile-control__manage-row");
|
|
11711
|
+
devicesRemoteRow.append(remoteReset);
|
|
11712
|
+
devicesRemoteGroup.append(remoteDevicePanel, devicesRemoteRow);
|
|
11713
|
+
devicesView.append(devicesSwitcher, devicesLanGroup, devicesRemoteGroup);
|
|
11714
|
+
const showDevicesGroup = (which) => {
|
|
11715
|
+
const lan = which === "lan";
|
|
11716
|
+
devicesLanGroup.hidden = !lan;
|
|
11717
|
+
devicesRemoteGroup.hidden = lan;
|
|
11718
|
+
devicesLanTab.classList.toggle("is-active", lan);
|
|
11719
|
+
devicesRemoteTab.classList.toggle("is-active", !lan);
|
|
11720
|
+
if (lan) loadDevices();
|
|
11721
|
+
else loadRemoteDevices();
|
|
11722
|
+
};
|
|
11723
|
+
devicesLanTab.addEventListener("click", () => showDevicesGroup("lan"));
|
|
11724
|
+
devicesRemoteTab.addEventListener("click", () => showDevicesGroup("remote"));
|
|
11107
11725
|
const diagnosticsIntro = element("p", "dsh-mobile-control__intro");
|
|
11108
11726
|
diagnosticsIntro.textContent = t("diagnosticsIntro");
|
|
11109
11727
|
const diagnosticsSummary = element("section", "dsh-mobile-control__diagnostic-summary is-idle");
|
|
@@ -11145,10 +11763,14 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11145
11763
|
diagnosticsDetails.append(diagnosticsDetailsSummary, diagnosticsReport);
|
|
11146
11764
|
header.append(title, headerActions);
|
|
11147
11765
|
actions.append(toggle, pair, linkPair);
|
|
11148
|
-
lanView.append(lanSetup, access, qrBox, status, extensionStatus, actions,
|
|
11149
|
-
|
|
11766
|
+
lanView.append(lanSetup, access, qrBox, lanPairingValidity, status, extensionStatus, actions, trustedNetworks);
|
|
11767
|
+
const channelPane = element("div", "dsh-mobile-control__channel-pane");
|
|
11768
|
+
channelPane.append(lanView, remoteWorkspace);
|
|
11769
|
+
const remoteColumns = element("div", "dsh-mobile-control__remote-columns");
|
|
11770
|
+
remoteColumns.append(providerSection, channelPane);
|
|
11771
|
+
remoteView.append(remoteIntro, httpFrameWarning, remoteColumns);
|
|
11150
11772
|
diagnosticsView.append(diagnosticsIntro, diagnosticsSummary, diagnosticsToolbar, diagnosticsFeedback, diagnosticsChecks, wsPathsSection, diagnosticsDetails);
|
|
11151
|
-
panel.append(header, releaseNotice, updateCard,
|
|
11773
|
+
panel.append(header, releaseNotice, updateCard, appDownloads, extensionRecovery.section, remoteView, diagnosticsView, devicesView);
|
|
11152
11774
|
root.append(panel);
|
|
11153
11775
|
document.body.append(root);
|
|
11154
11776
|
let running = false;
|
|
@@ -11171,6 +11793,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11171
11793
|
let cloudflaredInstalled = false;
|
|
11172
11794
|
let cloudflaredDownloadSize = "";
|
|
11173
11795
|
let cloudflaredTunnelMode = "quick";
|
|
11796
|
+
let cloudflaredConfigEditing = false;
|
|
11174
11797
|
let cloudflaredModeDraft = null;
|
|
11175
11798
|
let cloudflaredTunnelConfigured = false;
|
|
11176
11799
|
let configuredCloudflaredHostname = "";
|
|
@@ -11201,6 +11824,9 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11201
11824
|
let latestRemoteState = {};
|
|
11202
11825
|
let frpInstalled = false;
|
|
11203
11826
|
let frpConfigured = false;
|
|
11827
|
+
let irohInstalled = false;
|
|
11828
|
+
/** True while a companion checksum verification is in flight; polls must not wipe its progress. */
|
|
11829
|
+
let irohVerifying = false;
|
|
11204
11830
|
let frpLayoutInitialized = false;
|
|
11205
11831
|
let frpDownloadSize = "14.0";
|
|
11206
11832
|
let configuredFrpServer = "";
|
|
@@ -11216,8 +11842,6 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11216
11842
|
let frpEntryTlsDraft = false;
|
|
11217
11843
|
let frpVhostDraft = false;
|
|
11218
11844
|
let frpAttachVhostExplicit = false;
|
|
11219
|
-
let providerInfoPinned = false;
|
|
11220
|
-
let providerInfoHovered = false;
|
|
11221
11845
|
let previousAccessView = "lan";
|
|
11222
11846
|
let diagnosticsBusy = false;
|
|
11223
11847
|
let copiedDiagnosticReport = "";
|
|
@@ -11234,78 +11858,140 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11234
11858
|
updatePlugin.textContent = t("updatePlugin");
|
|
11235
11859
|
if (pluginLatestVersion !== "") updatePlugin.setAttribute("aria-label", t("updatePluginAria", { version: pluginLatestVersion }));
|
|
11236
11860
|
if (release.androidVersion !== void 0) {
|
|
11237
|
-
|
|
11238
|
-
|
|
11861
|
+
const version = release.androidVersion;
|
|
11862
|
+
const sizeMb = (bytes) => bytes === void 0 ? void 0 : (bytes / 1048576).toFixed(1);
|
|
11863
|
+
const compatSize = sizeMb(release.androidCompatBytes);
|
|
11864
|
+
const irohSize = sizeMb(release.androidIrohBytes);
|
|
11865
|
+
appDownload.textContent = compatSize === void 0 ? t("downloadAndroidVersion", { version }) : t("downloadAndroidVersionSize", {
|
|
11866
|
+
version,
|
|
11867
|
+
size: compatSize
|
|
11868
|
+
});
|
|
11869
|
+
appDownload.setAttribute("aria-label", t("downloadAndroidVersionAria", { version }));
|
|
11870
|
+
appDownloadIroh.textContent = irohSize === void 0 ? t("downloadAndroidIrohVersion", { version }) : t("downloadAndroidIrohVersionSize", {
|
|
11871
|
+
version,
|
|
11872
|
+
size: irohSize
|
|
11873
|
+
});
|
|
11874
|
+
appDownloadIroh.setAttribute("aria-label", t("downloadAndroidIrohAria", { version }));
|
|
11239
11875
|
}
|
|
11240
11876
|
appDownload.href = release.androidDownloadUrl;
|
|
11877
|
+
if (release.androidIrohDownloadUrl === void 0) appDownloadIroh.hidden = true;
|
|
11878
|
+
else {
|
|
11879
|
+
appDownloadIroh.href = release.androidIrohDownloadUrl;
|
|
11880
|
+
appDownloadIroh.hidden = false;
|
|
11881
|
+
}
|
|
11882
|
+
};
|
|
11883
|
+
let currentAccessView = "lan";
|
|
11884
|
+
const applyChoiceSelection = () => {
|
|
11885
|
+
const inRemote = currentAccessView === "remote";
|
|
11886
|
+
lanChoice.classList.toggle("is-selected", currentAccessView === "lan");
|
|
11887
|
+
lanChoice.setAttribute("aria-checked", String(currentAccessView === "lan"));
|
|
11888
|
+
tailscaleChoice.classList.toggle("is-selected", inRemote && remoteProvider === "tailscale");
|
|
11889
|
+
cpolarChoice.classList.toggle("is-selected", inRemote && remoteProvider === "cpolar");
|
|
11890
|
+
cloudflaredChoice.classList.toggle("is-selected", inRemote && remoteProvider === "cloudflared");
|
|
11891
|
+
frpChoice.classList.toggle("is-selected", inRemote && remoteProvider === "frp");
|
|
11892
|
+
originChoice.classList.toggle("is-selected", inRemote && remoteProvider === "origin");
|
|
11893
|
+
irohChoice.classList.toggle("is-selected", inRemote && remoteProvider === "iroh");
|
|
11241
11894
|
};
|
|
11242
|
-
const syncProviderInfo = () => {
|
|
11243
|
-
const open = providerInfoPinned || providerInfoHovered || providerInfo.contains(document.activeElement);
|
|
11244
|
-
providerInfoPopover.hidden = !open;
|
|
11245
|
-
providerInfoButton.setAttribute("aria-expanded", String(open));
|
|
11246
|
-
};
|
|
11247
|
-
providerInfo.addEventListener("pointerenter", () => {
|
|
11248
|
-
providerInfoHovered = true;
|
|
11249
|
-
syncProviderInfo();
|
|
11250
|
-
});
|
|
11251
|
-
providerInfo.addEventListener("pointerleave", () => {
|
|
11252
|
-
providerInfoHovered = false;
|
|
11253
|
-
syncProviderInfo();
|
|
11254
|
-
});
|
|
11255
|
-
providerInfo.addEventListener("focusin", syncProviderInfo);
|
|
11256
|
-
providerInfo.addEventListener("focusout", () => {
|
|
11257
|
-
window.setTimeout(syncProviderInfo, 0);
|
|
11258
|
-
});
|
|
11259
|
-
providerInfoButton.addEventListener("click", () => {
|
|
11260
|
-
providerInfoPinned = !providerInfoPinned;
|
|
11261
|
-
syncProviderInfo();
|
|
11262
|
-
});
|
|
11263
|
-
providerInfoButton.addEventListener("keydown", (event) => {
|
|
11264
|
-
if (event.key !== "Escape") return;
|
|
11265
|
-
providerInfoPinned = false;
|
|
11266
|
-
providerInfoHovered = false;
|
|
11267
|
-
providerInfoPopover.hidden = true;
|
|
11268
|
-
providerInfoButton.setAttribute("aria-expanded", "false");
|
|
11269
|
-
});
|
|
11270
11895
|
const selectView = (view) => {
|
|
11271
11896
|
if (!panel.hidden) extensionRecovery.refresh();
|
|
11272
|
-
|
|
11897
|
+
currentAccessView = view;
|
|
11898
|
+
if (view === "lan" || view === "remote") previousAccessView = view;
|
|
11899
|
+
const subpage = view === "diagnostics" || view === "devices";
|
|
11900
|
+
remoteView.hidden = subpage;
|
|
11273
11901
|
lanView.hidden = view !== "lan";
|
|
11274
|
-
|
|
11902
|
+
remoteWorkspace.hidden = view !== "remote";
|
|
11275
11903
|
diagnosticsView.hidden = view !== "diagnostics";
|
|
11904
|
+
devicesView.hidden = view !== "devices";
|
|
11276
11905
|
lanTab.classList.toggle("is-active", view === "lan");
|
|
11277
11906
|
remoteTab.classList.toggle("is-active", view === "remote");
|
|
11907
|
+
applyChoiceSelection();
|
|
11278
11908
|
lanTab.setAttribute("aria-pressed", String(view === "lan"));
|
|
11279
11909
|
remoteTab.setAttribute("aria-pressed", String(view === "remote"));
|
|
11280
11910
|
diagnosticsEntry.setAttribute("aria-pressed", String(view === "diagnostics"));
|
|
11281
11911
|
diagnosticsEntry.textContent = view === "diagnostics" ? t("back") : t("diagnostics");
|
|
11282
11912
|
diagnosticsEntry.setAttribute("aria-label", view === "diagnostics" ? t("backToMobile") : t("openDiagnostics"));
|
|
11913
|
+
devicesEntry.setAttribute("aria-pressed", String(view === "devices"));
|
|
11914
|
+
devicesEntry.textContent = view === "devices" ? t("back") : t("device");
|
|
11915
|
+
devicesEntry.setAttribute("aria-label", view === "devices" ? t("backToMobile") : t("managePairedDevices"));
|
|
11283
11916
|
if (view === "diagnostics") {
|
|
11284
11917
|
wsPathsSeenAttempts = wsBlockedTotal(wsPathsBlocked);
|
|
11285
11918
|
renderWsDot();
|
|
11286
11919
|
if (!wsPathsLoaded) loadWsPaths();
|
|
11287
11920
|
}
|
|
11288
|
-
updatePlugin.hidden =
|
|
11289
|
-
|
|
11290
|
-
switcher.hidden =
|
|
11291
|
-
title.textContent = view === "lan" ? t("lanAccess") : view === "remote" ? t("remoteAccess") : t("connectionDiagnostics");
|
|
11921
|
+
updatePlugin.hidden = subpage || !pluginUpdateAvailable;
|
|
11922
|
+
appDownloads.hidden = subpage;
|
|
11923
|
+
switcher.hidden = subpage;
|
|
11924
|
+
title.textContent = view === "lan" ? t("lanAccess") : view === "remote" ? t("remoteAccess") : view === "devices" ? t("managePairedDevices") : t("connectionDiagnostics");
|
|
11292
11925
|
};
|
|
11293
11926
|
lanTab.addEventListener("click", () => {
|
|
11294
11927
|
selectView("lan");
|
|
11295
11928
|
});
|
|
11929
|
+
lanChoice.addEventListener("click", () => {
|
|
11930
|
+
selectView("lan");
|
|
11931
|
+
});
|
|
11932
|
+
providerChoices.addEventListener("click", () => {
|
|
11933
|
+
selectView("remote");
|
|
11934
|
+
loadRemote();
|
|
11935
|
+
});
|
|
11296
11936
|
remoteTab.addEventListener("click", () => {
|
|
11297
11937
|
selectView("remote");
|
|
11298
11938
|
loadRemote();
|
|
11299
11939
|
});
|
|
11940
|
+
selectView("lan");
|
|
11941
|
+
let returnFocus;
|
|
11300
11942
|
const setOpen = (open) => {
|
|
11943
|
+
const changed = panel.hidden === open;
|
|
11944
|
+
if (open && changed && document.activeElement instanceof HTMLElement) returnFocus = document.activeElement;
|
|
11301
11945
|
panel.hidden = !open;
|
|
11946
|
+
if (open) {
|
|
11947
|
+
panel.setAttribute("role", "dialog");
|
|
11948
|
+
panel.setAttribute("aria-modal", "true");
|
|
11949
|
+
} else {
|
|
11950
|
+
panel.removeAttribute("role");
|
|
11951
|
+
panel.removeAttribute("aria-modal");
|
|
11952
|
+
}
|
|
11302
11953
|
if (open) extensionRecovery.refresh();
|
|
11954
|
+
if (open && changed) controlRequestJson("/api/mobile-access/release").then(renderRelease, () => {});
|
|
11955
|
+
if (changed) {
|
|
11956
|
+
if (open) close.focus();
|
|
11957
|
+
else if (returnFocus?.isConnected) returnFocus.focus();
|
|
11958
|
+
}
|
|
11303
11959
|
for (const trigger of document.querySelectorAll(".dsh-mobile-control__trigger")) trigger.setAttribute("aria-expanded", String(open));
|
|
11304
11960
|
};
|
|
11961
|
+
panel.addEventListener("keydown", (event) => {
|
|
11962
|
+
if (event.defaultPrevented) return;
|
|
11963
|
+
if (event.key === "Escape") {
|
|
11964
|
+
event.preventDefault();
|
|
11965
|
+
setOpen(false);
|
|
11966
|
+
return;
|
|
11967
|
+
}
|
|
11968
|
+
if (event.key !== "Tab") return;
|
|
11969
|
+
const controls = [...panel.querySelectorAll("button,a[href],input,select,textarea,[tabindex]")].filter((node) => node.tabIndex >= 0 && !node.matches(":disabled") && node.getClientRects().length > 0);
|
|
11970
|
+
const first = controls[0], last = controls.at(-1);
|
|
11971
|
+
if (event.shiftKey && document.activeElement === first && last !== void 0) {
|
|
11972
|
+
event.preventDefault();
|
|
11973
|
+
last.focus();
|
|
11974
|
+
} else if (!event.shiftKey && document.activeElement === last && first !== void 0) {
|
|
11975
|
+
event.preventDefault();
|
|
11976
|
+
first.focus();
|
|
11977
|
+
}
|
|
11978
|
+
});
|
|
11979
|
+
const renderLanPairingExpiry = () => {
|
|
11980
|
+
if (!running || activeLanPairExpiresAt === 0) return;
|
|
11981
|
+
lanPairingValidity.hidden = false;
|
|
11982
|
+
if (Date.now() >= activeLanPairExpiresAt) {
|
|
11983
|
+
activeLanPairUrl = "";
|
|
11984
|
+
showQr("");
|
|
11985
|
+
qrBox.textContent = t("pairingExpired");
|
|
11986
|
+
lanPairingValidity.textContent = t("pairingExpired");
|
|
11987
|
+
} else lanPairingValidity.textContent = t("pairingExpires", { time: formatTime(activeLanPairExpiresAt) });
|
|
11988
|
+
};
|
|
11305
11989
|
const render = (data) => {
|
|
11990
|
+
const previousOrigin = origin;
|
|
11306
11991
|
lanConfigured = data.configured !== false;
|
|
11307
11992
|
lanRestartRequired = data.restartRequired === true;
|
|
11308
11993
|
running = data.running === true;
|
|
11994
|
+
lanChoiceBadge.textContent = running ? t("lanBadgeOn") : t("lanBadgeOff");
|
|
11309
11995
|
origin = running && typeof data.origin === "string" ? data.origin : "";
|
|
11310
11996
|
lanSetup.hidden = lanConfigured && !lanRestartRequired;
|
|
11311
11997
|
lanSetupForm.hidden = lanRestartRequired;
|
|
@@ -11332,12 +12018,18 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11332
12018
|
failed
|
|
11333
12019
|
});
|
|
11334
12020
|
} else extensionStatus.hidden = true;
|
|
11335
|
-
if (!running
|
|
12021
|
+
if (!running || previousOrigin !== origin) {
|
|
12022
|
+
lanPairingGeneration += 1;
|
|
12023
|
+
activeLanPairUrl = "";
|
|
12024
|
+
activeLanPairExpiresAt = 0;
|
|
12025
|
+
lanPairingValidity.hidden = true;
|
|
12026
|
+
showQr("");
|
|
12027
|
+
} else renderLanPairingExpiry();
|
|
11336
12028
|
toggle.textContent = running ? t("disableLan") : t("enableLan");
|
|
11337
12029
|
actions.hidden = !lanConfigured || lanRestartRequired;
|
|
11338
12030
|
manageRow.hidden = !lanConfigured || lanRestartRequired;
|
|
11339
|
-
pair.disabled = !running;
|
|
11340
|
-
linkPair.disabled = !running;
|
|
12031
|
+
pair.disabled = !running || lanPairingBusy;
|
|
12032
|
+
linkPair.disabled = !running || lanPairingBusy;
|
|
11341
12033
|
manageDevices.disabled = !running;
|
|
11342
12034
|
resetAll.disabled = !running;
|
|
11343
12035
|
};
|
|
@@ -11406,10 +12098,25 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11406
12098
|
lanSetupConfigure.disabled = lanRestartRequired || lanSetupSelect.options.length === 0;
|
|
11407
12099
|
});
|
|
11408
12100
|
});
|
|
12101
|
+
const clearQrTarget = (target) => {
|
|
12102
|
+
if (typeof target.replaceChildren === "function") {
|
|
12103
|
+
target.replaceChildren();
|
|
12104
|
+
return;
|
|
12105
|
+
}
|
|
12106
|
+
try {
|
|
12107
|
+
while (target.firstChild) target.removeChild(target.firstChild);
|
|
12108
|
+
} catch {}
|
|
12109
|
+
};
|
|
11409
12110
|
const showQr = (svg, target = qrBox) => {
|
|
11410
|
-
target
|
|
12111
|
+
clearQrTarget(target);
|
|
11411
12112
|
if (svg === "") {
|
|
11412
|
-
|
|
12113
|
+
const placeholderText = target === irohQr ? t("irohQrPlaceholder") : target === remoteQr ? t("remoteQrPlaceholder") : target === qrBox ? t("qrPlaceholder") : void 0;
|
|
12114
|
+
if (placeholderText !== void 0) {
|
|
12115
|
+
const placeholder = element("span", "dsh-mobile-control__qr-placeholder");
|
|
12116
|
+
placeholder.textContent = placeholderText;
|
|
12117
|
+
target.hidden = false;
|
|
12118
|
+
target.append(placeholder);
|
|
12119
|
+
} else target.hidden = true;
|
|
11413
12120
|
return;
|
|
11414
12121
|
}
|
|
11415
12122
|
const image = element("img");
|
|
@@ -11420,23 +12127,68 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11420
12127
|
target.hidden = false;
|
|
11421
12128
|
target.append(image);
|
|
11422
12129
|
};
|
|
12130
|
+
showQr("");
|
|
12131
|
+
const showIrohQrPlaceholder = () => {
|
|
12132
|
+
clearQrTarget(irohQr);
|
|
12133
|
+
const placeholder = element("span", "dsh-mobile-control__qr-placeholder");
|
|
12134
|
+
placeholder.textContent = t("irohQrPlaceholder");
|
|
12135
|
+
irohQr.hidden = false;
|
|
12136
|
+
irohQr.append(placeholder);
|
|
12137
|
+
};
|
|
12138
|
+
const showRemoteQrPlaceholder = (text) => {
|
|
12139
|
+
clearQrTarget(remoteQr);
|
|
12140
|
+
const placeholder = element("span", "dsh-mobile-control__qr-placeholder");
|
|
12141
|
+
placeholder.textContent = text;
|
|
12142
|
+
remoteQr.hidden = false;
|
|
12143
|
+
remoteQr.append(placeholder);
|
|
12144
|
+
};
|
|
11423
12145
|
const openPairing = (target) => {
|
|
11424
|
-
|
|
12146
|
+
if (lanPairingBusy || !running) return;
|
|
12147
|
+
lanPairingBusy = true;
|
|
12148
|
+
pair.disabled = true;
|
|
12149
|
+
linkPair.disabled = true;
|
|
12150
|
+
const pairingOrigin = origin;
|
|
12151
|
+
const generation = lanPairingGeneration;
|
|
12152
|
+
if (target === "key" || activeLanPairExpiresAt <= Date.now()) {
|
|
12153
|
+
activeLanPairUrl = "";
|
|
12154
|
+
activeLanPairExpiresAt = 0;
|
|
12155
|
+
lanPairingValidity.hidden = true;
|
|
12156
|
+
status.classList.remove("is-key");
|
|
12157
|
+
showQr("");
|
|
12158
|
+
}
|
|
12159
|
+
(target === "link" && activeLanPairUrl !== "" && activeLanPairExpiresAt > Date.now() ? Promise.resolve({
|
|
12160
|
+
pairUrl: activeLanPairUrl,
|
|
12161
|
+
expiresAt: activeLanPairExpiresAt
|
|
12162
|
+
}) : controlRequestJson("/api/mobile-access/lan/pairing/open", {
|
|
11425
12163
|
method: "POST",
|
|
11426
12164
|
body: "{}"
|
|
11427
|
-
}).then(async (data) => {
|
|
11428
|
-
|
|
11429
|
-
|
|
12165
|
+
})).then(async (data) => {
|
|
12166
|
+
if (lifecycle.signal.aborted || !running || origin !== pairingOrigin || generation !== lanPairingGeneration) return;
|
|
12167
|
+
const value = typeof data.pairUrl === "string" ? data.pairUrl : "";
|
|
12168
|
+
if (value === "" || typeof data.expiresAt !== "number" || !Number.isFinite(data.expiresAt) || data.expiresAt <= Date.now()) {
|
|
12169
|
+
status.textContent = t("keyGenerationFailed");
|
|
12170
|
+
return;
|
|
12171
|
+
}
|
|
12172
|
+
activeLanPairUrl = value;
|
|
12173
|
+
activeLanPairExpiresAt = data.expiresAt;
|
|
12174
|
+
lanPairingValidity.hidden = false;
|
|
12175
|
+
lanPairingValidity.textContent = t("pairingExpires", { time: formatTime(data.expiresAt) });
|
|
12176
|
+
if ("qrSvg" in data) showQr(typeof data.qrSvg === "string" ? data.qrSvg : "");
|
|
12177
|
+
if (target === "key") {
|
|
12178
|
+
status.classList.remove("is-key");
|
|
12179
|
+
status.textContent = t("qrGenerated");
|
|
12180
|
+
return;
|
|
12181
|
+
}
|
|
11430
12182
|
if (value === "") {
|
|
11431
12183
|
status.textContent = t("keyGenerationFailed");
|
|
11432
12184
|
return;
|
|
11433
12185
|
}
|
|
11434
12186
|
try {
|
|
11435
12187
|
await navigator.clipboard.writeText(value);
|
|
11436
|
-
status.textContent =
|
|
12188
|
+
status.textContent = t("linkCopied");
|
|
11437
12189
|
} catch {
|
|
11438
12190
|
status.textContent = t("copySecret", {
|
|
11439
|
-
kind:
|
|
12191
|
+
kind: t("pairingLink"),
|
|
11440
12192
|
value
|
|
11441
12193
|
});
|
|
11442
12194
|
status.classList.add("is-key");
|
|
@@ -11444,6 +12196,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11444
12196
|
}, (error) => {
|
|
11445
12197
|
status.textContent = t("requestFailed", { error: String(error) });
|
|
11446
12198
|
}).finally(() => {
|
|
12199
|
+
lanPairingBusy = false;
|
|
11447
12200
|
pair.disabled = !running;
|
|
11448
12201
|
linkPair.disabled = !running;
|
|
11449
12202
|
});
|
|
@@ -11498,12 +12251,15 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11498
12251
|
const loadDevices = () => {
|
|
11499
12252
|
controlRequestJson("/api/mobile-access/lan/devices").then(renderDevices, (error) => {
|
|
11500
12253
|
status.textContent = t("requestFailed", { error: String(error) });
|
|
12254
|
+
devicePanel.replaceChildren();
|
|
12255
|
+
const note = element("p", "dsh-mobile-control__device-empty");
|
|
12256
|
+
note.textContent = t("noDevices");
|
|
12257
|
+
devicePanel.append(note);
|
|
11501
12258
|
});
|
|
11502
12259
|
};
|
|
11503
12260
|
manageDevices.addEventListener("click", () => {
|
|
11504
|
-
|
|
11505
|
-
|
|
11506
|
-
if (show) loadDevices();
|
|
12261
|
+
selectView("devices");
|
|
12262
|
+
loadDevices();
|
|
11507
12263
|
});
|
|
11508
12264
|
resetAll.addEventListener("click", () => {
|
|
11509
12265
|
if (!window.confirm(t("confirmResetDevices"))) return;
|
|
@@ -11515,6 +12271,15 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11515
12271
|
});
|
|
11516
12272
|
});
|
|
11517
12273
|
const renderRemote = (data) => {
|
|
12274
|
+
if (data.provider !== latestRemoteState.provider || data.origin !== latestRemoteState.origin) {
|
|
12275
|
+
remotePairingGeneration += 1;
|
|
12276
|
+
activeRemotePairUrl = "";
|
|
12277
|
+
activeRemotePairExpiresAt = 0;
|
|
12278
|
+
remotePairLink.hidden = true;
|
|
12279
|
+
remotePairLink.textContent = "";
|
|
12280
|
+
pairingValidity.hidden = true;
|
|
12281
|
+
showQr("", remoteQr);
|
|
12282
|
+
}
|
|
11518
12283
|
latestRemoteState = data;
|
|
11519
12284
|
remoteRunning = data.running === true;
|
|
11520
12285
|
remoteProvider = parseMobileRemoteProvider(data.provider);
|
|
@@ -11522,13 +12287,9 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11522
12287
|
const cloudflared = remoteProvider === "cloudflared";
|
|
11523
12288
|
const frp = remoteProvider === "frp";
|
|
11524
12289
|
const origin = remoteProvider === "origin";
|
|
12290
|
+
const iroh = remoteProvider === "iroh";
|
|
11525
12291
|
const managedOrigin = origin && data.originMode === "managed";
|
|
11526
12292
|
const tailscale = remoteProvider === "tailscale";
|
|
11527
|
-
tailscaleChoice.classList.toggle("is-selected", tailscale);
|
|
11528
|
-
cpolarChoice.classList.toggle("is-selected", cpolar);
|
|
11529
|
-
cloudflaredChoice.classList.toggle("is-selected", cloudflared);
|
|
11530
|
-
frpChoice.classList.toggle("is-selected", frp);
|
|
11531
|
-
originChoice.classList.toggle("is-selected", origin);
|
|
11532
12293
|
tailscaleChoice.setAttribute("aria-checked", String(tailscale));
|
|
11533
12294
|
cpolarChoice.setAttribute("aria-checked", String(cpolar));
|
|
11534
12295
|
cloudflaredChoice.setAttribute("aria-checked", String(cloudflared));
|
|
@@ -11537,17 +12298,19 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11537
12298
|
cloudflaredChoice.tabIndex = cloudflared ? 0 : -1;
|
|
11538
12299
|
frpChoice.setAttribute("aria-pressed", String(frp));
|
|
11539
12300
|
originChoice.setAttribute("aria-pressed", String(origin));
|
|
11540
|
-
|
|
12301
|
+
irohChoice.setAttribute("aria-pressed", String(iroh));
|
|
12302
|
+
applyChoiceSelection();
|
|
11541
12303
|
tailscaleChoice.disabled = remoteProviderBusy;
|
|
11542
12304
|
cpolarChoice.disabled = remoteProviderBusy;
|
|
11543
12305
|
cloudflaredChoice.disabled = remoteProviderBusy;
|
|
11544
12306
|
frpChoice.disabled = remoteProviderBusy;
|
|
11545
12307
|
originChoice.disabled = remoteProviderBusy;
|
|
12308
|
+
irohChoice.disabled = remoteProviderBusy;
|
|
11546
12309
|
cpolarSetup.hidden = !cpolar;
|
|
11547
12310
|
cloudflaredSetup.hidden = !cloudflared;
|
|
11548
12311
|
frpSetup.hidden = !frp;
|
|
11549
12312
|
originSetup.hidden = !origin;
|
|
11550
|
-
|
|
12313
|
+
irohSetup.hidden = !iroh;
|
|
11551
12314
|
tailscaleInfo.hidden = !tailscale;
|
|
11552
12315
|
remoteReset.textContent = tailscale ? t("resetRemoteLogin") : t("resetRemoteDevices");
|
|
11553
12316
|
const providers = data.providers !== null && typeof data.providers === "object" ? data.providers : {};
|
|
@@ -11562,7 +12325,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11562
12325
|
const componentStorage = typeof component.storagePath === "string" ? component.storagePath : `DSH Mobile ${t("pluginPrivateDirectory")}`;
|
|
11563
12326
|
cpolarStorage.textContent = componentStorage;
|
|
11564
12327
|
cpolarStorage.title = componentStorage;
|
|
11565
|
-
cpolarInstall.hidden = cpolarInstalled || !cpolarSupported;
|
|
12328
|
+
cpolarInstall.hidden = !cpolar || cpolarInstalled || !cpolarSupported;
|
|
11566
12329
|
cpolarInstall.textContent = componentDownloadBytes > 0 ? t("installWithSize", { size: formatMegabytes(componentDownloadBytes) }) : t("installOfficial");
|
|
11567
12330
|
cpolarInstall.disabled = remoteProviderBusy;
|
|
11568
12331
|
cpolarAccount.hidden = !cpolarInstalled || cpolarConfigured;
|
|
@@ -11581,7 +12344,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11581
12344
|
cloudflaredStorage.textContent = cloudflaredStoragePath;
|
|
11582
12345
|
cloudflaredStorage.title = cloudflaredStoragePath;
|
|
11583
12346
|
cloudflaredChoiceBadge.textContent = cloudflaredInstalled ? t("ready") : t("cloudflaredBadge");
|
|
11584
|
-
cloudflaredInstall.hidden = cloudflaredInstalled || !cloudflaredSupported;
|
|
12347
|
+
cloudflaredInstall.hidden = !cloudflared || cloudflaredInstalled || !cloudflaredSupported;
|
|
11585
12348
|
cloudflaredInstall.textContent = cloudflaredDownloadBytes > 0 ? t("installWithSize", { size: formatMegabytes(cloudflaredDownloadBytes) }) : t("installOfficial");
|
|
11586
12349
|
cloudflaredInstall.disabled = remoteProviderBusy;
|
|
11587
12350
|
cloudflaredPurge.hidden = !cloudflaredInstalled;
|
|
@@ -11602,8 +12365,8 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11602
12365
|
cloudflaredModeNamed.disabled = remoteProviderBusy;
|
|
11603
12366
|
cloudflaredModeHint.textContent = cloudflaredShownMode === "named" ? t("cloudflaredTunnelNamedHint") : t("cloudflaredTunnelQuickHint");
|
|
11604
12367
|
cloudflaredNote.textContent = cloudflaredShownMode === "named" ? t("cloudflaredNamedNote") : t("cloudflaredQuickNote");
|
|
11605
|
-
cloudflaredTunnel.hidden = cloudflaredShownMode !== "named";
|
|
11606
12368
|
cloudflaredTunnelSave.textContent = cloudflaredSaveLabel();
|
|
12369
|
+
syncCloudflaredForm();
|
|
11607
12370
|
if (!cloudflaredHostnameDirty) cloudflaredHostname.value = configuredCloudflaredHostname;
|
|
11608
12371
|
if (!cloudflaredPortDirty) cloudflaredPort.value = String(configuredCloudflaredPort);
|
|
11609
12372
|
cloudflaredToken.placeholder = cloudflaredTunnelConfigured ? t("cloudflaredTunnelTokenSaved") : t("cloudflaredTunnelTokenPlaceholder");
|
|
@@ -11702,6 +12465,32 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11702
12465
|
frpVpsGroup.open = !frpConfigured;
|
|
11703
12466
|
frpLayoutInitialized = true;
|
|
11704
12467
|
}
|
|
12468
|
+
const irohProvider = providers.iroh !== null && typeof providers.iroh === "object" ? providers.iroh : {};
|
|
12469
|
+
const irohComponent = irohProvider.component !== null && typeof irohProvider.component === "object" ? irohProvider.component : {};
|
|
12470
|
+
irohInstalled = irohComponent.installed === true;
|
|
12471
|
+
const irohSupported = irohComponent.supported !== false;
|
|
12472
|
+
const irohComponentError = typeof irohComponent.errorCode === "string" ? irohComponent.errorCode : "";
|
|
12473
|
+
const irohVersion = typeof irohComponent.version === "string" ? irohComponent.version : "";
|
|
12474
|
+
const irohDownloadBytes = typeof irohComponent.downloadBytes === "number" ? irohComponent.downloadBytes : 0;
|
|
12475
|
+
const irohStoragePath = typeof irohComponent.storagePath === "string" ? irohComponent.storagePath : `DSH Mobile ${t("pluginPrivateDirectory")}`;
|
|
12476
|
+
irohInstall.hidden = irohInstalled || !irohSupported;
|
|
12477
|
+
irohPurge.hidden = !irohInstalled;
|
|
12478
|
+
irohPurge.disabled = iroh && remoteRunning;
|
|
12479
|
+
if (irohVerifying) {
|
|
12480
|
+
irohInstallProgress.hidden = false;
|
|
12481
|
+
irohInstall.disabled = true;
|
|
12482
|
+
irohInstall.textContent = t("installingIroh");
|
|
12483
|
+
irohInstall.setAttribute("aria-busy", "true");
|
|
12484
|
+
irohComponentStatus.textContent = t("installingIroh");
|
|
12485
|
+
} else {
|
|
12486
|
+
irohInstallProgress.hidden = true;
|
|
12487
|
+
irohInstall.setAttribute("aria-busy", "false");
|
|
12488
|
+
irohInstall.disabled = remoteProviderBusy;
|
|
12489
|
+
irohInstall.textContent = irohDownloadBytes > 0 ? t("installWithSize", { size: formatMegabytes(irohDownloadBytes) }) : t("installOfficial");
|
|
12490
|
+
irohComponentStatus.textContent = !irohSupported ? t("irohPlatformUnsupported") : irohInstalled ? t("installed") + (irohVersion === "" ? "" : ` · ${irohVersion}`) : irohComponentError === "irohSidecarInvalid" ? t("irohSidecarInvalid") : t("irohUnavailable");
|
|
12491
|
+
}
|
|
12492
|
+
irohStorage.textContent = irohStoragePath;
|
|
12493
|
+
irohStorage.title = irohStoragePath;
|
|
11705
12494
|
const originProvider = providers.origin !== null && typeof providers.origin === "object" ? providers.origin : {};
|
|
11706
12495
|
const originConfiguration = originProvider.configuration !== null && typeof originProvider.configuration === "object" ? originProvider.configuration : {};
|
|
11707
12496
|
originConfigured = originConfiguration.configured === true;
|
|
@@ -11780,11 +12569,9 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11780
12569
|
}[errorCode] ?? "";
|
|
11781
12570
|
const needsFunnelSetup = state === "error" && remoteSetupUrl !== "";
|
|
11782
12571
|
remoteReady = remoteRunning && state === "ready" && remoteOrigin !== "";
|
|
11783
|
-
|
|
11784
|
-
|
|
11785
|
-
|
|
11786
|
-
remoteStateBadge.classList.toggle("is-attention", state === "error" || state === "unavailable");
|
|
11787
|
-
remoteStateBadge.textContent = remoteReady ? origin && !managedOrigin ? t("originBackendReady") : t("ready") : state === "starting" || state === "connecting" || state === "needs-login" ? t("remoteStateConnecting") : state === "error" || state === "unavailable" ? t("remoteStateAttention") : t("remoteStateOff");
|
|
12572
|
+
frpChoiceBadge.textContent = frpConfigured && frpInstalled ? t("ready") : t("advanced");
|
|
12573
|
+
originChoiceBadge.textContent = managedOrigin ? t(remoteReady ? "ready" : "advanced") : originListening ? t("originBackendReady") : t("advanced");
|
|
12574
|
+
if (remoteStatus.style) remoteStatus.style.display = state === "off" && remoteFailureText === "" ? "none" : "";
|
|
11788
12575
|
remoteAccess.hidden = !remoteReady;
|
|
11789
12576
|
remoteAccessLink.href = remoteOrigin;
|
|
11790
12577
|
remoteAccessLink.textContent = remoteOrigin;
|
|
@@ -11792,10 +12579,10 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11792
12579
|
remoteStatus.classList.toggle("is-running", remoteReady);
|
|
11793
12580
|
const labels = {
|
|
11794
12581
|
off: t("remoteOff"),
|
|
11795
|
-
unavailable: cpolar ? t("remoteUnavailableCpolar") : cloudflared ? t("remoteUnavailableCloudflared") : frp ? t("remoteUnavailableFrp") : origin ? t("originUnavailable") : t("remoteUnavailableTailscale"),
|
|
11796
|
-
starting: cpolar ? t("remoteStartingCpolar") : cloudflared ? t("remoteStartingCloudflared") : frp ? t("remoteStartingFrp") : origin ? t("originStarting") : t("remoteStartingTailscale"),
|
|
12582
|
+
unavailable: cpolar ? t("remoteUnavailableCpolar") : cloudflared ? t("remoteUnavailableCloudflared") : frp ? t("remoteUnavailableFrp") : origin ? t("originUnavailable") : iroh ? irohInstalled ? t("irohInstalledNotReady") : t("irohUnavailable") : t("remoteUnavailableTailscale"),
|
|
12583
|
+
starting: cpolar ? t("remoteStartingCpolar") : cloudflared ? t("remoteStartingCloudflared") : frp ? t("remoteStartingFrp") : origin ? t("originStarting") : iroh ? t("irohStarting") : t("remoteStartingTailscale"),
|
|
11797
12584
|
"needs-login": t("remoteNeedsLogin"),
|
|
11798
|
-
connecting: cpolar ? t("remoteConnectingCpolar") : cloudflared ? t("remoteConnectingCloudflared") : frp ? t("remoteConnectingFrp") : t("remoteConnectingTailscale"),
|
|
12585
|
+
connecting: cpolar ? t("remoteConnectingCpolar") : cloudflared ? t("remoteConnectingCloudflared") : frp ? t("remoteConnectingFrp") : iroh ? t("irohConnecting") : t("remoteConnectingTailscale"),
|
|
11799
12586
|
ready: origin ? t(managedOrigin ? "caddyVerified" : "originReady") : t("remoteReady"),
|
|
11800
12587
|
error: t("remoteError")
|
|
11801
12588
|
};
|
|
@@ -11810,24 +12597,71 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11810
12597
|
remoteSetupRetry.disabled = remoteReconnectBusy;
|
|
11811
12598
|
remoteToggle.textContent = remoteRunning ? t("disableRemote") : t("enableRemote");
|
|
11812
12599
|
const originPrepared = data.originMode === "managed" ? caddySupported && caddyInstalled && caddyConfiguration.configured === true : originConfigured && !caddyModeManagedSelected;
|
|
11813
|
-
const providerPrepared = cpolar ? cpolarInstalled && cpolarConfigured : cloudflared ? cloudflaredInstalled : frp ? frpInstalled && frpConfigured : origin ? originPrepared : true;
|
|
11814
|
-
|
|
12600
|
+
const providerPrepared = cpolar ? cpolarInstalled && cpolarConfigured : cloudflared ? cloudflaredInstalled : frp ? frpInstalled && frpConfigured : origin ? originPrepared : iroh ? irohInstalled : true;
|
|
12601
|
+
const namedUnconfigured = cloudflared && cloudflaredShownMode === "named" && cloudflaredTunnelMode !== "named" && !remoteRunning;
|
|
12602
|
+
remoteToggle.disabled = remoteProviderBusy || !providerPrepared && !(origin && remoteRunning) || namedUnconfigured;
|
|
12603
|
+
if (namedUnconfigured) {
|
|
12604
|
+
remoteStatus.textContent = t("cloudflaredEnableNeedsConfig");
|
|
12605
|
+
if (remoteStatus.style) remoteStatus.style.display = "";
|
|
12606
|
+
}
|
|
11815
12607
|
remoteLogin.hidden = !tailscale || state !== "needs-login" || remoteLoginUrl === "";
|
|
11816
12608
|
remoteReconnect.hidden = needsFunnelSetup || state !== "error" && state !== "unavailable" || !providerPrepared;
|
|
11817
12609
|
remoteActions.hidden = !providerPrepared && !(origin && remoteRunning);
|
|
11818
|
-
|
|
11819
|
-
|
|
12610
|
+
if (!providerPrepared) remoteQr.hidden = true;
|
|
12611
|
+
providerInstallRow.hidden = cpolarInstall.hidden && cloudflaredInstall.hidden;
|
|
12612
|
+
remotePair.disabled = !remoteReady || remoteProviderBusy || remotePairingBusy;
|
|
12613
|
+
remoteCopyLink.disabled = !remoteReady || remoteProviderBusy || remotePairingBusy;
|
|
11820
12614
|
remoteDevices.disabled = !remoteReady || remoteProviderBusy;
|
|
12615
|
+
remotePair.hidden = iroh;
|
|
12616
|
+
remoteCopyLink.hidden = iroh;
|
|
12617
|
+
if (iroh) {
|
|
12618
|
+
remoteQr.hidden = true;
|
|
12619
|
+
remotePairLink.hidden = true;
|
|
12620
|
+
}
|
|
12621
|
+
irohPair.disabled = !remoteReady || !iroh || remoteProviderBusy;
|
|
12622
|
+
irohPair.hidden = !iroh;
|
|
12623
|
+
irohCopyDescriptor.hidden = !iroh;
|
|
12624
|
+
if (iroh && irohInstalled && activeIrohQrSvg === "") showIrohQrPlaceholder();
|
|
12625
|
+
else if (!iroh || !irohInstalled) irohQr.hidden = true;
|
|
12626
|
+
if ((!iroh || !remoteReady) && activeIrohDescriptor !== "") {
|
|
12627
|
+
activeIrohDescriptor = "";
|
|
12628
|
+
activeIrohPairExpiresAt = 0;
|
|
12629
|
+
activeIrohQrSvg = "";
|
|
12630
|
+
showQr("", irohQr);
|
|
12631
|
+
setIrohQrZoomOpen(false);
|
|
12632
|
+
irohPairStatus.textContent = "";
|
|
12633
|
+
}
|
|
12634
|
+
irohCopyDescriptor.disabled = !iroh || !remoteReady || remoteProviderBusy || activeIrohDescriptor === "" || activeIrohPairExpiresAt <= Date.now();
|
|
11821
12635
|
if (origin) {
|
|
11822
12636
|
remoteReconnect.disabled = remoteProviderBusy || remoteReconnectBusy;
|
|
11823
12637
|
remoteReset.disabled = remoteProviderBusy;
|
|
11824
12638
|
} else remoteReset.disabled = false;
|
|
11825
12639
|
if (!remoteReady) {
|
|
11826
|
-
|
|
12640
|
+
remotePairingGeneration += 1;
|
|
11827
12641
|
remotePairLink.hidden = true;
|
|
11828
12642
|
activeRemotePairUrl = "";
|
|
11829
12643
|
activeRemotePairExpiresAt = 0;
|
|
12644
|
+
pairingValidity.hidden = true;
|
|
12645
|
+
if (iroh) remoteQr.hidden = true;
|
|
12646
|
+
else showRemoteQrPlaceholder(t("remoteQrPlaceholderIdle"));
|
|
12647
|
+
} else if (!iroh && activeRemotePairExpiresAt > 0 && Date.now() >= activeRemotePairExpiresAt) {
|
|
12648
|
+
activeRemotePairUrl = "";
|
|
12649
|
+
remotePairLink.hidden = true;
|
|
12650
|
+
remotePairLink.textContent = "";
|
|
12651
|
+
showRemoteQrPlaceholder(t("pairingExpired"));
|
|
12652
|
+
pairingValidity.hidden = false;
|
|
12653
|
+
pairingValidity.textContent = t("pairingExpired");
|
|
12654
|
+
} else if (!iroh && activeRemotePairUrl !== "") {
|
|
12655
|
+
pairingValidity.hidden = false;
|
|
12656
|
+
pairingValidity.textContent = t("pairingExpires", { time: formatTime(activeRemotePairExpiresAt) });
|
|
12657
|
+
} else {
|
|
12658
|
+
pairingValidity.hidden = true;
|
|
12659
|
+
if (!iroh) showQr("", remoteQr);
|
|
11830
12660
|
}
|
|
12661
|
+
cpolarDetails.hidden = !cpolar;
|
|
12662
|
+
cloudflaredDetails.hidden = !cloudflared;
|
|
12663
|
+
frpDetails.hidden = !frp;
|
|
12664
|
+
irohDetails.hidden = !iroh;
|
|
11831
12665
|
if (!needsFunnelSetup) remoteSetupPending = false;
|
|
11832
12666
|
};
|
|
11833
12667
|
frpMode.addEventListener("change", () => {
|
|
@@ -11882,7 +12716,8 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11882
12716
|
cloudflaredChoice.disabled = true;
|
|
11883
12717
|
frpChoice.disabled = true;
|
|
11884
12718
|
originChoice.disabled = true;
|
|
11885
|
-
|
|
12719
|
+
irohChoice.disabled = true;
|
|
12720
|
+
remoteStatus.textContent = provider === "cpolar" ? t("switchingCpolar") : provider === "cloudflared" ? t("switchingCloudflared") : provider === "frp" ? t("switchingFrp") : provider === "origin" ? t("switchingOrigin") : provider === "iroh" ? t("switchingIroh") : t("switchingTailscale");
|
|
11886
12721
|
controlRequestJson("/api/mobile-access/remote/provider", {
|
|
11887
12722
|
method: "POST",
|
|
11888
12723
|
body: JSON.stringify({ provider })
|
|
@@ -11909,6 +12744,140 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11909
12744
|
originChoice.addEventListener("click", () => {
|
|
11910
12745
|
chooseRemoteProvider("origin");
|
|
11911
12746
|
});
|
|
12747
|
+
irohChoice.addEventListener("click", () => {
|
|
12748
|
+
chooseRemoteProvider("iroh");
|
|
12749
|
+
});
|
|
12750
|
+
irohInstall.addEventListener("click", () => {
|
|
12751
|
+
if (remoteProviderBusy) return;
|
|
12752
|
+
if (!window.confirm(t("irohInstallConfirm"))) return;
|
|
12753
|
+
remoteProviderBusy = true;
|
|
12754
|
+
irohVerifying = true;
|
|
12755
|
+
irohInstall.disabled = true;
|
|
12756
|
+
irohInstall.textContent = t("installingIroh");
|
|
12757
|
+
irohInstall.setAttribute("aria-busy", "true");
|
|
12758
|
+
irohInstallProgress.hidden = false;
|
|
12759
|
+
irohComponentStatus.textContent = t("installingIroh");
|
|
12760
|
+
remoteStatus.textContent = t("installingIroh");
|
|
12761
|
+
controlRequestJson("/api/mobile-access/remote/iroh/component/install", {
|
|
12762
|
+
method: "POST",
|
|
12763
|
+
body: JSON.stringify({ confirm: true })
|
|
12764
|
+
}, LONG_CONTROL_REQUEST_TIMEOUT_MS).then(renderRemote, (error) => {
|
|
12765
|
+
remoteFailureText = remoteFailureTextFor(error, "installFailed");
|
|
12766
|
+
remoteStatus.textContent = remoteFailureText;
|
|
12767
|
+
irohComponentStatus.textContent = remoteFailureText;
|
|
12768
|
+
}).finally(() => {
|
|
12769
|
+
remoteProviderBusy = false;
|
|
12770
|
+
irohVerifying = false;
|
|
12771
|
+
loadRemote();
|
|
12772
|
+
});
|
|
12773
|
+
});
|
|
12774
|
+
irohPurge.addEventListener("click", () => {
|
|
12775
|
+
if (remoteProviderBusy) return;
|
|
12776
|
+
if (!window.confirm(t("irohPurgeConfirm"))) return;
|
|
12777
|
+
remoteProviderBusy = true;
|
|
12778
|
+
irohPurge.disabled = true;
|
|
12779
|
+
irohComponentStatus.textContent = t("purgingIroh");
|
|
12780
|
+
remoteStatus.textContent = t("purgingIroh");
|
|
12781
|
+
controlRequestJson("/api/mobile-access/remote/iroh/component/purge", {
|
|
12782
|
+
method: "POST",
|
|
12783
|
+
body: JSON.stringify({ confirm: true })
|
|
12784
|
+
}, LONG_CONTROL_REQUEST_TIMEOUT_MS).then(renderRemote, (error) => {
|
|
12785
|
+
remoteFailureText = remoteFailureTextFor(error, "purgeFailed");
|
|
12786
|
+
remoteStatus.textContent = remoteFailureText;
|
|
12787
|
+
irohComponentStatus.textContent = remoteFailureText;
|
|
12788
|
+
}).finally(() => {
|
|
12789
|
+
remoteProviderBusy = false;
|
|
12790
|
+
irohPurge.disabled = false;
|
|
12791
|
+
loadRemote();
|
|
12792
|
+
});
|
|
12793
|
+
});
|
|
12794
|
+
const openIrohQrZoom = () => {
|
|
12795
|
+
if (activeIrohQrSvg === "" || irohQr.hidden) return;
|
|
12796
|
+
irohQrZoomImage.src = `data:image/svg+xml;base64,${btoa(activeIrohQrSvg)}`;
|
|
12797
|
+
setIrohQrZoomOpen(true);
|
|
12798
|
+
irohQrZoomClose.focus();
|
|
12799
|
+
};
|
|
12800
|
+
const closeIrohQrZoom = () => {
|
|
12801
|
+
setIrohQrZoomOpen(false);
|
|
12802
|
+
if (!irohQr.hidden) irohQr.focus();
|
|
12803
|
+
};
|
|
12804
|
+
irohQr.setAttribute("role", "button");
|
|
12805
|
+
irohQr.setAttribute("tabindex", "0");
|
|
12806
|
+
irohQr.setAttribute("aria-label", t("enlargeQr"));
|
|
12807
|
+
irohQr.addEventListener("click", openIrohQrZoom);
|
|
12808
|
+
irohQr.addEventListener("keydown", (event) => {
|
|
12809
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
12810
|
+
event.preventDefault();
|
|
12811
|
+
openIrohQrZoom();
|
|
12812
|
+
});
|
|
12813
|
+
irohQrZoom.addEventListener("click", closeIrohQrZoom);
|
|
12814
|
+
irohQrZoomClose.addEventListener("click", closeIrohQrZoom);
|
|
12815
|
+
irohQrZoom.addEventListener("keydown", (event) => {
|
|
12816
|
+
if (event.key !== "Escape") return;
|
|
12817
|
+
closeIrohQrZoom();
|
|
12818
|
+
});
|
|
12819
|
+
const openIrohPairing = async () => {
|
|
12820
|
+
const generation = remotePairingGeneration;
|
|
12821
|
+
activeIrohDescriptor = "";
|
|
12822
|
+
activeIrohPairExpiresAt = 0;
|
|
12823
|
+
irohCopyDescriptor.disabled = true;
|
|
12824
|
+
const data = await controlRequestJson("/api/mobile-access/remote/iroh/pairing/begin", {
|
|
12825
|
+
method: "POST",
|
|
12826
|
+
body: "{}"
|
|
12827
|
+
});
|
|
12828
|
+
if (lifecycle.signal.aborted || remoteProvider !== "iroh" || !remoteReady || generation !== remotePairingGeneration) return;
|
|
12829
|
+
const record = data;
|
|
12830
|
+
const qrSvg = typeof record.qrSvg === "string" ? record.qrSvg : "";
|
|
12831
|
+
const code = typeof record.irohCode === "string" ? record.irohCode : "";
|
|
12832
|
+
const expiresAt = typeof record.expiresAt === "number" && Number.isFinite(record.expiresAt) ? record.expiresAt : 0;
|
|
12833
|
+
showQr(qrSvg, irohQr);
|
|
12834
|
+
if (qrSvg === "" || code === "" || expiresAt <= Date.now()) {
|
|
12835
|
+
activeIrohQrSvg = "";
|
|
12836
|
+
irohPairStatus.textContent = t("keyGenerationFailed");
|
|
12837
|
+
return;
|
|
12838
|
+
}
|
|
12839
|
+
activeIrohQrSvg = qrSvg;
|
|
12840
|
+
const fields = [
|
|
12841
|
+
"version",
|
|
12842
|
+
"type",
|
|
12843
|
+
"endpointId",
|
|
12844
|
+
"origin",
|
|
12845
|
+
"localPort",
|
|
12846
|
+
"irohCode",
|
|
12847
|
+
"gatewayToken",
|
|
12848
|
+
"caCertificate",
|
|
12849
|
+
"instanceId",
|
|
12850
|
+
"expiresAt"
|
|
12851
|
+
];
|
|
12852
|
+
const descriptor = {};
|
|
12853
|
+
for (const field of fields) descriptor[field] = record[field];
|
|
12854
|
+
activeIrohDescriptor = JSON.stringify(descriptor);
|
|
12855
|
+
activeIrohPairExpiresAt = expiresAt;
|
|
12856
|
+
irohPairStatus.textContent = t("irohPairingReady", { code });
|
|
12857
|
+
};
|
|
12858
|
+
irohPair.addEventListener("click", () => {
|
|
12859
|
+
if (remoteProviderBusy || !remoteReady || remoteProvider !== "iroh") return;
|
|
12860
|
+
remoteProviderBusy = true;
|
|
12861
|
+
remoteSnapshotEpoch += 1;
|
|
12862
|
+
renderRemote(latestRemoteState);
|
|
12863
|
+
irohPair.disabled = true;
|
|
12864
|
+
irohPairStatus.textContent = t("loadingRemoteStatus");
|
|
12865
|
+
openIrohPairing().catch((error) => {
|
|
12866
|
+
remoteFailureText = remoteFailureTextFor(error, "requestFailed");
|
|
12867
|
+
irohPairStatus.textContent = remoteFailureText;
|
|
12868
|
+
}).finally(() => {
|
|
12869
|
+
remoteProviderBusy = false;
|
|
12870
|
+
loadRemote();
|
|
12871
|
+
});
|
|
12872
|
+
});
|
|
12873
|
+
irohCopyDescriptor.addEventListener("click", () => {
|
|
12874
|
+
if (remoteProviderBusy || !remoteReady || remoteProvider !== "iroh" || activeIrohDescriptor === "" || activeIrohPairExpiresAt <= Date.now()) return;
|
|
12875
|
+
navigator.clipboard.writeText(activeIrohDescriptor).then(() => {
|
|
12876
|
+
irohPairStatus.textContent = t("irohLinkCopied");
|
|
12877
|
+
}, () => {
|
|
12878
|
+
irohPairStatus.textContent = t("remoteQrReady");
|
|
12879
|
+
});
|
|
12880
|
+
});
|
|
11912
12881
|
cloudflaredInstall.addEventListener("click", () => {
|
|
11913
12882
|
if (remoteProviderBusy) return;
|
|
11914
12883
|
if (!window.confirm(t("installConfirmCloudflared", { size: cloudflaredDownloadSize }))) return;
|
|
@@ -11945,6 +12914,19 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11945
12914
|
loadRemote();
|
|
11946
12915
|
});
|
|
11947
12916
|
});
|
|
12917
|
+
const syncCloudflaredForm = () => {
|
|
12918
|
+
const named = (cloudflaredModeDraft ?? cloudflaredTunnelMode) === "named";
|
|
12919
|
+
const locked = named && cloudflaredTunnelConfigured && cloudflaredTunnelMode === "named" && !cloudflaredConfigEditing;
|
|
12920
|
+
cloudflaredHostname.disabled = locked || remoteProviderBusy;
|
|
12921
|
+
cloudflaredPort.disabled = locked || remoteProviderBusy;
|
|
12922
|
+
cloudflaredToken.disabled = locked || remoteProviderBusy;
|
|
12923
|
+
cloudflaredEditButton.disabled = remoteProviderBusy;
|
|
12924
|
+
cloudflaredTunnelForget.disabled = remoteProviderBusy;
|
|
12925
|
+
cloudflaredTunnelToggle.hidden = !named;
|
|
12926
|
+
cloudflaredEditButton.hidden = !locked;
|
|
12927
|
+
cloudflaredTunnelSave.hidden = !named || locked;
|
|
12928
|
+
cloudflaredTunnelForget.hidden = !named || cloudflaredTunnelMode !== "named" || cloudflaredConfigEditing;
|
|
12929
|
+
};
|
|
11948
12930
|
const showCloudflaredMode = (mode) => {
|
|
11949
12931
|
cloudflaredModeDraft = mode;
|
|
11950
12932
|
cloudflaredModeQuick.classList.toggle("is-active", mode === "quick");
|
|
@@ -11953,14 +12935,60 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11953
12935
|
cloudflaredModeNamed.setAttribute("aria-pressed", String(mode === "named"));
|
|
11954
12936
|
cloudflaredModeHint.textContent = mode === "named" ? t("cloudflaredTunnelNamedHint") : t("cloudflaredTunnelQuickHint");
|
|
11955
12937
|
cloudflaredNote.textContent = mode === "named" ? t("cloudflaredNamedNote") : t("cloudflaredQuickNote");
|
|
11956
|
-
cloudflaredTunnel.hidden =
|
|
12938
|
+
cloudflaredTunnel.hidden = true;
|
|
12939
|
+
cloudflaredTunnelToggle.textContent = t("cloudflaredShowConfig") + " ▾";
|
|
11957
12940
|
cloudflaredTunnelSave.textContent = cloudflaredSaveLabel();
|
|
12941
|
+
syncCloudflaredForm();
|
|
12942
|
+
if (mode === "named" && cloudflaredTunnelMode !== "named") {
|
|
12943
|
+
remoteToggle.disabled = true;
|
|
12944
|
+
remoteStatus.textContent = t("cloudflaredEnableNeedsConfig");
|
|
12945
|
+
if (remoteStatus.style) remoteStatus.style.display = "";
|
|
12946
|
+
}
|
|
12947
|
+
};
|
|
12948
|
+
cloudflaredTunnelToggle.addEventListener("click", () => {
|
|
12949
|
+
const open = cloudflaredTunnel.hidden;
|
|
12950
|
+
cloudflaredTunnel.hidden = !open;
|
|
12951
|
+
cloudflaredTunnelToggle.textContent = open ? t("cloudflaredHideConfig") + " ▴" : t("cloudflaredShowConfig") + " ▾";
|
|
12952
|
+
});
|
|
12953
|
+
cloudflaredEditButton.addEventListener("click", () => {
|
|
12954
|
+
cloudflaredConfigEditing = true;
|
|
12955
|
+
syncCloudflaredForm();
|
|
12956
|
+
cloudflaredTunnel.hidden = false;
|
|
12957
|
+
cloudflaredTunnelToggle.textContent = t("cloudflaredHideConfig") + " ▴";
|
|
12958
|
+
});
|
|
12959
|
+
const switchCloudflaredMode = (mode) => {
|
|
12960
|
+
if (remoteProviderBusy || (cloudflaredModeDraft ?? cloudflaredTunnelMode) === mode) return;
|
|
12961
|
+
const activeState = typeof latestRemoteState.state === "string" ? latestRemoteState.state : "off";
|
|
12962
|
+
if (remoteRunning || activeState !== "off") {
|
|
12963
|
+
if (!window.confirm(t("cloudflaredSwitchModeDisconnect"))) return;
|
|
12964
|
+
remoteProviderBusy = true;
|
|
12965
|
+
remoteSnapshotEpoch += 1;
|
|
12966
|
+
renderRemote(latestRemoteState);
|
|
12967
|
+
controlRequestJson("/api/mobile-access/remote/control", {
|
|
12968
|
+
method: "POST",
|
|
12969
|
+
body: JSON.stringify({
|
|
12970
|
+
running: false,
|
|
12971
|
+
provider: "cloudflared"
|
|
12972
|
+
})
|
|
12973
|
+
}).then((data) => {
|
|
12974
|
+
renderRemote(data);
|
|
12975
|
+
showCloudflaredMode(mode);
|
|
12976
|
+
}, (error) => {
|
|
12977
|
+
remoteFailureText = remoteFailureTextFor(error, "requestFailed");
|
|
12978
|
+
remoteStatus.textContent = remoteFailureText;
|
|
12979
|
+
}).finally(() => {
|
|
12980
|
+
remoteProviderBusy = false;
|
|
12981
|
+
loadRemote();
|
|
12982
|
+
});
|
|
12983
|
+
return;
|
|
12984
|
+
}
|
|
12985
|
+
showCloudflaredMode(mode);
|
|
11958
12986
|
};
|
|
11959
12987
|
cloudflaredModeQuick.addEventListener("click", () => {
|
|
11960
|
-
|
|
12988
|
+
switchCloudflaredMode("quick");
|
|
11961
12989
|
});
|
|
11962
12990
|
cloudflaredModeNamed.addEventListener("click", () => {
|
|
11963
|
-
|
|
12991
|
+
switchCloudflaredMode("named");
|
|
11964
12992
|
});
|
|
11965
12993
|
cloudflaredHostname.addEventListener("input", () => {
|
|
11966
12994
|
cloudflaredHostnameDirty = true;
|
|
@@ -11970,6 +12998,11 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11970
12998
|
});
|
|
11971
12999
|
cloudflaredTunnelSave.addEventListener("click", () => {
|
|
11972
13000
|
if (remoteProviderBusy) return;
|
|
13001
|
+
const report = (message) => {
|
|
13002
|
+
remoteStatus.textContent = message;
|
|
13003
|
+
cloudflaredTunnelFeedback.hidden = false;
|
|
13004
|
+
cloudflaredTunnelFeedback.textContent = message;
|
|
13005
|
+
};
|
|
11973
13006
|
const mode = cloudflaredModeDraft ?? cloudflaredTunnelMode;
|
|
11974
13007
|
const hostname = String(cloudflaredHostname.value ?? "").trim();
|
|
11975
13008
|
const portValue = String(cloudflaredPort.value ?? "").trim();
|
|
@@ -11977,48 +13010,57 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11977
13010
|
const token = String(cloudflaredToken.value ?? "");
|
|
11978
13011
|
if (mode === "named") {
|
|
11979
13012
|
if (hostname === "") {
|
|
11980
|
-
|
|
13013
|
+
report(t("cloudflaredTunnelHostnameInvalid"));
|
|
11981
13014
|
return;
|
|
11982
13015
|
}
|
|
11983
13016
|
if (portValue === "" || !Number.isSafeInteger(port) || port < 1024 || port > 65535) {
|
|
11984
|
-
|
|
13017
|
+
report(t("cloudflaredTunnelPortInvalid"));
|
|
11985
13018
|
return;
|
|
11986
13019
|
}
|
|
11987
13020
|
if (port === 3443) {
|
|
11988
|
-
|
|
13021
|
+
report(t("cloudflaredTunnelPortReserved"));
|
|
11989
13022
|
return;
|
|
11990
13023
|
}
|
|
11991
13024
|
if (token === "" && !cloudflaredTunnelConfigured) {
|
|
11992
|
-
|
|
13025
|
+
report(t("cloudflaredTunnelConfigMissing"));
|
|
11993
13026
|
return;
|
|
11994
13027
|
}
|
|
11995
13028
|
if (token !== "" && (token.length < 32 || !/^[A-Za-z0-9_=+/-]+$/u.test(token))) {
|
|
11996
|
-
|
|
13029
|
+
report(t("cloudflaredTunnelTokenInvalid"));
|
|
11997
13030
|
return;
|
|
11998
13031
|
}
|
|
11999
13032
|
}
|
|
12000
13033
|
remoteProviderBusy = true;
|
|
13034
|
+
remoteSnapshotEpoch += 1;
|
|
13035
|
+
renderRemote(latestRemoteState);
|
|
12001
13036
|
cloudflaredTunnelSave.disabled = true;
|
|
12002
13037
|
cloudflaredTunnelSave.textContent = t("saving");
|
|
12003
|
-
|
|
13038
|
+
report(mode === "named" ? t("cloudflaredTunnelSaving") : t("switchingCloudflared"));
|
|
12004
13039
|
controlRequestJson("/api/mobile-access/remote/cloudflared/tunnel", {
|
|
12005
13040
|
method: "POST",
|
|
12006
13041
|
body: JSON.stringify(mode === "named" ? {
|
|
12007
13042
|
mode,
|
|
12008
13043
|
hostname,
|
|
12009
13044
|
port,
|
|
12010
|
-
token
|
|
12011
|
-
|
|
13045
|
+
token,
|
|
13046
|
+
connect: true
|
|
13047
|
+
} : {
|
|
13048
|
+
mode: "quick",
|
|
13049
|
+
connect: true
|
|
13050
|
+
})
|
|
12012
13051
|
}).then((data) => {
|
|
12013
13052
|
cloudflaredToken.value = "";
|
|
12014
13053
|
cloudflaredModeDraft = null;
|
|
13054
|
+
cloudflaredConfigEditing = false;
|
|
12015
13055
|
cloudflaredHostnameDirty = false;
|
|
12016
13056
|
cloudflaredPortDirty = false;
|
|
12017
13057
|
remoteFailureText = "";
|
|
13058
|
+
cloudflaredTunnelFeedback.hidden = false;
|
|
13059
|
+
cloudflaredTunnelFeedback.textContent = t("cloudflaredTunnelSaved");
|
|
12018
13060
|
renderRemote(data);
|
|
12019
13061
|
}, (error) => {
|
|
12020
13062
|
remoteFailureText = remoteFailureTextFor(error, "requestFailed");
|
|
12021
|
-
|
|
13063
|
+
report(remoteFailureText);
|
|
12022
13064
|
}).finally(() => {
|
|
12023
13065
|
remoteProviderBusy = false;
|
|
12024
13066
|
cloudflaredTunnelSave.disabled = false;
|
|
@@ -12883,14 +13925,35 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
12883
13925
|
});
|
|
12884
13926
|
});
|
|
12885
13927
|
remoteToggle.addEventListener("click", () => {
|
|
12886
|
-
|
|
12887
|
-
|
|
13928
|
+
if (remoteProviderBusy) return;
|
|
13929
|
+
const nextRunning = !remoteRunning;
|
|
13930
|
+
const provider = remoteProvider;
|
|
13931
|
+
remoteProviderBusy = true;
|
|
13932
|
+
remoteSnapshotEpoch += 1;
|
|
13933
|
+
renderRemote(latestRemoteState);
|
|
13934
|
+
(nextRunning && provider === "cloudflared" && cloudflaredModeDraft !== null && cloudflaredModeDraft !== cloudflaredTunnelMode ? controlRequestJson("/api/mobile-access/remote/cloudflared/tunnel", {
|
|
12888
13935
|
method: "POST",
|
|
12889
|
-
body: JSON.stringify({
|
|
12890
|
-
|
|
13936
|
+
body: JSON.stringify({
|
|
13937
|
+
mode: cloudflaredModeDraft,
|
|
13938
|
+
connect: true
|
|
13939
|
+
})
|
|
13940
|
+
}) : controlRequestJson("/api/mobile-access/remote/control", {
|
|
13941
|
+
method: "POST",
|
|
13942
|
+
body: JSON.stringify({
|
|
13943
|
+
running: nextRunning,
|
|
13944
|
+
provider
|
|
13945
|
+
})
|
|
13946
|
+
})).then(renderRemote, (error) => {
|
|
12891
13947
|
remoteFailureText = remoteFailureTextFor(error, "requestFailed");
|
|
12892
13948
|
remoteStatus.textContent = remoteFailureText;
|
|
12893
|
-
|
|
13949
|
+
if (remoteProvider === "cloudflared") {
|
|
13950
|
+
cloudflaredTunnelFeedback.hidden = false;
|
|
13951
|
+
cloudflaredTunnelFeedback.textContent = remoteFailureText;
|
|
13952
|
+
}
|
|
13953
|
+
}).finally(() => {
|
|
13954
|
+
remoteProviderBusy = false;
|
|
13955
|
+
loadRemote();
|
|
13956
|
+
});
|
|
12894
13957
|
});
|
|
12895
13958
|
remoteLogin.addEventListener("click", () => {
|
|
12896
13959
|
if (remoteLoginUrl !== "") window.open(remoteLoginUrl, "_blank", "noopener,noreferrer");
|
|
@@ -12901,7 +13964,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
12901
13964
|
remoteReconnect.disabled = true;
|
|
12902
13965
|
remoteSetup.disabled = true;
|
|
12903
13966
|
remoteSetupRetry.disabled = true;
|
|
12904
|
-
remoteStatus.textContent = remoteProvider === "cpolar" ? t("reconnectingCpolar") : remoteProvider === "cloudflared" ? t("reconnectingCloudflared") : remoteProvider === "frp" ? t("reconnectingFrp") : remoteProvider === "origin" ? t("reconnectingOrigin") : t("reconnectingTailscale");
|
|
13967
|
+
remoteStatus.textContent = remoteProvider === "cpolar" ? t("reconnectingCpolar") : remoteProvider === "cloudflared" ? t("reconnectingCloudflared") : remoteProvider === "frp" ? t("reconnectingFrp") : remoteProvider === "origin" ? t("reconnectingOrigin") : remoteProvider === "iroh" ? t("reconnectingIroh") : t("reconnectingTailscale");
|
|
12905
13968
|
controlRequestJson("/api/mobile-access/remote/reconnect", {
|
|
12906
13969
|
method: "POST",
|
|
12907
13970
|
body: "{}"
|
|
@@ -12942,40 +14005,64 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
12942
14005
|
remoteStatus.textContent = t("remoteQrReady");
|
|
12943
14006
|
}
|
|
12944
14007
|
};
|
|
12945
|
-
const openRemotePairing = () =>
|
|
12946
|
-
|
|
12947
|
-
|
|
12948
|
-
|
|
12949
|
-
|
|
12950
|
-
|
|
12951
|
-
|
|
12952
|
-
|
|
12953
|
-
|
|
12954
|
-
|
|
12955
|
-
|
|
12956
|
-
|
|
12957
|
-
|
|
12958
|
-
|
|
12959
|
-
|
|
12960
|
-
|
|
12961
|
-
|
|
12962
|
-
|
|
14008
|
+
const openRemotePairing = () => {
|
|
14009
|
+
const provider = remoteProvider;
|
|
14010
|
+
const origin = latestRemoteState.origin;
|
|
14011
|
+
const generation = remotePairingGeneration;
|
|
14012
|
+
activeRemotePairUrl = "";
|
|
14013
|
+
activeRemotePairExpiresAt = 0;
|
|
14014
|
+
pairingValidity.hidden = true;
|
|
14015
|
+
remotePairLink.hidden = true;
|
|
14016
|
+
remotePairLink.textContent = "";
|
|
14017
|
+
showQr("", remoteQr);
|
|
14018
|
+
return controlRequestJson("/api/mobile-access/remote/pairing/open", {
|
|
14019
|
+
method: "POST",
|
|
14020
|
+
body: "{}"
|
|
14021
|
+
}).then(async (data) => {
|
|
14022
|
+
if (lifecycle.signal.aborted || !remoteReady || provider !== remoteProvider || origin !== latestRemoteState.origin || generation !== remotePairingGeneration) return;
|
|
14023
|
+
const pairUrl = typeof data.pairUrl === "string" ? data.pairUrl : "";
|
|
14024
|
+
const expiresAt = typeof data.expiresAt === "number" && Number.isFinite(data.expiresAt) ? data.expiresAt : 0;
|
|
14025
|
+
if (pairUrl === "" || expiresAt <= Date.now()) {
|
|
14026
|
+
remoteStatus.textContent = t("keyGenerationFailed");
|
|
14027
|
+
return;
|
|
14028
|
+
}
|
|
14029
|
+
showQr(typeof data.qrSvg === "string" ? data.qrSvg : "", remoteQr);
|
|
14030
|
+
activeRemotePairUrl = pairUrl;
|
|
14031
|
+
activeRemotePairExpiresAt = expiresAt;
|
|
14032
|
+
pairingValidity.hidden = false;
|
|
14033
|
+
pairingValidity.textContent = t("pairingExpires", { time: formatTime(expiresAt) });
|
|
14034
|
+
remotePairLink.textContent = pairUrl;
|
|
14035
|
+
remotePairLink.classList.add("is-key");
|
|
14036
|
+
remotePairLink.hidden = false;
|
|
14037
|
+
await copyRemotePairUrl();
|
|
14038
|
+
});
|
|
14039
|
+
};
|
|
12963
14040
|
remotePair.addEventListener("click", () => {
|
|
14041
|
+
if (!remoteReady || remoteProviderBusy || remotePairingBusy) return;
|
|
14042
|
+
remotePairingBusy = true;
|
|
12964
14043
|
remotePair.disabled = true;
|
|
14044
|
+
remoteCopyLink.disabled = true;
|
|
12965
14045
|
openRemotePairing().catch((error) => {
|
|
12966
14046
|
remoteFailureText = remoteFailureTextFor(error, "requestFailed");
|
|
12967
14047
|
remoteStatus.textContent = remoteFailureText;
|
|
12968
14048
|
}).finally(() => {
|
|
12969
|
-
|
|
14049
|
+
remotePairingBusy = false;
|
|
14050
|
+
remotePair.disabled = !remoteReady || remoteProviderBusy;
|
|
14051
|
+
remoteCopyLink.disabled = !remoteReady || remoteProviderBusy;
|
|
12970
14052
|
});
|
|
12971
14053
|
});
|
|
12972
14054
|
remoteCopyLink.addEventListener("click", () => {
|
|
14055
|
+
if (!remoteReady || remoteProviderBusy || remotePairingBusy) return;
|
|
14056
|
+
remotePairingBusy = true;
|
|
14057
|
+
remotePair.disabled = true;
|
|
12973
14058
|
remoteCopyLink.disabled = true;
|
|
12974
14059
|
(activeRemotePairUrl !== "" && activeRemotePairExpiresAt > Date.now() ? copyRemotePairUrl() : openRemotePairing()).catch((error) => {
|
|
12975
14060
|
remoteFailureText = remoteFailureTextFor(error, "requestFailed");
|
|
12976
14061
|
remoteStatus.textContent = remoteFailureText;
|
|
12977
14062
|
}).finally(() => {
|
|
12978
|
-
|
|
14063
|
+
remotePairingBusy = false;
|
|
14064
|
+
remotePair.disabled = !remoteReady || remoteProviderBusy;
|
|
14065
|
+
remoteCopyLink.disabled = !remoteReady || remoteProviderBusy;
|
|
12979
14066
|
});
|
|
12980
14067
|
});
|
|
12981
14068
|
const renderRemoteDevices = (data) => {
|
|
@@ -13014,6 +14101,10 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
13014
14101
|
controlRequestJson("/api/mobile-access/remote/devices").then(renderRemoteDevices, (error) => {
|
|
13015
14102
|
remoteFailureText = remoteFailureTextFor(error, "requestFailed");
|
|
13016
14103
|
remoteStatus.textContent = remoteFailureText;
|
|
14104
|
+
remoteDevicePanel.replaceChildren();
|
|
14105
|
+
const note = element("p", "dsh-mobile-control__device-empty");
|
|
14106
|
+
note.textContent = remoteRunning ? t("noRemoteDevices") : t("remoteOff");
|
|
14107
|
+
remoteDevicePanel.append(note);
|
|
13017
14108
|
});
|
|
13018
14109
|
};
|
|
13019
14110
|
remoteDevices.addEventListener("click", () => {
|
|
@@ -13022,7 +14113,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
13022
14113
|
if (show) loadRemoteDevices();
|
|
13023
14114
|
});
|
|
13024
14115
|
remoteReset.addEventListener("click", () => {
|
|
13025
|
-
const prompt = remoteProvider === "cpolar" ? t("resetCpolarConfirm") : remoteProvider === "cloudflared" ? t("resetCloudflaredConfirm") : remoteProvider === "frp" ? t("resetFrpConfirm") : remoteProvider === "origin" ? t("resetOriginConfirm") : t("resetTailscaleConfirm");
|
|
14116
|
+
const prompt = remoteProvider === "cpolar" ? t("resetCpolarConfirm") : remoteProvider === "cloudflared" ? t("resetCloudflaredConfirm") : remoteProvider === "frp" ? t("resetFrpConfirm") : remoteProvider === "origin" ? t("resetOriginConfirm") : remoteProvider === "iroh" ? t("resetIrohConfirm") : t("resetTailscaleConfirm");
|
|
13026
14117
|
if (!window.confirm(prompt)) return;
|
|
13027
14118
|
controlRequestJson("/api/mobile-access/remote/reset", {
|
|
13028
14119
|
method: "POST",
|
|
@@ -13172,6 +14263,14 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
13172
14263
|
selectView("diagnostics");
|
|
13173
14264
|
loadDiagnostics();
|
|
13174
14265
|
});
|
|
14266
|
+
devicesEntry.addEventListener("click", () => {
|
|
14267
|
+
if (!devicesView.hidden) {
|
|
14268
|
+
selectView(previousAccessView);
|
|
14269
|
+
return;
|
|
14270
|
+
}
|
|
14271
|
+
selectView("devices");
|
|
14272
|
+
showDevicesGroup("lan");
|
|
14273
|
+
});
|
|
13175
14274
|
diagnosticsRun.addEventListener("click", loadDiagnostics);
|
|
13176
14275
|
diagnosticsRun.addEventListener("click", () => {
|
|
13177
14276
|
extensionRecovery.refresh();
|
|
@@ -13232,11 +14331,9 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
13232
14331
|
});
|
|
13233
14332
|
});
|
|
13234
14333
|
pair.addEventListener("click", () => {
|
|
13235
|
-
pair.disabled = true;
|
|
13236
14334
|
openPairing("key");
|
|
13237
14335
|
});
|
|
13238
14336
|
linkPair.addEventListener("click", () => {
|
|
13239
|
-
linkPair.disabled = true;
|
|
13240
14337
|
openPairing("link");
|
|
13241
14338
|
});
|
|
13242
14339
|
close.addEventListener("click", () => {
|
|
@@ -13244,12 +14341,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
13244
14341
|
});
|
|
13245
14342
|
const dismiss = (event) => {
|
|
13246
14343
|
if (panel.hidden || !(event.target instanceof Node)) return;
|
|
13247
|
-
if (!
|
|
13248
|
-
providerInfoPinned = false;
|
|
13249
|
-
providerInfoHovered = false;
|
|
13250
|
-
syncProviderInfo();
|
|
13251
|
-
}
|
|
13252
|
-
if (!panel.contains(event.target) && !document.querySelector(".dsh-mobile-control__trigger")?.contains(event.target)) setOpen(false);
|
|
14344
|
+
if (!panel.contains(event.target) && !irohQrZoom.contains(event.target) && !document.querySelector(".dsh-mobile-control__trigger")?.contains(event.target)) setOpen(false);
|
|
13253
14345
|
};
|
|
13254
14346
|
document.addEventListener("pointerdown", dismiss);
|
|
13255
14347
|
controlRequestJson("/api/mobile-access/release").then(renderRelease, () => {});
|
|
@@ -13258,7 +14350,10 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
13258
14350
|
});
|
|
13259
14351
|
loadRemote();
|
|
13260
14352
|
const remotePoll = window.setInterval(() => {
|
|
13261
|
-
if (!panel.hidden && !remoteView.hidden)
|
|
14353
|
+
if (!panel.hidden && !remoteView.hidden) {
|
|
14354
|
+
renderLanPairingExpiry();
|
|
14355
|
+
loadRemote();
|
|
14356
|
+
}
|
|
13262
14357
|
}, 1500);
|
|
13263
14358
|
const pollWsBlocked = () => {
|
|
13264
14359
|
controlRequestJson("/api/mobile-access/remote/websocket-paths/blocked").then((blocked) => {
|
|
@@ -13821,10 +14916,28 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
13821
14916
|
});
|
|
13822
14917
|
}
|
|
13823
14918
|
};
|
|
13824
|
-
/**
|
|
13825
|
-
|
|
14919
|
+
/**
|
|
14920
|
+
* Install authenticated customization refresh, retaining assets through temporary failures.
|
|
14921
|
+
* @param recovery - optional instance-owned authentication feedback and refresh registration.
|
|
14922
|
+
* @returns synchronous teardown for the customization UI and refresh admission.
|
|
14923
|
+
*/
|
|
14924
|
+
function installCustomAssets(recovery) {
|
|
13826
14925
|
const reporter = createMobileAssetLoadReporter(selectedMobileControlLocale);
|
|
14926
|
+
let refreshNow = () => {};
|
|
13827
14927
|
const customizationOwner = ownsMobileCustomization(document.querySelector("meta[name=\"dsh-mobile-gateway\"][content=\"authenticated\"]") !== null, window.__DSH_MOBILE_FRONTEND__);
|
|
14928
|
+
const removeNotice = customizationOwner ? installCustomizationNotice(document, {
|
|
14929
|
+
locale: () => {
|
|
14930
|
+
const locale = selectedMobileControlLocale();
|
|
14931
|
+
return locale === "zh" || locale === "it" ? locale : "en";
|
|
14932
|
+
},
|
|
14933
|
+
reconnect: () => {
|
|
14934
|
+
const attempted = recovery?.retry() ?? false;
|
|
14935
|
+
refreshNow();
|
|
14936
|
+
return attempted;
|
|
14937
|
+
},
|
|
14938
|
+
subscribe: (listener) => reporter.subscribe(listener),
|
|
14939
|
+
message: (reason) => reporter.message(reason)
|
|
14940
|
+
}) : () => {};
|
|
13828
14941
|
const legacyStyle = element("style");
|
|
13829
14942
|
legacyStyle.dataset.plugin = "dsh-mobile-custom";
|
|
13830
14943
|
document.head.append(legacyStyle);
|
|
@@ -13856,7 +14969,9 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
13856
14969
|
if (reason === "authentication_required") events?.setEnabled(false);
|
|
13857
14970
|
};
|
|
13858
14971
|
const unavailable = (response, signal, fallback) => {
|
|
13859
|
-
|
|
14972
|
+
const rejectedAuth = response.status === 401 || response.status === 403;
|
|
14973
|
+
failed(rejectedAuth ? "authentication_required" : fallback, signal);
|
|
14974
|
+
if (rejectedAuth && !disposed && !signal.aborted) recovery?.rejected(response.status);
|
|
13860
14975
|
response.body?.cancel().catch((error) => {});
|
|
13861
14976
|
};
|
|
13862
14977
|
let expectedDefinitionId;
|
|
@@ -14416,6 +15531,10 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
14416
15531
|
events?.setEnabled(true);
|
|
14417
15532
|
}
|
|
14418
15533
|
});
|
|
15534
|
+
refreshNow = () => {
|
|
15535
|
+
stopRefresh.refresh();
|
|
15536
|
+
};
|
|
15537
|
+
const unregisterRefresh = recovery?.registerRefresh(refreshNow);
|
|
14419
15538
|
const stopEvents = startExtensionChangeStream(() => {
|
|
14420
15539
|
stopRefresh.refresh();
|
|
14421
15540
|
}, void 0, (payload) => {
|
|
@@ -14436,6 +15555,8 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
14436
15555
|
events = stopEvents;
|
|
14437
15556
|
return () => {
|
|
14438
15557
|
disposed = true;
|
|
15558
|
+
unregisterRefresh?.();
|
|
15559
|
+
removeNotice();
|
|
14439
15560
|
reporter.dispose();
|
|
14440
15561
|
stopNativeBack();
|
|
14441
15562
|
stopEvents();
|
|
@@ -14495,9 +15616,31 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
14495
15616
|
/** Theme-aware desktop Mobile Access panel styles. */
|
|
14496
15617
|
const CONTROL_STYLES = `
|
|
14497
15618
|
.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)}
|
|
14498
|
-
.dsh-mobile-control__panel{box-sizing:border-box;width:min(
|
|
15619
|
+
.dsh-mobile-control__panel{box-sizing:border-box;width:min(704px,calc(100vw - 32px));max-height:calc(100vh - 140px);overflow-y:auto;padding:16px;border:0;border-radius:16px;background:var(--dsw-alias-bg-layer-2,#fff);box-shadow:0 12px 34px rgb(15 23 42 / 13%)}.dsh-mobile-control__remote-columns{display:grid;grid-template-columns:minmax(230px,300px) minmax(0,1fr);gap:14px;align-items:start;max-height:calc(100vh - 330px)}
|
|
15620
|
+
/* Floating panel with dense, borderless content. */
|
|
15621
|
+
.dsh-mobile-control{inset:auto;left:16px;bottom:112px;pointer-events:none}
|
|
15622
|
+
.dsh-mobile-control__panel{position:relative;inset:auto;width:min(704px,calc(100vw - 32px));height:auto;max-height:calc(100vh - 140px);border:0;border-radius:16px;box-shadow:0 12px 34px rgb(15 23 42 / 13%);display:flex;flex-direction:column;overflow:hidden;pointer-events:auto;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__panel[hidden]{display:none}
|
|
15623
|
+
.dsh-mobile-control__header{flex:none;margin:0;padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l2,#e1e5eb)}
|
|
15624
|
+
.dsh-mobile-control__app-downloads{flex:none;padding:8px 16px;border-bottom:1px solid var(--dsw-alias-border-l2,#e1e5eb);margin:0}
|
|
15625
|
+
.dsh-mobile-control__view.is-remote{flex:1;min-height:0;display:flex;flex-direction:column}
|
|
15626
|
+
.dsh-mobile-control__remote-columns{flex:1;min-height:0;display:grid;grid-template-columns:260px minmax(0,1fr);gap:0;max-height:none;align-items:stretch}
|
|
15627
|
+
.dsh-mobile-control__remote-columns>.dsh-mobile-control__provider-section{overflow-y:auto;border-right:1px solid var(--dsw-alias-border-l2,#e1e5eb);background:var(--dsw-alias-bg-layer-1,#f7f8fa);padding:10px 8px}
|
|
15628
|
+
.dsh-mobile-control__channel-pane{overflow-y:auto;padding:14px 20px;min-width:0;scrollbar-gutter:stable}
|
|
15629
|
+
.dsh-mobile-control__provider{position:relative;border:0;border-radius:8px;padding:8px 10px;min-height:0;background:transparent;box-shadow:none}
|
|
15630
|
+
.dsh-mobile-control__provider:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f5f9)}
|
|
15631
|
+
.dsh-mobile-control__provider.is-selected{background:var(--dsw-alias-interactive-bg-active,#f5f8ff);box-shadow:none}
|
|
15632
|
+
.dsh-mobile-control__provider.is-selected::before{content:"";position:absolute;left:0;top:8px;bottom:8px;width:2px;border-radius:2px;background:var(--dsw-alias-label-primary-bluish,#2563eb)}
|
|
15633
|
+
.dsh-mobile-control__remote-workspace section{border:0;border-radius:0;padding:12px 0;background:transparent;box-shadow:none;border-top:1px solid var(--dsw-alias-border-l2,#e1e5eb)}
|
|
15634
|
+
.dsh-mobile-control__remote-workspace section:first-child{border-top:0}
|
|
15635
|
+
.dsh-mobile-control__remote-workspace .dsh-mobile-control__details{border:0;border-radius:0;background:transparent;box-shadow:none}
|
|
15636
|
+
.dsh-mobile-control__btn,.dsh-mobile-control__primary,.dsh-mobile-control__secondary{min-height:32px;padding:6px 12px;border-radius:8px;font-size:12px}
|
|
15637
|
+
.dsh-mobile-control__self-hosted{border:0;border-radius:0;background:transparent;box-shadow:none}
|
|
15638
|
+
.dsh-mobile-control__self-hosted-summary{border-bottom:1px solid var(--dsw-alias-border-l2,#e1e5eb)}
|
|
15639
|
+
.dsh-mobile-control__self-hosted-body{padding:0}
|
|
15640
|
+
@media (max-width:860px){.dsh-mobile-control__remote-columns{grid-template-columns:1fr}.dsh-mobile-control__remote-columns>.dsh-mobile-control__provider-section{border-right:0;border-bottom:1px solid var(--dsw-alias-border-l2,#e1e5eb);max-height:40vh}}
|
|
15641
|
+
.dsh-mobile-control__channel-pane{min-width:0;overflow-y:auto;padding-right:4px;max-height:100%}.dsh-mobile-control__channel-pane>.dsh-mobile-control__view{display:block}.dsh-mobile-control__provider.is-lan{border-color:var(--dsw-alias-label-primary-bluish,#2563eb);background:var(--dsw-alias-interactive-bg-active,var(--dsw-alias-bg-layer-1,#f5f8ff))}.dsh-mobile-control__remote-columns>.dsh-mobile-control__provider-section{overflow-y:auto;padding-right:4px}.dsh-mobile-control__remote-columns>.dsh-mobile-control__remote-workspace{overflow-y:auto;padding-right:4px}.dsh-mobile-control__remote-columns>.dsh-mobile-control__provider-section{margin:0}@media (max-width:760px){.dsh-mobile-control__remote-columns{grid-template-columns:1fr}}
|
|
14499
15642
|
.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)}
|
|
14500
|
-
.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)}
|
|
15643
|
+
.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__primary{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;min-height:40px;padding:9px 14px;border:1px solid var(--dsw-alias-label-primary-bluish,#2563eb);border-radius:10px;background:var(--dsw-alias-label-primary-bluish,#2563eb);color:#fff;font:600 12px/1.3 system-ui;cursor:pointer;touch-action:manipulation}.dsh-mobile-control__primary:hover:not(:disabled){border-color:#1d4ed8;background:#1d4ed8}.dsh-mobile-control__primary:active:not(:disabled){border-color:#1e40af;background:#1e40af}.dsh-mobile-control__primary:disabled{cursor:wait;opacity:.55}.dsh-mobile-control__primary[hidden]{display:none}.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-downloads{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 10px}.dsh-mobile-control__app-downloads .dsh-mobile-control__app-download{flex:1 1 240px;margin:0}.dsh-mobile-control__app-download.is-iroh{border-color:var(--dsw-alias-label-primary-bluish,#2563eb);color:var(--dsw-alias-label-primary-bluish,#2563eb)}.dsh-mobile-control__app-download.is-iroh[hidden]{display: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)}
|
|
14501
15644
|
.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}
|
|
14502
15645
|
.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}
|
|
14503
15646
|
.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)}
|
|
@@ -14506,12 +15649,12 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
14506
15649
|
/* The cloudflared setup section deliberately reuses this card, so
|
|
14507
15650
|
.dsh-mobile-control__cloudflared-setup is a DOM hook for targeting rather than a
|
|
14508
15651
|
style rule of its own. */
|
|
14509
|
-
.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}
|
|
15652
|
+
.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__progress{box-sizing:border-box;width:100%;height:6px;margin:0 0 10px}.dsh-mobile-control__progress[hidden]{display:none}.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}
|
|
14510
15653
|
.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%))}
|
|
14511
15654
|
.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__frp-fields>.dsh-mobile-control__field:nth-child(5),.dsh-mobile-control__frp-fields>.dsh-mobile-control__field:nth-child(6){grid-column:1/-1}.dsh-mobile-control__frp-fields>.dsh-mobile-control__field[hidden]{display:none}.dsh-mobile-control__frp-fields 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__frp-fields select:disabled{cursor:not-allowed;opacity:.55}.dsh-mobile-control__frp-hint{margin:0;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.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)}
|
|
14512
15655
|
.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}
|
|
14513
15656
|
.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}
|
|
14514
|
-
.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}
|
|
15657
|
+
.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}.dsh-mobile-control__qr.is-expandable{cursor:zoom-in}.dsh-mobile-control__qr.is-expandable:focus-visible{outline:3px solid var(--dsw-alias-state-business-primary,#2563eb);outline-offset:2px;border-radius:12px}.dsh-mobile-control__qr-zoom{position:fixed;inset:0;z-index:1001;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.72)}.dsh-mobile-control__qr-zoom[hidden]{display:none}.dsh-mobile-control__qr-zoom-image{width:min(84vw,360px);height:min(84vw,360px);border-radius:12px;background:#fff;padding:12px}.dsh-mobile-control__qr-zoom-close{position:absolute;top:12px;right:16px;min-width:44px;min-height:44px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);font:600 18px/1 system-ui;cursor:pointer}
|
|
14515
15658
|
.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}
|
|
14516
15659
|
.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}
|
|
14517
15660
|
.dsh-mobile-control__extensions{margin:0 0 12px;color:var(--dsw-alias-label-secondary,#606873);font-size:12px}
|
|
@@ -14521,7 +15664,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
14521
15664
|
.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}
|
|
14522
15665
|
.dsh-mobile-control__diagnostic-checks{transition:opacity 150ms ease}.dsh-mobile-control__diagnostic-checks.is-refreshing{opacity:.52}
|
|
14523
15666
|
@keyframes dsh-diagnostic-spin{to{transform:rotate(360deg)}}@keyframes dsh-diagnostic-reveal{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}
|
|
14524
|
-
.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}
|
|
15667
|
+
.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{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;min-height:40px;padding:9px 14px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:10px;background:transparent;color:inherit;font:600 12px/1.3 system-ui;cursor:pointer;touch-action:manipulation}.dsh-mobile-control__secondary:hover:not(:disabled){border-color:#6f96db;background:var(--dsw-alias-interactive-bg-hover-solid,var(--dsw-alias-bg-layer-1,#f8fbff))}.dsh-mobile-control__secondary:disabled{cursor:wait;opacity:.55}.dsh-mobile-control__secondary[hidden]{display:none}.dsh-mobile-control__primary{border:1px solid #2563eb;background:#2563eb;color:#fff}.dsh-mobile-control__actions button:disabled{cursor:not-allowed;opacity:.45}
|
|
14525
15668
|
.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}
|
|
14526
15669
|
/* 42px matches the DSH sidebar footer row; it is not one of the panel target tiers. */
|
|
14527
15670
|
/* Other footer entries share the shell's row; only our own action becomes compact. */
|
|
@@ -14535,9 +15678,71 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
14535
15678
|
.dsh-mobile-control__origin-modes{min-width:0;margin:0 0 14px;padding:0;border:0}.dsh-mobile-control__origin-modes legend{margin:0 0 8px;padding:0;color:var(--dsw-alias-label-secondary,#606873);font:600 12px/1.4 system-ui}.dsh-mobile-control__origin-mode{display:flex;box-sizing:border-box;min-height:44px;align-items:center;gap:10px;padding:10px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:10px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);font:12px/1.5 system-ui;cursor:pointer}.dsh-mobile-control__origin-mode+.dsh-mobile-control__origin-mode{margin-top:8px}.dsh-mobile-control__origin-mode:has(input:checked){border-color:var(--dsw-alias-label-primary-bluish,#2563eb)}.dsh-mobile-control__origin-mode:focus-within{outline:2px solid var(--dsw-alias-state-business-primary,#2563eb);outline-offset:2px}.dsh-mobile-control__origin-mode:has(input:disabled){cursor:wait;opacity:.55}.dsh-mobile-control__origin-mode input[type=radio]{flex:0 0 18px;box-sizing:border-box;width:18px;height:18px;min-height:18px;margin:0;padding:0;accent-color:var(--dsw-alias-label-primary-bluish,#2563eb)}.dsh-mobile-control__origin-mode span{min-width:0;overflow-wrap:anywhere}
|
|
14536
15679
|
/* Base the columns on the card width, not the desktop viewport behind the popup. */
|
|
14537
15680
|
.dsh-mobile-control__origin-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,140px),1fr));gap:12px}.dsh-mobile-control__origin-fields>.dsh-mobile-control__field{min-width:0;grid-column:auto;font-size:12px}.dsh-mobile-control__origin-fields>.dsh-mobile-control__field--full{grid-column:1/-1}.dsh-mobile-control__origin-fields select{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__origin-fields select:disabled{cursor:not-allowed;opacity:.55}.dsh-mobile-control__caddy-form{display:grid;gap:12px}.dsh-mobile-control__caddy-form>.dsh-mobile-control__component-note,.dsh-mobile-control__caddy-form>.dsh-mobile-control__component-status,.dsh-mobile-control__caddy-form>.dsh-mobile-control__origin-feedback{margin:0;overflow-wrap:anywhere}.dsh-mobile-control__origin-setup .dsh-mobile-control__actions.dsh-mobile-control__caddy-actions{grid-template-columns:minmax(0,1fr);margin:0}.dsh-mobile-control__caddy-actions button{box-sizing:border-box;min-width:0;min-height:44px;padding:10px 12px;border-radius:10px;font:600 12px/1.4 system-ui;cursor:pointer}.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}
|
|
15681
|
+
/* Final overrides: dense borderless layout must beat the base rules above.
|
|
15682
|
+
The earlier dense block sits before those base rules, so these repeat the
|
|
15683
|
+
essential de-boxing here at the end of the sheet where they win. */
|
|
15684
|
+
.dsh-mobile-control__provider{position:relative;border:0;border-radius:8px;padding:8px 10px;min-height:0;background:transparent;box-shadow:none}
|
|
15685
|
+
.dsh-mobile-control__provider:hover{border-color:transparent;background:var(--dsw-alias-interactive-bg-hover,#f1f5f9)}
|
|
15686
|
+
.dsh-mobile-control__provider.is-selected{border-color:transparent;background:var(--dsw-alias-interactive-bg-active,#f5f8ff);box-shadow:none}
|
|
15687
|
+
.dsh-mobile-control__provider.is-lan:not(.is-selected){border-color:transparent;background:transparent}
|
|
15688
|
+
.dsh-mobile-control__view:not(.is-remote)>.dsh-mobile-control__actions{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}
|
|
15689
|
+
.dsh-mobile-control__view.is-diagnostics,.dsh-mobile-control__view.is-devices{flex:1;min-height:0;overflow-y:auto;padding:14px 20px}
|
|
15690
|
+
.dsh-mobile-control__view.is-devices .dsh-mobile-control__devices{max-height:none;margin-top:0}
|
|
15691
|
+
.dsh-mobile-control__actions button{white-space:normal;overflow:visible;text-overflow:clip;line-height:1.25;height:auto;padding-top:8px;padding-bottom:8px;word-break:break-word}
|
|
15692
|
+
.dsh-mobile-control__qr-placeholder{display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:192px;height:192px;padding:12px;border:1px dashed var(--dsw-alias-border-l2,#dbe1e8);border-radius:12px;color:var(--dsw-alias-label-secondary,#606873);font-size:12px;line-height:1.5;text-align:center}
|
|
15693
|
+
.dsh-mobile-control__lan-setup .dsh-mobile-control__actions,.dsh-mobile-control__cpolar-setup .dsh-mobile-control__actions,.dsh-mobile-control__frp-setup .dsh-mobile-control__actions,.dsh-mobile-control__origin-setup .dsh-mobile-control__actions{margin-top:12px}
|
|
15694
|
+
.dsh-mobile-control__origin-setup .dsh-mobile-control__actions.dsh-mobile-control__caddy-actions{margin-top:12px}
|
|
15695
|
+
.dsh-mobile-control__qr-zoom{pointer-events:auto}
|
|
15696
|
+
.dsh-mobile-control__iroh-pairing{border:0;border-radius:0;background:transparent;padding:0;margin:12px 0 0}
|
|
15697
|
+
/* Config setup panels: drop the rounded card frame so provider configuration
|
|
15698
|
+
reads as one flat page with hairline dividers instead of nested boxes. */
|
|
15699
|
+
.dsh-mobile-control__lan-setup,.dsh-mobile-control__cpolar-setup,.dsh-mobile-control__frp-setup,.dsh-mobile-control__origin-setup{border:0;border-radius:0;background:transparent;padding:0;margin:0 0 12px}
|
|
15700
|
+
.dsh-mobile-control__frp-setup>.dsh-mobile-control__frp-step,.dsh-mobile-control__frp-group,.dsh-mobile-control__frp-overview{border:0;border-radius:0;background:transparent}
|
|
15701
|
+
.dsh-mobile-control__ws-paths{border:0;border-radius:0;background:transparent;padding:0;margin:10px 0 0}
|
|
15702
|
+
.dsh-mobile-control__view.is-remote .dsh-mobile-control__remote-workspace>.dsh-mobile-control__actions{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
|
|
15703
|
+
.dsh-mobile-control__view.is-remote .dsh-mobile-control__remote-workspace>.dsh-mobile-control__actions[hidden]{display:none}
|
|
15704
|
+
.dsh-mobile-control__view.is-remote .dsh-mobile-control__origin-setup .dsh-mobile-control__actions.dsh-mobile-control__caddy-actions{display:grid;grid-template-columns:minmax(0,1fr)}
|
|
15705
|
+
.dsh-mobile-control__remote-workspace{border:0;border-radius:0;background:transparent;box-shadow:none}
|
|
15706
|
+
.dsh-mobile-control__stage-header{border:0;border-radius:0;background:transparent;box-shadow:none}
|
|
15707
|
+
.dsh-mobile-control__app-downloads{display:flex;gap:8px;padding:6px 16px;border-bottom:1px solid var(--dsw-alias-border-l2,#e1e5eb)}
|
|
15708
|
+
.dsh-mobile-control__app-download{flex:1 1 0;display:flex;align-items:center;justify-content:space-between;gap:6px;box-sizing:border-box;min-height:34px;padding:6px 10px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:8px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);font-size:12px;text-decoration:none;white-space:normal}
|
|
15709
|
+
.dsh-mobile-control__app-download.is-iroh{border-color:var(--dsw-alias-label-primary-bluish,#2563eb)}
|
|
15710
|
+
.dsh-mobile-control__setup-details{margin-top:12px;padding-top:8px;border-top:1px solid var(--dsw-alias-border-l2,#e1e5eb)}
|
|
15711
|
+
.dsh-mobile-control__remote-workspace>.dsh-mobile-control__qr{margin-bottom:18px}
|
|
15712
|
+
.dsh-mobile-control__stage-header{display:none}
|
|
15713
|
+
.dsh-mobile-control__provider-install{margin:0 0 12px}
|
|
15714
|
+
.dsh-mobile-control__provider-install>.dsh-mobile-control__primary{width:100%;min-height:44px}
|
|
15715
|
+
.dsh-mobile-control__tunnel-toggle{display:inline-flex;align-items:center;gap:4px;box-sizing:border-box;margin:0 0 10px;padding:6px 12px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary-bluish,#2563eb);font:600 12px/1 system-ui;cursor:pointer}
|
|
15716
|
+
.dsh-mobile-control__tunnel-toggle:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f3f6)}
|
|
15717
|
+
.dsh-mobile-control__tunnel>.dsh-mobile-control__primary,.dsh-mobile-control__tunnel>.dsh-mobile-control__secondary{width:100%;min-height:44px;margin-top:10px}
|
|
15718
|
+
.dsh-mobile-control__provider-info-popover p{margin:0 0 8px;font-size:11px;line-height:1.5}
|
|
15719
|
+
.dsh-mobile-control__provider-info-popover p:last-child{margin-bottom:0}
|
|
15720
|
+
.dsh-mobile-control__provider-info-popover{max-width:240px;max-height:44vh;overflow:auto;white-space:normal}
|
|
15721
|
+
.dsh-mobile-control__mode-row{position:relative;display:flex;align-items:center;gap:8px;margin:0 0 8px}
|
|
15722
|
+
.dsh-mobile-control__mode-row>.dsh-mobile-control__switcher{flex:1 1 auto;margin:0}
|
|
15723
|
+
.dsh-mobile-control__notes-toggle{flex:none;display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:50%;background:transparent;color:var(--dsw-alias-label-secondary,#606873);font:600 13px/1 system-ui;cursor:pointer}
|
|
15724
|
+
.dsh-mobile-control__notes-toggle:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f3f6)}
|
|
15725
|
+
.dsh-mobile-control__notes-panel{position:absolute;top:calc(100% + 6px);right:0;z-index:40;width:min(320px,78vw);max-height:50vh;overflow:auto;box-sizing:border-box;margin:0;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:8px;background:var(--dsw-alias-bg-layer-2,#fff);box-shadow:0 6px 20px rgb(15 23 42 / 12%);font-size:11px;line-height:1.5;color:var(--dsw-alias-label-secondary,#475569)}
|
|
15726
|
+
.dsh-mobile-control__notes-panel p{margin:0 0 8px}.dsh-mobile-control__notes-panel p:last-child{margin:0}
|
|
15727
|
+
.dsh-mobile-control__notes-panel[hidden]{display:none}
|
|
15728
|
+
.dsh-mobile-control__tunnel-toggle[hidden]{display:none}
|
|
15729
|
+
.dsh-mobile-control__provider-setup-body .dsh-mobile-control__switcher{border-radius:0;background:transparent;padding:0;gap:8px}
|
|
15730
|
+
.dsh-mobile-control__provider-setup-body .dsh-mobile-control__tab{border-radius:0;box-shadow:none;border-bottom:2px solid transparent}
|
|
15731
|
+
.dsh-mobile-control__provider-setup-body .dsh-mobile-control__tab.is-active{background:transparent;box-shadow:none;border-bottom-color:var(--dsw-alias-label-primary-bluish,#2563eb)}
|
|
15732
|
+
.dsh-mobile-control__origin-setup .dsh-mobile-control__origin-mode{border-radius:0;background:transparent}
|
|
15733
|
+
.dsh-mobile-control__devices{border:0;border-radius:0;padding:0;margin:0;background:transparent;max-height:none}
|
|
15734
|
+
.dsh-mobile-control__provider.is-frp:not(.is-selected),.dsh-mobile-control__provider.is-origin:not(.is-selected),.dsh-mobile-control__provider.is-iroh:not(.is-selected){border-color:transparent;background:transparent}
|
|
15735
|
+
.dsh-mobile-control__provider.is-selected::before{content:"";position:absolute;left:0;top:8px;bottom:8px;width:2px;border-radius:2px;background:var(--dsw-alias-label-primary-bluish,#2563eb)}
|
|
15736
|
+
.dsh-mobile-control__remote-workspace section{border:0;border-radius:0;padding:12px 0;background:transparent;box-shadow:none;border-top:1px solid var(--dsw-alias-border-l2,#e1e5eb)}
|
|
15737
|
+
.dsh-mobile-control__remote-workspace section:first-child{border-top:0}
|
|
15738
|
+
.dsh-mobile-control__remote-workspace .dsh-mobile-control__details{border:0;border-radius:0;background:transparent;box-shadow:none}
|
|
15739
|
+
.dsh-mobile-control__self-hosted{border:0;border-radius:0;background:transparent;box-shadow:none}
|
|
14538
15740
|
` + EXTENSION_RECOVERY_STYLES;
|
|
14539
15741
|
/** Mount the desktop control or mobile feature enhancements. */
|
|
14540
15742
|
function apply(ctx) {
|
|
15743
|
+
let gatewayRejectionSink;
|
|
15744
|
+
let retryRecovery;
|
|
15745
|
+
let refreshCustomization;
|
|
14541
15746
|
ctx.effect(() => installMobileUiLocalePersistence(document, window), "dsh-mobile: browser UI locale persistence");
|
|
14542
15747
|
ctx.effect(() => {
|
|
14543
15748
|
if (window.__DSH_MOBILE_FRONTEND__ !== "dedicated") return;
|
|
@@ -14546,7 +15751,26 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
14546
15751
|
}, "dsh-mobile: authenticated gateway client trust");
|
|
14547
15752
|
ctx.effect(() => {
|
|
14548
15753
|
if (!ownsMobileCustomization(document.querySelector("meta[name=\"dsh-mobile-gateway\"][content=\"authenticated\"]") !== null, window.__DSH_MOBILE_FRONTEND__)) return;
|
|
14549
|
-
|
|
15754
|
+
let ownedRejection;
|
|
15755
|
+
const recovery = installMobileConnectionRecovery(() => ctx.get("connection"), window, document, (rejected, retry) => {
|
|
15756
|
+
ownedRejection = rejected;
|
|
15757
|
+
gatewayRejectionSink = rejected;
|
|
15758
|
+
retryRecovery = retry;
|
|
15759
|
+
}, () => {
|
|
15760
|
+
refreshCustomization?.();
|
|
15761
|
+
});
|
|
15762
|
+
if (recovery === void 0) {
|
|
15763
|
+
gatewayRejectionSink = void 0;
|
|
15764
|
+
retryRecovery = void 0;
|
|
15765
|
+
return;
|
|
15766
|
+
}
|
|
15767
|
+
return () => {
|
|
15768
|
+
if (gatewayRejectionSink === ownedRejection) {
|
|
15769
|
+
gatewayRejectionSink = void 0;
|
|
15770
|
+
retryRecovery = void 0;
|
|
15771
|
+
}
|
|
15772
|
+
return recovery();
|
|
15773
|
+
};
|
|
14550
15774
|
}, "dsh-mobile: connection recovery");
|
|
14551
15775
|
ctx.effect(() => {
|
|
14552
15776
|
const desktopAdmin = isDesktopAdminSurface(location.hostname, location.search, window.__DSH_MOBILE_FRONTEND__, location.protocol);
|
|
@@ -14556,7 +15780,18 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
14556
15780
|
style.textContent = desktopAdmin ? CONTROL_STYLES : NATIVE_MOBILE_STYLES;
|
|
14557
15781
|
style.textContent += CLIENT_MODULE_STYLES;
|
|
14558
15782
|
document.head.append(style);
|
|
14559
|
-
const removeCustom = !desktopAdmin || gatewayCustomization ? installCustomAssets(
|
|
15783
|
+
const removeCustom = !desktopAdmin || gatewayCustomization ? installCustomAssets({
|
|
15784
|
+
rejected: (status) => {
|
|
15785
|
+
gatewayRejectionSink?.(status);
|
|
15786
|
+
},
|
|
15787
|
+
retry: () => retryRecovery?.() ?? false,
|
|
15788
|
+
registerRefresh: (refresh) => {
|
|
15789
|
+
refreshCustomization = refresh;
|
|
15790
|
+
return () => {
|
|
15791
|
+
if (refreshCustomization === refresh) refreshCustomization = void 0;
|
|
15792
|
+
};
|
|
15793
|
+
}
|
|
15794
|
+
}) : () => void 0;
|
|
14560
15795
|
const removeVoiceSession = installVoiceSession(!desktopAdmin);
|
|
14561
15796
|
const removeDraftTransfer = !desktopAdmin && document.querySelector("meta[name=\"dsh-mobile-gateway\"][content=\"authenticated\"]") !== null ? installDraftTransfer(ctx, window, document) : () => void 0;
|
|
14562
15797
|
const removeModuleSettings = ctx.slots.inject("settings.general.item", () => ctx.slots.register({
|