dsh-mobile 0.3.16 → 0.4.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 +36 -4
- package/README.en.md +82 -14
- package/README.md +84 -22
- package/SECURITY.md +2 -0
- package/docs/SELF_HOSTED_FRP.en.md +75 -0
- package/docs/SELF_HOSTED_FRP.md +3 -1
- package/lib/cli.js +99 -82
- package/lib/cli.js.map +1 -1
- package/lib/client.js +808 -103
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +89 -3
- package/lib/index.mjs +727 -131
- package/lib/index.mjs.map +1 -1
- package/package.json +18 -9
package/lib/client.js
CHANGED
|
@@ -39,6 +39,7 @@ window.__ModuleLoader__.load({
|
|
|
39
39
|
clearAllDevices: "Clear all devices",
|
|
40
40
|
pairingQr: "Pairing QR code",
|
|
41
41
|
remoteIntro: "Choose the remote channel that suits you. Switching or disabling remote access does not affect the local network.",
|
|
42
|
+
httpFrameWarning: "HTTP pages may be embedded for compatibility, but their contents are unencrypted and can be altered. Use HTTPS for sensitive work.",
|
|
42
43
|
chooseProvider: "Choose connection method",
|
|
43
44
|
currentProvider: "Current connection",
|
|
44
45
|
remoteStateOff: "Off",
|
|
@@ -83,6 +84,7 @@ window.__ModuleLoader__.load({
|
|
|
83
84
|
continueLogin: "Continue login",
|
|
84
85
|
reconnect: "Reconnect",
|
|
85
86
|
generateRemoteQr: "Generate remote pairing QR code",
|
|
87
|
+
copyRemoteLink: "Copy remote pairing link",
|
|
86
88
|
manageRemoteDevices: "Manage remote devices",
|
|
87
89
|
resetRemoteLogin: "Sign out and clear remote login",
|
|
88
90
|
resetRemoteDevices: "Disable and clear remote devices",
|
|
@@ -206,6 +208,7 @@ window.__ModuleLoader__.load({
|
|
|
206
208
|
clearAllDevices: "Rimuovi tutti i dispositivi",
|
|
207
209
|
pairingQr: "Codice QR di abbinamento",
|
|
208
210
|
remoteIntro: "Scegli il canale remoto più adatto. Cambiare o disattivare l’accesso remoto non influisce sulla rete locale.",
|
|
211
|
+
httpFrameWarning: "Le pagine HTTP possono essere incorporate per compatibilità, ma i contenuti non sono cifrati e possono essere alterati. Usa HTTPS per le attività sensibili.",
|
|
209
212
|
chooseProvider: "Scegli metodo di connessione",
|
|
210
213
|
currentProvider: "Connessione attuale",
|
|
211
214
|
remoteStateOff: "Disattivata",
|
|
@@ -250,6 +253,7 @@ window.__ModuleLoader__.load({
|
|
|
250
253
|
continueLogin: "Continua accesso",
|
|
251
254
|
reconnect: "Riconnetti",
|
|
252
255
|
generateRemoteQr: "Genera QR di abbinamento remoto",
|
|
256
|
+
copyRemoteLink: "Copia link di abbinamento remoto",
|
|
253
257
|
manageRemoteDevices: "Gestisci dispositivi remoti",
|
|
254
258
|
resetRemoteLogin: "Esci e rimuovi accesso remoto",
|
|
255
259
|
resetRemoteDevices: "Disattiva e rimuovi dispositivi remoti",
|
|
@@ -375,6 +379,7 @@ window.__ModuleLoader__.load({
|
|
|
375
379
|
clearAllDevices: "清除所有设备",
|
|
376
380
|
pairingQr: "配对二维码",
|
|
377
381
|
remoteIntro: "选择更适合你的远程通道。切换或关闭远程访问不会影响局域网。",
|
|
382
|
+
httpFrameWarning: "为兼容性允许嵌入 HTTP 页面,但内容未加密,可能被篡改;处理敏感内容请使用 HTTPS。",
|
|
378
383
|
chooseProvider: "选择连接方式",
|
|
379
384
|
currentProvider: "当前连接",
|
|
380
385
|
remoteStateOff: "未启用",
|
|
@@ -419,6 +424,7 @@ window.__ModuleLoader__.load({
|
|
|
419
424
|
continueLogin: "继续登录",
|
|
420
425
|
reconnect: "重新连接",
|
|
421
426
|
generateRemoteQr: "生成远程配对二维码",
|
|
427
|
+
copyRemoteLink: "复制远程配对链接",
|
|
422
428
|
manageRemoteDevices: "管理远程设备",
|
|
423
429
|
resetRemoteLogin: "退出并清除远程登录",
|
|
424
430
|
resetRemoteDevices: "关闭并清除远程设备",
|
|
@@ -510,6 +516,51 @@ window.__ModuleLoader__.load({
|
|
|
510
516
|
wsPathsAnnounce: "{n} 条插件连接等待放行",
|
|
511
517
|
wsPathsAdvancedHint: "通常不需要手动填写:用上方按钮即可。只有当你确切知道所需路径时才填。"
|
|
512
518
|
});
|
|
519
|
+
Object.assign(MOBILE_CONTROL_MESSAGES.en, {
|
|
520
|
+
lanSetupTitle: "Finish local network setup",
|
|
521
|
+
lanSetupIntro: "Choose the network shared by this computer and your phone. DSH Mobile creates private certificates only after you confirm.",
|
|
522
|
+
lanSetupNetwork: "Computer network",
|
|
523
|
+
lanSetupChooseNetwork: "Choose a network",
|
|
524
|
+
lanSetupRecommended: "Recommended",
|
|
525
|
+
lanSetupNoNetworks: "No private Wi-Fi or Ethernet address was found. Connect this computer to the local network, then scan again.",
|
|
526
|
+
lanSetupRefresh: "Scan again",
|
|
527
|
+
lanSetupFirewallNote: "Windows may request administrator approval for two inbound rules limited to the local subnet.",
|
|
528
|
+
lanSetupConfigure: "Configure local network",
|
|
529
|
+
lanSetupConfiguring: "Creating private certificates and local network rules…",
|
|
530
|
+
lanSetupRequired: "Local network setup has not been completed. Choose a network above to continue.",
|
|
531
|
+
lanSetupRestart: "Setup complete for {origin}. Restart DSH once to start local network access.",
|
|
532
|
+
lanSetupFailed: "Setup was not completed. Confirm the network selection and approve the Windows prompt, then try again."
|
|
533
|
+
});
|
|
534
|
+
Object.assign(MOBILE_CONTROL_MESSAGES.it, {
|
|
535
|
+
lanSetupTitle: "Completa la configurazione della rete locale",
|
|
536
|
+
lanSetupIntro: "Scegli la rete condivisa da questo computer e dal telefono. DSH Mobile crea certificati privati solo dopo la conferma.",
|
|
537
|
+
lanSetupNetwork: "Rete del computer",
|
|
538
|
+
lanSetupChooseNetwork: "Scegli una rete",
|
|
539
|
+
lanSetupRecommended: "Consigliata",
|
|
540
|
+
lanSetupNoNetworks: "Nessun indirizzo Wi-Fi o Ethernet privato trovato. Collega il computer alla rete locale e ripeti la ricerca.",
|
|
541
|
+
lanSetupRefresh: "Cerca di nuovo",
|
|
542
|
+
lanSetupFirewallNote: "Windows può richiedere l’approvazione amministratore per due regole in ingresso limitate alla sottorete locale.",
|
|
543
|
+
lanSetupConfigure: "Configura rete locale",
|
|
544
|
+
lanSetupConfiguring: "Creazione dei certificati privati e delle regole di rete locale…",
|
|
545
|
+
lanSetupRequired: "La configurazione della rete locale non è completa. Scegli una rete qui sopra per continuare.",
|
|
546
|
+
lanSetupRestart: "Configurazione completata per {origin}. Riavvia DSH una volta per attivare l’accesso locale.",
|
|
547
|
+
lanSetupFailed: "Configurazione non completata. Controlla la rete e approva la richiesta di Windows, quindi riprova."
|
|
548
|
+
});
|
|
549
|
+
Object.assign(MOBILE_CONTROL_MESSAGES.zh, {
|
|
550
|
+
lanSetupTitle: "完成局域网配置",
|
|
551
|
+
lanSetupIntro: "选择电脑与手机共同使用的网络。确认后才会生成私有证书,不会在安装时自动开放端口。",
|
|
552
|
+
lanSetupNetwork: "电脑当前网络",
|
|
553
|
+
lanSetupChooseNetwork: "请选择网络",
|
|
554
|
+
lanSetupRecommended: "推荐",
|
|
555
|
+
lanSetupNoNetworks: "未找到可用的私有 Wi-Fi 或以太网地址。请先让电脑接入局域网,然后重新扫描。",
|
|
556
|
+
lanSetupRefresh: "重新扫描",
|
|
557
|
+
lanSetupFirewallNote: "Windows 可能请求管理员授权,仅创建两条限本地子网访问的入站规则。",
|
|
558
|
+
lanSetupConfigure: "完成配置",
|
|
559
|
+
lanSetupConfiguring: "正在生成私有证书并配置局域网规则…",
|
|
560
|
+
lanSetupRequired: "尚未完成局域网配置,请选择上方网络继续。",
|
|
561
|
+
lanSetupRestart: "已为 {origin} 完成配置。请重启一次 DSH,局域网访问会自动开启。",
|
|
562
|
+
lanSetupFailed: "配置未完成。请确认网卡选择并允许 Windows 授权提示,然后重试。"
|
|
563
|
+
});
|
|
513
564
|
Object.assign(MOBILE_CONTROL_MESSAGES.en, {
|
|
514
565
|
selfHostedConnections: "Self-hosted connection",
|
|
515
566
|
selfHostedDescription: "For users with a VPS and domain. It does not depend on public tunnel bandwidth.",
|
|
@@ -966,12 +1017,32 @@ window.__ModuleLoader__.load({
|
|
|
966
1017
|
generated: "Generato"
|
|
967
1018
|
},
|
|
968
1019
|
zh: {
|
|
1020
|
+
versions: "已显示插件、DSH 和 Android App 最低版本。",
|
|
1021
|
+
networkOk: "已配置的局域网网卡可用。",
|
|
1022
|
+
networkError: "已保存的局域网网卡不可用。",
|
|
1023
|
+
networkInfo: "当前使用固定局域网配置。",
|
|
1024
|
+
networkAction: "重新运行 dsh-mobile setup。",
|
|
1025
|
+
lanOk: "局域网网关正在监听,配对入口可用。",
|
|
1026
|
+
lanInfo: "局域网访问当前未开启。",
|
|
1027
|
+
lanAction: "需要手机直连时开启局域网访问。",
|
|
1028
|
+
firewallOk: "局域网 TCP 与发现防火墙规则已启用。",
|
|
1029
|
+
firewallWarning: "未找到完整的局域网防火墙规则。",
|
|
1030
|
+
firewallInfo: "系统未允许插件读取防火墙状态。",
|
|
1031
|
+
firewallAction: "以管理员身份运行 dsh-mobile setup 并检查防火墙规则。",
|
|
1032
|
+
remoteOk: "远程公网地址已通过可达性检查。",
|
|
1033
|
+
remoteWarning: "远程通道需要处理或仍在连接。",
|
|
1034
|
+
remoteError: "远程连接当前不可达。",
|
|
1035
|
+
remoteInfo: "远程访问当前未开启。",
|
|
1036
|
+
remoteAction: "返回远程访问,按提供方提示操作并重新连接。",
|
|
1037
|
+
phone: "电脑无法判断路由器是否隔离了手机。",
|
|
1038
|
+
phoneAction: "确认手机与电脑使用同一网络,并关闭访客网络或 AP 隔离。",
|
|
969
1039
|
reportTitle: "DSH Mobile 诊断报告",
|
|
970
1040
|
generated: "生成时间"
|
|
971
1041
|
}
|
|
972
1042
|
};
|
|
973
1043
|
const DIAGNOSTIC_REASON_MESSAGES = {
|
|
974
1044
|
en: {
|
|
1045
|
+
"lan-setup-required": ["Local network setup has not been completed.", "Return to Local network and choose a network to finish setup."],
|
|
975
1046
|
"versions-current": ["Installed plugin, DSH, and minimum Android app versions are shown.", ""],
|
|
976
1047
|
"network-unavailable": ["The saved local network adapter is unavailable.", "Run dsh-mobile setup again."],
|
|
977
1048
|
"network-interface": ["Using network interface {interfaceName}.", ""],
|
|
@@ -992,6 +1063,7 @@ window.__ModuleLoader__.load({
|
|
|
992
1063
|
"phone-network-unknown": ["The computer cannot determine whether the router isolates the phone.", "Confirm the phone and computer use the same network, then disable guest-network or AP isolation."]
|
|
993
1064
|
},
|
|
994
1065
|
it: {
|
|
1066
|
+
"lan-setup-required": ["La configurazione della rete locale non è completa.", "Torna a Rete locale e scegli una rete per completare la configurazione."],
|
|
995
1067
|
"versions-current": ["Sono indicate le versioni installate di plugin e DSH e la versione minima dell’app Android.", ""],
|
|
996
1068
|
"network-unavailable": ["La scheda di rete locale salvata non è disponibile.", "Esegui di nuovo dsh-mobile setup."],
|
|
997
1069
|
"network-interface": ["È in uso l’interfaccia di rete {interfaceName}.", ""],
|
|
@@ -1012,6 +1084,7 @@ window.__ModuleLoader__.load({
|
|
|
1012
1084
|
"phone-network-unknown": ["Il computer non può stabilire se il router isola il telefono.", "Verifica che telefono e computer usino la stessa rete, poi disattiva rete ospiti o isolamento AP."]
|
|
1013
1085
|
},
|
|
1014
1086
|
zh: {
|
|
1087
|
+
"lan-setup-required": ["尚未完成局域网初始化。", "返回局域网页选择网卡并完成配置。"],
|
|
1015
1088
|
"versions-current": ["已显示插件、DSH 和 Android App 最低版本。", ""],
|
|
1016
1089
|
"network-unavailable": ["已保存的局域网网卡当前不可用。", "重新运行 dsh-mobile setup。"],
|
|
1017
1090
|
"network-interface": ["正在使用网卡 {interfaceName}。", ""],
|
|
@@ -1129,6 +1202,304 @@ window.__ModuleLoader__.load({
|
|
|
1129
1202
|
if (publicIpv4Address(publicHost)) lines.push(manualIpCertificateGuide(publicHost), "");
|
|
1130
1203
|
return lines.join("\n");
|
|
1131
1204
|
}
|
|
1205
|
+
/** Wire bounds mirrored by the Android notification policy. */
|
|
1206
|
+
const TASK_NOTIFY_LIMITS = Object.freeze({
|
|
1207
|
+
title: 80,
|
|
1208
|
+
body: 200,
|
|
1209
|
+
tag: 64
|
|
1210
|
+
});
|
|
1211
|
+
const MAX_SEEN_QUESTION_KEYS = 20;
|
|
1212
|
+
function sanitizeTagFragment(value) {
|
|
1213
|
+
return value.toLowerCase().replaceAll(/[^a-z0-9-]+/gu, "-").replaceAll(/^-+|-+$/gu, "").slice(0, TASK_NOTIFY_LIMITS.tag);
|
|
1214
|
+
}
|
|
1215
|
+
/** Stable per-turn tag so concurrent sessions keep separate notifications. */
|
|
1216
|
+
function taskCompletionTag(sessionId, turn) {
|
|
1217
|
+
return `dsh-task-done-${sanitizeTagFragment(`${sessionId}-${String(turn)}`) || "task"}`.slice(0, TASK_NOTIFY_LIMITS.tag);
|
|
1218
|
+
}
|
|
1219
|
+
/** Decide whether the current snapshot deserves a system notification. */
|
|
1220
|
+
var TaskNotifyTracker = class {
|
|
1221
|
+
lastActivityAt;
|
|
1222
|
+
notifiedDoneAt;
|
|
1223
|
+
wasBusy = false;
|
|
1224
|
+
busySince;
|
|
1225
|
+
idleAnchoredAt;
|
|
1226
|
+
seenQuestionKeys = /* @__PURE__ */ new Set();
|
|
1227
|
+
quietMs;
|
|
1228
|
+
graceMs;
|
|
1229
|
+
minBusyMs;
|
|
1230
|
+
now;
|
|
1231
|
+
format;
|
|
1232
|
+
constructor(options) {
|
|
1233
|
+
this.quietMs = options.quietMs ?? 9e4;
|
|
1234
|
+
this.graceMs = options.graceMs ?? 1e4;
|
|
1235
|
+
this.minBusyMs = options.minBusyMs ?? 2e4;
|
|
1236
|
+
this.now = options.now ?? Date.now;
|
|
1237
|
+
this.format = options.format;
|
|
1238
|
+
}
|
|
1239
|
+
/** Record assistant-side activity; re-arms completion after a notification. */
|
|
1240
|
+
markActivity() {
|
|
1241
|
+
const now = this.now();
|
|
1242
|
+
this.lastActivityAt = now;
|
|
1243
|
+
if (this.notifiedDoneAt !== void 0 && this.notifiedDoneAt <= now) this.notifiedDoneAt = void 0;
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Milliseconds until a pending completion anchor fires, or undefined when
|
|
1247
|
+
* no anchor exists. Lets the watcher schedule one exact timer instead of
|
|
1248
|
+
* waiting for the next coarse poll tick.
|
|
1249
|
+
*/
|
|
1250
|
+
pendingAnchorDelayMs() {
|
|
1251
|
+
if (this.idleAnchoredAt === void 0) return void 0;
|
|
1252
|
+
return Math.max(0, this.graceMs - (this.now() - this.idleAnchoredAt));
|
|
1253
|
+
}
|
|
1254
|
+
evaluate(snapshot) {
|
|
1255
|
+
const now = this.now();
|
|
1256
|
+
if (!this.wasBusy && snapshot.composerBusy) this.busySince = now;
|
|
1257
|
+
if (this.wasBusy && !snapshot.composerBusy) {
|
|
1258
|
+
const busyFor = this.busySince === void 0 ? 0 : now - this.busySince;
|
|
1259
|
+
this.busySince = void 0;
|
|
1260
|
+
if (snapshot.pageHidden && !snapshot.pendingQuestion && busyFor >= this.minBusyMs) {
|
|
1261
|
+
this.idleAnchoredAt = void 0;
|
|
1262
|
+
this.notifiedDoneAt = now;
|
|
1263
|
+
return this.event("done", snapshot.sessionLabel, void 0);
|
|
1264
|
+
}
|
|
1265
|
+
this.idleAnchoredAt = snapshot.pageHidden ? now : void 0;
|
|
1266
|
+
if (!snapshot.pageHidden) this.notifiedDoneAt = now;
|
|
1267
|
+
}
|
|
1268
|
+
this.wasBusy = snapshot.composerBusy;
|
|
1269
|
+
if (snapshot.composerBusy) this.idleAnchoredAt = void 0;
|
|
1270
|
+
else if (this.idleAnchoredAt !== void 0 && !snapshot.pageHidden) {
|
|
1271
|
+
this.idleAnchoredAt = void 0;
|
|
1272
|
+
this.notifiedDoneAt = now;
|
|
1273
|
+
}
|
|
1274
|
+
if (!snapshot.pageHidden) {
|
|
1275
|
+
if (snapshot.pendingQuestion && snapshot.questionKey !== void 0) this.rememberQuestionKey(snapshot.questionKey);
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
if (snapshot.pendingQuestion) {
|
|
1279
|
+
if (snapshot.questionKey === void 0 || this.seenQuestionKeys.has(snapshot.questionKey)) return void 0;
|
|
1280
|
+
this.rememberQuestionKey(snapshot.questionKey);
|
|
1281
|
+
return this.event("question", snapshot.sessionLabel, snapshot.questionKey);
|
|
1282
|
+
}
|
|
1283
|
+
if (this.idleAnchoredAt !== void 0) {
|
|
1284
|
+
if (now - this.idleAnchoredAt < this.graceMs) return void 0;
|
|
1285
|
+
this.idleAnchoredAt = void 0;
|
|
1286
|
+
this.notifiedDoneAt = now;
|
|
1287
|
+
return this.event("done", snapshot.sessionLabel, void 0);
|
|
1288
|
+
}
|
|
1289
|
+
if (this.lastActivityAt === void 0) return void 0;
|
|
1290
|
+
if (now - this.lastActivityAt < this.quietMs) return void 0;
|
|
1291
|
+
if (now - this.lastActivityAt > 18e5) {
|
|
1292
|
+
this.lastActivityAt = void 0;
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
if (this.notifiedDoneAt !== void 0 && this.notifiedDoneAt >= this.lastActivityAt) return void 0;
|
|
1296
|
+
this.notifiedDoneAt = now;
|
|
1297
|
+
return this.event("done", snapshot.sessionLabel, void 0);
|
|
1298
|
+
}
|
|
1299
|
+
rememberQuestionKey(key) {
|
|
1300
|
+
this.seenQuestionKeys.add(key);
|
|
1301
|
+
if (this.seenQuestionKeys.size > MAX_SEEN_QUESTION_KEYS) {
|
|
1302
|
+
const oldest = this.seenQuestionKeys.values().next();
|
|
1303
|
+
if (!oldest.done) this.seenQuestionKeys.delete(oldest.value);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
event(kind, sessionLabel, questionKey) {
|
|
1307
|
+
const text = this.format(kind, sessionLabel);
|
|
1308
|
+
const tag = kind === "question" && questionKey !== void 0 ? `dsh-task-question-${sanitizeTagFragment(questionKey) || "pending"}` : "dsh-task-done";
|
|
1309
|
+
return Object.freeze({
|
|
1310
|
+
kind,
|
|
1311
|
+
title: text.title,
|
|
1312
|
+
body: text.body,
|
|
1313
|
+
tag
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
};
|
|
1317
|
+
/**
|
|
1318
|
+
* Mirror of the composer busy signal used for media actions: the run is busy
|
|
1319
|
+
* while the composer is aria-busy, disabled, read-only, or non-interactive.
|
|
1320
|
+
*/
|
|
1321
|
+
function readComposerBusyState(root) {
|
|
1322
|
+
if (typeof root.querySelector !== "function") return false;
|
|
1323
|
+
const card = root.querySelector("[data-composer-card]");
|
|
1324
|
+
if (card === null || typeof card.getAttribute !== "function" || typeof card.querySelector !== "function") return false;
|
|
1325
|
+
if (card.getAttribute("aria-busy") === "true") return true;
|
|
1326
|
+
const field = card.querySelector("textarea,input,[contenteditable=\"true\"],[contenteditable=\"plaintext-only\"]");
|
|
1327
|
+
if (field === null) return false;
|
|
1328
|
+
return field.disabled === true || field.readOnly === true || field.getAttribute?.("aria-disabled") === "true";
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Best-effort session label for the notification body, bounded for the wire.
|
|
1332
|
+
* Callers accept that titles may name the active session on the lock screen.
|
|
1333
|
+
*/
|
|
1334
|
+
function readSessionLabel(title) {
|
|
1335
|
+
if (typeof title !== "string") return "";
|
|
1336
|
+
return title.normalize("NFC").replace(/[\u0000-\u001f\u007f]+/gu, " ").trim().slice(0, 80);
|
|
1337
|
+
}
|
|
1338
|
+
/** Whether the document currently shows a card that waits for user input. */
|
|
1339
|
+
function hasPendingInputQuestion(root) {
|
|
1340
|
+
if (typeof root.querySelector !== "function") return false;
|
|
1341
|
+
return root.querySelector("[data-question-key],[data-plan-review-key]") !== null;
|
|
1342
|
+
}
|
|
1343
|
+
/** Stable key of the waiting input card, when the DOM exposes one. */
|
|
1344
|
+
function pendingInputQuestionKey(root) {
|
|
1345
|
+
if (typeof root.querySelector !== "function") return void 0;
|
|
1346
|
+
const card = root.querySelector("[data-question-key],[data-plan-review-key]");
|
|
1347
|
+
const getAttribute = card?.getAttribute;
|
|
1348
|
+
if (typeof getAttribute !== "function") return void 0;
|
|
1349
|
+
const key = getAttribute.call(card, "data-question-key") ?? getAttribute.call(card, "data-plan-review-key");
|
|
1350
|
+
if (key === null || key === "") return void 0;
|
|
1351
|
+
return key;
|
|
1352
|
+
}
|
|
1353
|
+
/** Observe assistant-side conversation mutations; returns a disposer. */
|
|
1354
|
+
function observeTaskActivity(target, onActivity, observe = typeof MutationObserver === "function" ? MutationObserver : void 0) {
|
|
1355
|
+
if (observe === void 0) return () => void 0;
|
|
1356
|
+
const observer = new observe(() => {
|
|
1357
|
+
onActivity();
|
|
1358
|
+
});
|
|
1359
|
+
try {
|
|
1360
|
+
observer.observe(target, {
|
|
1361
|
+
childList: true,
|
|
1362
|
+
characterData: true,
|
|
1363
|
+
subtree: true
|
|
1364
|
+
});
|
|
1365
|
+
} catch {
|
|
1366
|
+
return () => void 0;
|
|
1367
|
+
}
|
|
1368
|
+
return () => {
|
|
1369
|
+
observer.disconnect();
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
function readNativeBridge() {
|
|
1373
|
+
if (typeof window === "undefined") return void 0;
|
|
1374
|
+
const bridge = window.__DSH_MOBILE_NATIVE__;
|
|
1375
|
+
if (bridge === void 0 || typeof bridge.capabilities !== "function" || typeof bridge.invoke !== "function") return void 0;
|
|
1376
|
+
return bridge;
|
|
1377
|
+
}
|
|
1378
|
+
/** Parse a `task-notify` SSE payload; rejects anything malformed or empty. */
|
|
1379
|
+
function parseTaskNotifyPayload(value) {
|
|
1380
|
+
let record;
|
|
1381
|
+
if (typeof value === "string") try {
|
|
1382
|
+
const parsed = JSON.parse(value);
|
|
1383
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
|
|
1384
|
+
record = parsed;
|
|
1385
|
+
} catch {
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
else if (value !== null && typeof value === "object" && !Array.isArray(value)) record = value;
|
|
1389
|
+
else return void 0;
|
|
1390
|
+
if (typeof record.sessionId !== "string" || record.sessionId.trim() === "") return void 0;
|
|
1391
|
+
const turn = typeof record.turn === "number" && Number.isSafeInteger(record.turn) && record.turn >= 0 ? record.turn : 0;
|
|
1392
|
+
return Object.freeze({
|
|
1393
|
+
sessionId: record.sessionId.slice(0, 128),
|
|
1394
|
+
turn
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
/** Parse the small authenticated event used when the Host revokes this device. */
|
|
1398
|
+
function isDeviceRevokedPayload(value) {
|
|
1399
|
+
let record;
|
|
1400
|
+
if (typeof value === "string") try {
|
|
1401
|
+
const parsed = JSON.parse(value);
|
|
1402
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return false;
|
|
1403
|
+
record = parsed;
|
|
1404
|
+
} catch {
|
|
1405
|
+
return false;
|
|
1406
|
+
}
|
|
1407
|
+
else if (value !== null && typeof value === "object" && !Array.isArray(value)) record = value;
|
|
1408
|
+
else return false;
|
|
1409
|
+
return record.reason === "device_revoked";
|
|
1410
|
+
}
|
|
1411
|
+
/** Tell the Android shell to transition the active record to the revoked state. */
|
|
1412
|
+
function fireDeviceRevoked() {
|
|
1413
|
+
const bridge = readNativeBridge();
|
|
1414
|
+
if (bridge === void 0) return false;
|
|
1415
|
+
bridge.invoke("device.revoked", {}).catch(() => void 0);
|
|
1416
|
+
return true;
|
|
1417
|
+
}
|
|
1418
|
+
/**
|
|
1419
|
+
* Deliver one notification through the native bridge when available.
|
|
1420
|
+
* Returns false when there is no bridge (desktop browsers keep their own
|
|
1421
|
+
* UI) so callers can fall back; a denial flips the module sticky flag via
|
|
1422
|
+
* onDenied for the session-lifetime nag guard.
|
|
1423
|
+
*/
|
|
1424
|
+
function fireTaskNotifyEvent(event, onDenied) {
|
|
1425
|
+
const bridge = readNativeBridge();
|
|
1426
|
+
if (bridge === void 0) return false;
|
|
1427
|
+
Promise.resolve().then(() => bridge.capabilities()).then((capabilities) => {
|
|
1428
|
+
if (!capabilities.includes("notification.notify")) return;
|
|
1429
|
+
return bridge.invoke("notification.notify", {
|
|
1430
|
+
title: event.title,
|
|
1431
|
+
body: event.body,
|
|
1432
|
+
tag: event.tag
|
|
1433
|
+
});
|
|
1434
|
+
}).then(() => void 0, (error) => {
|
|
1435
|
+
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
|
|
1436
|
+
if ((code === "permission_denied" || code === "denied") && onDenied !== void 0) onDenied();
|
|
1437
|
+
});
|
|
1438
|
+
return true;
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Watch the rendered conversation for finished runs and waiting questions,
|
|
1442
|
+
* notifying through the native bridge while the page is hidden. Returns a
|
|
1443
|
+
* disposer. Silent when no native bridge exists (desktop browsers keep their
|
|
1444
|
+
* own UI) or after the user denies the permission.
|
|
1445
|
+
*/
|
|
1446
|
+
function installTaskCompletionWatcher(options) {
|
|
1447
|
+
if (typeof document === "undefined" || typeof window === "undefined") return () => void 0;
|
|
1448
|
+
const tracker = new TaskNotifyTracker({ format: options.label });
|
|
1449
|
+
const completionFallback = options.completionFallback ?? true;
|
|
1450
|
+
let notifyBlocked = false;
|
|
1451
|
+
let timer = 0;
|
|
1452
|
+
let anchorTimer = 0;
|
|
1453
|
+
const fire = (event) => {
|
|
1454
|
+
if (notifyBlocked) return;
|
|
1455
|
+
if (!fireTaskNotifyEvent(event, () => {
|
|
1456
|
+
notifyBlocked = true;
|
|
1457
|
+
})) return;
|
|
1458
|
+
};
|
|
1459
|
+
const evaluateNow = () => {
|
|
1460
|
+
const busy = completionFallback && readComposerBusyState(document);
|
|
1461
|
+
const event = tracker.evaluate({
|
|
1462
|
+
pageHidden: document.hidden,
|
|
1463
|
+
pendingQuestion: hasPendingInputQuestion(document),
|
|
1464
|
+
questionKey: pendingInputQuestionKey(document),
|
|
1465
|
+
sessionLabel: readSessionLabel(document.title),
|
|
1466
|
+
composerBusy: busy
|
|
1467
|
+
});
|
|
1468
|
+
if (event !== void 0) fire(event);
|
|
1469
|
+
scheduleAnchorTimer();
|
|
1470
|
+
};
|
|
1471
|
+
const scheduleAnchorTimer = () => {
|
|
1472
|
+
const delay = tracker.pendingAnchorDelayMs();
|
|
1473
|
+
if (delay === void 0) {
|
|
1474
|
+
if (anchorTimer !== 0) {
|
|
1475
|
+
window.clearTimeout(anchorTimer);
|
|
1476
|
+
anchorTimer = 0;
|
|
1477
|
+
}
|
|
1478
|
+
return;
|
|
1479
|
+
}
|
|
1480
|
+
if (anchorTimer !== 0) return;
|
|
1481
|
+
anchorTimer = window.setTimeout(() => {
|
|
1482
|
+
anchorTimer = 0;
|
|
1483
|
+
evaluateNow();
|
|
1484
|
+
}, delay);
|
|
1485
|
+
};
|
|
1486
|
+
const disposeObservation = observeTaskActivity(document.documentElement, () => {
|
|
1487
|
+
if (completionFallback) tracker.markActivity();
|
|
1488
|
+
evaluateNow();
|
|
1489
|
+
});
|
|
1490
|
+
const onVisibility = () => {
|
|
1491
|
+
evaluateNow();
|
|
1492
|
+
};
|
|
1493
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
1494
|
+
if (typeof window.setInterval === "function") timer = window.setInterval(evaluateNow, 15e3);
|
|
1495
|
+
evaluateNow();
|
|
1496
|
+
return () => {
|
|
1497
|
+
disposeObservation();
|
|
1498
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
1499
|
+
if (timer !== 0) window.clearInterval(timer);
|
|
1500
|
+
if (anchorTimer !== 0) window.clearTimeout(anchorTimer);
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1132
1503
|
//#endregion
|
|
1133
1504
|
//#region src/native-mobile.ts
|
|
1134
1505
|
/**
|
|
@@ -1148,7 +1519,14 @@ window.__ModuleLoader__.load({
|
|
|
1148
1519
|
still collapsed the sidebar. Keep the neutral state explicitly invisible —
|
|
1149
1520
|
the query restores the fixed scrim, and its more specific [hidden] rule
|
|
1150
1521
|
keeps winning there. */
|
|
1151
|
-
.dsh-native-mobile-backdrop,.dsh-mobile-branch-toast,.dsh-mobile-media-toast { display:none; }
|
|
1522
|
+
.dsh-native-mobile-backdrop,.dsh-mobile-branch-toast,.dsh-mobile-media-toast { display:none; }
|
|
1523
|
+
.dsh-mobile-settings_row { display:flex; align-items:center; gap:8px; padding:16px 0; border-bottom:0.5px solid var(--dsw-alias-border-l2); }
|
|
1524
|
+
.dsh-mobile-settings_rowText { flex:1; min-width:0; display:flex; flex-direction:column; gap:4px; padding-right:48px; }
|
|
1525
|
+
.dsh-mobile-settings_title { color:var(--dsw-alias-label-primary); font-size:14px; font-weight:400; line-height:22px; }
|
|
1526
|
+
.dsh-mobile-settings_desc { color:var(--dsw-alias-label-tertiary); font-size:12px; font-weight:400; line-height:18px; }
|
|
1527
|
+
.dsh-mobile-settings_selector { display:inline-flex; align-items:center; justify-content:center; min-width:max-content; height:36px; padding:0 14px; border:0; border-radius:18px; background:var(--dsw-alias-bg-module-platform); color:var(--dsw-alias-label-primary); font:inherit; font-size:14px; line-height:22px; cursor:pointer; }
|
|
1528
|
+
.dsh-mobile-settings_selector:hover:not(:disabled) { background:var(--dsw-alias-interactive-bg-hover); }
|
|
1529
|
+
.dsh-mobile-settings_selector:disabled { cursor:wait; opacity:.55; }
|
|
1152
1530
|
@media ${NATIVE_MOBILE_OVERLAY_QUERY} {
|
|
1153
1531
|
html.dsh-native-mobile-active,html.dsh-native-mobile-active body { width:100%; height:100%; overflow:hidden; }
|
|
1154
1532
|
html.dsh-native-mobile-active { --dsh-mobile-motion-duration:200ms; --dsh-mobile-motion-ease:cubic-bezier(.22,1,.36,1); }
|
|
@@ -1191,8 +1569,12 @@ window.__ModuleLoader__.load({
|
|
|
1191
1569
|
[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; }
|
|
1192
1570
|
[data-dsh-mobile-sidebar][data-open="false"] [data-dsh-mobile-sidebar-root] > :not(:has([data-dsh-mobile-toggle])) { display:none !important; }
|
|
1193
1571
|
[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; }
|
|
1194
|
-
|
|
1195
|
-
|
|
1572
|
+
[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; }
|
|
1573
|
+
/* The stock settings shell is mounted under the collapsed sidebar. Let
|
|
1574
|
+
its fixed overlay escape the zero-width drawer while it is open. */
|
|
1575
|
+
[data-dsh-mobile-sidebar]:has([data-dsh-mobile-settings]) { width:100vw !important; inset:0 !important; }
|
|
1576
|
+
[data-dsh-mobile-sidebar]:has([data-dsh-mobile-settings]) > * { width:100vw !important; overflow:visible !important; }
|
|
1577
|
+
[data-dsh-mobile-toggle] { width:44px !important; height:44px !important; min-width:44px !important; min-height:44px !important; }
|
|
1196
1578
|
[data-dsh-mobile-sidebar][data-open="false"] [data-dsh-mobile-toggle] > svg[class*="_railFish"] { transform:translateY(-4px) !important; }
|
|
1197
1579
|
.dsh-native-mobile-backdrop { display:block; position:fixed; z-index:235; inset:env(safe-area-inset-top) 0 0; border:0; background:rgb(15 23 42 / 32%); }
|
|
1198
1580
|
.dsh-native-mobile-backdrop:not([hidden]) { animation:dsh-mobile-fade-in var(--dsh-mobile-motion-duration) ease-out; }
|
|
@@ -1208,11 +1590,11 @@ window.__ModuleLoader__.load({
|
|
|
1208
1590
|
[data-dsh-mobile-settings-list] [class*="_navCell"] { flex:0 0 auto !important; min-width:max-content !important; height:44px !important; padding:10px 12px !important; }
|
|
1209
1591
|
[data-dsh-mobile-settings-list] [aria-current="true"] { border-color:transparent !important; outline:0 !important; box-shadow:none !important; }
|
|
1210
1592
|
[data-dsh-mobile-settings-content] { flex:1 1 auto !important; width:100% !important; min-height:0 !important; }
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1593
|
+
[data-dsh-mobile-settings-header] { height:48px !important; min-height:48px !important; padding:10px 12px 6px !important; }
|
|
1594
|
+
[data-dsh-mobile-settings-header] [class*="_close"] { width:36px !important; height:36px !important; }
|
|
1595
|
+
[data-dsh-mobile-settings-options] { box-sizing:border-box !important; width:100% !important; padding:4px 16px max(24px,env(safe-area-inset-bottom)) !important; overflow-x:hidden !important; }
|
|
1596
|
+
[data-dsh-mobile-settings-options] > * { width:100% !important; min-width:0 !important; }
|
|
1597
|
+
[data-dsh-mobile-settings-options] [data-slot="settings.general.item"] > [class*="_row"] { flex-direction:column !important; align-items:stretch !important; gap:12px !important; }
|
|
1216
1598
|
[data-dsh-mobile-settings-options] [data-slot="settings.general.item"] [class*="_rowText"] { width:100% !important; padding-right:0 !important; }
|
|
1217
1599
|
[data-dsh-mobile-settings-options] [data-slot="settings.general.item"] [class*="_selector"] { box-sizing:border-box !important; align-self:flex-start !important; justify-content:space-between !important; min-width:0 !important; min-height:44px !important; max-width:100% !important; }
|
|
1218
1600
|
[data-dsh-mobile-settings-options] :is(input,select,textarea,button) { max-width:100%; }
|
|
@@ -1281,8 +1663,9 @@ window.__ModuleLoader__.load({
|
|
|
1281
1663
|
[data-dsh-mobile-center] [role="dialog"][aria-label*="Context"] :is(dt,dd) { white-space:nowrap !important; word-break:keep-all !important; }
|
|
1282
1664
|
.dsh-mobile-branch-toast,.dsh-mobile-media-toast { display:block; position:fixed; z-index:330; top:max(12px,env(safe-area-inset-top)); left:50%; max-width:calc(100vw - 32px); box-sizing:border-box; padding:7px 14px; border:1px solid rgb(15 23 42 / 10%); border-radius:999px; background:rgb(15 23 42 / 92%); color:#fff; font-size:13px; line-height:20px; text-align:center; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; opacity:0; pointer-events:none; transform:translate(-50%,-8px); transition:opacity 160ms ease,transform 160ms ease; }
|
|
1283
1665
|
.dsh-mobile-branch-toast[data-visible="true"],.dsh-mobile-media-toast[data-visible="true"] { opacity:1; transform:translate(-50%,0); }
|
|
1284
|
-
|
|
1285
|
-
|
|
1666
|
+
/* Fallback only: when the native Add group has no item to clone, keep the
|
|
1667
|
+
camera action on the same row metrics and tokens as the native menu. */
|
|
1668
|
+
.dsh-mobile-media-action { box-sizing:border-box; display:flex; align-items:center; justify-content:flex-start; gap:8px; width:100%; min-width:0; min-height:40px; padding:8px 10px; border:0; border-radius:10px; background:transparent; color:var(--dsw-alias-label-primary,inherit); cursor:pointer; font:inherit; font-size:14px; line-height:22px; text-align:left; touch-action:manipulation; }
|
|
1286
1669
|
.dsh-mobile-media-action:active { opacity:.72; }
|
|
1287
1670
|
.dsh-mobile-media-action:focus-visible { outline:2px solid var(--dsw-alias-interactive-border-focus,#4c82f7); outline-offset:1px; }
|
|
1288
1671
|
.dsh-mobile-media-action:disabled { cursor:default; opacity:.38; }
|
|
@@ -1416,19 +1799,12 @@ window.__ModuleLoader__.load({
|
|
|
1416
1799
|
const language = resolveNativeMobileLanguage(document.documentElement.lang, browserLanguages);
|
|
1417
1800
|
const restoreLanguageMarker = applyNativeMobileLanguageMarker(document.documentElement, language);
|
|
1418
1801
|
const label = (italian, english, chinese) => language === "it" ? italian : language === "zh" ? chinese : english;
|
|
1419
|
-
const mediaIcon = (
|
|
1802
|
+
const mediaIcon = () => {
|
|
1420
1803
|
const namespace = "http://www.w3.org/2000/svg";
|
|
1421
1804
|
const icon = document.createElementNS(namespace, "svg");
|
|
1422
1805
|
icon.setAttribute("viewBox", "0 0 16 16");
|
|
1423
1806
|
icon.setAttribute("fill", "none");
|
|
1424
1807
|
icon.setAttribute("aria-hidden", "true");
|
|
1425
|
-
if (kind === "attachment") {
|
|
1426
|
-
const path = document.createElementNS(namespace, "path");
|
|
1427
|
-
path.setAttribute("d", "M5.5498 9.75V5H6.9502V9.75C6.9502 10.3299 7.4201 10.7998 8 10.7998C8.5799 10.7998 9.0498 10.3299 9.0498 9.75V4.5C9.0498 2.9536 7.7964 1.7002 6.25 1.7002C4.7036 1.7002 3.4502 2.9536 3.4502 4.5V9.75C3.4502 12.2629 5.4871 14.2998 8 14.2998C10.5129 14.2998 12.5498 12.2629 12.5498 9.75V4H13.9502V9.75C13.9502 13.0361 11.2861 15.7002 8 15.7002C4.71391 15.7002 2.0498 13.0361 2.0498 9.75V4.5C2.04981 2.1804 3.9304 0.299806 6.25 0.299805C8.5696 0.299805 10.4502 2.1804 10.4502 4.5V9.75C10.4502 11.1031 9.3531 12.2002 8 12.2002C6.6469 12.2002 5.5498 11.1031 5.5498 9.75Z");
|
|
1428
|
-
path.setAttribute("fill", "currentColor");
|
|
1429
|
-
icon.append(path);
|
|
1430
|
-
return icon;
|
|
1431
|
-
}
|
|
1432
1808
|
const body = document.createElementNS(namespace, "path");
|
|
1433
1809
|
body.setAttribute("d", "M5.15 3.2 6.05 2h3.9l.9 1.2h1.45c1.05 0 1.9.85 1.9 1.9v6c0 1.05-.85 1.9-1.9 1.9H3.7a1.9 1.9 0 0 1-1.9-1.9v-6c0-1.05.85-1.9 1.9-1.9h1.45Zm-1.45 1.3a.6.6 0 0 0-.6.6v6c0 .33.27.6.6.6h8.6a.6.6 0 0 0 .6-.6v-6a.6.6 0 0 0-.6-.6h-2.1l-.9-1.2H6.7l-.9 1.2H3.7Z");
|
|
1434
1810
|
body.setAttribute("fill", "currentColor");
|
|
@@ -1441,13 +1817,15 @@ window.__ModuleLoader__.load({
|
|
|
1441
1817
|
icon.append(body, lens);
|
|
1442
1818
|
return icon;
|
|
1443
1819
|
};
|
|
1444
|
-
const createMediaAction = (
|
|
1820
|
+
const createMediaAction = (text) => {
|
|
1445
1821
|
const button = document.createElement("button");
|
|
1446
1822
|
button.type = "button";
|
|
1447
1823
|
button.className = "dsh-mobile-media-action";
|
|
1448
1824
|
button.lang = language;
|
|
1449
|
-
button.dataset.dshMobileMediaAction =
|
|
1450
|
-
|
|
1825
|
+
button.dataset.dshMobileMediaAction = "camera";
|
|
1826
|
+
const icon = document.createElement("span");
|
|
1827
|
+
icon.append(mediaIcon());
|
|
1828
|
+
button.append(icon);
|
|
1451
1829
|
const caption = document.createElement("span");
|
|
1452
1830
|
caption.textContent = text;
|
|
1453
1831
|
button.append(caption);
|
|
@@ -1484,17 +1862,8 @@ window.__ModuleLoader__.load({
|
|
|
1484
1862
|
mediaToast.setAttribute("role", "status");
|
|
1485
1863
|
mediaToast.setAttribute("aria-live", "polite");
|
|
1486
1864
|
document.body.append(mediaToast);
|
|
1487
|
-
const
|
|
1488
|
-
mediaActions.className = "dsh-mobile-media-shortcuts";
|
|
1489
|
-
mediaActions.lang = language;
|
|
1490
|
-
mediaActions.dataset.dshMobileMediaShortcuts = "true";
|
|
1491
|
-
mediaActions.setAttribute("role", "group");
|
|
1492
|
-
mediaActions.setAttribute("aria-label", label("Aggiungi immagine", "Add image", "添加图片"));
|
|
1493
|
-
const fileButton = createMediaAction("attachment", label("Scegli immagine", "Choose image", "选择图片"));
|
|
1494
|
-
const cameraButton = createMediaAction("camera", label("Scatta foto", "Take photo", "拍照"));
|
|
1495
|
-
fileButton.disabled = true;
|
|
1865
|
+
const cameraButton = createMediaAction(label("Scatta foto", "Take photo", "拍照"));
|
|
1496
1866
|
cameraButton.disabled = true;
|
|
1497
|
-
mediaActions.append(fileButton, cameraButton);
|
|
1498
1867
|
let branchToastTimer = 0;
|
|
1499
1868
|
let mediaToastTimer = 0;
|
|
1500
1869
|
const showMediaToast = (message) => {
|
|
@@ -1566,11 +1935,11 @@ window.__ModuleLoader__.load({
|
|
|
1566
1935
|
if (files.length === 0 || !mediaRequestIsCurrent(context)) return;
|
|
1567
1936
|
dispatchComposerImageDrop(document, files);
|
|
1568
1937
|
};
|
|
1569
|
-
const
|
|
1938
|
+
const launchBrowserCamera = (context) => {
|
|
1570
1939
|
const input = document.createElement("input");
|
|
1571
1940
|
input.type = "file";
|
|
1572
|
-
input.accept = "image
|
|
1573
|
-
|
|
1941
|
+
input.accept = "image/*";
|
|
1942
|
+
input.capture = "environment";
|
|
1574
1943
|
input.hidden = true;
|
|
1575
1944
|
const signal = mediaPickerAbortController.signal;
|
|
1576
1945
|
let cleanupTimer = 0;
|
|
@@ -1619,17 +1988,51 @@ window.__ModuleLoader__.load({
|
|
|
1619
1988
|
input.click();
|
|
1620
1989
|
} catch {
|
|
1621
1990
|
cleanup();
|
|
1622
|
-
if (mediaRequestIsCurrent(context)) showMediaToast(label("Impossibile aprire
|
|
1991
|
+
if (mediaRequestIsCurrent(context)) showMediaToast(label("Impossibile aprire la fotocamera", "Could not open the camera", "无法打开相机"));
|
|
1623
1992
|
}
|
|
1624
1993
|
};
|
|
1625
1994
|
const dismissCommandMenu = () => {
|
|
1626
1995
|
(boundComposer?.querySelector("button[aria-haspopup=\"listbox\"][aria-expanded=\"true\"]"))?.click();
|
|
1627
1996
|
};
|
|
1628
|
-
const
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1997
|
+
const addGroupLabel = label("Aggiungi", "Add", "添加");
|
|
1998
|
+
const placeCameraAction = (commandMenu) => {
|
|
1999
|
+
const sectionTitle = Array.from(commandMenu.querySelectorAll("[role=\"listbox\"] [role=\"presentation\"]")).find((candidate) => !candidate.hasAttribute("data-source") && candidate.textContent?.trim() === addGroupLabel);
|
|
2000
|
+
if (sectionTitle === void 0) {
|
|
2001
|
+
cameraButton.remove();
|
|
2002
|
+
return false;
|
|
1632
2003
|
}
|
|
2004
|
+
cameraButton.className = "dsh-mobile-media-action";
|
|
2005
|
+
const nativeItem = (() => {
|
|
2006
|
+
let sibling = sectionTitle.nextElementSibling;
|
|
2007
|
+
while (sibling instanceof HTMLElement && sibling.getAttribute("role") !== "presentation") {
|
|
2008
|
+
if (sibling.matches("button[role=\"option\"]:not([aria-selected=\"true\"])")) return sibling;
|
|
2009
|
+
sibling = sibling.nextElementSibling;
|
|
2010
|
+
}
|
|
2011
|
+
})();
|
|
2012
|
+
if (nativeItem !== void 0) {
|
|
2013
|
+
cameraButton.className = nativeItem.className;
|
|
2014
|
+
const cameraIcon = cameraButton.firstElementChild;
|
|
2015
|
+
const cameraLabel = cameraButton.lastElementChild;
|
|
2016
|
+
const nativeIcon = firstByClassSuffix(nativeItem, "_itemIcon");
|
|
2017
|
+
const nativeLabel = firstByClassSuffix(nativeItem, "_itemName");
|
|
2018
|
+
if (cameraIcon instanceof HTMLElement && nativeIcon !== void 0) cameraIcon.className = nativeIcon.className;
|
|
2019
|
+
if (cameraLabel instanceof HTMLElement && nativeLabel !== void 0) cameraLabel.className = nativeLabel.className;
|
|
2020
|
+
}
|
|
2021
|
+
cameraButton.setAttribute("role", "option");
|
|
2022
|
+
cameraButton.setAttribute("aria-selected", "false");
|
|
2023
|
+
cameraButton.setAttribute("aria-label", label("Scatta foto", "Take photo", "拍照"));
|
|
2024
|
+
let lastItem = sectionTitle;
|
|
2025
|
+
let sibling = sectionTitle.nextElementSibling;
|
|
2026
|
+
while (sibling instanceof HTMLElement && sibling.getAttribute("role") !== "presentation") {
|
|
2027
|
+
if (sibling !== cameraButton) lastItem = sibling;
|
|
2028
|
+
sibling = sibling.nextElementSibling;
|
|
2029
|
+
}
|
|
2030
|
+
if (cameraButton.parentElement !== sectionTitle.parentElement || cameraButton.previousElementSibling !== lastItem) lastItem.after(cameraButton);
|
|
2031
|
+
return true;
|
|
2032
|
+
};
|
|
2033
|
+
const setCameraActionDisabled = (disabled, title) => {
|
|
2034
|
+
if (cameraButton.disabled !== disabled) cameraButton.disabled = disabled;
|
|
2035
|
+
if (cameraButton.title !== title) cameraButton.title = title;
|
|
1633
2036
|
};
|
|
1634
2037
|
const quietMediaPointer = (event) => {
|
|
1635
2038
|
event.preventDefault();
|
|
@@ -1637,9 +2040,9 @@ window.__ModuleLoader__.load({
|
|
|
1637
2040
|
const active = document.activeElement;
|
|
1638
2041
|
if (active instanceof HTMLElement && (active.matches("input,textarea") || active.isContentEditable)) active.blur();
|
|
1639
2042
|
};
|
|
1640
|
-
const
|
|
2043
|
+
const takePhoto = () => {
|
|
1641
2044
|
if (!canAcceptComposerDrop()) {
|
|
1642
|
-
|
|
2045
|
+
setCameraActionDisabled(true, label("Allegati immagine non disponibili", "Image attachments are unavailable", "图片附件不可用"));
|
|
1643
2046
|
dismissCommandMenu();
|
|
1644
2047
|
return;
|
|
1645
2048
|
}
|
|
@@ -1647,36 +2050,21 @@ window.__ModuleLoader__.load({
|
|
|
1647
2050
|
dismissCommandMenu();
|
|
1648
2051
|
const bridge = window.__DSH_MOBILE_NATIVE__;
|
|
1649
2052
|
if (bridge === void 0) {
|
|
1650
|
-
|
|
2053
|
+
launchBrowserCamera(context);
|
|
1651
2054
|
return;
|
|
1652
2055
|
}
|
|
1653
|
-
|
|
1654
|
-
const input = camera ? {} : { accept: [
|
|
1655
|
-
"image/png",
|
|
1656
|
-
"image/jpeg",
|
|
1657
|
-
"image/webp",
|
|
1658
|
-
"image/gif"
|
|
1659
|
-
] };
|
|
1660
|
-
Promise.resolve().then(() => bridge.invoke(action, input)).then((value) => {
|
|
2056
|
+
Promise.resolve().then(() => bridge.invoke("camera.capture", {})).then((value) => {
|
|
1661
2057
|
if (!mediaRequestIsCurrent(context)) return;
|
|
1662
2058
|
if (value instanceof File) deliverImages([value], context);
|
|
1663
|
-
else showMediaToast(label("
|
|
2059
|
+
else showMediaToast(label("La foto scattata non è utilizzabile", "The captured photo is unavailable", "所拍照片不可用"));
|
|
1664
2060
|
}).catch((error) => {
|
|
1665
2061
|
if (!mediaRequestIsCurrent(context)) return;
|
|
1666
2062
|
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
|
|
1667
2063
|
if (code === "cancelled") return;
|
|
1668
|
-
showMediaToast(code === "payload_too_large" ? label("
|
|
2064
|
+
showMediaToast(code === "payload_too_large" ? label("La foto supera il limite di 8 MiB", "The photo exceeds the 8 MiB limit", "照片超过 8 MiB 限制") : label("Impossibile aggiungere la foto", "Could not attach the photo", "无法附加照片"));
|
|
1669
2065
|
});
|
|
1670
2066
|
};
|
|
1671
|
-
const chooseImage = () => {
|
|
1672
|
-
pickImage(false);
|
|
1673
|
-
};
|
|
1674
|
-
const takePhoto = () => {
|
|
1675
|
-
pickImage(true);
|
|
1676
|
-
};
|
|
1677
|
-
fileButton.addEventListener("pointerdown", quietMediaPointer);
|
|
1678
2067
|
cameraButton.addEventListener("pointerdown", quietMediaPointer);
|
|
1679
|
-
fileButton.addEventListener("click", chooseImage);
|
|
1680
2068
|
cameraButton.addEventListener("click", takePhoto);
|
|
1681
2069
|
const showBranchToast = () => {
|
|
1682
2070
|
const header = document.querySelector("[data-dsh-mobile-header]");
|
|
@@ -1785,8 +2173,8 @@ window.__ModuleLoader__.load({
|
|
|
1785
2173
|
if (center === void 0) {
|
|
1786
2174
|
bindHistoryScroller(void 0);
|
|
1787
2175
|
syncMediaBinding(null);
|
|
1788
|
-
|
|
1789
|
-
|
|
2176
|
+
setCameraActionDisabled(true, label("Apri prima una sessione", "Open a session first", "请先打开会话"));
|
|
2177
|
+
cameraButton.remove();
|
|
1790
2178
|
return;
|
|
1791
2179
|
}
|
|
1792
2180
|
if (center !== void 0) {
|
|
@@ -1824,8 +2212,8 @@ window.__ModuleLoader__.load({
|
|
|
1824
2212
|
const composerRow = composerCard?.querySelector(":scope > [data-input-scroll]")?.nextElementSibling;
|
|
1825
2213
|
if (!(composerRow instanceof HTMLElement)) {
|
|
1826
2214
|
syncMediaBinding(null);
|
|
1827
|
-
|
|
1828
|
-
|
|
2215
|
+
setCameraActionDisabled(true, label("Apri prima una sessione", "Open a session first", "请先打开会话"));
|
|
2216
|
+
cameraButton.remove();
|
|
1829
2217
|
}
|
|
1830
2218
|
if (composerRow instanceof HTMLElement) {
|
|
1831
2219
|
composerRow.dataset.dshMobileComposerRow = "true";
|
|
@@ -1840,10 +2228,11 @@ window.__ModuleLoader__.load({
|
|
|
1840
2228
|
const composerBusy = composerCard?.getAttribute("aria-busy") === "true" || composerInput?.disabled === true || composerInput?.readOnly === true || composerEditor?.getAttribute("aria-disabled") === "true";
|
|
1841
2229
|
const attachmentBlocked = !canAcceptComposerDrop();
|
|
1842
2230
|
const mediaDisabled = conversation === null || currentSessionOrigin().sessionId === null || composerBusy || attachmentBlocked;
|
|
1843
|
-
const mediaTitle = composerBusy ? label("Attendi il completamento della risposta", "Wait for the response to finish", "请等待回复完成") : attachmentBlocked ? label("Allegati immagine non disponibili", "Image attachments are unavailable", "图片附件不可用") : mediaDisabled ? label("Apri prima una sessione", "Open a session first", "请先打开会话") : label("
|
|
1844
|
-
|
|
2231
|
+
const mediaTitle = composerBusy ? label("Attendi il completamento della risposta", "Wait for the response to finish", "请等待回复完成") : attachmentBlocked ? label("Allegati immagine non disponibili", "Image attachments are unavailable", "图片附件不可用") : mediaDisabled ? label("Apri prima una sessione", "Open a session first", "请先打开会话") : label("Scatta una foto e allegala", "Take a photo and attach it", "拍照并附加到当前消息");
|
|
2232
|
+
setCameraActionDisabled(mediaDisabled, mediaTitle);
|
|
1845
2233
|
const commandMenu = composerCard?.querySelector("[data-trigger-menu]") ?? null;
|
|
1846
|
-
if (commandMenu !== null
|
|
2234
|
+
if (commandMenu !== null) placeCameraAction(commandMenu);
|
|
2235
|
+
else cameraButton.remove();
|
|
1847
2236
|
}
|
|
1848
2237
|
if (composerTrailing !== void 0 && composerTrailing !== composerTools) {
|
|
1849
2238
|
composerTrailing.dataset.dshMobileComposerTrailing = "true";
|
|
@@ -1899,8 +2288,19 @@ window.__ModuleLoader__.load({
|
|
|
1899
2288
|
if (sidebar?.dataset.open === "true") toggle?.click();
|
|
1900
2289
|
});
|
|
1901
2290
|
sync();
|
|
2291
|
+
const disposeTaskWatcher = installTaskCompletionWatcher({
|
|
2292
|
+
completionFallback: false,
|
|
2293
|
+
label: (kind, sessionLabel) => kind === "done" ? {
|
|
2294
|
+
title: label("Attività completata", "Task finished", "任务已完成"),
|
|
2295
|
+
body: sessionLabel === "" ? label("Il tuo task DSH è terminato", "Your DSH task finished", "你的 DSH 任务已完成") : label(`Il tuo task DSH è terminato: ${sessionLabel}`, `Your DSH task finished: ${sessionLabel}`, `你的 DSH 任务已完成:${sessionLabel}`)
|
|
2296
|
+
} : {
|
|
2297
|
+
title: label("È richiesto un input", "Input needed", "需要你确认"),
|
|
2298
|
+
body: label("DSH attende una tua scelta", "DSH waits for your choice", "DSH 等待你的选择")
|
|
2299
|
+
}
|
|
2300
|
+
});
|
|
1902
2301
|
return () => {
|
|
1903
2302
|
disposed = true;
|
|
2303
|
+
disposeTaskWatcher();
|
|
1904
2304
|
mediaRequestGeneration += 1;
|
|
1905
2305
|
mediaPickerAbortController.abort();
|
|
1906
2306
|
restoreLanguageMarker();
|
|
@@ -1908,15 +2308,13 @@ window.__ModuleLoader__.load({
|
|
|
1908
2308
|
observer.disconnect();
|
|
1909
2309
|
overlayQuery.removeEventListener("change", schedule);
|
|
1910
2310
|
document.removeEventListener("click", onBranchClick, true);
|
|
1911
|
-
fileButton.removeEventListener("pointerdown", quietMediaPointer);
|
|
1912
2311
|
cameraButton.removeEventListener("pointerdown", quietMediaPointer);
|
|
1913
|
-
fileButton.removeEventListener("click", chooseImage);
|
|
1914
2312
|
cameraButton.removeEventListener("click", takePhoto);
|
|
1915
2313
|
if (branchToastTimer !== 0) window.clearTimeout(branchToastTimer);
|
|
1916
2314
|
if (mediaToastTimer !== 0) window.clearTimeout(mediaToastTimer);
|
|
1917
2315
|
branchToast.remove();
|
|
1918
2316
|
mediaToast.remove();
|
|
1919
|
-
|
|
2317
|
+
cameraButton.remove();
|
|
1920
2318
|
if (scheduled !== 0) cancelAnimationFrame(scheduled);
|
|
1921
2319
|
if (transitionFrame !== 0) cancelAnimationFrame(transitionFrame);
|
|
1922
2320
|
if (transitionRestartFrame !== 0) cancelAnimationFrame(transitionRestartFrame);
|
|
@@ -1932,6 +2330,55 @@ window.__ModuleLoader__.load({
|
|
|
1932
2330
|
};
|
|
1933
2331
|
}
|
|
1934
2332
|
//#endregion
|
|
2333
|
+
//#region src/local-admin-host.ts
|
|
2334
|
+
/** Browser-safe desktop-admin Host checks. Do not import Node APIs here. */
|
|
2335
|
+
function unwrapBrackets(hostname) {
|
|
2336
|
+
return hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
|
|
2337
|
+
}
|
|
2338
|
+
function parseIpv4(hostname) {
|
|
2339
|
+
const parts = hostname.split(".");
|
|
2340
|
+
if (parts.length !== 4) return void 0;
|
|
2341
|
+
const octets = [];
|
|
2342
|
+
for (const part of parts) {
|
|
2343
|
+
if (!/^(?:0|[1-9]\d{0,2})$/u.test(part)) return void 0;
|
|
2344
|
+
const value = Number(part);
|
|
2345
|
+
if (value > 255) return void 0;
|
|
2346
|
+
octets.push(value);
|
|
2347
|
+
}
|
|
2348
|
+
return octets;
|
|
2349
|
+
}
|
|
2350
|
+
function isLoopbackIpv4(octets) {
|
|
2351
|
+
return octets[0] === 127;
|
|
2352
|
+
}
|
|
2353
|
+
function isPrivateIpv4(octets) {
|
|
2354
|
+
return octets[0] === 10 || octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31 || octets[0] === 192 && octets[1] === 168;
|
|
2355
|
+
}
|
|
2356
|
+
function isLinkLocalIpv4(octets) {
|
|
2357
|
+
return octets[0] === 169 && octets[1] === 254;
|
|
2358
|
+
}
|
|
2359
|
+
/**
|
|
2360
|
+
* Hostnames that may appear on the DSH desktop admin surface.
|
|
2361
|
+
* Loopback, RFC1918, and IPv4 link-local are accepted. Public IPs,
|
|
2362
|
+
* CGNAT, and arbitrary DNS names stay rejected so DNS rebinding
|
|
2363
|
+
* cannot reach `/api/mobile-access`.
|
|
2364
|
+
*/
|
|
2365
|
+
function isLocalAdminHostname(hostname) {
|
|
2366
|
+
const host = unwrapBrackets(hostname).trim().toLowerCase();
|
|
2367
|
+
if (host === "localhost" || host === "::1") return true;
|
|
2368
|
+
const octets = parseIpv4(host);
|
|
2369
|
+
if (octets === void 0) return false;
|
|
2370
|
+
return isLoopbackIpv4(octets) || isPrivateIpv4(octets) || isLinkLocalIpv4(octets);
|
|
2371
|
+
}
|
|
2372
|
+
/**
|
|
2373
|
+
* Whether the current browser document should mount the desktop Mobile access
|
|
2374
|
+
* control. Dedicated Mobile HTTPS (the phone surface) stays native even when
|
|
2375
|
+
* the Host is a private LAN address.
|
|
2376
|
+
*/
|
|
2377
|
+
function isDesktopAdminSurface(hostname, search = "", frontend) {
|
|
2378
|
+
const query = search.startsWith("?") ? search.slice(1) : search;
|
|
2379
|
+
return isLocalAdminHostname(hostname) && frontend !== "dedicated" && !new URLSearchParams(query).has("dsh-mobile-preview");
|
|
2380
|
+
}
|
|
2381
|
+
//#endregion
|
|
1935
2382
|
//#region src/client.ts
|
|
1936
2383
|
const queuedDefinitions = [];
|
|
1937
2384
|
let queuedLegacyMount;
|
|
@@ -1943,9 +2390,6 @@ window.__ModuleLoader__.load({
|
|
|
1943
2390
|
queuedDefinitions.push(definition);
|
|
1944
2391
|
}
|
|
1945
2392
|
};
|
|
1946
|
-
function isLoopbackHost(hostname) {
|
|
1947
|
-
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
1948
|
-
}
|
|
1949
2393
|
function selectMobileControlLocale(documentLanguage = "", navigatorLanguages = []) {
|
|
1950
2394
|
for (const value of [documentLanguage, ...navigatorLanguages]) {
|
|
1951
2395
|
const language = value.trim().toLowerCase().split(/[-_]/u)[0];
|
|
@@ -2053,6 +2497,82 @@ window.__ModuleLoader__.load({
|
|
|
2053
2497
|
if (className !== void 0) node.className = className;
|
|
2054
2498
|
return node;
|
|
2055
2499
|
}
|
|
2500
|
+
function mobileSwitchComputerCopy(locale) {
|
|
2501
|
+
if (locale === "zh") return {
|
|
2502
|
+
title: "切换电脑",
|
|
2503
|
+
description: "返回已配对设备列表,选择要打开的电脑。",
|
|
2504
|
+
action: "切换电脑",
|
|
2505
|
+
busy: "正在切换…",
|
|
2506
|
+
retry: "重试",
|
|
2507
|
+
error: "切换失败,请重试。"
|
|
2508
|
+
};
|
|
2509
|
+
if (locale === "it") return {
|
|
2510
|
+
title: "Cambia computer",
|
|
2511
|
+
description: "Torna all’elenco dei computer associati e scegli quale aprire.",
|
|
2512
|
+
action: "Cambia computer",
|
|
2513
|
+
busy: "Cambio in corso…",
|
|
2514
|
+
retry: "Riprova",
|
|
2515
|
+
error: "Cambio non riuscito. Riprova."
|
|
2516
|
+
};
|
|
2517
|
+
return {
|
|
2518
|
+
title: "Switch computer",
|
|
2519
|
+
description: "Return to the paired-device list and choose a computer to open.",
|
|
2520
|
+
action: "Switch computer",
|
|
2521
|
+
busy: "Switching…",
|
|
2522
|
+
retry: "Retry",
|
|
2523
|
+
error: "Could not switch computers. Try again."
|
|
2524
|
+
};
|
|
2525
|
+
}
|
|
2526
|
+
/** Render the native-app-only action in DSH's General settings row style. */
|
|
2527
|
+
function MobileSwitchComputerRow() {
|
|
2528
|
+
const [nativeReady, setNativeReady] = (0, react.useState)(false);
|
|
2529
|
+
const [state, setState] = (0, react.useState)("idle");
|
|
2530
|
+
(0, react.useEffect)(() => {
|
|
2531
|
+
let disposed = false;
|
|
2532
|
+
const checkNativeAction = () => {
|
|
2533
|
+
const bridge = window.__DSH_MOBILE_NATIVE__;
|
|
2534
|
+
if (bridge === void 0) {
|
|
2535
|
+
setNativeReady(false);
|
|
2536
|
+
return;
|
|
2537
|
+
}
|
|
2538
|
+
Promise.resolve().then(() => bridge.capabilities()).then((capabilities) => {
|
|
2539
|
+
if (!disposed) setNativeReady(capabilities.includes("mobile.switch-computer"));
|
|
2540
|
+
}, () => {
|
|
2541
|
+
if (!disposed) setNativeReady(false);
|
|
2542
|
+
});
|
|
2543
|
+
};
|
|
2544
|
+
checkNativeAction();
|
|
2545
|
+
window.addEventListener("dsh-mobile-native-ready", checkNativeAction);
|
|
2546
|
+
return () => {
|
|
2547
|
+
disposed = true;
|
|
2548
|
+
window.removeEventListener("dsh-mobile-native-ready", checkNativeAction);
|
|
2549
|
+
};
|
|
2550
|
+
}, []);
|
|
2551
|
+
if (!nativeReady) return null;
|
|
2552
|
+
const locale = selectedMobileControlLocale();
|
|
2553
|
+
const copy = mobileSwitchComputerCopy(locale);
|
|
2554
|
+
const invoke = () => {
|
|
2555
|
+
const bridge = window.__DSH_MOBILE_NATIVE__;
|
|
2556
|
+
if (bridge === void 0 || state === "busy") return;
|
|
2557
|
+
setState("busy");
|
|
2558
|
+
bridge.invoke("mobile.switch-computer", {}).catch(() => {
|
|
2559
|
+
setState("error");
|
|
2560
|
+
});
|
|
2561
|
+
};
|
|
2562
|
+
return (0, react.createElement)("div", {
|
|
2563
|
+
className: "dsh-mobile-settings_row",
|
|
2564
|
+
lang: locale
|
|
2565
|
+
}, (0, react.createElement)("div", { className: "dsh-mobile-settings_rowText" }, (0, react.createElement)("div", { className: "dsh-mobile-settings_title" }, copy.title), (0, react.createElement)("div", {
|
|
2566
|
+
className: "dsh-mobile-settings_desc",
|
|
2567
|
+
"aria-live": "polite"
|
|
2568
|
+
}, state === "error" ? copy.error : copy.description)), (0, react.createElement)("button", {
|
|
2569
|
+
type: "button",
|
|
2570
|
+
className: "dsh-mobile-settings_selector",
|
|
2571
|
+
disabled: state === "busy",
|
|
2572
|
+
"aria-busy": state === "busy" ? "true" : void 0,
|
|
2573
|
+
onClick: invoke
|
|
2574
|
+
}, state === "busy" ? copy.busy : state === "error" ? copy.retry : copy.action));
|
|
2575
|
+
}
|
|
2056
2576
|
const CONTROL_REQUEST_TIMEOUT_MS = 15e3;
|
|
2057
2577
|
const LONG_CONTROL_REQUEST_TIMEOUT_MS = 21e4;
|
|
2058
2578
|
const GITHUB_RELEASES_URL = "https://github.com/saya-ch/dsh-mobile/releases";
|
|
@@ -2193,6 +2713,33 @@ window.__ModuleLoader__.load({
|
|
|
2193
2713
|
remoteTab.setAttribute("aria-pressed", "false");
|
|
2194
2714
|
switcher.append(lanTab, remoteTab);
|
|
2195
2715
|
const lanView = element("div", "dsh-mobile-control__view");
|
|
2716
|
+
const lanSetup = element("section", "dsh-mobile-control__lan-setup");
|
|
2717
|
+
lanSetup.hidden = true;
|
|
2718
|
+
const lanSetupTitle = element("h3", "dsh-mobile-control__section-title");
|
|
2719
|
+
lanSetupTitle.textContent = t("lanSetupTitle");
|
|
2720
|
+
const lanSetupIntro = element("p", "dsh-mobile-control__intro");
|
|
2721
|
+
lanSetupIntro.textContent = t("lanSetupIntro");
|
|
2722
|
+
const lanSetupForm = element("div", "dsh-mobile-control__lan-setup-form");
|
|
2723
|
+
const lanSetupLabel = element("label", "dsh-mobile-control__field");
|
|
2724
|
+
lanSetupLabel.textContent = t("lanSetupNetwork");
|
|
2725
|
+
const lanSetupSelect = element("select");
|
|
2726
|
+
lanSetupSelect.setAttribute("aria-label", t("lanSetupNetwork"));
|
|
2727
|
+
const lanSetupNote = element("p", "dsh-mobile-control__component-note");
|
|
2728
|
+
lanSetupNote.textContent = t("lanSetupFirewallNote");
|
|
2729
|
+
const lanSetupActions = element("div", "dsh-mobile-control__lan-setup-actions");
|
|
2730
|
+
const lanSetupRefresh = element("button", "dsh-mobile-control__secondary");
|
|
2731
|
+
lanSetupRefresh.type = "button";
|
|
2732
|
+
lanSetupRefresh.textContent = t("lanSetupRefresh");
|
|
2733
|
+
const lanSetupConfigure = element("button", "dsh-mobile-control__primary");
|
|
2734
|
+
lanSetupConfigure.type = "button";
|
|
2735
|
+
lanSetupConfigure.textContent = t("lanSetupConfigure");
|
|
2736
|
+
const lanSetupStatus = element("p", "dsh-mobile-control__status");
|
|
2737
|
+
lanSetupStatus.setAttribute("role", "status");
|
|
2738
|
+
lanSetupStatus.setAttribute("aria-live", "polite");
|
|
2739
|
+
lanSetupLabel.append(lanSetupSelect);
|
|
2740
|
+
lanSetupActions.append(lanSetupRefresh, lanSetupConfigure);
|
|
2741
|
+
lanSetupForm.append(lanSetupLabel, lanSetupNote, lanSetupActions);
|
|
2742
|
+
lanSetup.append(lanSetupTitle, lanSetupIntro, lanSetupForm, lanSetupStatus);
|
|
2196
2743
|
const access = element("div", "dsh-mobile-control__access");
|
|
2197
2744
|
access.hidden = true;
|
|
2198
2745
|
const accessLabel = element("span", "dsh-mobile-control__access-label");
|
|
@@ -2230,6 +2777,10 @@ window.__ModuleLoader__.load({
|
|
|
2230
2777
|
remoteView.hidden = true;
|
|
2231
2778
|
const remoteIntro = element("p", "dsh-mobile-control__intro");
|
|
2232
2779
|
remoteIntro.textContent = t("remoteIntro");
|
|
2780
|
+
const httpFrameWarning = element("p", "dsh-mobile-control__http-frame-warning");
|
|
2781
|
+
httpFrameWarning.textContent = t("httpFrameWarning");
|
|
2782
|
+
httpFrameWarning.hidden = location.protocol !== "https:";
|
|
2783
|
+
httpFrameWarning.setAttribute("role", "note");
|
|
2233
2784
|
const providerSection = element("section", "dsh-mobile-control__provider-section");
|
|
2234
2785
|
const providerHeading = element("h3", "dsh-mobile-control__section-title");
|
|
2235
2786
|
providerHeading.textContent = t("chooseProvider");
|
|
@@ -2625,7 +3176,15 @@ window.__ModuleLoader__.load({
|
|
|
2625
3176
|
remotePair.type = "button";
|
|
2626
3177
|
remotePair.textContent = t("generateRemoteQr");
|
|
2627
3178
|
remotePair.disabled = true;
|
|
2628
|
-
|
|
3179
|
+
const remoteCopyLink = element("button", "dsh-mobile-control__secondary");
|
|
3180
|
+
remoteCopyLink.type = "button";
|
|
3181
|
+
remoteCopyLink.textContent = t("copyRemoteLink");
|
|
3182
|
+
remoteCopyLink.disabled = true;
|
|
3183
|
+
const remotePairLink = element("p", "dsh-mobile-control__status");
|
|
3184
|
+
remotePairLink.hidden = true;
|
|
3185
|
+
let activeRemotePairUrl = "";
|
|
3186
|
+
let activeRemotePairExpiresAt = 0;
|
|
3187
|
+
remoteActions.append(remoteToggle, remoteLogin, remoteReconnect, remotePair, remoteCopyLink);
|
|
2629
3188
|
const remoteManageRow = element("div", "dsh-mobile-control__manage-row");
|
|
2630
3189
|
const remoteDevices = element("button", "dsh-mobile-control__manage");
|
|
2631
3190
|
remoteDevices.type = "button";
|
|
@@ -2821,7 +3380,7 @@ window.__ModuleLoader__.load({
|
|
|
2821
3380
|
saveWsPaths([...wsPathsCurrent, value]);
|
|
2822
3381
|
});
|
|
2823
3382
|
const remoteWorkspace = element("section", "dsh-mobile-control__remote-workspace");
|
|
2824
|
-
remoteWorkspace.append(providerSetupHeader, remoteStatus, remoteAccess, remoteGuide, providerSetupBody, remoteActions, remoteQr, remoteManageRow, remoteDevicePanel);
|
|
3383
|
+
remoteWorkspace.append(providerSetupHeader, remoteStatus, remoteAccess, remoteGuide, providerSetupBody, remoteActions, remoteQr, remotePairLink, remoteManageRow, remoteDevicePanel);
|
|
2825
3384
|
const diagnosticsView = element("div", "dsh-mobile-control__view is-diagnostics");
|
|
2826
3385
|
diagnosticsView.hidden = true;
|
|
2827
3386
|
const diagnosticsIntro = element("p", "dsh-mobile-control__intro");
|
|
@@ -2865,14 +3424,17 @@ window.__ModuleLoader__.load({
|
|
|
2865
3424
|
diagnosticsDetails.append(diagnosticsDetailsSummary, diagnosticsReport);
|
|
2866
3425
|
header.append(title, headerActions);
|
|
2867
3426
|
actions.append(toggle, pair, linkPair);
|
|
2868
|
-
lanView.append(access, qrBox, status, extensionStatus, actions, manageRow, devicePanel);
|
|
2869
|
-
remoteView.append(remoteIntro, providerSection, remoteWorkspace);
|
|
3427
|
+
lanView.append(lanSetup, access, qrBox, status, extensionStatus, actions, manageRow, devicePanel);
|
|
3428
|
+
remoteView.append(remoteIntro, httpFrameWarning, providerSection, remoteWorkspace);
|
|
2870
3429
|
diagnosticsView.append(diagnosticsIntro, diagnosticsSummary, diagnosticsToolbar, diagnosticsFeedback, diagnosticsChecks, wsPathsSection, diagnosticsDetails);
|
|
2871
3430
|
panel.append(header, releaseNotice, updateCard, appDownload, switcher, lanView, remoteView, diagnosticsView);
|
|
2872
3431
|
root.append(panel);
|
|
2873
3432
|
document.body.append(root);
|
|
2874
3433
|
let running = false;
|
|
2875
3434
|
let origin = "";
|
|
3435
|
+
let lanConfigured = true;
|
|
3436
|
+
let lanRestartRequired = false;
|
|
3437
|
+
let lanSetupLoaded = false;
|
|
2876
3438
|
let remoteRunning = false;
|
|
2877
3439
|
let remoteReady = false;
|
|
2878
3440
|
let remoteProvider = "tailscale";
|
|
@@ -2976,14 +3538,25 @@ window.__ModuleLoader__.load({
|
|
|
2976
3538
|
for (const trigger of document.querySelectorAll(".dsh-mobile-control__trigger")) trigger.setAttribute("aria-expanded", String(open));
|
|
2977
3539
|
};
|
|
2978
3540
|
const render = (data) => {
|
|
3541
|
+
lanConfigured = data.configured !== false;
|
|
3542
|
+
lanRestartRequired = data.restartRequired === true;
|
|
2979
3543
|
running = data.running === true;
|
|
2980
3544
|
origin = running && typeof data.origin === "string" ? data.origin : "";
|
|
3545
|
+
lanSetup.hidden = lanConfigured && !lanRestartRequired;
|
|
3546
|
+
lanSetupForm.hidden = lanRestartRequired;
|
|
3547
|
+
lanSetupIntro.hidden = lanRestartRequired;
|
|
3548
|
+
status.hidden = !lanConfigured || lanRestartRequired;
|
|
3549
|
+
if (!lanConfigured && !lanSetupLoaded) loadLanSetup();
|
|
3550
|
+
if (lanRestartRequired) {
|
|
3551
|
+
const pendingOrigin = typeof data.pendingOrigin === "string" ? data.pendingOrigin : "";
|
|
3552
|
+
lanSetupStatus.textContent = t("lanSetupRestart", { origin: pendingOrigin });
|
|
3553
|
+
} else if (!lanConfigured && lanSetupStatus.textContent === "") lanSetupStatus.textContent = t("lanSetupRequired");
|
|
2981
3554
|
access.hidden = origin === "";
|
|
2982
3555
|
accessLink.href = origin;
|
|
2983
3556
|
accessLink.textContent = origin;
|
|
2984
3557
|
accessLink.title = origin;
|
|
2985
3558
|
status.classList.toggle("is-running", running);
|
|
2986
|
-
status.textContent = running ? t("lanOn") : t("lanOff");
|
|
3559
|
+
status.textContent = !lanConfigured ? t("lanSetupRequired") : lanRestartRequired ? t("lanSetupRestart", { origin: typeof data.pendingOrigin === "string" ? data.pendingOrigin : "" }) : running ? t("lanOn") : t("lanOff");
|
|
2987
3560
|
const extensionData = data.extensions;
|
|
2988
3561
|
if (extensionData !== null && typeof extensionData === "object") {
|
|
2989
3562
|
const loaded = typeof extensionData.loaded === "number" ? extensionData.loaded : 0;
|
|
@@ -2996,11 +3569,78 @@ window.__ModuleLoader__.load({
|
|
|
2996
3569
|
} else extensionStatus.hidden = true;
|
|
2997
3570
|
if (!running) qrBox.hidden = true;
|
|
2998
3571
|
toggle.textContent = running ? t("disableLan") : t("enableLan");
|
|
3572
|
+
actions.hidden = !lanConfigured || lanRestartRequired;
|
|
3573
|
+
manageRow.hidden = !lanConfigured || lanRestartRequired;
|
|
2999
3574
|
pair.disabled = !running;
|
|
3000
3575
|
linkPair.disabled = !running;
|
|
3001
3576
|
manageDevices.disabled = !running;
|
|
3002
3577
|
resetAll.disabled = !running;
|
|
3003
3578
|
};
|
|
3579
|
+
const renderLanSetup = (data) => {
|
|
3580
|
+
if (data.restartRequired === true || data.configured === true) {
|
|
3581
|
+
render(data);
|
|
3582
|
+
return;
|
|
3583
|
+
}
|
|
3584
|
+
const networks = Array.isArray(data.networks) ? data.networks.filter((entry) => typeof entry === "object" && entry !== null) : [];
|
|
3585
|
+
lanSetupSelect.replaceChildren();
|
|
3586
|
+
const hasRecommended = networks.some((network) => network.recommended === true);
|
|
3587
|
+
if (!hasRecommended && networks.length > 1) {
|
|
3588
|
+
const placeholder = element("option");
|
|
3589
|
+
placeholder.value = "";
|
|
3590
|
+
placeholder.textContent = t("lanSetupChooseNetwork");
|
|
3591
|
+
placeholder.disabled = true;
|
|
3592
|
+
placeholder.selected = true;
|
|
3593
|
+
lanSetupSelect.append(placeholder);
|
|
3594
|
+
}
|
|
3595
|
+
for (const network of networks) {
|
|
3596
|
+
if (typeof network.address !== "string" || typeof network.name !== "string") continue;
|
|
3597
|
+
const option = element("option");
|
|
3598
|
+
option.value = network.address;
|
|
3599
|
+
option.textContent = `${network.name} · ${network.address}${network.recommended === true ? ` · ${t("lanSetupRecommended")}` : ""}`;
|
|
3600
|
+
option.selected = network.recommended === true || !hasRecommended && networks.length === 1;
|
|
3601
|
+
lanSetupSelect.append(option);
|
|
3602
|
+
}
|
|
3603
|
+
const available = networks.some((network) => typeof network.address === "string" && typeof network.name === "string");
|
|
3604
|
+
lanSetupConfigure.disabled = lanSetupSelect.value === "";
|
|
3605
|
+
lanSetupStatus.textContent = available ? t("lanSetupRequired") : t("lanSetupNoNetworks");
|
|
3606
|
+
lanSetupNote.hidden = data.windowsFirewall !== true;
|
|
3607
|
+
};
|
|
3608
|
+
lanSetupSelect.addEventListener("change", () => {
|
|
3609
|
+
lanSetupConfigure.disabled = lanSetupSelect.value === "";
|
|
3610
|
+
});
|
|
3611
|
+
const loadLanSetup = () => {
|
|
3612
|
+
lanSetupLoaded = true;
|
|
3613
|
+
lanSetupRefresh.disabled = true;
|
|
3614
|
+
controlRequestJson("/api/mobile-access/lan/setup").then(renderLanSetup, () => {
|
|
3615
|
+
lanSetupStatus.textContent = t("lanSetupFailed");
|
|
3616
|
+
}).finally(() => {
|
|
3617
|
+
lanSetupRefresh.disabled = false;
|
|
3618
|
+
});
|
|
3619
|
+
};
|
|
3620
|
+
lanSetupRefresh.addEventListener("click", () => {
|
|
3621
|
+
loadLanSetup();
|
|
3622
|
+
});
|
|
3623
|
+
lanSetupConfigure.addEventListener("click", () => {
|
|
3624
|
+
if (lanSetupSelect.value === "") return;
|
|
3625
|
+
lanSetupConfigure.disabled = true;
|
|
3626
|
+
lanSetupRefresh.disabled = true;
|
|
3627
|
+
lanSetupConfigure.textContent = t("lanSetupConfiguring");
|
|
3628
|
+
lanSetupStatus.textContent = t("lanSetupConfiguring");
|
|
3629
|
+
controlRequestJson("/api/mobile-access/lan/setup", {
|
|
3630
|
+
method: "POST",
|
|
3631
|
+
body: JSON.stringify({
|
|
3632
|
+
confirm: true,
|
|
3633
|
+
address: lanSetupSelect.value,
|
|
3634
|
+
configureFirewall: true
|
|
3635
|
+
})
|
|
3636
|
+
}, LONG_CONTROL_REQUEST_TIMEOUT_MS).then(renderLanSetup, () => {
|
|
3637
|
+
lanSetupStatus.textContent = t("lanSetupFailed");
|
|
3638
|
+
}).finally(() => {
|
|
3639
|
+
lanSetupConfigure.textContent = t("lanSetupConfigure");
|
|
3640
|
+
lanSetupRefresh.disabled = false;
|
|
3641
|
+
lanSetupConfigure.disabled = lanRestartRequired || lanSetupSelect.options.length === 0;
|
|
3642
|
+
});
|
|
3643
|
+
});
|
|
3004
3644
|
const showQr = (svg, target = qrBox) => {
|
|
3005
3645
|
target.replaceChildren();
|
|
3006
3646
|
if (svg === "") {
|
|
@@ -3263,8 +3903,14 @@ window.__ModuleLoader__.load({
|
|
|
3263
3903
|
remoteReconnect.hidden = needsFunnelSetup || state !== "error" && state !== "unavailable" || !providerPrepared;
|
|
3264
3904
|
remoteActions.hidden = !providerPrepared;
|
|
3265
3905
|
remotePair.disabled = !remoteReady;
|
|
3906
|
+
remoteCopyLink.disabled = !remoteReady;
|
|
3266
3907
|
remoteDevices.disabled = !remoteReady;
|
|
3267
|
-
if (!remoteReady)
|
|
3908
|
+
if (!remoteReady) {
|
|
3909
|
+
remoteQr.hidden = true;
|
|
3910
|
+
remotePairLink.hidden = true;
|
|
3911
|
+
activeRemotePairUrl = "";
|
|
3912
|
+
activeRemotePairExpiresAt = 0;
|
|
3913
|
+
}
|
|
3268
3914
|
if (!needsFunnelSetup) remoteSetupPending = false;
|
|
3269
3915
|
};
|
|
3270
3916
|
let remoteLoadInFlight = false;
|
|
@@ -3765,24 +4411,48 @@ window.__ModuleLoader__.load({
|
|
|
3765
4411
|
};
|
|
3766
4412
|
window.addEventListener("focus", retryAfterSetup);
|
|
3767
4413
|
document.addEventListener("visibilitychange", retryAfterSetup);
|
|
4414
|
+
const copyRemotePairUrl = async () => {
|
|
4415
|
+
try {
|
|
4416
|
+
await navigator.clipboard.writeText(activeRemotePairUrl);
|
|
4417
|
+
remoteStatus.textContent = t("linkCopied");
|
|
4418
|
+
} catch {
|
|
4419
|
+
remoteStatus.textContent = t("remoteQrReady");
|
|
4420
|
+
}
|
|
4421
|
+
};
|
|
4422
|
+
const openRemotePairing = () => controlRequestJson("/api/mobile-access/remote/pairing/open", {
|
|
4423
|
+
method: "POST",
|
|
4424
|
+
body: "{}"
|
|
4425
|
+
}).then(async (data) => {
|
|
4426
|
+
const pairUrl = typeof data.pairUrl === "string" ? data.pairUrl : "";
|
|
4427
|
+
const expiresAt = typeof data.expiresAt === "number" && Number.isFinite(data.expiresAt) ? data.expiresAt : 0;
|
|
4428
|
+
showQr(typeof data.qrSvg === "string" ? data.qrSvg : "", remoteQr);
|
|
4429
|
+
if (pairUrl === "") {
|
|
4430
|
+
remoteStatus.textContent = t("keyGenerationFailed");
|
|
4431
|
+
return;
|
|
4432
|
+
}
|
|
4433
|
+
activeRemotePairUrl = pairUrl;
|
|
4434
|
+
activeRemotePairExpiresAt = expiresAt;
|
|
4435
|
+
remotePairLink.textContent = pairUrl;
|
|
4436
|
+
remotePairLink.classList.add("is-key");
|
|
4437
|
+
remotePairLink.hidden = false;
|
|
4438
|
+
await copyRemotePairUrl();
|
|
4439
|
+
});
|
|
3768
4440
|
remotePair.addEventListener("click", () => {
|
|
3769
4441
|
remotePair.disabled = true;
|
|
3770
|
-
|
|
3771
|
-
method: "POST",
|
|
3772
|
-
body: "{}"
|
|
3773
|
-
}).then(async (data) => {
|
|
3774
|
-
const pairUrl = typeof data.pairUrl === "string" ? data.pairUrl : "";
|
|
3775
|
-
showQr(typeof data.qrSvg === "string" ? data.qrSvg : "", remoteQr);
|
|
3776
|
-
if (pairUrl !== "") try {
|
|
3777
|
-
await navigator.clipboard.writeText(pairUrl);
|
|
3778
|
-
} catch {}
|
|
3779
|
-
remoteStatus.textContent = t("remoteQrReady");
|
|
3780
|
-
}, (error) => {
|
|
4442
|
+
openRemotePairing().catch((error) => {
|
|
3781
4443
|
remoteStatus.textContent = t("requestFailed", { error: String(error) });
|
|
3782
4444
|
}).finally(() => {
|
|
3783
4445
|
remotePair.disabled = !remoteReady;
|
|
3784
4446
|
});
|
|
3785
4447
|
});
|
|
4448
|
+
remoteCopyLink.addEventListener("click", () => {
|
|
4449
|
+
remoteCopyLink.disabled = true;
|
|
4450
|
+
(activeRemotePairUrl !== "" && activeRemotePairExpiresAt > Date.now() ? copyRemotePairUrl() : openRemotePairing()).catch((error) => {
|
|
4451
|
+
remoteStatus.textContent = t("requestFailed", { error: String(error) });
|
|
4452
|
+
}).finally(() => {
|
|
4453
|
+
remoteCopyLink.disabled = !remoteReady;
|
|
4454
|
+
});
|
|
4455
|
+
});
|
|
3786
4456
|
const renderRemoteDevices = (data) => {
|
|
3787
4457
|
const devices = Array.isArray(data.devices) ? data.devices : [];
|
|
3788
4458
|
remoteDevicePanel.replaceChildren();
|
|
@@ -4301,6 +4971,15 @@ window.__ModuleLoader__.load({
|
|
|
4301
4971
|
if (generation !== void 0) result.set("x-dsh-mobile-extension-generation", generation);
|
|
4302
4972
|
return result;
|
|
4303
4973
|
}
|
|
4974
|
+
/** Build the JSON request used by a mobile extension to invoke a Host action. */
|
|
4975
|
+
function extensionActionRequestInit(generation, input, signal) {
|
|
4976
|
+
return {
|
|
4977
|
+
method: "POST",
|
|
4978
|
+
headers: extensionGenerationHeaders(generation, { "content-type": "application/json" }),
|
|
4979
|
+
body: JSON.stringify(input ?? {}),
|
|
4980
|
+
signal
|
|
4981
|
+
};
|
|
4982
|
+
}
|
|
4304
4983
|
function registerUniqueDisposable(entries, claimedIds, id, mount) {
|
|
4305
4984
|
if (claimedIds.has(id)) throw new Error(`duplicate lifecycle id: ${id}`);
|
|
4306
4985
|
claimedIds.add(id);
|
|
@@ -4473,7 +5152,7 @@ window.__ModuleLoader__.load({
|
|
|
4473
5152
|
function startExtensionChangeStream(changed, runtime = {
|
|
4474
5153
|
window,
|
|
4475
5154
|
create: (url) => new EventSource(url, { withCredentials: true })
|
|
4476
|
-
}) {
|
|
5155
|
+
}, onTaskEvent, onDeviceRevoked) {
|
|
4477
5156
|
let source;
|
|
4478
5157
|
let timer;
|
|
4479
5158
|
let disposed = false;
|
|
@@ -4492,6 +5171,14 @@ window.__ModuleLoader__.load({
|
|
|
4492
5171
|
retryMs = 1e3;
|
|
4493
5172
|
};
|
|
4494
5173
|
next.addEventListener("extensions-changed", changed);
|
|
5174
|
+
if (onTaskEvent !== void 0) next.addEventListener("task-notify", (message) => {
|
|
5175
|
+
const data = message.data;
|
|
5176
|
+
onTaskEvent(data);
|
|
5177
|
+
});
|
|
5178
|
+
if (onDeviceRevoked !== void 0) next.addEventListener("device-revoked", (message) => {
|
|
5179
|
+
const data = message.data;
|
|
5180
|
+
if (isDeviceRevokedPayload(data)) onDeviceRevoked();
|
|
5181
|
+
});
|
|
4495
5182
|
next.onerror = () => {
|
|
4496
5183
|
if (source !== next) return;
|
|
4497
5184
|
next.close();
|
|
@@ -4857,12 +5544,7 @@ window.__ModuleLoader__.load({
|
|
|
4857
5544
|
host: {
|
|
4858
5545
|
invoke: (action, input) => {
|
|
4859
5546
|
ensureCurrent();
|
|
4860
|
-
return mobileRequest(`/mobile-access/extensions/${encodeURIComponent(id)}/actions/${encodeURIComponent(action)}`, {
|
|
4861
|
-
method: "POST",
|
|
4862
|
-
headers: extensionGenerationHeaders(hostGeneration),
|
|
4863
|
-
body: JSON.stringify(input ?? {}),
|
|
4864
|
-
signal: controller.signal
|
|
4865
|
-
}).then(async (response) => {
|
|
5547
|
+
return mobileRequest(`/mobile-access/extensions/${encodeURIComponent(id)}/actions/${encodeURIComponent(action)}`, extensionActionRequestInit(hostGeneration, input, controller.signal)).then(async (response) => {
|
|
4866
5548
|
const value = await response.json();
|
|
4867
5549
|
if (!response.ok) throw new Error(typeof value === "object" && value !== null && "error" in value ? String(value.error) : `HTTP ${String(response.status)}`);
|
|
4868
5550
|
return value;
|
|
@@ -5252,6 +5934,20 @@ window.__ModuleLoader__.load({
|
|
|
5252
5934
|
});
|
|
5253
5935
|
const stopEvents = startExtensionChangeStream(() => {
|
|
5254
5936
|
stopRefresh.refresh();
|
|
5937
|
+
}, void 0, (payload) => {
|
|
5938
|
+
if (document.hidden !== true) return;
|
|
5939
|
+
const parsed = parseTaskNotifyPayload(payload);
|
|
5940
|
+
if (parsed === void 0) return;
|
|
5941
|
+
const language = resolveNativeMobileLanguage(document.documentElement.lang, navigator.languages.length > 0 ? [...navigator.languages] : [navigator.language]);
|
|
5942
|
+
const label = (italian, english, chinese) => language === "it" ? italian : language === "zh" ? chinese : english;
|
|
5943
|
+
fireTaskNotifyEvent({
|
|
5944
|
+
kind: "done",
|
|
5945
|
+
title: label("Attività completata", "Task finished", "任务已完成"),
|
|
5946
|
+
body: label("Il tuo task DSH è terminato", "Your DSH task finished", "你的 DSH 任务已完成"),
|
|
5947
|
+
tag: taskCompletionTag(parsed.sessionId, parsed.turn)
|
|
5948
|
+
});
|
|
5949
|
+
}, () => {
|
|
5950
|
+
fireDeviceRevoked();
|
|
5255
5951
|
});
|
|
5256
5952
|
return () => {
|
|
5257
5953
|
disposed = true;
|
|
@@ -5314,12 +6010,13 @@ window.__ModuleLoader__.load({
|
|
|
5314
6010
|
.dsh-mobile-control__panel{box-sizing:border-box;width:min(380px,calc(100vw - 32px));max-height:calc(100vh - 140px);overflow-y:auto;padding:16px;border:1px solid var(--dsw-alias-border-subtle,#e1e5eb);border-radius:18px;background:var(--dsw-alias-bg-layer-2,#fff);box-shadow:0 18px 50px rgb(15 23 42 / 18%)}
|
|
5315
6011
|
.dsh-mobile-control__header{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:10px}.dsh-mobile-control__panel h2{margin:0;font-size:17px;line-height:24px}.dsh-mobile-control__header-actions{display:flex;align-items:center;gap:2px}.dsh-mobile-control__update-plugin,.dsh-mobile-control__diagnostic-entry,.dsh-mobile-control__close{display:inline-flex;align-items:center;justify-content:center;min-width:44px;height:44px;padding:0;border:0;border-radius:10px;background:transparent;color:inherit;cursor:pointer}.dsh-mobile-control__update-plugin,.dsh-mobile-control__diagnostic-entry{padding:0 8px;color:#2563eb;font:650 12px/1 system-ui;white-space:nowrap}.dsh-mobile-control__update-plugin[hidden]{display:none}.dsh-mobile-control__update-plugin:disabled{cursor:wait;opacity:.55}.dsh-mobile-control__close{font-size:24px;line-height:1}.dsh-mobile-control__update-plugin:hover:not(:disabled),.dsh-mobile-control__diagnostic-entry:hover,.dsh-mobile-control__close:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f3f6)}
|
|
5316
6012
|
.dsh-mobile-control__release-notice{margin:-2px 0 10px;padding:8px 10px;border-radius:9px;background:var(--dsw-alias-bg-layer-1,#eff6ff);color:var(--dsw-alias-label-primary,#1d4ed8);font-size:11px;line-height:1.45}.dsh-mobile-control__release-notice.is-error{color:#dc2626}.dsh-mobile-control__release-notice[hidden]{display:none}.dsh-mobile-control__update-card{margin:0 0 10px;padding:11px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:12px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__update-card[hidden]{display:none}.dsh-mobile-control__update-card-title{margin:0 0 7px;color:var(--dsw-alias-label-primary,#16181d);font:650 13px/1.4 system-ui}.dsh-mobile-control__update-notes{display:block;box-sizing:border-box;max-height:150px;overflow-y:auto;margin:0 0 8px;padding:8px 10px;border:1px solid var(--dsw-alias-border-subtle,#e1e5eb);border-radius:9px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-secondary,#475569);white-space:pre-wrap;word-break:break-word;font:11px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.dsh-mobile-control__update-notes[hidden]{display:none}.dsh-mobile-control__update-notice{margin:0 0 9px;padding:7px 9px;border-radius:8px;background:#fff4d6;color:#92400e;font-size:11px;line-height:1.5}.dsh-mobile-control__update-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px}.dsh-mobile-control__update-actions .dsh-mobile-control__primary,.dsh-mobile-control__update-actions .dsh-mobile-control__secondary{min-height:34px;padding:0 12px}.dsh-mobile-control__app-download{display:flex;align-items:center;justify-content:space-between;box-sizing:border-box;min-height:38px;margin:0 0 10px;padding:8px 11px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-1,#f7f8fa);color:var(--dsw-alias-label-primary,#16181d);font:600 12px/1.3 system-ui;text-decoration:none}.dsh-mobile-control__app-download[hidden]{display:none}.dsh-mobile-control__app-download::after{color:#2563eb;font-size:14px;content:"↗"}.dsh-mobile-control__app-download:hover{border-color:#9fb9e8;background:var(--dsw-alias-interactive-bg-hover-solid,var(--dsw-alias-bg-layer-2,#f5f8ff));color:var(--dsw-alias-label-primary,#1d4ed8)}
|
|
5317
|
-
.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__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}
|
|
6013
|
+
.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}
|
|
5318
6014
|
.dsh-mobile-control__provider-section{position:relative;margin:0 0 14px}.dsh-mobile-control__section-title{margin:0 0 8px;color:var(--dsw-alias-label-primary,#16181d);font:650 13px/1.4 system-ui}.dsh-mobile-control__provider-section>.dsh-mobile-control__section-title{padding-right:42px}.dsh-mobile-control__provider-choices{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.dsh-mobile-control__provider{display:flex;min-width:0;flex-direction:column;gap:6px;min-height:94px;padding:10px 11px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);text-align:left;cursor:pointer;touch-action:manipulation;transition:border-color 160ms ease,background-color 160ms ease,box-shadow 160ms ease}.dsh-mobile-control__provider:hover{border-color:#6f96db;background:var(--dsw-alias-interactive-bg-hover-solid,var(--dsw-alias-bg-layer-1,#f8fbff))}.dsh-mobile-control__provider.is-selected{border-color:#2563eb;background:var(--dsw-alias-interactive-bg-active,var(--dsw-alias-bg-layer-1,#f5f8ff));box-shadow:0 0 0 1px #2563eb inset}.dsh-mobile-control__provider:disabled{cursor:wait;opacity:.62}.dsh-mobile-control__provider-top{display:flex;min-width:0;align-items:flex-start;justify-content:space-between;gap:5px}.dsh-mobile-control__provider-top strong{min-width:0;font-size:12px;line-height:1.3}.dsh-mobile-control__provider-badge{flex:none;padding:2px 5px;border-radius:999px;background:#e8f0ff;color:#1d4ed8;font:650 9px/1.25 system-ui}.dsh-mobile-control__provider-badge.is-cpolar{background:#eaf8f2;color:#087454}.dsh-mobile-control__provider-description{color:var(--dsw-alias-label-secondary,#606873);font-size:10px;line-height:1.45}.dsh-mobile-control__provider-info{position:absolute;z-index:5;top:-13px;right:-8px}.dsh-mobile-control__provider-info-button{display:flex;align-items:center;justify-content:center;width:44px;height:44px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--dsw-alias-label-secondary,#475569);cursor:pointer;touch-action:manipulation}.dsh-mobile-control__provider-info-button:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f5f9);color:#2563eb}.dsh-mobile-control__provider-info-glyph{display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:18px;height:18px;border:1.5px solid currentColor;border-radius:50%;font:700 12px/1 system-ui}.dsh-mobile-control__provider-info-popover{position:absolute;z-index:6;top:38px;right:4px;box-sizing:border-box;width:min(292px,calc(100vw - 72px));padding:10px 12px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:12px;background:var(--dsw-alias-bg-layer-2,#fff);box-shadow:0 10px 28px rgb(15 23 42 / 16%)}.dsh-mobile-control__provider-info-popover[hidden]{display:none}.dsh-mobile-control__provider-info-popover strong,.dsh-mobile-control__provider-info-popover span{display:block}.dsh-mobile-control__provider-info-popover strong{margin-bottom:3px;font-size:12px}.dsh-mobile-control__provider-info-popover span{color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.55}
|
|
5319
6015
|
.dsh-mobile-control__self-hosted{margin:8px 0 0;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:12px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__self-hosted-summary{display:flex;box-sizing:border-box;min-height:48px;align-items:center;justify-content:space-between;gap:10px;padding:8px 11px;cursor:pointer;list-style-position:inside}.dsh-mobile-control__self-hosted-summary>span:first-child{display:flex;min-width:0;flex-direction:column;gap:1px}.dsh-mobile-control__self-hosted-summary strong{font-size:11px}.dsh-mobile-control__self-hosted-summary span span{color:var(--dsw-alias-label-secondary,#606873);font-size:9px;line-height:1.35}.dsh-mobile-control__provider-badge.is-frp{background:#eef0f3;color:#475569}.dsh-mobile-control__self-hosted-body{padding:0 8px 8px}.dsh-mobile-control__provider.is-frp{width:100%;min-height:64px;background:var(--dsw-alias-bg-layer-2,#fff)}
|
|
5320
6016
|
.dsh-mobile-control__cpolar-setup{margin:0 0 12px;padding:12px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__cpolar-setup[hidden],.dsh-mobile-control__cpolar-account[hidden],.dsh-mobile-control__details[hidden],.dsh-mobile-control__view.is-remote .dsh-mobile-control__actions[hidden],.dsh-mobile-control__danger[hidden]{display:none}.dsh-mobile-control__component-status,.dsh-mobile-control__component-note{margin:0 0 10px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.55}.dsh-mobile-control__cpolar-setup>.dsh-mobile-control__primary{width:100%;min-height:44px;padding:9px 12px;border-radius:10px;font:600 12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__cpolar-account{margin-top:10px}.dsh-mobile-control__link-row{display:flex;flex-wrap:wrap;gap:6px 12px;margin:0 0 10px}.dsh-mobile-control__text-link{color:#2563eb;font-size:11px;text-decoration:none}.dsh-mobile-control__text-link:hover{text-decoration:underline}.dsh-mobile-control__token-label{display:flex;flex-direction:column;gap:5px;margin:0 0 8px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px}.dsh-mobile-control__token{box-sizing:border-box;width:100%;min-height:44px;padding:9px 10px;border:1px solid var(--dsw-alias-border-normal,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-bg-layer-2,#fff));color:var(--dsw-alias-label-primary,#16181d);font:16px/1.4 system-ui}.dsh-mobile-control__cpolar-connect{display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:100%;min-height:44px;padding:10px 14px;border-radius:12px;font:650 13px/1.2 system-ui;cursor:pointer;transition:background-color 160ms ease,border-color 160ms ease,opacity 160ms ease}.dsh-mobile-control__cpolar-connect:hover:not(:disabled){border-color:#1d4ed8;background:#1d4ed8}.dsh-mobile-control__cpolar-connect:active:not(:disabled){border-color:#1e40af;background:#1e40af}.dsh-mobile-control__cpolar-connect:disabled{cursor:wait;opacity:.55}.dsh-mobile-control__details{margin:10px 0 0;border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb);padding-top:9px}.dsh-mobile-control__details>summary{min-height:30px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:30px;cursor:pointer}.dsh-mobile-control__details-body{display:flex;flex-wrap:wrap;align-items:center;gap:7px 12px;padding:4px 0}.dsh-mobile-control__details-body p{flex:1 0 100%;margin:0;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__storage{display:block;flex:1 0 100%;max-width:100%;overflow:hidden;padding:7px 8px;border-radius:8px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-secondary,#475569);font:10px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__danger{flex:1 0 100%;min-height:38px;margin-top:3px;padding:7px 10px;border:1px solid #dc2626;border-radius:9px;background:transparent;color:#dc2626;font:12px/1.3 system-ui;cursor:pointer}
|
|
5321
6017
|
.dsh-mobile-control__frp-setup{margin:0;padding:12px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__frp-setup[hidden]{display:none}.dsh-mobile-control__frp-step{padding:11px 0}.dsh-mobile-control__frp-step + .dsh-mobile-control__frp-step{border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}.dsh-mobile-control__frp-step>strong{display:block;margin-bottom:3px;font-size:12px;line-height:1.4}.dsh-mobile-control__frp-step>p{margin:0 0 9px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__frp-changes{margin:0 0 9px;padding:0;list-style:none;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__frp-changes li{position:relative;margin:0;padding-left:12px}.dsh-mobile-control__frp-changes li+li{margin-top:5px}.dsh-mobile-control__frp-changes li::before{content:"•";position:absolute;left:2px;color:var(--dsw-alias-label-tertiary,#98a1ad)}.dsh-mobile-control__frp-step>.dsh-mobile-control__frp-requirement{padding:8px 9px;border-radius:9px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-primary,#384152);font-size:11px}.dsh-mobile-control__frp-fields{display:grid;grid-template-columns:minmax(0,1fr) 96px;gap:8px}.dsh-mobile-control__field{display:flex;min-width:0;flex-direction:column;gap:5px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px}.dsh-mobile-control__field:nth-child(3),.dsh-mobile-control__field:nth-child(4){grid-column:1/-1}.dsh-mobile-control__field input{box-sizing:border-box;width:100%;min-height:44px;padding:9px 10px;border:1px solid var(--dsw-alias-border-normal,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);font:16px/1.4 system-ui}.dsh-mobile-control__frp-action{box-sizing:border-box;width:100%;min-height:44px;padding:9px 12px;border-radius:10px;font:650 12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__frp-action:disabled{cursor:not-allowed;opacity:.5}.dsh-mobile-control__remote-workspace{margin:0;padding:12px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:15px;background:var(--dsw-alias-bg-layer-1,#f7f8fa)}.dsh-mobile-control__stage-header{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:9px}.dsh-mobile-control__stage-header .dsh-mobile-control__section-title{margin:0}.dsh-mobile-control__stage-meta{display:flex;min-width:0;align-items:center;justify-content:flex-end;gap:5px}.dsh-mobile-control__stage-value{max-width:115px;overflow:hidden;color:var(--dsw-alias-label-primary,#16181d);font:650 10px/1.3 system-ui;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__state-badge{flex:none;padding:3px 7px;border-radius:999px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-secondary,#606873);font:650 9px/1.25 system-ui}.dsh-mobile-control__state-badge.is-ready{background:#e6f7f0;color:#087454}.dsh-mobile-control__state-badge.is-busy{background:#e8f0ff;color:#1d4ed8}.dsh-mobile-control__state-badge.is-attention{background:#fff4dc;color:#935100}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__status{box-sizing:border-box;margin:0 0 10px;padding:9px 10px;border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);font-size:11px;line-height:1.45}.dsh-mobile-control__provider-setup-body{margin:0 0 10px}.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__cpolar-setup{margin:0}.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__cpolar-setup>.dsh-mobile-control__section-title,.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__frp-setup>.dsh-mobile-control__section-title{display:none}.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__details{margin:0;padding:9px 10px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__actions{margin-top:2px}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__qr{margin:10px 0 0}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__manage-row{margin-top:10px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}
|
|
5322
6018
|
.dsh-mobile-control__frp-overview{display:grid;grid-template-columns:32px minmax(0,1fr);align-items:center;gap:10px;margin:0 0 10px;padding:10px;border:1px solid #a9dfc9;border-radius:11px;background:#edf9f4}.dsh-mobile-control__frp-overview[hidden]{display:none}.dsh-mobile-control__frp-overview-mark{display:grid;width:32px;height:32px;place-items:center;border-radius:50%;background:#087454;color:#fff;font:700 15px/1 system-ui}.dsh-mobile-control__frp-overview-body{display:flex;min-width:0;flex-direction:column;gap:2px}.dsh-mobile-control__frp-overview-body strong{color:#075d46;font-size:12px;line-height:1.35}.dsh-mobile-control__frp-overview-body span{overflow:hidden;color:#357061;font:10px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__frp-group{margin:8px 0 0;overflow:hidden;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__frp-group>summary{box-sizing:border-box;min-height:44px;padding:12px 34px 10px 12px;color:var(--dsw-alias-label-primary,#16181d);font:650 12px/1.4 system-ui;cursor:pointer}.dsh-mobile-control__frp-group[open]>summary{border-bottom:1px solid var(--dsw-alias-border-subtle,#e1e5eb);background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__frp-group>.dsh-mobile-control__frp-step{padding:12px}.dsh-mobile-control__frp-group>.dsh-mobile-control__frp-step>strong:first-child{display:none}.dsh-mobile-control__frp-group .dsh-mobile-control__frp-step{border-top:0}.dsh-mobile-control__frp-setup>.dsh-mobile-control__frp-step{margin-bottom:8px;padding:10px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}
|
|
6019
|
+
.dsh-mobile-control__lan-setup{box-sizing:border-box;margin:0 0 12px;padding:14px;border:1px solid #6f96db;border-radius:15px;background:var(--dsw-alias-bg-layer-1,#f5f8ff)}.dsh-mobile-control__lan-setup[hidden],.dsh-mobile-control__lan-setup-form[hidden]{display:none}.dsh-mobile-control__lan-setup>.dsh-mobile-control__section-title{margin-bottom:5px}.dsh-mobile-control__lan-setup>.dsh-mobile-control__intro{margin-bottom:12px}.dsh-mobile-control__lan-setup-form{display:grid;gap:9px}.dsh-mobile-control__lan-setup .dsh-mobile-control__field select{box-sizing:border-box;width:100%;min-height:44px;padding:9px 32px 9px 10px;border:1px solid var(--dsw-alias-border-normal,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);font:14px/1.4 system-ui}.dsh-mobile-control__lan-setup .dsh-mobile-control__component-note{margin:0}.dsh-mobile-control__lan-setup-actions{display:grid;grid-template-columns:1fr 1.35fr;gap:8px}.dsh-mobile-control__lan-setup-actions button{box-sizing:border-box;min-width:0;min-height:44px;padding:9px 10px;border-radius:10px;font:650 12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__lan-setup-actions button:disabled{cursor:wait;opacity:.55}.dsh-mobile-control__lan-setup>.dsh-mobile-control__status{margin:10px 0 0;line-height:1.5}.dsh-mobile-control__lan-setup>.dsh-mobile-control__status:empty{display:none}
|
|
5323
6020
|
.dsh-mobile-control__access{display:flex;align-items:baseline;gap:6px;min-width:0;margin:0 0 12px}.dsh-mobile-control__access[hidden]{display:none}.dsh-mobile-control__access-label{flex:none;color:var(--dsw-alias-label-secondary,#606873);white-space:nowrap}.dsh-mobile-control__access-label::after{content:":"}.dsh-mobile-control__access-link{min-width:0;overflow:hidden;color:#2563eb;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__access-link:hover{text-decoration:underline}.dsh-mobile-control__qr{display:flex;justify-content:center;margin:0 0 12px}.dsh-mobile-control__qr[hidden]{display:none}.dsh-mobile-control__qr img{border-radius:12px;background:#fff;padding:8px}
|
|
5324
6021
|
.dsh-mobile-control__status{margin:0 0 14px;overflow-wrap:anywhere;color:var(--dsw-alias-label-secondary,#606873)}.dsh-mobile-control__status::before{display:inline-block;width:8px;height:8px;margin-right:7px;border-radius:50%;background:#98a1ad;content:""}.dsh-mobile-control__status.is-running::before{background:#16a36a}.dsh-mobile-control__status.is-key{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:12px;word-break:break-all}
|
|
5325
6022
|
.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}
|
|
@@ -5330,8 +6027,8 @@ window.__ModuleLoader__.load({
|
|
|
5330
6027
|
.dsh-mobile-control__diagnostic-checks{display:grid;gap:12px;margin-top:12px;animation:dsh-diagnostic-reveal 160ms ease-out both}.dsh-mobile-control__diagnostic-checks[hidden]{display:none}.dsh-mobile-control__diagnostic-group{overflow:hidden;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__diagnostic-group-header{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px 11px;border-bottom:1px solid var(--dsw-alias-border-subtle,#e1e5eb);background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__diagnostic-group-header h3{margin:0;font:650 11px/1.4 system-ui}.dsh-mobile-control__diagnostic-group-header span{color:var(--dsw-alias-label-secondary,#606873);font-size:10px}.dsh-mobile-control__diagnostic-list{display:flex;flex-direction:column}.dsh-mobile-control__diagnostic-check{display:grid;grid-template-columns:26px minmax(0,1fr);gap:9px;padding:11px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__diagnostic-check + .dsh-mobile-control__diagnostic-check{border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}.dsh-mobile-control__diagnostic-marker{position:relative;width:26px;height:26px;border-radius:50%;background:#edf1f5;color:var(--dsh-diagnostic-info)}.dsh-mobile-control__diagnostic-marker::before,.dsh-mobile-control__diagnostic-marker::after{position:absolute;content:""}.dsh-mobile-control__diagnostic-check.is-ok .dsh-mobile-control__diagnostic-marker{background:#e6f7f0;color:var(--dsh-diagnostic-ok)}.dsh-mobile-control__diagnostic-check.is-ok .dsh-mobile-control__diagnostic-marker::before{top:7px;left:7px;width:9px;height:5px;border-bottom:1.8px solid currentColor;border-left:1.8px solid currentColor;transform:rotate(-45deg)}.dsh-mobile-control__diagnostic-check.is-warning .dsh-mobile-control__diagnostic-marker{background:#fff4dc;color:var(--dsh-diagnostic-warning)}.dsh-mobile-control__diagnostic-check.is-error .dsh-mobile-control__diagnostic-marker{background:#fdecec;color:var(--dsh-diagnostic-error)}.dsh-mobile-control__diagnostic-check.is-warning .dsh-mobile-control__diagnostic-marker::before,.dsh-mobile-control__diagnostic-check.is-error .dsh-mobile-control__diagnostic-marker::before{top:6px;left:12px;width:2px;height:9px;border-radius:2px;background:currentColor}.dsh-mobile-control__diagnostic-check.is-warning .dsh-mobile-control__diagnostic-marker::after,.dsh-mobile-control__diagnostic-check.is-error .dsh-mobile-control__diagnostic-marker::after{bottom:6px;left:12px;width:2px;height:2px;border-radius:50%;background:currentColor}.dsh-mobile-control__diagnostic-check.is-info .dsh-mobile-control__diagnostic-marker::before{top:6px;left:12px;width:2px;height:2px;border-radius:50%;background:currentColor}.dsh-mobile-control__diagnostic-check.is-info .dsh-mobile-control__diagnostic-marker::after{top:10px;left:12px;width:2px;height:9px;border-radius:2px;background:currentColor}.dsh-mobile-control__diagnostic-check-body{min-width:0}.dsh-mobile-control__diagnostic-check-header{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}.dsh-mobile-control__diagnostic-check-header strong{min-width:0;font-size:12px;line-height:1.4}.dsh-mobile-control__diagnostic-badge{flex:none;padding:2px 6px;border-radius:999px;background:#edf1f5;color:var(--dsh-diagnostic-info);font:650 10px/1.3 system-ui}.dsh-mobile-control__diagnostic-check.is-ok .dsh-mobile-control__diagnostic-badge{background:#e6f7f0;color:var(--dsh-diagnostic-ok)}.dsh-mobile-control__diagnostic-check.is-warning .dsh-mobile-control__diagnostic-badge{background:#fff4dc;color:var(--dsh-diagnostic-warning)}.dsh-mobile-control__diagnostic-check.is-error .dsh-mobile-control__diagnostic-badge{background:#fdecec;color:var(--dsh-diagnostic-error)}.dsh-mobile-control__diagnostic-check p{margin:4px 0 0;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5;overflow-wrap:anywhere}.dsh-mobile-control__diagnostic-check .dsh-mobile-control__diagnostic-action{margin-top:7px;padding:7px 8px;border-radius:8px;background:var(--dsw-alias-bg-layer-1,#f8fafc);color:var(--dsw-alias-label-primary,#16181d)}.dsh-mobile-control__diagnostic-action span{display:inline-block;margin-right:6px;color:#2563eb;font-weight:700}.dsh-mobile-control__diagnostic-details{margin-top:12px}.dsh-mobile-control__diagnostic-details[hidden]{display:none}.dsh-mobile-control__diagnostic-details>summary{box-sizing:border-box;min-height:44px;line-height:44px}.dsh-mobile-control__diagnostic-report{box-sizing:border-box;max-height:220px;margin:4px 0 0;overflow:auto;padding:10px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:10px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-secondary,#606873);font:10px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere}
|
|
5331
6028
|
.dsh-mobile-control__diagnostic-checks{transition:opacity 150ms ease}.dsh-mobile-control__diagnostic-checks.is-refreshing{opacity:.52}
|
|
5332
6029
|
@keyframes dsh-diagnostic-spin{to{transform:rotate(360deg)}}@keyframes dsh-diagnostic-reveal{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}
|
|
5333
|
-
.dsh-mobile-control__actions{display:flex;flex-wrap:nowrap;gap:6px}.dsh-mobile-control__actions button{flex:1 1 0;min-width:0;min-height:40px;padding:8px 4px;border-radius:10px;font:12px/1.2 system-ui;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dsh-mobile-control__secondary{border:1px solid var(--dsw-alias-border-normal,#cfd5dd);background:transparent;color:inherit}.dsh-mobile-control__primary{border:1px solid #2563eb;background:#2563eb;color:#fff}.dsh-mobile-control__actions button:disabled{cursor:not-allowed;opacity:.45}
|
|
5334
|
-
.dsh-mobile-control button:focus-visible,.dsh-mobile-control a:focus-visible,.dsh-mobile-control input:focus-visible,.dsh-mobile-control summary:focus-visible{outline:3px solid rgb(37 99 235 / 28%);outline-offset:2px}
|
|
6030
|
+
.dsh-mobile-control__actions{display:flex;flex-wrap:nowrap;gap:6px}.dsh-mobile-control__actions[hidden],.dsh-mobile-control__manage-row[hidden]{display:none}.dsh-mobile-control__actions button{flex:1 1 0;min-width:0;min-height:40px;padding:8px 4px;border-radius:10px;font:12px/1.2 system-ui;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dsh-mobile-control__secondary{border:1px solid var(--dsw-alias-border-normal,#cfd5dd);background:transparent;color:inherit}.dsh-mobile-control__primary{border:1px solid #2563eb;background:#2563eb;color:#fff}.dsh-mobile-control__actions button:disabled{cursor:not-allowed;opacity:.45}
|
|
6031
|
+
.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}
|
|
5335
6032
|
.dsh-mobile-control__trigger{box-sizing:border-box;flex:1 1 auto;display:flex;align-items:center;gap:8px;width:100%;height:42px;margin:4px 0;padding:0 10px 0 8px;border:0;border-radius:12px;background:transparent;color:var(--dsw-alias-label-primary,#16181d);font-family:inherit;font-size:14px;line-height:22px;cursor:pointer;overflow:hidden}.dsh-mobile-control__trigger:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f3f6)}.dsh-mobile-control__trigger:active,.dsh-mobile-control__trigger[aria-expanded="true"]{background:var(--dsw-alias-interactive-bg-active,#e8ebf0)}.dsh-mobile-control__trigger:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,currentColor);outline-offset:2px}.dsh-mobile-control__trigger.is-rail{flex:0 0 auto;width:36px;height:36px;margin:8px 0 10px;padding:0;justify-content:center;gap:0;border-radius:50%}.dsh-mobile-control__trigger-icon{display:block;flex:none}.dsh-mobile-control__trigger-label{min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
|
|
5336
6033
|
.dsh-mobile-control__manage-row{display:flex;justify-content:space-between;gap:8px;margin-top:10px}.dsh-mobile-control__manage{flex:1 1 0;min-width:0;min-height:34px;padding:6px 8px;border:1px solid var(--dsw-alias-border-normal,#cfd5dd);border-radius:10px;background:transparent;color:inherit;font:12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__devices{margin-top:10px;border:1px solid var(--dsw-alias-border-subtle,#e1e5eb);border-radius:10px;padding:8px;max-height:220px;overflow-y:auto}.dsh-mobile-control__device-empty{color:var(--dsw-alias-label-secondary,#606873);font-size:12px;margin:0}.dsh-mobile-control__device{display:flex;align-items:center;gap:8px;padding:6px 2px}.dsh-mobile-control__device + .dsh-mobile-control__device{border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}.dsh-mobile-control__device-label{flex:1 1 0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px}.dsh-mobile-control__device-meta{flex:none;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;white-space:nowrap}.dsh-mobile-control__device-revoke{flex:none;min-height:28px;padding:4px 8px;border:1px solid #dc2626;border-radius:8px;background:transparent;color:#dc2626;font:12px/1.2 system-ui;cursor:pointer}.dsh-mobile-control__ws-paths{margin:10px 0 0;padding:12px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__ws-paths-list{margin:0 0 8px;padding:0;list-style:none}.dsh-mobile-control__ws-paths-empty{color:var(--dsw-alias-label-secondary,#606873);font-size:12px}.dsh-mobile-control__ws-paths-item{display:flex;align-items:center;gap:8px;padding:6px 0}.dsh-mobile-control__ws-paths-item + .dsh-mobile-control__ws-paths-item{border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}.dsh-mobile-control__ws-paths-item code{flex:1 1 0;min-width:0;overflow:hidden;font:11px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__ws-paths-remove{flex:none;min-height:28px;padding:4px 8px;border:1px solid var(--dsw-alias-border-normal,#cfd5dd);border-radius:8px;background:transparent;color:inherit;font:12px/1.2 system-ui;cursor:pointer}.dsh-mobile-control__trigger,.dsh-mobile-control__diagnostic-entry{position:relative}.dsh-mobile-control__ws-dot{position:absolute;top:2px;right:2px;z-index:1;min-width:16px;height:16px;padding:0 4px;border-radius:999px;background:#dc2626;color:#fff;font:650 10px/16px system-ui;text-align:center;cursor:pointer}.dsh-mobile-control__ws-dot::after{content:'';position:absolute;inset:-5px}.dsh-mobile-control__ws-sr-status{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.dsh-mobile-control__ws-paths-row{display:flex;gap:8px}.dsh-mobile-control__ws-paths-row .dsh-mobile-control__primary{width:auto;flex:none;padding:9px 14px;border-radius:10px}.dsh-mobile-control__ws-paths-group{padding:6px 0}.dsh-mobile-control__ws-paths-group + .dsh-mobile-control__ws-paths-group{border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}.dsh-mobile-control__ws-paths-group-head{display:flex;align-items:center;gap:8px}.dsh-mobile-control__ws-count{flex:none;padding:1px 7px;border-radius:999px;background:#e8f0ff;color:#1d4ed8;font:650 10px/1.6 system-ui}.dsh-mobile-control__ws-paths-item.is-child{padding-left:12px}.dsh-mobile-control__ws-paths-group-head .dsh-mobile-control__primary,.dsh-mobile-control__ws-paths-item .dsh-mobile-control__primary{width:auto;flex:none;min-height:34px;padding:6px 12px;border-radius:10px}.dsh-mobile-control__ws-paths-row input{flex:1 1 0;min-width:0;box-sizing:border-box;min-height:44px;padding:9px 10px;border:1px solid var(--dsw-alias-border-normal,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);font:16px/1.4 system-ui}.dsh-mobile-control__ws-paths-row button{flex:none}.dsh-mobile-control__ws-paths-detected-title{margin:0 0 2px;font:600 12px/1.4 system-ui}.dsh-mobile-control__ws-paths-advanced{margin-top:8px}.dsh-mobile-control__ws-paths-advanced summary{cursor:pointer;color:var(--dsw-alias-label-secondary,#606873);font:12px/1.4 system-ui;user-select:none}.dsh-mobile-control__ws-paths-advanced summary:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,currentColor);outline-offset:2px;border-radius:4px}.dsh-mobile-control__ws-paths-advanced[open] summary{margin-bottom:6px}.dsh-mobile-control__ws-paths-advanced .dsh-mobile-control__status{margin:6px 0 0}
|
|
5337
6034
|
@media (max-width:359px){.dsh-mobile-control__provider-choices{grid-template-columns:1fr}.dsh-mobile-control__provider{min-height:68px}}@media (prefers-reduced-motion:reduce){.dsh-mobile-control__provider,.dsh-mobile-control__cpolar-connect{transition:none}.dsh-mobile-control__diagnostic-summary.is-running .dsh-mobile-control__diagnostic-summary-icon::before,.dsh-mobile-control__diagnostic-checks{animation:none}}
|
|
@@ -5343,15 +6040,21 @@ window.__ModuleLoader__.load({
|
|
|
5343
6040
|
return trustAuthenticatedGatewayConnection(ctx.get("connection"));
|
|
5344
6041
|
}, "dsh-mobile: authenticated gateway client trust");
|
|
5345
6042
|
ctx.effect(() => {
|
|
5346
|
-
const
|
|
6043
|
+
const desktopAdmin = isDesktopAdminSurface(location.hostname, location.search, window.__DSH_MOBILE_FRONTEND__);
|
|
5347
6044
|
const style = element("style");
|
|
5348
6045
|
style.dataset.plugin = "dsh-mobile";
|
|
5349
|
-
style.textContent =
|
|
6046
|
+
style.textContent = desktopAdmin ? CONTROL_STYLES : NATIVE_MOBILE_STYLES;
|
|
5350
6047
|
document.head.append(style);
|
|
5351
|
-
if (!
|
|
6048
|
+
if (!desktopAdmin) {
|
|
6049
|
+
const removeSettingsAction = ctx.slots.inject("settings.general.item", () => ctx.slots.register({
|
|
6050
|
+
name: "settings.general.item",
|
|
6051
|
+
id: "dsh-mobile-switch-computer",
|
|
6052
|
+
order: 100
|
|
6053
|
+
}, MobileSwitchComputerRow));
|
|
5352
6054
|
const removeCustom = installCustomAssets();
|
|
5353
6055
|
const removeSurface = installDshLanguageBoundSurface(installNativeMobileSurface);
|
|
5354
6056
|
return () => {
|
|
6057
|
+
removeSettingsAction();
|
|
5355
6058
|
removeCustom();
|
|
5356
6059
|
removeSurface();
|
|
5357
6060
|
style.remove();
|
|
@@ -5408,6 +6111,7 @@ window.__ModuleLoader__.load({
|
|
|
5408
6111
|
//#endregion
|
|
5409
6112
|
exports.CONTROL_STYLES = CONTROL_STYLES;
|
|
5410
6113
|
exports.DIAGNOSTIC_REASON_MESSAGES = DIAGNOSTIC_REASON_MESSAGES;
|
|
6114
|
+
exports.LOCALIZED_DIAGNOSTIC_COPY = LOCALIZED_DIAGNOSTIC_COPY;
|
|
5411
6115
|
exports.MOBILE_CONTROL_MESSAGES = MOBILE_CONTROL_MESSAGES;
|
|
5412
6116
|
exports.PerIdActivationLifecycle = PerIdActivationLifecycle;
|
|
5413
6117
|
exports.apply = apply;
|
|
@@ -5419,6 +6123,7 @@ window.__ModuleLoader__.load({
|
|
|
5419
6123
|
exports.diagnosticEntriesForRender = diagnosticEntriesForRender;
|
|
5420
6124
|
exports.diagnosticOverallForChecks = diagnosticOverallForChecks;
|
|
5421
6125
|
exports.diagnosticServerCopy = diagnosticServerCopy;
|
|
6126
|
+
exports.extensionActionRequestInit = extensionActionRequestInit;
|
|
5422
6127
|
exports.extensionAssetUrl = extensionAssetUrl;
|
|
5423
6128
|
exports.extensionGenerationHeaders = extensionGenerationHeaders;
|
|
5424
6129
|
exports.extensionRouteUrl = extensionRouteUrl;
|