dsh-mobile 0.6.17 → 0.7.0
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 +47 -11
- package/CONTRIBUTORS.md +12 -6
- package/README.en.md +22 -14
- package/README.md +21 -14
- package/SECURITY.md +1 -1
- package/THIRD_PARTY_NOTICES.md +33 -0
- package/apps/mobile/README.md +28 -3
- package/apps/mobile/README.zh-CN.md +28 -3
- package/apps/mobile/release.json +3 -3
- package/cordis.patch.yml +5 -9
- package/docs/ATTACH_EXISTING_FRPS.en.md +32 -2
- package/docs/ATTACH_EXISTING_FRPS.md +2 -2
- package/docs/CLIENT_MODULES.en.md +2 -0
- package/docs/CLIENT_MODULES.md +2 -0
- package/docs/CLOUDFLARE_TUNNEL.en.md +2 -0
- package/docs/CLOUDFLARE_TUNNEL.md +2 -0
- package/docs/IROH.en.md +44 -0
- package/docs/IROH.md +44 -0
- package/docs/MOBILE_PLUGIN_ADAPTATION.en.md +196 -0
- package/docs/MOBILE_PLUGIN_ADAPTATION.md +196 -0
- package/docs/README.md +9 -0
- package/docs/SELF_HOSTED_ORIGIN.en.md +26 -1
- package/docs/SELF_HOSTED_ORIGIN.md +27 -2
- package/docs/SKIN_COMPATIBILITY.en.md +2 -0
- package/docs/SKIN_COMPATIBILITY.md +2 -0
- package/docs/SLOW_CONNECTIONS.en.md +10 -2
- package/docs/SLOW_CONNECTIONS.md +10 -2
- package/docs/TRUSTED_NETWORKS.en.md +17 -0
- package/docs/TRUSTED_NETWORKS.md +17 -0
- package/lib/cli.js.map +1 -1
- package/lib/client.js +1081 -70
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +271 -2
- package/lib/index.mjs +1908 -245
- package/lib/index.mjs.map +1 -1
- package/lib/mobile-layout.js +14 -6
- package/lib/mobile-layout.js.map +1 -1
- package/native/iroh-host/LICENSE.dsh-mobile-iroh +21 -0
- package/native/iroh-host/LICENSE.dsh-tether +21 -0
- package/native/iroh-host/NOTICE.dsh-mobile-iroh +13 -0
- package/native/iroh-host/README.md +66 -0
- package/package.json +18 -5
- package/packages/question-fixes/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -1021,6 +1021,105 @@ window.__ModuleLoader__.load({
|
|
|
1021
1021
|
frpStopped: "FRP 连接已停止。",
|
|
1022
1022
|
frpExited: "frpc 意外退出,请检查 VPS 配置后重新连接。"
|
|
1023
1023
|
});
|
|
1024
|
+
Object.assign(MOBILE_CONTROL_MESSAGES.en, {
|
|
1025
|
+
irohName: "iroh (Experimental)",
|
|
1026
|
+
irohExperimental: "Experimental",
|
|
1027
|
+
irohNotReady: "iroh is not running. Start the channel first, then generate a pairing code.",
|
|
1028
|
+
irohPairingFailed: "Could not open an iroh pairing window. Reconnect the channel and retry.",
|
|
1029
|
+
irohPairingTitle: "Phone pairing (experimental)",
|
|
1030
|
+
generateIrohQr: "Generate iroh pairing QR code",
|
|
1031
|
+
enlargeQr: "Enlarge the QR code",
|
|
1032
|
+
closeEnlargedQr: "Close the enlarged QR code",
|
|
1033
|
+
irohPairingReady: "iroh pairing code {code} is live for five minutes. Scan it from the iroh pairing screen; opening another code replaces this one.",
|
|
1034
|
+
irohDescription: "Direct encrypted connection with relay fallback. No public domain or tunnel account needed.",
|
|
1035
|
+
prepareIroh: "Prepare iroh",
|
|
1036
|
+
irohInstallNote: "The Install button downloads the pinned sidecar release (dsh-mobile-iroh 0.1.0-alpha.1, about 15 MB on Windows x64, 24 MB on Linux x64) into the plugin private directory and verifies its SHA-256. An installed companion with a verified copy is reused instead, with no download.",
|
|
1037
|
+
irohComponentNote: "An installed sidecar is re-verified by SHA-256 on every use. It is not bundled with the plugin package; a companion-provided copy can only be removed by removing the companion.",
|
|
1038
|
+
irohPhaseNote: "Experimental connection. Requires the full Android App 0.7.0+ with iroh; the compatibility APK and mobile browsers cannot use it.",
|
|
1039
|
+
irohUnavailable: "iroh is not ready: no sidecar is installed yet. Install the official component below to download and verify it.",
|
|
1040
|
+
irohStarting: "Resolving the iroh endpoint identity…",
|
|
1041
|
+
irohConnecting: "Sidecar connected; starting the DSH authentication gateway…",
|
|
1042
|
+
irohCompanionMissing: "No verified iroh sidecar is available. Use Install official component below; a separate companion plugin is optional.",
|
|
1043
|
+
irohDataDirectoryInvalid: "Could not prepare the private sidecar directory. Check that DSH can manage its own profile directory, then retry.",
|
|
1044
|
+
irohSidecarInvalid: "The installed sidecar failed checksum verification. Reinstall the component.",
|
|
1045
|
+
irohDownloadFailed: "The sidecar download failed before verification. Check the network and retry.",
|
|
1046
|
+
irohPlatformUnsupported: "iroh sidecars are published for Windows x64 and Linux x64 only.",
|
|
1047
|
+
irohIdentityFailed: "Could not read the iroh endpoint identity from the sidecar.",
|
|
1048
|
+
irohLaunchFailed: "The iroh sidecar failed to start.",
|
|
1049
|
+
irohExited: "The iroh sidecar exited unexpectedly. Reconnect.",
|
|
1050
|
+
irohTimeout: "Timed out waiting for the iroh sidecar. Reconnect.",
|
|
1051
|
+
irohInstallConfirm: "Download and verify the pinned iroh sidecar now (about 15 MB on Windows x64, 24 MB on Linux x64)? Nothing else is installed.",
|
|
1052
|
+
installingIroh: "Downloading and verifying the iroh sidecar…",
|
|
1053
|
+
switchingIroh: "Switching to iroh…",
|
|
1054
|
+
reconnectingIroh: "Reconnecting the iroh transport…",
|
|
1055
|
+
resetIrohConfirm: "Disable the iroh channel? The companion and its pairings stay installed."
|
|
1056
|
+
});
|
|
1057
|
+
Object.assign(MOBILE_CONTROL_MESSAGES.it, {
|
|
1058
|
+
irohName: "iroh (Sperimentale)",
|
|
1059
|
+
irohExperimental: "Sperimentale",
|
|
1060
|
+
irohNotReady: "iroh non è attivo. Avvia il canale e genera di nuovo il codice.",
|
|
1061
|
+
irohPairingFailed: "Impossibile aprire una finestra di abbinamento iroh. Riconnetti il canale e riprova.",
|
|
1062
|
+
irohPairingTitle: "Abbinamento telefono (sperimentale)",
|
|
1063
|
+
generateIrohQr: "Genera QR di abbinamento iroh",
|
|
1064
|
+
enlargeQr: "Ingrandisci il codice QR",
|
|
1065
|
+
closeEnlargedQr: "Chiudi il codice QR ingrandito",
|
|
1066
|
+
irohPairingReady: "Il codice iroh {code} è attivo per cinque minuti. Scansionalo dalla schermata di abbinamento iroh; aprirne un altro sostituisce questo.",
|
|
1067
|
+
irohDescription: "Connessione cifrata diretta con ripiego su relay. Nessun dominio pubblico o account tunnel richiesto.",
|
|
1068
|
+
prepareIroh: "Prepara iroh",
|
|
1069
|
+
irohInstallNote: "Il pulsante Installa scarica la release pinnata del sidecar (dsh-mobile-iroh 0.1.0-alpha.1, circa 15 MB su Windows x64, 24 MB su Linux x64) nella directory privata del plugin e ne verifica lo SHA-256. Un complemento installato con una copia verificata viene invece riutilizzato senza scaricare nulla.",
|
|
1070
|
+
irohComponentNote: "Un sidecar installato viene riverificato tramite SHA-256 a ogni uso. Non è incluso nel pacchetto del plugin; una copia fornita dal complemento può essere rimossa solo rimuovendo il complemento.",
|
|
1071
|
+
irohPhaseNote: "Connessione sperimentale. Richiede l’App Android completa 0.7.0+ con iroh; APK compatibile e browser mobili non sono supportati.",
|
|
1072
|
+
irohUnavailable: "iroh non è pronto: nessun sidecar è ancora installato. Installa il componente ufficiale qui sotto per scaricarlo e verificarlo.",
|
|
1073
|
+
irohStarting: "Risoluzione dell’identità endpoint iroh…",
|
|
1074
|
+
irohConnecting: "Sidecar connesso; avvio del gateway di autenticazione DSH…",
|
|
1075
|
+
irohCompanionMissing: "Nessun sidecar iroh verificato disponibile. Usa Installa componente ufficiale qui sotto; il plugin complementare è facoltativo.",
|
|
1076
|
+
irohDataDirectoryInvalid: "Impossibile preparare la directory privata del sidecar. Verifica che DSH possa gestire la propria directory del profilo, poi riprova.",
|
|
1077
|
+
irohSidecarInvalid: "Il sidecar installato non ha superato la verifica del checksum. Reinstalla il componente.",
|
|
1078
|
+
irohDownloadFailed: "Download del sidecar non riuscito prima della verifica. Controlla la rete e riprova.",
|
|
1079
|
+
irohPlatformUnsupported: "I sidecar iroh sono pubblicati solo per Windows x64 e Linux x64.",
|
|
1080
|
+
irohIdentityFailed: "Impossibile leggere l’identità endpoint iroh dal sidecar.",
|
|
1081
|
+
irohLaunchFailed: "Avvio del sidecar iroh non riuscito.",
|
|
1082
|
+
irohExited: "Il sidecar iroh è terminato inaspettatamente. Riconnettiti.",
|
|
1083
|
+
irohTimeout: "Attesa del sidecar iroh scaduta. Riconnettiti.",
|
|
1084
|
+
irohInstallConfirm: "Scaricare e verificare ora il sidecar iroh pinnato (circa 15 MB su Windows x64, 24 MB su Linux x64)? Non viene installato altro.",
|
|
1085
|
+
installingIroh: "Download e verifica del sidecar iroh…",
|
|
1086
|
+
switchingIroh: "Passaggio a iroh…",
|
|
1087
|
+
reconnectingIroh: "Riconnessione del trasporto iroh…",
|
|
1088
|
+
resetIrohConfirm: "Disattivare il canale iroh? Complemento e abbinamenti restano installati."
|
|
1089
|
+
});
|
|
1090
|
+
Object.assign(MOBILE_CONTROL_MESSAGES.zh, {
|
|
1091
|
+
irohName: "iroh(实验性)",
|
|
1092
|
+
irohExperimental: "实验性",
|
|
1093
|
+
irohNotReady: "iroh 通道未运行,请先启动通道再生成配对码。",
|
|
1094
|
+
irohPairingFailed: "无法打开 iroh 配对窗口,请重连通道后重试。",
|
|
1095
|
+
irohPairingTitle: "手机配对(实验性)",
|
|
1096
|
+
generateIrohQr: "生成 iroh 配对二维码",
|
|
1097
|
+
enlargeQr: "放大二维码",
|
|
1098
|
+
closeEnlargedQr: "关闭放大的二维码",
|
|
1099
|
+
irohPairingReady: "iroh 配对码 {code} 已生效,有效期 5 分钟。请在 App 的 iroh 配对页扫码;再生成一个会替换当前这个。",
|
|
1100
|
+
irohDescription: "加密直连,失败时走中继。不需要公网域名或隧道账号。",
|
|
1101
|
+
prepareIroh: "准备 iroh",
|
|
1102
|
+
irohInstallNote: "点击安装会下载固定的 sidecar 发行版(dsh-mobile-iroh 0.1.0-alpha.1,Windows x64 约 15 MB,Linux x64 约 24 MB)到插件私有目录并校验 SHA-256;已安装的配套插件如有校验通过的副本会直接复用,不再下载。",
|
|
1103
|
+
irohComponentNote: "已安装的 sidecar 每次使用都会重新校验 SHA-256。它不随插件包发布;配套提供的副本只能通过移除配套插件来删除。",
|
|
1104
|
+
irohPhaseNote: "实验性连接。需使用含 iroh 的 Android App 0.7.0 及以上完整版;兼容版 APK 和手机浏览器不支持。",
|
|
1105
|
+
irohUnavailable: "iroh 未就绪:尚未安装 sidecar,点击下方「安装官方组件」下载并校验。",
|
|
1106
|
+
irohStarting: "正在解析 iroh 端点身份…",
|
|
1107
|
+
irohConnecting: "Sidecar 已连接,正在启动 DSH 认证网关…",
|
|
1108
|
+
irohCompanionMissing: "尚无校验通过的 iroh 组件,请点击下方「安装官方组件」;无需另装配套插件。",
|
|
1109
|
+
irohDataDirectoryInvalid: "无法准备 sidecar 私有目录。请确认 DSH 能管理自己的配置目录后重试。",
|
|
1110
|
+
irohSidecarInvalid: "已安装的 sidecar 哈希校验失败,请重装组件。",
|
|
1111
|
+
irohDownloadFailed: "下载 sidecar 失败,还没开始校验。请检查网络后重试。",
|
|
1112
|
+
irohPlatformUnsupported: "iroh sidecar 仅发布 Windows x64 与 Linux x64 版本。",
|
|
1113
|
+
irohIdentityFailed: "无法从 sidecar 读取 iroh 端点身份。",
|
|
1114
|
+
irohLaunchFailed: "iroh sidecar 启动失败。",
|
|
1115
|
+
irohExited: "iroh sidecar 意外退出,请重新连接。",
|
|
1116
|
+
irohTimeout: "等待 iroh sidecar 超时,请重新连接。",
|
|
1117
|
+
irohInstallConfirm: "现在下载并校验固定的 iroh sidecar 吗(Windows x64 约 15 MB,Linux x64 约 24 MB)?不会安装其他任何内容。",
|
|
1118
|
+
installingIroh: "正在下载并校验 iroh sidecar…",
|
|
1119
|
+
switchingIroh: "正在切换到 iroh…",
|
|
1120
|
+
reconnectingIroh: "正在重连 iroh 传输…",
|
|
1121
|
+
resetIrohConfirm: "要停用 iroh 通道吗?配套插件与配对不受影响。"
|
|
1122
|
+
});
|
|
1024
1123
|
Object.assign(MOBILE_CONTROL_MESSAGES.en, {
|
|
1025
1124
|
requestTimeout: "The operation timed out. Confirm that DSH is still running, then try again.",
|
|
1026
1125
|
diagnostics: "Diagnostics",
|
|
@@ -7857,25 +7956,64 @@ window.__ModuleLoader__.load({
|
|
|
7857
7956
|
function ownsMobileCustomization(gateway, frontend) {
|
|
7858
7957
|
return gateway === true || frontend === "dedicated";
|
|
7859
7958
|
}
|
|
7860
|
-
/**
|
|
7959
|
+
/**
|
|
7960
|
+
* Emit each safe failure once until recovery, and ignore work after cancellation
|
|
7961
|
+
* or disposal.
|
|
7962
|
+
*
|
|
7963
|
+
* A failure that only reaches the console is invisible to the user, who sees the
|
|
7964
|
+
* customization disappear and cannot tell it from a third-party plugin bug
|
|
7965
|
+
* (issue #298). The reporter therefore also publishes its current state so the
|
|
7966
|
+
* page can render a visible, localized notice with an explicit recovery action.
|
|
7967
|
+
* @param locale - resolves the active UI locale for the copy.
|
|
7968
|
+
* @param output - console sink for the developer-facing line.
|
|
7969
|
+
* @returns the reporting calls plus a subscribable snapshot.
|
|
7970
|
+
*/
|
|
7861
7971
|
function createMobileAssetLoadReporter(locale, output = console) {
|
|
7862
7972
|
const reported = /* @__PURE__ */ new Set();
|
|
7973
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
7863
7974
|
let disposed = false;
|
|
7975
|
+
const visible = () => {
|
|
7976
|
+
if (reported.has("authentication_required")) return "authentication_required";
|
|
7977
|
+
if (reported.has("gateway_required")) return "gateway_required";
|
|
7978
|
+
return reported.values().next().done === true ? void 0 : reported.values().next().value;
|
|
7979
|
+
};
|
|
7980
|
+
const publish = () => {
|
|
7981
|
+
const state = visible();
|
|
7982
|
+
for (const listener of listeners) try {
|
|
7983
|
+
listener(state);
|
|
7984
|
+
} catch (error) {}
|
|
7985
|
+
};
|
|
7864
7986
|
return {
|
|
7865
7987
|
fail(reason, signal) {
|
|
7866
7988
|
if (disposed || signal?.aborted === true || reported.has(reason)) return;
|
|
7867
7989
|
if (reason === "resource_unavailable" && reported.has("authentication_required")) return;
|
|
7868
7990
|
reported.add(reason);
|
|
7869
7991
|
output.warn(`[DSH Mobile] ${COPY[locale()][reason]}`);
|
|
7992
|
+
publish();
|
|
7870
7993
|
},
|
|
7871
7994
|
recover(signal) {
|
|
7872
7995
|
if (disposed || signal.aborted || reported.size === 0) return;
|
|
7873
7996
|
reported.clear();
|
|
7874
7997
|
output.info(`[DSH Mobile] ${COPY[locale()].recovered}`);
|
|
7998
|
+
publish();
|
|
7875
7999
|
},
|
|
7876
8000
|
dispose() {
|
|
7877
8001
|
disposed = true;
|
|
7878
8002
|
reported.clear();
|
|
8003
|
+
listeners.clear();
|
|
8004
|
+
},
|
|
8005
|
+
getState() {
|
|
8006
|
+
return disposed ? void 0 : visible();
|
|
8007
|
+
},
|
|
8008
|
+
subscribe(listener) {
|
|
8009
|
+
if (disposed) return () => {};
|
|
8010
|
+
listeners.add(listener);
|
|
8011
|
+
return () => {
|
|
8012
|
+
listeners.delete(listener);
|
|
8013
|
+
};
|
|
8014
|
+
},
|
|
8015
|
+
message(reason) {
|
|
8016
|
+
return COPY[locale()][reason];
|
|
7879
8017
|
}
|
|
7880
8018
|
};
|
|
7881
8019
|
}
|
|
@@ -7925,6 +8063,125 @@ window.__ModuleLoader__.load({
|
|
|
7925
8063
|
}
|
|
7926
8064
|
}
|
|
7927
8065
|
//#endregion
|
|
8066
|
+
//#region src/mobile-customization-notice.ts
|
|
8067
|
+
/** Copy for the notice, per locale, kept beside the existing dictionaries. */
|
|
8068
|
+
const NOTICE_COPY = {
|
|
8069
|
+
en: {
|
|
8070
|
+
title: {
|
|
8071
|
+
authentication_required: "Device authentication is unavailable.",
|
|
8072
|
+
gateway_required: "Custom mobile CSS, scripts and extensions need the paired Mobile access entry.",
|
|
8073
|
+
other: "Mobile customization could not be loaded."
|
|
8074
|
+
},
|
|
8075
|
+
action: "Retry loading",
|
|
8076
|
+
dismiss: "Dismiss"
|
|
8077
|
+
},
|
|
8078
|
+
zh: {
|
|
8079
|
+
title: {
|
|
8080
|
+
authentication_required: "设备认证暂不可用。",
|
|
8081
|
+
gateway_required: "自定义移动样式、脚本与扩展需要通过已配对的移动访问入口打开。",
|
|
8082
|
+
other: "移动定制加载失败。"
|
|
8083
|
+
},
|
|
8084
|
+
action: "重试加载",
|
|
8085
|
+
dismiss: "忽略"
|
|
8086
|
+
},
|
|
8087
|
+
it: {
|
|
8088
|
+
title: {
|
|
8089
|
+
authentication_required: "Autenticazione del dispositivo non disponibile.",
|
|
8090
|
+
gateway_required: "CSS, script ed estensioni mobili richiedono l’accesso Mobile abbinato.",
|
|
8091
|
+
other: "Impossibile caricare la personalizzazione mobile."
|
|
8092
|
+
},
|
|
8093
|
+
action: "Riprova il caricamento",
|
|
8094
|
+
dismiss: "Ignora"
|
|
8095
|
+
}
|
|
8096
|
+
};
|
|
8097
|
+
const NOTICE_ATTRIBUTE = "data-dsh-mobile-customization-notice";
|
|
8098
|
+
const NOTICE_STYLES = `
|
|
8099
|
+
[${NOTICE_ATTRIBUTE}]{position:fixed;z-index:1000;left:8px;right:8px;bottom:calc(env(safe-area-inset-bottom) + 8px);display:flex;align-items:flex-start;gap:10px;box-sizing:border-box;padding:10px 12px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:12px;background:var(--dsw-alias-bg-layer-2,#fff);box-shadow:0 8px 24px rgb(15 23 42 / 18%);font:13px/1.45 system-ui;color:var(--dsw-alias-label-primary,#16181d)}
|
|
8100
|
+
[${NOTICE_ATTRIBUTE}][hidden]{display:none}
|
|
8101
|
+
[${NOTICE_ATTRIBUTE}] .dshm-notice-text{flex:1 1 auto;min-width:0}
|
|
8102
|
+
[${NOTICE_ATTRIBUTE}] button{flex:none;box-sizing:border-box;min-height:44px;min-width:44px;padding:0 10px;border:1px solid transparent;border-radius:9px;background:transparent;color:var(--dsw-alias-label-primary,#16181d);font:600 13px/1.2 system-ui;cursor:pointer}
|
|
8103
|
+
[${NOTICE_ATTRIBUTE}] button.dshm-notice-action{background:var(--dsw-alias-state-business-primary,#2563eb);color:#fff;border-color:transparent}
|
|
8104
|
+
[${NOTICE_ATTRIBUTE}] button:hover{opacity:.9}
|
|
8105
|
+
[${NOTICE_ATTRIBUTE}]{max-height:40vh;overflow:auto;left:max(8px,env(safe-area-inset-left));right:max(8px,env(safe-area-inset-right));flex-wrap:wrap}
|
|
8106
|
+
[${NOTICE_ATTRIBUTE}] .dshm-notice-text{margin:0;flex-basis:100%}
|
|
8107
|
+
[${NOTICE_ATTRIBUTE}] button:focus-visible{outline:2px solid currentColor;outline-offset:2px}
|
|
8108
|
+
[${NOTICE_ATTRIBUTE}]{background-color:Canvas;background-image:linear-gradient(var(--dsw-alias-bg-layer-2,Canvas),var(--dsw-alias-bg-layer-2,Canvas));color:var(--dsw-alias-label-primary,CanvasText)}
|
|
8109
|
+
`;
|
|
8110
|
+
/** Copy key for a failure, so the notice and the console never disagree. */
|
|
8111
|
+
function titleKey(reason) {
|
|
8112
|
+
return reason === "authentication_required" || reason === "gateway_required" ? reason : "other";
|
|
8113
|
+
}
|
|
8114
|
+
/**
|
|
8115
|
+
* Mount one visible notice that follows the customization reporter's state.
|
|
8116
|
+
* @param document - the page that owns the notice.
|
|
8117
|
+
* @param options - locale lookup and the reconnect action.
|
|
8118
|
+
* @returns a disposer that removes the notice and its stylesheet.
|
|
8119
|
+
*/
|
|
8120
|
+
function installCustomizationNotice(document, options) {
|
|
8121
|
+
if (!(typeof document.createElement === "function" && typeof document.body?.append === "function" && typeof document.head?.append === "function")) return () => {};
|
|
8122
|
+
const style = document.createElement("style");
|
|
8123
|
+
style.dataset.plugin = "dsh-mobile-notice";
|
|
8124
|
+
style.textContent = NOTICE_STYLES;
|
|
8125
|
+
document.head.append(style);
|
|
8126
|
+
const notice = document.createElement("div");
|
|
8127
|
+
if (typeof notice.setAttribute !== "function" || typeof notice.append !== "function") {
|
|
8128
|
+
style.remove();
|
|
8129
|
+
return () => {};
|
|
8130
|
+
}
|
|
8131
|
+
notice.setAttribute(NOTICE_ATTRIBUTE, "");
|
|
8132
|
+
notice.setAttribute("role", "status");
|
|
8133
|
+
notice.setAttribute("aria-live", "polite");
|
|
8134
|
+
notice.hidden = true;
|
|
8135
|
+
const text = document.createElement("p");
|
|
8136
|
+
text.className = "dshm-notice-text";
|
|
8137
|
+
const action = document.createElement("button");
|
|
8138
|
+
action.type = "button";
|
|
8139
|
+
action.className = "dshm-notice-action";
|
|
8140
|
+
const dismiss = document.createElement("button");
|
|
8141
|
+
dismiss.type = "button";
|
|
8142
|
+
notice.append(text, action, dismiss);
|
|
8143
|
+
document.body.append(notice);
|
|
8144
|
+
let current;
|
|
8145
|
+
let dismissed;
|
|
8146
|
+
const render = () => {
|
|
8147
|
+
const copy = NOTICE_COPY[options.locale()] ?? NOTICE_COPY.en;
|
|
8148
|
+
if (current === void 0 || current === dismissed) {
|
|
8149
|
+
notice.hidden = true;
|
|
8150
|
+
return;
|
|
8151
|
+
}
|
|
8152
|
+
const detail = options.message(current);
|
|
8153
|
+
text.textContent = `${copy.title[titleKey(current)]} ${detail}`;
|
|
8154
|
+
action.textContent = copy.action;
|
|
8155
|
+
dismiss.textContent = copy.dismiss;
|
|
8156
|
+
notice.hidden = false;
|
|
8157
|
+
};
|
|
8158
|
+
let disposed = false;
|
|
8159
|
+
const retry = () => {
|
|
8160
|
+
if (!disposed) options.reconnect();
|
|
8161
|
+
};
|
|
8162
|
+
const close = () => {
|
|
8163
|
+
if (disposed) return;
|
|
8164
|
+
dismissed = current;
|
|
8165
|
+
render();
|
|
8166
|
+
};
|
|
8167
|
+
action.addEventListener("click", retry);
|
|
8168
|
+
dismiss.addEventListener("click", close);
|
|
8169
|
+
const unsubscribe = options.subscribe((reason) => {
|
|
8170
|
+
if (reason !== current) dismissed = void 0;
|
|
8171
|
+
current = reason;
|
|
8172
|
+
render();
|
|
8173
|
+
});
|
|
8174
|
+
render();
|
|
8175
|
+
return () => {
|
|
8176
|
+
disposed = true;
|
|
8177
|
+
action.removeEventListener("click", retry);
|
|
8178
|
+
dismiss.removeEventListener("click", close);
|
|
8179
|
+
unsubscribe();
|
|
8180
|
+
notice.remove();
|
|
8181
|
+
style.remove();
|
|
8182
|
+
};
|
|
8183
|
+
}
|
|
8184
|
+
//#endregion
|
|
7928
8185
|
//#region src/extension-limits.ts
|
|
7929
8186
|
/** Maximum sizes accepted for legacy mobile customization resources. */
|
|
7930
8187
|
const LEGACY_CUSTOM_ASSET_LIMIT = 262144;
|
|
@@ -7948,6 +8205,25 @@ window.__ModuleLoader__.load({
|
|
|
7948
8205
|
function shouldRenewSession(connection, downForMs) {
|
|
7949
8206
|
return isDisconnected(connection) && downForMs >= 5e3;
|
|
7950
8207
|
}
|
|
8208
|
+
/**
|
|
8209
|
+
* Authentication rejection can warrant one renewal even while transport appears
|
|
8210
|
+
* connected. A 403 can also indicate a permission failure, so it is not proof
|
|
8211
|
+
* that the Session expired.
|
|
8212
|
+
*
|
|
8213
|
+
* Issue #298: DSH keeps Sessions in memory, so a Host restart kills every phone
|
|
8214
|
+
* Session while the device credential survives. The live WebSocket can keep
|
|
8215
|
+
* reporting `connected` for a while, so waiting for a disconnect never fires and
|
|
8216
|
+
* the customization channel stays dark. A 401/403 on an authenticated gateway
|
|
8217
|
+
* request is the signal that actually arrives, and it must be able to start the
|
|
8218
|
+
* same renewal the disconnect path uses.
|
|
8219
|
+
* @param status - HTTP status of a rejected gateway request.
|
|
8220
|
+
* @param connection - current public connection state, if known.
|
|
8221
|
+
* @returns whether this rejection should request a renewal.
|
|
8222
|
+
*/
|
|
8223
|
+
function shouldRenewAfterRejection(status, connection) {
|
|
8224
|
+
if (status !== 401 && status !== 403) return false;
|
|
8225
|
+
return !isDisconnected(connection) || connection === void 0;
|
|
8226
|
+
}
|
|
7951
8227
|
function isDisconnected(connection) {
|
|
7952
8228
|
return connection === "connecting" || connection === "disconnected";
|
|
7953
8229
|
}
|
|
@@ -7956,9 +8232,12 @@ window.__ModuleLoader__.load({
|
|
|
7956
8232
|
* @param getConnection - Resolve the current service to reject a replaced owner.
|
|
7957
8233
|
* @param page - The document's window and advertised native adapter.
|
|
7958
8234
|
* @param document - The mounted document; page replacement withdraws its work.
|
|
8235
|
+
* @param onRejected - receives the rejection signal, so a caller that observes a
|
|
8236
|
+
* 401 can ask this same owner for one renewal instead of starting its own.
|
|
8237
|
+
* @param onRenewed - refresh read-only customization after confirmed Cookie installation.
|
|
7959
8238
|
* @returns An awaited disposer, or nothing when no public connection is present.
|
|
7960
8239
|
*/
|
|
7961
|
-
function installMobileConnectionRecovery(getConnection, page, document) {
|
|
8240
|
+
function installMobileConnectionRecovery(getConnection, page, document, onRejected, onRenewed) {
|
|
7962
8241
|
const connection = getConnection();
|
|
7963
8242
|
const state = connection?.state;
|
|
7964
8243
|
if (connection === void 0 || state === void 0 || typeof connection.reconnect !== "function") return;
|
|
@@ -7968,6 +8247,8 @@ window.__ModuleLoader__.load({
|
|
|
7968
8247
|
let currentState = state.getSnapshot();
|
|
7969
8248
|
let downSince = isDisconnected(currentState) ? Date.now() : void 0;
|
|
7970
8249
|
let attempted = false;
|
|
8250
|
+
let credentialsRejected = false;
|
|
8251
|
+
let rejectedSince;
|
|
7971
8252
|
let bridge = page.__DSH_MOBILE_NATIVE__;
|
|
7972
8253
|
let pending;
|
|
7973
8254
|
let pendingAbort;
|
|
@@ -7981,8 +8262,13 @@ window.__ModuleLoader__.load({
|
|
|
7981
8262
|
const next = state.getSnapshot();
|
|
7982
8263
|
if (!isDisconnected(next)) {
|
|
7983
8264
|
downSince = void 0;
|
|
7984
|
-
|
|
7985
|
-
|
|
8265
|
+
if (currentState !== next) {
|
|
8266
|
+
if (next === "connected") {
|
|
8267
|
+
rejectedSince = void 0;
|
|
8268
|
+
attempted = false;
|
|
8269
|
+
}
|
|
8270
|
+
cancelPending();
|
|
8271
|
+
}
|
|
7986
8272
|
} else if (downSince === void 0) {
|
|
7987
8273
|
downSince = Date.now();
|
|
7988
8274
|
attempted = false;
|
|
@@ -7999,30 +8285,63 @@ window.__ModuleLoader__.load({
|
|
|
7999
8285
|
const renew = () => {
|
|
8000
8286
|
if (!current()) {
|
|
8001
8287
|
dispose();
|
|
8002
|
-
return;
|
|
8288
|
+
return false;
|
|
8003
8289
|
}
|
|
8004
8290
|
updateState();
|
|
8005
8291
|
updateBridge();
|
|
8006
8292
|
const adapter = bridge;
|
|
8007
|
-
|
|
8293
|
+
const downLongEnough = downSince !== void 0 && shouldRenewSession(currentState, Date.now() - downSince);
|
|
8294
|
+
const rejectionPending = rejectedSince !== void 0;
|
|
8295
|
+
if (document.visibilityState !== "visible" || adapter === void 0 || pending !== void 0 || attempted || credentialsRejected || !downLongEnough && !rejectionPending) return false;
|
|
8008
8296
|
attempted = true;
|
|
8009
8297
|
const owner = generation;
|
|
8010
8298
|
const abort = new AbortController();
|
|
8011
8299
|
pendingAbort = abort;
|
|
8012
|
-
const owns = () => current() && !abort.signal.aborted && generation === owner && page.__DSH_MOBILE_NATIVE__ === adapter
|
|
8300
|
+
const owns = () => current() && !abort.signal.aborted && generation === owner && page.__DSH_MOBILE_NATIVE__ === adapter;
|
|
8013
8301
|
const task = Promise.resolve().then(async () => {
|
|
8014
8302
|
if (!owns()) return;
|
|
8015
8303
|
const capabilities = await adapter.capabilities();
|
|
8016
8304
|
if (!owns() || !capabilities.includes("auth.renew")) return;
|
|
8017
8305
|
const result = await adapter.invoke("auth.renew", {}, { signal: abort.signal });
|
|
8018
8306
|
if (!owns() || typeof result !== "object" || result === null || !("renewed" in result) || result.renewed !== true) return;
|
|
8307
|
+
rejectedSince = void 0;
|
|
8019
8308
|
connection.reconnect?.();
|
|
8020
|
-
|
|
8309
|
+
if (current()) onRenewed?.();
|
|
8310
|
+
}).catch((error) => {
|
|
8311
|
+
if (!owns() || typeof error !== "object" || error === null || !("code" in error)) return;
|
|
8312
|
+
credentialsRejected = error.code === "device_revoked" || error.code === "device_expired" || error.code === "authentication_failed";
|
|
8313
|
+
}).finally(() => {
|
|
8021
8314
|
if (pending !== task) return;
|
|
8022
8315
|
pending = void 0;
|
|
8023
8316
|
pendingAbort = void 0;
|
|
8024
8317
|
});
|
|
8025
8318
|
pending = task;
|
|
8319
|
+
return true;
|
|
8320
|
+
};
|
|
8321
|
+
const retry = () => {
|
|
8322
|
+
if (!current()) return false;
|
|
8323
|
+
updateState();
|
|
8324
|
+
updateBridge();
|
|
8325
|
+
if (!current() || pending !== void 0 || credentialsRejected || bridge === void 0 || rejectedSince === void 0 && downSince === void 0) return false;
|
|
8326
|
+
attempted = false;
|
|
8327
|
+
return renew();
|
|
8328
|
+
};
|
|
8329
|
+
/**
|
|
8330
|
+
* Record a rejected authenticated request and try the same single renewal.
|
|
8331
|
+
*
|
|
8332
|
+
* Only an owner that is still current and has not already attempted may act, so
|
|
8333
|
+
* a burst of 401s cannot start parallel renewals, and a revoked device cannot
|
|
8334
|
+
* keep retrying: revocation arrives as 401 too, but the native bridge refuses
|
|
8335
|
+
* `auth.renew` and the attempt is not restarted.
|
|
8336
|
+
* @param status - HTTP status the gateway returned.
|
|
8337
|
+
*/
|
|
8338
|
+
const rejected = (status) => {
|
|
8339
|
+
if (!current()) return;
|
|
8340
|
+
updateState();
|
|
8341
|
+
updateBridge();
|
|
8342
|
+
if (!shouldRenewAfterRejection(status, state.getSnapshot())) return;
|
|
8343
|
+
rejectedSince ??= Date.now();
|
|
8344
|
+
renew();
|
|
8026
8345
|
};
|
|
8027
8346
|
const resume = () => {
|
|
8028
8347
|
if (!current()) {
|
|
@@ -8054,6 +8373,7 @@ window.__ModuleLoader__.load({
|
|
|
8054
8373
|
document.addEventListener("visibilitychange", resume);
|
|
8055
8374
|
page.addEventListener("dsh-mobile-native-ready", updateBridge);
|
|
8056
8375
|
page.addEventListener("pagehide", pagehide);
|
|
8376
|
+
onRejected?.(rejected, retry);
|
|
8057
8377
|
return dispose;
|
|
8058
8378
|
}
|
|
8059
8379
|
//#endregion
|
|
@@ -8145,7 +8465,8 @@ window.__ModuleLoader__.load({
|
|
|
8145
8465
|
draft: "",
|
|
8146
8466
|
error: null,
|
|
8147
8467
|
hostBusy: false,
|
|
8148
|
-
committed: false
|
|
8468
|
+
committed: false,
|
|
8469
|
+
creating: false
|
|
8149
8470
|
});
|
|
8150
8471
|
/** Read navigation stops on close; committing is final until the owner withdraws this flow. */
|
|
8151
8472
|
var MobileDirectoryBrowser = class {
|
|
@@ -8199,6 +8520,14 @@ window.__ModuleLoader__.load({
|
|
|
8199
8520
|
hostBusy
|
|
8200
8521
|
});
|
|
8201
8522
|
}
|
|
8523
|
+
/** Block navigation/adoption while this flow's child-folder editor owns interaction. */
|
|
8524
|
+
setCreating(creating) {
|
|
8525
|
+
if (this.disposed || this.snapshot.creating === creating) return;
|
|
8526
|
+
this.publish({
|
|
8527
|
+
...this.snapshot,
|
|
8528
|
+
creating
|
|
8529
|
+
});
|
|
8530
|
+
}
|
|
8202
8531
|
/** Read a folder or the initial home; superseded results cannot publish into the new request. */
|
|
8203
8532
|
navigate(path) {
|
|
8204
8533
|
if (!this.canNavigate()) return false;
|
|
@@ -8281,7 +8610,7 @@ window.__ModuleLoader__.load({
|
|
|
8281
8610
|
}
|
|
8282
8611
|
/** Cancel reading immediately; a committed/Host-busy adoption cannot be reported as cancelled. */
|
|
8283
8612
|
cancel() {
|
|
8284
|
-
if (!this.
|
|
8613
|
+
if (!this.canClose()) return false;
|
|
8285
8614
|
this.withdrawRead();
|
|
8286
8615
|
this.publish(CLOSED);
|
|
8287
8616
|
return true;
|
|
@@ -8295,9 +8624,12 @@ window.__ModuleLoader__.load({
|
|
|
8295
8624
|
await Promise.allSettled([...this.tasks]);
|
|
8296
8625
|
if (this.taskFailures.length > 0) throw new AggregateError(this.taskFailures, "Mobile directory observers failed during a listing");
|
|
8297
8626
|
}
|
|
8298
|
-
|
|
8627
|
+
canClose() {
|
|
8299
8628
|
return !this.disposed && this.snapshot.open && !this.snapshot.hostBusy && !this.snapshot.committed;
|
|
8300
8629
|
}
|
|
8630
|
+
canNavigate() {
|
|
8631
|
+
return this.canClose() && !this.snapshot.creating;
|
|
8632
|
+
}
|
|
8301
8633
|
current(generation) {
|
|
8302
8634
|
return !this.disposed && this.snapshot.open && generation === this.generation;
|
|
8303
8635
|
}
|
|
@@ -8311,6 +8643,171 @@ window.__ModuleLoader__.load({
|
|
|
8311
8643
|
for (const listener of this.listeners) listener();
|
|
8312
8644
|
}
|
|
8313
8645
|
};
|
|
8646
|
+
/** A creation failure without raw response bodies or filesystem details. */
|
|
8647
|
+
var MobileDirectoryCreateError = class extends Error {
|
|
8648
|
+
code;
|
|
8649
|
+
constructor(code) {
|
|
8650
|
+
super(`Mobile directory creation failed: ${code}`);
|
|
8651
|
+
this.code = code;
|
|
8652
|
+
this.name = "MobileDirectoryCreateError";
|
|
8653
|
+
}
|
|
8654
|
+
};
|
|
8655
|
+
/** Portable single-segment validation; the Host additionally enforces its filesystem rules. */
|
|
8656
|
+
function isMobileDirectoryName(name) {
|
|
8657
|
+
return name.trim() !== "" && name.length <= 255 && name !== "." && name !== ".." && !/[\\/\0]/u.test(name);
|
|
8658
|
+
}
|
|
8659
|
+
const CREATE_CLOSED = Object.freeze({
|
|
8660
|
+
open: false,
|
|
8661
|
+
parent: null,
|
|
8662
|
+
name: "",
|
|
8663
|
+
pending: false,
|
|
8664
|
+
error: null
|
|
8665
|
+
});
|
|
8666
|
+
/** Owns one non-replayed write, its deadline, cancellation and physical teardown. */
|
|
8667
|
+
var MobileDirectoryCreator = class {
|
|
8668
|
+
create;
|
|
8669
|
+
onCreated;
|
|
8670
|
+
deadlineMs;
|
|
8671
|
+
snapshot = CREATE_CLOSED;
|
|
8672
|
+
listeners = /* @__PURE__ */ new Set();
|
|
8673
|
+
tasks = /* @__PURE__ */ new Set();
|
|
8674
|
+
failures = [];
|
|
8675
|
+
active;
|
|
8676
|
+
generation = 0;
|
|
8677
|
+
disposed = false;
|
|
8678
|
+
constructor(create, onCreated, deadlineMs = 3e4) {
|
|
8679
|
+
this.create = create;
|
|
8680
|
+
this.onCreated = onCreated;
|
|
8681
|
+
this.deadlineMs = deadlineMs;
|
|
8682
|
+
}
|
|
8683
|
+
getSnapshot = () => this.snapshot;
|
|
8684
|
+
/** Subscribe to this editor; the returned disposer withdraws the observer. */
|
|
8685
|
+
subscribe(listener) {
|
|
8686
|
+
this.listeners.add(listener);
|
|
8687
|
+
return () => {
|
|
8688
|
+
this.listeners.delete(listener);
|
|
8689
|
+
};
|
|
8690
|
+
}
|
|
8691
|
+
/** Open against one already confirmed parent; cancelled physical work retains admission. */
|
|
8692
|
+
open(parent) {
|
|
8693
|
+
if (this.disposed || this.snapshot.open || this.tasks.size > 0 || !isMobileDirectoryPath(parent)) return false;
|
|
8694
|
+
this.publish({
|
|
8695
|
+
...CREATE_CLOSED,
|
|
8696
|
+
open: true,
|
|
8697
|
+
parent
|
|
8698
|
+
});
|
|
8699
|
+
return true;
|
|
8700
|
+
}
|
|
8701
|
+
/** Edit only while no write is in flight; typing never starts a filesystem operation. */
|
|
8702
|
+
setName(name) {
|
|
8703
|
+
if (this.disposed || !this.snapshot.open || this.snapshot.pending) return;
|
|
8704
|
+
this.publish({
|
|
8705
|
+
...this.snapshot,
|
|
8706
|
+
name,
|
|
8707
|
+
error: null
|
|
8708
|
+
});
|
|
8709
|
+
}
|
|
8710
|
+
/** Admit one explicit creation. Repeated clicks/Enter cannot replay an admitted request. */
|
|
8711
|
+
submit() {
|
|
8712
|
+
if (this.disposed || !this.snapshot.open || this.snapshot.pending || this.tasks.size > 0 || this.snapshot.parent === null) return false;
|
|
8713
|
+
const name = this.snapshot.name.trim();
|
|
8714
|
+
if (!isMobileDirectoryName(name)) {
|
|
8715
|
+
this.publish({
|
|
8716
|
+
...this.snapshot,
|
|
8717
|
+
error: "invalid-name"
|
|
8718
|
+
});
|
|
8719
|
+
return false;
|
|
8720
|
+
}
|
|
8721
|
+
const parent = this.snapshot.parent;
|
|
8722
|
+
const generation = this.generation;
|
|
8723
|
+
const controller = new AbortController();
|
|
8724
|
+
this.active = controller;
|
|
8725
|
+
this.publish({
|
|
8726
|
+
...this.snapshot,
|
|
8727
|
+
pending: true,
|
|
8728
|
+
error: null
|
|
8729
|
+
});
|
|
8730
|
+
if (!this.current(generation) || controller.signal.aborted) {
|
|
8731
|
+
if (this.active === controller) this.active = void 0;
|
|
8732
|
+
if (!this.disposed) this.publish({
|
|
8733
|
+
...this.snapshot,
|
|
8734
|
+
pending: false
|
|
8735
|
+
});
|
|
8736
|
+
return false;
|
|
8737
|
+
}
|
|
8738
|
+
const timer = setTimeout(() => {
|
|
8739
|
+
controller.abort(new MobileDirectoryCreateError("timeout"));
|
|
8740
|
+
if (this.current(generation)) this.publish({
|
|
8741
|
+
...this.snapshot,
|
|
8742
|
+
error: "timeout"
|
|
8743
|
+
});
|
|
8744
|
+
}, this.deadlineMs);
|
|
8745
|
+
const task = (async () => {
|
|
8746
|
+
let created;
|
|
8747
|
+
try {
|
|
8748
|
+
created = await this.create(parent, name, controller.signal);
|
|
8749
|
+
controller.signal.throwIfAborted();
|
|
8750
|
+
if (!isMobileDirectoryPath(created)) throw new MobileDirectoryCreateError("invalid-response");
|
|
8751
|
+
} catch (error) {
|
|
8752
|
+
created = void 0;
|
|
8753
|
+
if (this.current(generation)) {
|
|
8754
|
+
const failure = controller.signal.aborted ? controller.signal.reason : error;
|
|
8755
|
+
this.publish({
|
|
8756
|
+
...this.snapshot,
|
|
8757
|
+
error: failure instanceof MobileDirectoryCreateError ? failure.code : "request-failed"
|
|
8758
|
+
});
|
|
8759
|
+
}
|
|
8760
|
+
} finally {
|
|
8761
|
+
clearTimeout(timer);
|
|
8762
|
+
if (this.active === controller) this.active = void 0;
|
|
8763
|
+
if (!this.disposed) this.publish({
|
|
8764
|
+
...this.snapshot,
|
|
8765
|
+
pending: false
|
|
8766
|
+
});
|
|
8767
|
+
}
|
|
8768
|
+
if (created !== void 0 && this.current(generation)) {
|
|
8769
|
+
this.publish(CREATE_CLOSED);
|
|
8770
|
+
this.onCreated(created);
|
|
8771
|
+
}
|
|
8772
|
+
})();
|
|
8773
|
+
this.tasks.add(task);
|
|
8774
|
+
task.then(() => {
|
|
8775
|
+
this.tasks.delete(task);
|
|
8776
|
+
}, (error) => {
|
|
8777
|
+
this.tasks.delete(task);
|
|
8778
|
+
this.failures.push(error);
|
|
8779
|
+
});
|
|
8780
|
+
return true;
|
|
8781
|
+
}
|
|
8782
|
+
/** Withdraw the editor and late results; an already completed mkdir is not rolled back. */
|
|
8783
|
+
close() {
|
|
8784
|
+
if (this.disposed || !this.snapshot.open) return false;
|
|
8785
|
+
this.generation++;
|
|
8786
|
+
this.active?.abort();
|
|
8787
|
+
this.publish({
|
|
8788
|
+
...CREATE_CLOSED,
|
|
8789
|
+
pending: this.active !== void 0
|
|
8790
|
+
});
|
|
8791
|
+
return true;
|
|
8792
|
+
}
|
|
8793
|
+
/** Stop admission synchronously and await every accepted write before completion. */
|
|
8794
|
+
async dispose() {
|
|
8795
|
+
this.disposed = true;
|
|
8796
|
+
this.generation++;
|
|
8797
|
+
this.active?.abort();
|
|
8798
|
+
this.snapshot = CREATE_CLOSED;
|
|
8799
|
+
this.listeners.clear();
|
|
8800
|
+
await Promise.allSettled([...this.tasks]);
|
|
8801
|
+
if (this.failures.length > 0) throw new AggregateError(this.failures, "Mobile directory creation observers failed");
|
|
8802
|
+
}
|
|
8803
|
+
current(generation) {
|
|
8804
|
+
return !this.disposed && this.snapshot.open && generation === this.generation;
|
|
8805
|
+
}
|
|
8806
|
+
publish(snapshot) {
|
|
8807
|
+
this.snapshot = Object.freeze(snapshot);
|
|
8808
|
+
for (const listener of this.listeners) listener();
|
|
8809
|
+
}
|
|
8810
|
+
};
|
|
8314
8811
|
//#endregion
|
|
8315
8812
|
//#region src/mobile-directory-picker.ts
|
|
8316
8813
|
/** Dedicated Mobile directory-flow occupant; stock DSH retains its own chooser. */
|
|
@@ -8335,7 +8832,17 @@ window.__ModuleLoader__.load({
|
|
|
8335
8832
|
"invalid-response": "目录响应不可用,请重试或重新连接电脑",
|
|
8336
8833
|
"request-failed": "读取目录失败,请检查连接后重试",
|
|
8337
8834
|
timeout: "读取目录超时,请重试或取消",
|
|
8338
|
-
failedOpen: "无法打开目录选择器,请重新连接后重试"
|
|
8835
|
+
failedOpen: "无法打开目录选择器,请重新连接后重试",
|
|
8836
|
+
create: "新建文件夹",
|
|
8837
|
+
createTitle: "新建文件夹名称",
|
|
8838
|
+
createAction: "创建",
|
|
8839
|
+
createInvalid: "请输入单个文件夹名称,不能包含路径分隔符。",
|
|
8840
|
+
createExists: "同名文件夹已存在。",
|
|
8841
|
+
createFailed: "无法创建文件夹,请检查该目录的写入权限后重试。",
|
|
8842
|
+
createParent: "创建位置",
|
|
8843
|
+
creating: "正在创建文件夹…",
|
|
8844
|
+
createStopping: "正在结束创建请求…",
|
|
8845
|
+
createUnknown: "无法确认创建结果,文件夹可能已创建。请返回并重新读取目录,确认后再决定是否重试。"
|
|
8339
8846
|
},
|
|
8340
8847
|
en: {
|
|
8341
8848
|
title: "Select workspace directory",
|
|
@@ -8357,7 +8864,17 @@ window.__ModuleLoader__.load({
|
|
|
8357
8864
|
"invalid-response": "The directory response is unavailable. Retry or reconnect to the computer.",
|
|
8358
8865
|
"request-failed": "Could not load this directory. Check the connection and retry.",
|
|
8359
8866
|
timeout: "Loading timed out. Retry or cancel.",
|
|
8360
|
-
failedOpen: "Could not open the directory picker. Reconnect and retry."
|
|
8867
|
+
failedOpen: "Could not open the directory picker. Reconnect and retry.",
|
|
8868
|
+
create: "New folder",
|
|
8869
|
+
createTitle: "New folder name",
|
|
8870
|
+
createAction: "Create",
|
|
8871
|
+
createInvalid: "Enter a single folder name without path separators.",
|
|
8872
|
+
createExists: "A folder with that name already exists.",
|
|
8873
|
+
createFailed: "Could not create the folder. Check write access to this directory and retry.",
|
|
8874
|
+
createParent: "Create in",
|
|
8875
|
+
creating: "Creating folder…",
|
|
8876
|
+
createStopping: "Finishing the creation request…",
|
|
8877
|
+
createUnknown: "The creation result is unknown; the folder may already exist. Go back and reload the directory before deciding whether to retry."
|
|
8361
8878
|
},
|
|
8362
8879
|
it: {
|
|
8363
8880
|
title: "Seleziona la cartella del workspace",
|
|
@@ -8378,11 +8895,21 @@ window.__ModuleLoader__.load({
|
|
|
8378
8895
|
unauthorized: "Autorizzazione non disponibile. Chiudi questa finestra e riconnettiti al computer.",
|
|
8379
8896
|
"invalid-response": "Risposta della cartella non disponibile. Riprova o riconnettiti al computer.",
|
|
8380
8897
|
"request-failed": "Impossibile caricare la cartella. Controlla la connessione e riprova.",
|
|
8898
|
+
create: "Nuova cartella",
|
|
8899
|
+
createTitle: "Nome della nuova cartella",
|
|
8900
|
+
createAction: "Crea",
|
|
8901
|
+
createInvalid: "Inserisci un singolo nome di cartella senza separatori di percorso.",
|
|
8902
|
+
createExists: "Esiste già una cartella con questo nome.",
|
|
8903
|
+
createFailed: "Impossibile creare la cartella. Controlla i permessi di scrittura e riprova.",
|
|
8381
8904
|
timeout: "Caricamento scaduto. Riprova o annulla.",
|
|
8382
|
-
failedOpen: "Impossibile aprire la selezione della cartella. Riconnettiti e riprova."
|
|
8905
|
+
failedOpen: "Impossibile aprire la selezione della cartella. Riconnettiti e riprova.",
|
|
8906
|
+
createParent: "Crea in",
|
|
8907
|
+
creating: "Creazione della cartella…",
|
|
8908
|
+
createStopping: "Chiusura della richiesta di creazione…",
|
|
8909
|
+
createUnknown: "Esito della creazione sconosciuto: la cartella potrebbe già esistere. Torna indietro e ricarica la directory prima di riprovare."
|
|
8383
8910
|
}
|
|
8384
8911
|
};
|
|
8385
|
-
/** Authenticated
|
|
8912
|
+
/** Authenticated directory browsing/creation route; no native picker or Host service is replaced. */
|
|
8386
8913
|
const WORKSPACE_DIRECTORIES_PATH = "/mobile-access/workspace-directories";
|
|
8387
8914
|
/** Ask only this plugin's live directory dialog to handle a routed native Back. */
|
|
8388
8915
|
function dismissMobileDirectoryPicker(dialog) {
|
|
@@ -8399,6 +8926,32 @@ window.__ModuleLoader__.load({
|
|
|
8399
8926
|
}
|
|
8400
8927
|
return "en";
|
|
8401
8928
|
}
|
|
8929
|
+
/** Create one child directory through the same authenticated route that lists them. */
|
|
8930
|
+
async function createDirectory(path, name, signal) {
|
|
8931
|
+
const response = await fetch(WORKSPACE_DIRECTORIES_PATH, {
|
|
8932
|
+
method: "POST",
|
|
8933
|
+
credentials: "same-origin",
|
|
8934
|
+
cache: "no-store",
|
|
8935
|
+
headers: { "content-type": "application/json" },
|
|
8936
|
+
body: JSON.stringify({
|
|
8937
|
+
path,
|
|
8938
|
+
name
|
|
8939
|
+
}),
|
|
8940
|
+
signal
|
|
8941
|
+
});
|
|
8942
|
+
if (!response.ok) {
|
|
8943
|
+
const code = response.status === 409 ? "directory-exists" : response.status === 400 ? "directory-create-failed" : response.status === 401 || response.status === 403 ? "unauthorized" : "directory-create-failed";
|
|
8944
|
+
try {
|
|
8945
|
+
await response.body?.cancel();
|
|
8946
|
+
} catch (error) {
|
|
8947
|
+
throw new MobileDirectoryCreateError(code);
|
|
8948
|
+
}
|
|
8949
|
+
throw new MobileDirectoryCreateError(code);
|
|
8950
|
+
}
|
|
8951
|
+
const payload = await response.json();
|
|
8952
|
+
if (typeof payload?.path !== "string" || !isMobileDirectoryPath(payload.path)) throw new MobileDirectoryCreateError("invalid-response");
|
|
8953
|
+
return payload.path;
|
|
8954
|
+
}
|
|
8402
8955
|
async function readDirectories(path, signal) {
|
|
8403
8956
|
const query = path === null ? "" : `?path=${encodeURIComponent(path)}`;
|
|
8404
8957
|
const response = await fetch(`${WORKSPACE_DIRECTORIES_PATH}${query}`, {
|
|
@@ -8424,25 +8977,51 @@ window.__ModuleLoader__.load({
|
|
|
8424
8977
|
const STYLES = `
|
|
8425
8978
|
dialog.dshm-picker{position:fixed;inset:0;box-sizing:border-box;width:100%;max-width:none;height:100%;max-height:none;margin:0;padding:max(16px,env(safe-area-inset-top)) max(16px,env(safe-area-inset-right)) max(16px,env(safe-area-inset-bottom)) max(16px,env(safe-area-inset-left));border:0;background:transparent;color:var(--dsw-alias-label-primary,#16181d);font:400 14px/1.5 system-ui;overflow:hidden}
|
|
8426
8979
|
dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::backdrop{background:rgb(0 0 0 / 42%)}
|
|
8427
|
-
.dshm-picker__panel{display:flex;flex-direction:column;box-sizing:border-box;width:min(680px,100%);height:min(
|
|
8428
|
-
.dshm-picker__header{display:flex;align-items:center;flex:none;gap:
|
|
8429
|
-
.dshm-picker__title{flex:1;min-width:0;margin:0;font-size:16px;font-weight:
|
|
8430
|
-
.dshm-picker__toolbar{display:flex;flex-wrap:
|
|
8431
|
-
.dshm-picker__path{
|
|
8980
|
+
.dshm-picker__panel{display:flex;flex-direction:column;box-sizing:border-box;width:min(680px,100%);height:min(720px,100%);min-height:0;overflow:hidden;border:0;border-radius:20px;box-shadow:0 0 0 .5px rgba(0 0 0 / 16%),0 3px 8px rgba(0 0 0 / 4%);background:var(--dsw-alias-bg-layer-1,#fff)}
|
|
8981
|
+
.dshm-picker__header{display:flex;align-items:center;flex:none;gap:8px;padding:4px 8px 4px 12px;border-bottom:1px solid var(--dsw-alias-border-l2,#dde0e4)}
|
|
8982
|
+
.dshm-picker__title{flex:1;min-width:0;margin:0;font-size:16px;font-weight:510;line-height:24px;overflow-wrap:anywhere}
|
|
8983
|
+
.dshm-picker__toolbar{display:flex;align-items:center;flex-wrap:nowrap;gap:8px;flex:none;padding:8px 10px}.dshm-picker__path-label{flex:1;min-width:0;display:flex;color:var(--dsw-alias-label-secondary,#606873)}
|
|
8984
|
+
.dshm-picker__path-caption{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
|
|
8985
|
+
.dshm-picker__path{box-sizing:border-box;width:100%;min-width:0;min-height:44px;padding:6px 8px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:8px;background:var(--dsw-alias-bg-layer-2,#f7f8fa);color:var(--dsw-alias-label-primary,#16181d);font:400 16px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace}
|
|
8432
8986
|
.dshm-picker__path[aria-invalid=true]{border-color:var(--dsw-alias-state-error-primary,#b42318)}.dshm-picker__go{align-self:flex-end}
|
|
8433
|
-
.dshm-picker__navigation{display:flex;flex:none;gap:8px;padding:0
|
|
8434
|
-
.dshm-picker__list{flex:1;min-height:0;margin:0;padding:4px
|
|
8435
|
-
.dshm-picker__row{display:flex;align-items:center;gap:
|
|
8987
|
+
.dshm-picker__navigation{display:flex;flex:none;gap:8px;padding:0}
|
|
8988
|
+
.dshm-picker__list{flex:1;min-height:0;margin:0;padding:0 10px 4px;overflow:auto;overscroll-behavior:contain;list-style:none;scroll-padding:4px}
|
|
8989
|
+
.dshm-picker__row{display:flex;align-items:center;gap:8px;box-sizing:border-box;width:100%;min-height:44px;padding:6px 8px;border:0;border-radius:8px;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer;touch-action:manipulation}
|
|
8436
8990
|
.dshm-picker__folder{flex:none;width:20px;height:20px;color:var(--dsw-alias-label-secondary,#606873)}
|
|
8437
8991
|
.dshm-picker__name{flex:1;min-width:0;overflow-wrap:anywhere}.dshm-picker__row:hover:not(:disabled),.dshm-picker__button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#eef1f5)}
|
|
8438
8992
|
.dshm-picker__row:active:not(:disabled),.dshm-picker__button:active:not(:disabled){background:var(--dsw-alias-interactive-bg-active,#e4e8ee)}
|
|
8439
|
-
.dshm-picker__button{flex:none;box-sizing:border-box;min-width:
|
|
8993
|
+
.dshm-picker__button{flex:none;box-sizing:border-box;min-width:44px;min-height:44px;padding:6px 10px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:8px;background:transparent;color:inherit;font:500 14px/1.4 system-ui;cursor:pointer;touch-action:manipulation}
|
|
8994
|
+
.dshm-picker__icon{display:inline-flex;align-items:center;justify-content:center;width:44px;padding:0;border-color:transparent}.dshm-picker__icon>svg{display:block;width:18px;height:18px}
|
|
8440
8995
|
.dshm-picker :is(button,input):focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#2563eb);outline-offset:2px}.dshm-picker button:disabled{opacity:.5;cursor:default}
|
|
8441
|
-
.dshm-
|
|
8442
|
-
.dshm-
|
|
8443
|
-
.dshm-
|
|
8444
|
-
|
|
8996
|
+
.dshm-picker [hidden]{display:none!important}.dshm-picker__create{flex:1;min-height:0;overflow:auto;display:flex;flex-direction:column;gap:8px;padding:12px 16px}
|
|
8997
|
+
.dshm-picker__create-title{margin:0;font-size:13px;color:var(--dsw-alias-label-secondary,#606873)}
|
|
8998
|
+
.dshm-picker__create-input{box-sizing:border-box;width:100%;min-width:0;min-height:48px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-2,#f7f8fa);color:var(--dsw-alias-label-primary,#0f1115);font:inherit;font-size:16px;line-height:1.4}
|
|
8999
|
+
.dshm-picker__create-actions{display:flex;justify-content:flex-end;gap:8px}.dshm-picker__create-error{color:var(--dsw-alias-state-error-primary,#b42318)}
|
|
9000
|
+
.dshm-picker__create-trigger{margin-right:auto;border-color:transparent;padding-left:8px;padding-right:8px}
|
|
9001
|
+
.dshm-picker__status{flex:none;margin:0;padding:4px 10px;font-size:13px;color:var(--dsw-alias-label-secondary,#606873);overflow-wrap:anywhere}.dshm-picker__error{color:var(--dsw-alias-state-error-primary,#b42318)}
|
|
9002
|
+
.dshm-picker__status[hidden],.dshm-picker__button[hidden]{display:none}.dshm-picker__footer{display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;flex:none;gap:8px;padding:6px 10px;border-top:1px solid var(--dsw-alias-border-l2,#dde0e4)}
|
|
9003
|
+
.dshm-picker__confirm{min-width:72px;border-color:transparent;background:var(--dsw-alias-button-primary-fill,#2563eb);color:var(--dsw-alias-label-primary-foreground,#fff)}.dshm-picker__confirm:hover:not(:disabled),.dshm-picker__confirm:active:not(:disabled){background:var(--dsw-alias-button-primary-hover,#1d4ed8)}
|
|
9004
|
+
@media(max-width:420px){dialog.dshm-picker{padding:max(8px,env(safe-area-inset-top)) max(8px,env(safe-area-inset-right)) max(8px,env(safe-area-inset-bottom)) max(8px,env(safe-area-inset-left))}}
|
|
8445
9005
|
`;
|
|
9006
|
+
/**
|
|
9007
|
+
* Whether DSH's own browse directory picker is already mounted on this page.
|
|
9008
|
+
*
|
|
9009
|
+
* DSH's adaptive `directory-picker` resolves to
|
|
9010
|
+
* `@deepseek-ai/dsh-host-directory-picker-auto`, which mounts the Host backend
|
|
9011
|
+
* and the Client face together and picks `browse` whenever the host is remotely
|
|
9012
|
+
* reachable. Its client face fills the same directory-flow slots this plugin
|
|
9013
|
+
* would use, so a plugin-owned picker would shadow the upstream one and take
|
|
9014
|
+
* away its folder creation. The desktop keeps the native OS chooser; a remote
|
|
9015
|
+
* browser can only reach the web implementation.
|
|
9016
|
+
*
|
|
9017
|
+
* @returns true when the upstream browse surface owns those slots on this page.
|
|
9018
|
+
*/
|
|
9019
|
+
function hasUpstreamBrowsePicker(view) {
|
|
9020
|
+
if (!("__DSH_BOOT__" in view)) return false;
|
|
9021
|
+
const graph = view.__DSH_BOOT__;
|
|
9022
|
+
if (typeof graph !== "object" || graph === null || !("entries" in graph) || !Array.isArray(graph.entries)) return false;
|
|
9023
|
+
return graph.entries.some((entry) => typeof entry === "object" && entry !== null && "id" in entry && entry.id === "@deepseek-ai/dsh-client-ui-directory-picker-browse");
|
|
9024
|
+
}
|
|
8446
9025
|
function openDirectoryDialog(doc, getProps, trackDisposal) {
|
|
8447
9026
|
const view = doc.defaultView;
|
|
8448
9027
|
if (view === null) throw new Error("Directory picker requires a browser window");
|
|
@@ -8468,18 +9047,40 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
8468
9047
|
if (name !== void 0) value.setAttribute(`data-dshm-picker-${name}`, "");
|
|
8469
9048
|
return value;
|
|
8470
9049
|
};
|
|
9050
|
+
const iconButton = (label, outline, name) => {
|
|
9051
|
+
const value = button("", name);
|
|
9052
|
+
value.classList.add("dshm-picker__icon");
|
|
9053
|
+
value.setAttribute("aria-label", label);
|
|
9054
|
+
value.title = label;
|
|
9055
|
+
const icon = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
9056
|
+
icon.setAttribute("viewBox", "0 0 24 24");
|
|
9057
|
+
icon.setAttribute("fill", "none");
|
|
9058
|
+
icon.setAttribute("stroke", "currentColor");
|
|
9059
|
+
icon.setAttribute("stroke-width", "1.75");
|
|
9060
|
+
icon.setAttribute("stroke-linecap", "round");
|
|
9061
|
+
icon.setAttribute("stroke-linejoin", "round");
|
|
9062
|
+
icon.setAttribute("aria-hidden", "true");
|
|
9063
|
+
const shape = doc.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
9064
|
+
shape.setAttribute("d", outline);
|
|
9065
|
+
icon.append(shape);
|
|
9066
|
+
value.append(icon);
|
|
9067
|
+
return value;
|
|
9068
|
+
};
|
|
8471
9069
|
const header = doc.createElement("header");
|
|
8472
9070
|
header.className = "dshm-picker__header";
|
|
8473
9071
|
const title = doc.createElement("h2");
|
|
8474
9072
|
title.className = "dshm-picker__title";
|
|
8475
9073
|
title.textContent = copy.title;
|
|
8476
|
-
const close =
|
|
9074
|
+
const close = iconButton(copy.close, "M6 6l12 12M6 18L18 6");
|
|
8477
9075
|
header.append(title, close);
|
|
8478
9076
|
const toolbar = doc.createElement("form");
|
|
8479
9077
|
toolbar.className = "dshm-picker__toolbar";
|
|
8480
9078
|
const pathLabel = doc.createElement("label");
|
|
8481
9079
|
pathLabel.className = "dshm-picker__path-label";
|
|
8482
|
-
|
|
9080
|
+
const pathCaption = doc.createElement("span");
|
|
9081
|
+
pathCaption.className = "dshm-picker__path-caption";
|
|
9082
|
+
pathCaption.textContent = copy.path;
|
|
9083
|
+
pathLabel.append(pathCaption);
|
|
8483
9084
|
const path = doc.createElement("input");
|
|
8484
9085
|
path.type = "text";
|
|
8485
9086
|
path.className = "dshm-picker__path";
|
|
@@ -8488,16 +9089,17 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
8488
9089
|
path.autocomplete = "off";
|
|
8489
9090
|
path.maxLength = 8192;
|
|
8490
9091
|
pathLabel.append(path);
|
|
8491
|
-
const go =
|
|
9092
|
+
const go = iconButton(copy.go, "M5 12h14m-6-6 6 6-6 6", "load");
|
|
8492
9093
|
go.type = "submit";
|
|
8493
9094
|
go.classList.add("dshm-picker__go");
|
|
8494
9095
|
toolbar.append(pathLabel, go);
|
|
8495
9096
|
const navigation = doc.createElement("nav");
|
|
8496
9097
|
navigation.className = "dshm-picker__navigation";
|
|
8497
9098
|
navigation.setAttribute("aria-label", copy.title);
|
|
8498
|
-
const home =
|
|
8499
|
-
const up =
|
|
9099
|
+
const home = iconButton(copy.home, "m3 10 9-7 9 7v10a1 1 0 0 1-1 1h-5v-7H9v7H4a1 1 0 0 1-1-1Z", "home");
|
|
9100
|
+
const up = iconButton(copy.up, "M12 20V4m-7 7 7-7 7 7", "up");
|
|
8500
9101
|
navigation.append(home, up);
|
|
9102
|
+
toolbar.prepend(navigation);
|
|
8501
9103
|
const status = doc.createElement("p");
|
|
8502
9104
|
status.className = "dshm-picker__status";
|
|
8503
9105
|
status.setAttribute("role", "status");
|
|
@@ -8513,13 +9115,82 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
8513
9115
|
footer.className = "dshm-picker__footer";
|
|
8514
9116
|
const retry = button(copy.retry, "retry");
|
|
8515
9117
|
retry.hidden = true;
|
|
9118
|
+
const create = button(copy.create, "create");
|
|
9119
|
+
create.classList.add("dshm-picker__create-trigger");
|
|
8516
9120
|
const cancel = button(copy.cancel, "cancel");
|
|
8517
9121
|
const confirm = button(copy.open, "confirm");
|
|
8518
9122
|
confirm.classList.add("dshm-picker__confirm");
|
|
8519
|
-
|
|
8520
|
-
|
|
9123
|
+
const createPanel = doc.createElement("div");
|
|
9124
|
+
createPanel.className = "dshm-picker__create";
|
|
9125
|
+
createPanel.hidden = true;
|
|
9126
|
+
const createTitle = doc.createElement("label");
|
|
9127
|
+
createTitle.className = "dshm-picker__create-title";
|
|
9128
|
+
createTitle.textContent = copy.createTitle;
|
|
9129
|
+
const createInput = doc.createElement("input");
|
|
9130
|
+
createInput.type = "text";
|
|
9131
|
+
createInput.className = "dshm-picker__create-input";
|
|
9132
|
+
createInput.setAttribute("data-dshm-picker-create-name", "");
|
|
9133
|
+
createInput.spellcheck = false;
|
|
9134
|
+
createInput.autocomplete = "off";
|
|
9135
|
+
createInput.maxLength = 255;
|
|
9136
|
+
createTitle.append(createInput);
|
|
9137
|
+
const createParent = doc.createElement("p");
|
|
9138
|
+
createParent.className = "dshm-picker__status";
|
|
9139
|
+
const createStatus = doc.createElement("p");
|
|
9140
|
+
createStatus.className = "dshm-picker__status";
|
|
9141
|
+
createStatus.setAttribute("role", "status");
|
|
9142
|
+
createStatus.setAttribute("aria-live", "polite");
|
|
9143
|
+
const createError = doc.createElement("p");
|
|
9144
|
+
createError.className = "dshm-picker__status dshm-picker__create-error";
|
|
9145
|
+
createError.setAttribute("role", "alert");
|
|
9146
|
+
createError.setAttribute("data-dshm-picker-create-error", "");
|
|
9147
|
+
createError.hidden = true;
|
|
9148
|
+
const createActions = doc.createElement("div");
|
|
9149
|
+
createActions.className = "dshm-picker__create-actions";
|
|
9150
|
+
const createCancel = button(copy.cancel, "create-cancel");
|
|
9151
|
+
const createConfirm = button(copy.createAction, "create-confirm");
|
|
9152
|
+
createActions.append(createCancel, createConfirm);
|
|
9153
|
+
createPanel.append(createParent, createTitle, createError, createStatus, createActions);
|
|
9154
|
+
footer.append(create, retry, cancel, confirm);
|
|
9155
|
+
panel.append(header, toolbar, status, error, list, createPanel, footer);
|
|
8521
9156
|
dialog.append(panel);
|
|
8522
9157
|
const browser = new MobileDirectoryBrowser(readDirectories);
|
|
9158
|
+
const creator = new MobileDirectoryCreator(createDirectory, (created) => {
|
|
9159
|
+
browser.navigate(created);
|
|
9160
|
+
});
|
|
9161
|
+
let creationNotice = false;
|
|
9162
|
+
let creationNoticeListing = null;
|
|
9163
|
+
const closeCreate = () => {
|
|
9164
|
+
const child = creator.getSnapshot();
|
|
9165
|
+
if (child.pending || child.error === "timeout" || child.error === "request-failed" || child.error === "invalid-response") {
|
|
9166
|
+
creationNotice = true;
|
|
9167
|
+
creationNoticeListing = browser.getSnapshot().listing;
|
|
9168
|
+
}
|
|
9169
|
+
if (creator.close()) {
|
|
9170
|
+
if (create.disabled) close.focus();
|
|
9171
|
+
else create.focus();
|
|
9172
|
+
}
|
|
9173
|
+
};
|
|
9174
|
+
create.addEventListener("click", () => {
|
|
9175
|
+
const parent = browser.getSnapshot().listing?.path;
|
|
9176
|
+
if (!browser.canConfirm() || parent === void 0 || !creator.open(parent)) return;
|
|
9177
|
+
creationNotice = false;
|
|
9178
|
+
createInput.focus();
|
|
9179
|
+
createInput.select();
|
|
9180
|
+
});
|
|
9181
|
+
createCancel.addEventListener("click", closeCreate);
|
|
9182
|
+
createConfirm.addEventListener("click", () => {
|
|
9183
|
+
creator.submit();
|
|
9184
|
+
});
|
|
9185
|
+
createInput.addEventListener("input", () => {
|
|
9186
|
+
creator.setName(createInput.value);
|
|
9187
|
+
});
|
|
9188
|
+
createInput.addEventListener("keydown", (event) => {
|
|
9189
|
+
if (event.key === "Enter" && !event.isComposing) {
|
|
9190
|
+
event.preventDefault();
|
|
9191
|
+
creator.submit();
|
|
9192
|
+
}
|
|
9193
|
+
});
|
|
8523
9194
|
let disposed = false;
|
|
8524
9195
|
let closedByOwner = false;
|
|
8525
9196
|
let renderedListing;
|
|
@@ -8527,22 +9198,41 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
8527
9198
|
const render = () => {
|
|
8528
9199
|
if (disposed) return;
|
|
8529
9200
|
const state = browser.getSnapshot();
|
|
9201
|
+
if (creationNotice && state.listing !== creationNoticeListing) creationNotice = false;
|
|
9202
|
+
const child = creator.getSnapshot();
|
|
8530
9203
|
const adopting = state.committed || state.hostBusy;
|
|
8531
|
-
|
|
9204
|
+
const blocked = adopting || state.creating;
|
|
9205
|
+
path.disabled = blocked;
|
|
8532
9206
|
if (path.value !== state.draft) path.value = state.draft;
|
|
8533
9207
|
path.setAttribute("aria-invalid", String(state.error === "bad-path"));
|
|
8534
|
-
go.disabled =
|
|
8535
|
-
home.disabled =
|
|
8536
|
-
up.disabled =
|
|
9208
|
+
go.disabled = blocked || state.phase === "loading" || !isMobileDirectoryPath(state.draft);
|
|
9209
|
+
home.disabled = blocked;
|
|
9210
|
+
up.disabled = blocked || state.listing?.parent === void 0;
|
|
8537
9211
|
confirm.disabled = !browser.canConfirm();
|
|
8538
9212
|
cancel.disabled = adopting;
|
|
8539
9213
|
close.disabled = adopting;
|
|
8540
9214
|
retry.hidden = state.phase !== "error" || state.error === "bad-path";
|
|
8541
|
-
retry.disabled =
|
|
8542
|
-
|
|
8543
|
-
|
|
9215
|
+
retry.disabled = blocked;
|
|
9216
|
+
create.disabled = !browser.canConfirm();
|
|
9217
|
+
title.textContent = child.open ? copy.create : copy.title;
|
|
9218
|
+
toolbar.hidden = child.open;
|
|
9219
|
+
navigation.hidden = child.open;
|
|
9220
|
+
list.hidden = child.open;
|
|
9221
|
+
footer.hidden = child.open;
|
|
9222
|
+
createPanel.hidden = !child.open;
|
|
9223
|
+
if (createInput.value !== child.name) createInput.value = child.name;
|
|
9224
|
+
createInput.disabled = child.pending;
|
|
9225
|
+
createInput.setAttribute("aria-invalid", String(child.error === "invalid-name"));
|
|
9226
|
+
createConfirm.disabled = child.pending || !isMobileDirectoryName(child.name.trim());
|
|
9227
|
+
createParent.textContent = `${copy.createParent}: ${child.parent ?? ""}`;
|
|
9228
|
+
createError.textContent = child.error === null ? "" : child.error === "invalid-name" ? copy.createInvalid : child.error === "directory-exists" ? copy.createExists : child.error === "directory-create-failed" ? copy.createFailed : child.error === "unauthorized" ? copy.unauthorized : copy.createUnknown;
|
|
9229
|
+
createError.hidden = child.error === null;
|
|
9230
|
+
createStatus.textContent = child.pending && child.error === null ? copy.creating : "";
|
|
9231
|
+
createStatus.hidden = createStatus.textContent === "";
|
|
9232
|
+
status.textContent = child.pending ? copy.createStopping : creationNotice ? copy.createUnknown : adopting ? copy.adopting : state.phase === "loading" ? copy.loading : state.listing?.truncated === true ? copy.truncated : state.listing?.entries.length === 0 ? copy.empty : "";
|
|
9233
|
+
status.hidden = child.open || status.textContent === "";
|
|
8544
9234
|
error.textContent = state.error === null ? "" : copy[state.error];
|
|
8545
|
-
error.hidden = state.error === null;
|
|
9235
|
+
error.hidden = child.open || state.error === null;
|
|
8546
9236
|
list.setAttribute("aria-busy", String(state.phase === "loading"));
|
|
8547
9237
|
if (renderedListing !== state.listing) {
|
|
8548
9238
|
renderedListing = state.listing;
|
|
@@ -8572,11 +9262,21 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
8572
9262
|
list.append(item);
|
|
8573
9263
|
}
|
|
8574
9264
|
}
|
|
8575
|
-
for (const row of list.querySelectorAll("button")) row.disabled =
|
|
9265
|
+
for (const row of list.querySelectorAll("button")) row.disabled = blocked;
|
|
8576
9266
|
};
|
|
8577
9267
|
const unsubscribe = browser.subscribe(render);
|
|
9268
|
+
const unsubscribeCreate = creator.subscribe(() => {
|
|
9269
|
+
const child = creator.getSnapshot();
|
|
9270
|
+
browser.setCreating(child.open || child.pending);
|
|
9271
|
+
render();
|
|
9272
|
+
});
|
|
8578
9273
|
const requestClose = () => {
|
|
9274
|
+
if (creator.getSnapshot().open) {
|
|
9275
|
+
closeCreate();
|
|
9276
|
+
return;
|
|
9277
|
+
}
|
|
8579
9278
|
if (disposed || !browser.cancel()) return;
|
|
9279
|
+
creator.close();
|
|
8580
9280
|
closedByOwner = true;
|
|
8581
9281
|
if (dialog.open) dialog.close();
|
|
8582
9282
|
getProps().onCancel();
|
|
@@ -8597,6 +9297,8 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
8597
9297
|
if (disposal !== void 0) return disposal;
|
|
8598
9298
|
disposed = true;
|
|
8599
9299
|
unsubscribe();
|
|
9300
|
+
unsubscribeCreate();
|
|
9301
|
+
const creationStopped = creator.dispose();
|
|
8600
9302
|
const stopped = browser.dispose();
|
|
8601
9303
|
const failures = [];
|
|
8602
9304
|
const clean = (action) => {
|
|
@@ -8623,11 +9325,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
8623
9325
|
if (trigger?.isConnected) trigger.focus({ preventScroll: true });
|
|
8624
9326
|
});
|
|
8625
9327
|
disposal = (async () => {
|
|
8626
|
-
|
|
8627
|
-
await stopped;
|
|
8628
|
-
} catch (error) {
|
|
8629
|
-
failures.push(error);
|
|
8630
|
-
}
|
|
9328
|
+
for (const result of await Promise.allSettled([stopped, creationStopped])) if (result.status === "rejected") failures.push(result.reason);
|
|
8631
9329
|
if (failures.length > 0) throw new AggregateError(failures, "Mobile directory dialog disposal failed");
|
|
8632
9330
|
})();
|
|
8633
9331
|
return disposal;
|
|
@@ -8787,7 +9485,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
8787
9485
|
//#region src/client.ts
|
|
8788
9486
|
/** Map a host-reported provider onto the UI union, defaulting to the built-in one. */
|
|
8789
9487
|
function parseMobileRemoteProvider(value) {
|
|
8790
|
-
return value === "cpolar" || value === "cloudflared" || value === "frp" || value === "origin" ? value : "tailscale";
|
|
9488
|
+
return value === "cpolar" || value === "cloudflared" || value === "frp" || value === "origin" || value === "iroh" ? value : "tailscale";
|
|
8791
9489
|
}
|
|
8792
9490
|
/**
|
|
8793
9491
|
* Set a surface's visibility; opening a page or overlay brings it to the front.
|
|
@@ -9333,6 +10031,23 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
9333
10031
|
frp_discovery_invalid: "frpDiscoveryInvalid",
|
|
9334
10032
|
frp_stopped: "frpStopped",
|
|
9335
10033
|
frp_exited: "frpExited",
|
|
10034
|
+
iroh_companion_missing: "irohCompanionMissing",
|
|
10035
|
+
iroh_data_directory_invalid: "irohDataDirectoryInvalid",
|
|
10036
|
+
iroh_sidecar_invalid: "irohSidecarInvalid",
|
|
10037
|
+
iroh_download_failed: "irohDownloadFailed",
|
|
10038
|
+
iroh_download_size_mismatch: "irohDownloadFailed",
|
|
10039
|
+
iroh_download_hash_mismatch: "irohSidecarInvalid",
|
|
10040
|
+
iroh_download_timeout: "irohTimeout",
|
|
10041
|
+
iroh_platform_unsupported: "irohPlatformUnsupported",
|
|
10042
|
+
iroh_identity_failed: "irohIdentityFailed",
|
|
10043
|
+
iroh_identity_timeout: "irohIdentityFailed",
|
|
10044
|
+
iroh_identity_invalid: "irohIdentityFailed",
|
|
10045
|
+
iroh_launch_failed: "irohLaunchFailed",
|
|
10046
|
+
iroh_start_timeout: "irohTimeout",
|
|
10047
|
+
iroh_start_failed: "irohLaunchFailed",
|
|
10048
|
+
iroh_exited: "irohExited",
|
|
10049
|
+
iroh_not_ready: "irohNotReady",
|
|
10050
|
+
iroh_pairing_failed: "irohPairingFailed",
|
|
9336
10051
|
frp_server_address_invalid: "frpServerAddressInvalid",
|
|
9337
10052
|
frp_server_port_invalid: "frpServerPortInvalid",
|
|
9338
10053
|
frp_token_invalid: "frpTokenInvalid",
|
|
@@ -9444,7 +10159,8 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
9444
10159
|
cpolar: "cpolar",
|
|
9445
10160
|
cloudflared: "cloudflared",
|
|
9446
10161
|
frp: t("frpName"),
|
|
9447
|
-
origin: t("originName")
|
|
10162
|
+
origin: t("originName"),
|
|
10163
|
+
iroh: t("irohName")
|
|
9448
10164
|
};
|
|
9449
10165
|
const providerId = typeof facts.provider === "string" ? facts.provider : "";
|
|
9450
10166
|
const controllerCode = typeof facts.controllerCode === "string" ? facts.controllerCode : "";
|
|
@@ -9977,7 +10693,19 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
9977
10693
|
originChoiceDescription.textContent = t("originDescription");
|
|
9978
10694
|
originChoiceTop.append(originChoiceName);
|
|
9979
10695
|
originChoice.append(originChoiceTop, originChoiceDescription);
|
|
9980
|
-
|
|
10696
|
+
const irohChoice = element("button", "dsh-mobile-control__provider is-iroh");
|
|
10697
|
+
irohChoice.type = "button";
|
|
10698
|
+
irohChoice.setAttribute("aria-pressed", "false");
|
|
10699
|
+
const irohChoiceTop = element("span", "dsh-mobile-control__provider-top");
|
|
10700
|
+
const irohChoiceName = element("strong");
|
|
10701
|
+
irohChoiceName.textContent = t("irohName");
|
|
10702
|
+
const irohChoiceBadge = element("span", "dsh-mobile-control__provider-badge is-iroh");
|
|
10703
|
+
irohChoiceBadge.textContent = t("irohExperimental");
|
|
10704
|
+
const irohChoiceDescription = element("span", "dsh-mobile-control__provider-description");
|
|
10705
|
+
irohChoiceDescription.textContent = t("irohDescription");
|
|
10706
|
+
irohChoiceTop.append(irohChoiceName, irohChoiceBadge);
|
|
10707
|
+
irohChoice.append(irohChoiceTop, irohChoiceDescription);
|
|
10708
|
+
selfHostedBody.append(frpChoice, originChoice, irohChoice);
|
|
9981
10709
|
selfHosted.append(selfHostedSummary, selfHostedBody);
|
|
9982
10710
|
providerSection.append(providerHeading, providerInfo, providerChoices, selfHosted);
|
|
9983
10711
|
const frpSetup = element("section", "dsh-mobile-control__frp-setup");
|
|
@@ -10461,7 +11189,77 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
10461
11189
|
providerSetupMeta.append(providerSetupName, remoteStateBadge);
|
|
10462
11190
|
providerSetupHeader.append(providerSetupHeading, providerSetupMeta);
|
|
10463
11191
|
const providerSetupBody = element("div", "dsh-mobile-control__provider-setup-body");
|
|
10464
|
-
|
|
11192
|
+
const irohSetup = element("section", "dsh-mobile-control__frp-setup");
|
|
11193
|
+
irohSetup.hidden = true;
|
|
11194
|
+
const irohSetupTitle = element("h3", "dsh-mobile-control__section-title");
|
|
11195
|
+
irohSetupTitle.textContent = t("prepareIroh");
|
|
11196
|
+
const irohPhaseNote = element("p", "dsh-mobile-control__component-note");
|
|
11197
|
+
irohPhaseNote.textContent = t("irohPhaseNote");
|
|
11198
|
+
const irohComponentStatus = element("p", "dsh-mobile-control__component-status");
|
|
11199
|
+
irohComponentStatus.textContent = t("checkingComponent");
|
|
11200
|
+
irohComponentStatus.setAttribute("role", "status");
|
|
11201
|
+
irohComponentStatus.setAttribute("aria-live", "polite");
|
|
11202
|
+
const irohInstall = element("button", "dsh-mobile-control__primary");
|
|
11203
|
+
irohInstall.type = "button";
|
|
11204
|
+
irohInstall.textContent = t("installOfficial");
|
|
11205
|
+
const irohInstallProgress = element("progress", "dsh-mobile-control__progress");
|
|
11206
|
+
irohInstallProgress.hidden = true;
|
|
11207
|
+
irohInstallProgress.setAttribute("aria-hidden", "true");
|
|
11208
|
+
const irohPairing = element("div", "dsh-mobile-control__ws-paths");
|
|
11209
|
+
const irohPairingTitle = element("p", "dsh-mobile-control__component-status");
|
|
11210
|
+
irohPairingTitle.textContent = t("irohPairingTitle");
|
|
11211
|
+
const irohPair = element("button", "dsh-mobile-control__primary");
|
|
11212
|
+
irohPair.type = "button";
|
|
11213
|
+
irohPair.textContent = t("generateIrohQr");
|
|
11214
|
+
const irohQr = element("div", "dsh-mobile-control__qr is-expandable");
|
|
11215
|
+
irohQr.hidden = true;
|
|
11216
|
+
const irohPairStatus = element("p", "dsh-mobile-control__component-status");
|
|
11217
|
+
const irohCopyDescriptor = element("button", "dsh-mobile-control__secondary");
|
|
11218
|
+
irohCopyDescriptor.type = "button";
|
|
11219
|
+
irohCopyDescriptor.textContent = t("copyPairLink");
|
|
11220
|
+
irohCopyDescriptor.hidden = true;
|
|
11221
|
+
irohPairing.append(irohPairingTitle, irohPair, irohQr, irohPairStatus, irohCopyDescriptor);
|
|
11222
|
+
const irohDetails = element("details", "dsh-mobile-control__details");
|
|
11223
|
+
const irohDetailsSummary = element("summary");
|
|
11224
|
+
irohDetailsSummary.textContent = t("componentDetails");
|
|
11225
|
+
const irohDetailsBody = element("div", "dsh-mobile-control__details-body");
|
|
11226
|
+
const irohDetailsText = element("p");
|
|
11227
|
+
irohDetailsText.textContent = t("irohComponentNote");
|
|
11228
|
+
const irohInstallNote = element("p");
|
|
11229
|
+
irohInstallNote.textContent = t("irohInstallNote");
|
|
11230
|
+
const irohStorage = element("code", "dsh-mobile-control__storage");
|
|
11231
|
+
irohStorage.textContent = t("pluginPrivateDirectory");
|
|
11232
|
+
const irohOfficial = element("a", "dsh-mobile-control__text-link");
|
|
11233
|
+
irohOfficial.href = "https://www.npmjs.com/package/dsh-mobile-iroh";
|
|
11234
|
+
irohOfficial.target = "_blank";
|
|
11235
|
+
irohOfficial.rel = "noopener noreferrer";
|
|
11236
|
+
irohOfficial.textContent = t("officialDownload");
|
|
11237
|
+
irohDetailsBody.append(irohDetailsText, irohInstallNote, irohStorage, irohOfficial);
|
|
11238
|
+
irohDetails.append(irohDetailsSummary, irohDetailsBody);
|
|
11239
|
+
irohSetup.append(irohSetupTitle, irohPhaseNote, irohComponentStatus, irohInstall, irohInstallProgress, irohPairing, irohDetails);
|
|
11240
|
+
providerSetupBody.append(cpolarSetup, cloudflaredSetup, frpSetup, originSetup, irohSetup, tailscaleInfo);
|
|
11241
|
+
const irohQrZoom = element("div", "dsh-mobile-control__qr-zoom");
|
|
11242
|
+
irohQrZoom.hidden = true;
|
|
11243
|
+
const irohQrZoomImage = element("img", "dsh-mobile-control__qr-zoom-image");
|
|
11244
|
+
irohQrZoomImage.alt = t("pairingQr");
|
|
11245
|
+
const irohQrZoomClose = element("button", "dsh-mobile-control__qr-zoom-close");
|
|
11246
|
+
irohQrZoomClose.type = "button";
|
|
11247
|
+
irohQrZoomClose.textContent = "×";
|
|
11248
|
+
irohQrZoomClose.setAttribute("aria-label", t("closeEnlargedQr"));
|
|
11249
|
+
const setIrohQrZoomOpen = (open) => {
|
|
11250
|
+
if (open) {
|
|
11251
|
+
irohQrZoom.setAttribute("role", "dialog");
|
|
11252
|
+
irohQrZoom.setAttribute("aria-modal", "true");
|
|
11253
|
+
irohQrZoom.setAttribute("aria-label", t("enlargeQr"));
|
|
11254
|
+
} else {
|
|
11255
|
+
irohQrZoom.removeAttribute("role");
|
|
11256
|
+
irohQrZoom.removeAttribute("aria-modal");
|
|
11257
|
+
irohQrZoom.removeAttribute("aria-label");
|
|
11258
|
+
}
|
|
11259
|
+
irohQrZoom.hidden = !open;
|
|
11260
|
+
};
|
|
11261
|
+
irohQrZoom.append(irohQrZoomImage, irohQrZoomClose);
|
|
11262
|
+
root.append(irohQrZoom);
|
|
10465
11263
|
const remoteAccess = element("div", "dsh-mobile-control__access");
|
|
10466
11264
|
remoteAccess.hidden = true;
|
|
10467
11265
|
const remoteAccessLabel = element("span", "dsh-mobile-control__access-label");
|
|
@@ -10527,6 +11325,8 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
10527
11325
|
remotePairLink.hidden = true;
|
|
10528
11326
|
let activeRemotePairUrl = "";
|
|
10529
11327
|
let activeRemotePairExpiresAt = 0;
|
|
11328
|
+
let activeIrohDescriptor = "";
|
|
11329
|
+
let activeIrohQrSvg = "";
|
|
10530
11330
|
remoteActions.append(remoteToggle, remoteLogin, remoteReconnect, remotePair, remoteCopyLink);
|
|
10531
11331
|
const remoteManageRow = element("div", "dsh-mobile-control__manage-row");
|
|
10532
11332
|
const remoteDevices = element("button", "dsh-mobile-control__manage");
|
|
@@ -10823,6 +11623,9 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
10823
11623
|
let latestRemoteState = {};
|
|
10824
11624
|
let frpInstalled = false;
|
|
10825
11625
|
let frpConfigured = false;
|
|
11626
|
+
let irohInstalled = false;
|
|
11627
|
+
/** True while a companion checksum verification is in flight; polls must not wipe its progress. */
|
|
11628
|
+
let irohVerifying = false;
|
|
10826
11629
|
let frpLayoutInitialized = false;
|
|
10827
11630
|
let frpDownloadSize = "14.0";
|
|
10828
11631
|
let configuredFrpServer = "";
|
|
@@ -11144,6 +11947,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11144
11947
|
const cloudflared = remoteProvider === "cloudflared";
|
|
11145
11948
|
const frp = remoteProvider === "frp";
|
|
11146
11949
|
const origin = remoteProvider === "origin";
|
|
11950
|
+
const iroh = remoteProvider === "iroh";
|
|
11147
11951
|
const managedOrigin = origin && data.originMode === "managed";
|
|
11148
11952
|
const tailscale = remoteProvider === "tailscale";
|
|
11149
11953
|
tailscaleChoice.classList.toggle("is-selected", tailscale);
|
|
@@ -11159,17 +11963,20 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11159
11963
|
cloudflaredChoice.tabIndex = cloudflared ? 0 : -1;
|
|
11160
11964
|
frpChoice.setAttribute("aria-pressed", String(frp));
|
|
11161
11965
|
originChoice.setAttribute("aria-pressed", String(origin));
|
|
11162
|
-
|
|
11966
|
+
irohChoice.setAttribute("aria-pressed", String(iroh));
|
|
11967
|
+
providerSetupName.textContent = cpolar ? "cpolar" : cloudflared ? "cloudflared" : frp ? t("frpName") : origin ? t(managedOrigin ? "caddyName" : "originName") : iroh ? t("irohName") : "Tailscale Funnel";
|
|
11163
11968
|
tailscaleChoice.disabled = remoteProviderBusy;
|
|
11164
11969
|
cpolarChoice.disabled = remoteProviderBusy;
|
|
11165
11970
|
cloudflaredChoice.disabled = remoteProviderBusy;
|
|
11166
11971
|
frpChoice.disabled = remoteProviderBusy;
|
|
11167
11972
|
originChoice.disabled = remoteProviderBusy;
|
|
11973
|
+
irohChoice.disabled = remoteProviderBusy;
|
|
11168
11974
|
cpolarSetup.hidden = !cpolar;
|
|
11169
11975
|
cloudflaredSetup.hidden = !cloudflared;
|
|
11170
11976
|
frpSetup.hidden = !frp;
|
|
11171
11977
|
originSetup.hidden = !origin;
|
|
11172
|
-
|
|
11978
|
+
irohSetup.hidden = !iroh;
|
|
11979
|
+
if (frp || origin || iroh) selfHosted.open = true;
|
|
11173
11980
|
tailscaleInfo.hidden = !tailscale;
|
|
11174
11981
|
remoteReset.textContent = tailscale ? t("resetRemoteLogin") : t("resetRemoteDevices");
|
|
11175
11982
|
const providers = data.providers !== null && typeof data.providers === "object" ? data.providers : {};
|
|
@@ -11324,6 +12131,30 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11324
12131
|
frpVpsGroup.open = !frpConfigured;
|
|
11325
12132
|
frpLayoutInitialized = true;
|
|
11326
12133
|
}
|
|
12134
|
+
const irohProvider = providers.iroh !== null && typeof providers.iroh === "object" ? providers.iroh : {};
|
|
12135
|
+
const irohComponent = irohProvider.component !== null && typeof irohProvider.component === "object" ? irohProvider.component : {};
|
|
12136
|
+
irohInstalled = irohComponent.installed === true;
|
|
12137
|
+
const irohSupported = irohComponent.supported !== false;
|
|
12138
|
+
const irohComponentError = typeof irohComponent.errorCode === "string" ? irohComponent.errorCode : "";
|
|
12139
|
+
const irohVersion = typeof irohComponent.version === "string" ? irohComponent.version : "";
|
|
12140
|
+
const irohDownloadBytes = typeof irohComponent.downloadBytes === "number" ? irohComponent.downloadBytes : 0;
|
|
12141
|
+
const irohStoragePath = typeof irohComponent.storagePath === "string" ? irohComponent.storagePath : `DSH Mobile ${t("pluginPrivateDirectory")}`;
|
|
12142
|
+
irohInstall.hidden = irohInstalled || !irohSupported;
|
|
12143
|
+
if (irohVerifying) {
|
|
12144
|
+
irohInstallProgress.hidden = false;
|
|
12145
|
+
irohInstall.disabled = true;
|
|
12146
|
+
irohInstall.textContent = t("installingIroh");
|
|
12147
|
+
irohInstall.setAttribute("aria-busy", "true");
|
|
12148
|
+
irohComponentStatus.textContent = t("installingIroh");
|
|
12149
|
+
} else {
|
|
12150
|
+
irohInstallProgress.hidden = true;
|
|
12151
|
+
irohInstall.setAttribute("aria-busy", "false");
|
|
12152
|
+
irohInstall.disabled = remoteProviderBusy;
|
|
12153
|
+
irohInstall.textContent = irohDownloadBytes > 0 ? t("installWithSize", { size: formatMegabytes(irohDownloadBytes) }) : t("installOfficial");
|
|
12154
|
+
irohComponentStatus.textContent = !irohSupported ? t("irohPlatformUnsupported") : irohInstalled ? t("installed") + (irohVersion === "" ? "" : ` · ${irohVersion}`) : irohComponentError === "irohSidecarInvalid" ? t("irohSidecarInvalid") : t("irohUnavailable");
|
|
12155
|
+
}
|
|
12156
|
+
irohStorage.textContent = irohStoragePath;
|
|
12157
|
+
irohStorage.title = irohStoragePath;
|
|
11327
12158
|
const originProvider = providers.origin !== null && typeof providers.origin === "object" ? providers.origin : {};
|
|
11328
12159
|
const originConfiguration = originProvider.configuration !== null && typeof originProvider.configuration === "object" ? originProvider.configuration : {};
|
|
11329
12160
|
originConfigured = originConfiguration.configured === true;
|
|
@@ -11414,10 +12245,10 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11414
12245
|
remoteStatus.classList.toggle("is-running", remoteReady);
|
|
11415
12246
|
const labels = {
|
|
11416
12247
|
off: t("remoteOff"),
|
|
11417
|
-
unavailable: cpolar ? t("remoteUnavailableCpolar") : cloudflared ? t("remoteUnavailableCloudflared") : frp ? t("remoteUnavailableFrp") : origin ? t("originUnavailable") : t("remoteUnavailableTailscale"),
|
|
11418
|
-
starting: cpolar ? t("remoteStartingCpolar") : cloudflared ? t("remoteStartingCloudflared") : frp ? t("remoteStartingFrp") : origin ? t("originStarting") : t("remoteStartingTailscale"),
|
|
12248
|
+
unavailable: cpolar ? t("remoteUnavailableCpolar") : cloudflared ? t("remoteUnavailableCloudflared") : frp ? t("remoteUnavailableFrp") : origin ? t("originUnavailable") : iroh ? t("irohUnavailable") : t("remoteUnavailableTailscale"),
|
|
12249
|
+
starting: cpolar ? t("remoteStartingCpolar") : cloudflared ? t("remoteStartingCloudflared") : frp ? t("remoteStartingFrp") : origin ? t("originStarting") : iroh ? t("irohStarting") : t("remoteStartingTailscale"),
|
|
11419
12250
|
"needs-login": t("remoteNeedsLogin"),
|
|
11420
|
-
connecting: cpolar ? t("remoteConnectingCpolar") : cloudflared ? t("remoteConnectingCloudflared") : frp ? t("remoteConnectingFrp") : t("remoteConnectingTailscale"),
|
|
12251
|
+
connecting: cpolar ? t("remoteConnectingCpolar") : cloudflared ? t("remoteConnectingCloudflared") : frp ? t("remoteConnectingFrp") : iroh ? t("irohConnecting") : t("remoteConnectingTailscale"),
|
|
11421
12252
|
ready: origin ? t(managedOrigin ? "caddyVerified" : "originReady") : t("remoteReady"),
|
|
11422
12253
|
error: t("remoteError")
|
|
11423
12254
|
};
|
|
@@ -11432,7 +12263,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11432
12263
|
remoteSetupRetry.disabled = remoteReconnectBusy;
|
|
11433
12264
|
remoteToggle.textContent = remoteRunning ? t("disableRemote") : t("enableRemote");
|
|
11434
12265
|
const originPrepared = data.originMode === "managed" ? caddySupported && caddyInstalled && caddyConfiguration.configured === true : originConfigured && !caddyModeManagedSelected;
|
|
11435
|
-
const providerPrepared = cpolar ? cpolarInstalled && cpolarConfigured : cloudflared ? cloudflaredInstalled : frp ? frpInstalled && frpConfigured : origin ? originPrepared : true;
|
|
12266
|
+
const providerPrepared = cpolar ? cpolarInstalled && cpolarConfigured : cloudflared ? cloudflaredInstalled : frp ? frpInstalled && frpConfigured : origin ? originPrepared : iroh ? irohInstalled : true;
|
|
11436
12267
|
remoteToggle.disabled = remoteProviderBusy || !providerPrepared && !(origin && remoteRunning);
|
|
11437
12268
|
remoteLogin.hidden = !tailscale || state !== "needs-login" || remoteLoginUrl === "";
|
|
11438
12269
|
remoteReconnect.hidden = needsFunnelSetup || state !== "error" && state !== "unavailable" || !providerPrepared;
|
|
@@ -11440,6 +12271,21 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11440
12271
|
remotePair.disabled = !remoteReady || remoteProviderBusy;
|
|
11441
12272
|
remoteCopyLink.disabled = !remoteReady || remoteProviderBusy;
|
|
11442
12273
|
remoteDevices.disabled = !remoteReady || remoteProviderBusy;
|
|
12274
|
+
remotePair.hidden = iroh;
|
|
12275
|
+
remoteCopyLink.hidden = iroh;
|
|
12276
|
+
if (iroh) {
|
|
12277
|
+
remoteQr.hidden = true;
|
|
12278
|
+
remotePairLink.hidden = true;
|
|
12279
|
+
}
|
|
12280
|
+
irohPair.disabled = !remoteReady || !iroh || remoteProviderBusy;
|
|
12281
|
+
if ((!iroh || !remoteReady) && activeIrohDescriptor !== "") {
|
|
12282
|
+
activeIrohDescriptor = "";
|
|
12283
|
+
activeIrohQrSvg = "";
|
|
12284
|
+
showQr("", irohQr);
|
|
12285
|
+
setIrohQrZoomOpen(false);
|
|
12286
|
+
irohCopyDescriptor.hidden = true;
|
|
12287
|
+
irohPairStatus.textContent = "";
|
|
12288
|
+
}
|
|
11443
12289
|
if (origin) {
|
|
11444
12290
|
remoteReconnect.disabled = remoteProviderBusy || remoteReconnectBusy;
|
|
11445
12291
|
remoteReset.disabled = remoteProviderBusy;
|
|
@@ -11504,7 +12350,8 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11504
12350
|
cloudflaredChoice.disabled = true;
|
|
11505
12351
|
frpChoice.disabled = true;
|
|
11506
12352
|
originChoice.disabled = true;
|
|
11507
|
-
|
|
12353
|
+
irohChoice.disabled = true;
|
|
12354
|
+
remoteStatus.textContent = provider === "cpolar" ? t("switchingCpolar") : provider === "cloudflared" ? t("switchingCloudflared") : provider === "frp" ? t("switchingFrp") : provider === "origin" ? t("switchingOrigin") : provider === "iroh" ? t("switchingIroh") : t("switchingTailscale");
|
|
11508
12355
|
controlRequestJson("/api/mobile-access/remote/provider", {
|
|
11509
12356
|
method: "POST",
|
|
11510
12357
|
body: JSON.stringify({ provider })
|
|
@@ -11531,6 +12378,111 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11531
12378
|
originChoice.addEventListener("click", () => {
|
|
11532
12379
|
chooseRemoteProvider("origin");
|
|
11533
12380
|
});
|
|
12381
|
+
irohChoice.addEventListener("click", () => {
|
|
12382
|
+
chooseRemoteProvider("iroh");
|
|
12383
|
+
});
|
|
12384
|
+
irohInstall.addEventListener("click", () => {
|
|
12385
|
+
if (remoteProviderBusy) return;
|
|
12386
|
+
if (!window.confirm(t("irohInstallConfirm"))) return;
|
|
12387
|
+
remoteProviderBusy = true;
|
|
12388
|
+
irohVerifying = true;
|
|
12389
|
+
irohInstall.disabled = true;
|
|
12390
|
+
irohInstall.textContent = t("installingIroh");
|
|
12391
|
+
irohInstall.setAttribute("aria-busy", "true");
|
|
12392
|
+
irohInstallProgress.hidden = false;
|
|
12393
|
+
irohComponentStatus.textContent = t("installingIroh");
|
|
12394
|
+
remoteStatus.textContent = t("installingIroh");
|
|
12395
|
+
controlRequestJson("/api/mobile-access/remote/iroh/component/install", {
|
|
12396
|
+
method: "POST",
|
|
12397
|
+
body: JSON.stringify({ confirm: true })
|
|
12398
|
+
}, LONG_CONTROL_REQUEST_TIMEOUT_MS).then(renderRemote, (error) => {
|
|
12399
|
+
remoteFailureText = remoteFailureTextFor(error, "installFailed");
|
|
12400
|
+
remoteStatus.textContent = remoteFailureText;
|
|
12401
|
+
irohComponentStatus.textContent = remoteFailureText;
|
|
12402
|
+
}).finally(() => {
|
|
12403
|
+
remoteProviderBusy = false;
|
|
12404
|
+
irohVerifying = false;
|
|
12405
|
+
loadRemote();
|
|
12406
|
+
});
|
|
12407
|
+
});
|
|
12408
|
+
const openIrohQrZoom = () => {
|
|
12409
|
+
if (activeIrohQrSvg === "" || irohQr.hidden) return;
|
|
12410
|
+
irohQrZoomImage.src = `data:image/svg+xml;base64,${btoa(activeIrohQrSvg)}`;
|
|
12411
|
+
setIrohQrZoomOpen(true);
|
|
12412
|
+
irohQrZoomClose.focus();
|
|
12413
|
+
};
|
|
12414
|
+
const closeIrohQrZoom = () => {
|
|
12415
|
+
setIrohQrZoomOpen(false);
|
|
12416
|
+
if (!irohQr.hidden) irohQr.focus();
|
|
12417
|
+
};
|
|
12418
|
+
irohQr.setAttribute("role", "button");
|
|
12419
|
+
irohQr.setAttribute("tabindex", "0");
|
|
12420
|
+
irohQr.setAttribute("aria-label", t("enlargeQr"));
|
|
12421
|
+
irohQr.addEventListener("click", openIrohQrZoom);
|
|
12422
|
+
irohQr.addEventListener("keydown", (event) => {
|
|
12423
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
12424
|
+
event.preventDefault();
|
|
12425
|
+
openIrohQrZoom();
|
|
12426
|
+
});
|
|
12427
|
+
irohQrZoom.addEventListener("click", closeIrohQrZoom);
|
|
12428
|
+
irohQrZoomClose.addEventListener("click", closeIrohQrZoom);
|
|
12429
|
+
irohQrZoom.addEventListener("keydown", (event) => {
|
|
12430
|
+
if (event.key !== "Escape") return;
|
|
12431
|
+
closeIrohQrZoom();
|
|
12432
|
+
});
|
|
12433
|
+
const openIrohPairing = () => controlRequestJson("/api/mobile-access/remote/iroh/pairing/begin", {
|
|
12434
|
+
method: "POST",
|
|
12435
|
+
body: "{}"
|
|
12436
|
+
}).then(async (data) => {
|
|
12437
|
+
const record = data;
|
|
12438
|
+
const qrSvg = typeof record.qrSvg === "string" ? record.qrSvg : "";
|
|
12439
|
+
const code = typeof record.irohCode === "string" ? record.irohCode : "";
|
|
12440
|
+
showQr(qrSvg, irohQr);
|
|
12441
|
+
if (qrSvg === "" || code === "") {
|
|
12442
|
+
activeIrohQrSvg = "";
|
|
12443
|
+
irohPairStatus.textContent = t("keyGenerationFailed");
|
|
12444
|
+
return;
|
|
12445
|
+
}
|
|
12446
|
+
activeIrohQrSvg = qrSvg;
|
|
12447
|
+
const fields = [
|
|
12448
|
+
"version",
|
|
12449
|
+
"type",
|
|
12450
|
+
"endpointId",
|
|
12451
|
+
"origin",
|
|
12452
|
+
"localPort",
|
|
12453
|
+
"irohCode",
|
|
12454
|
+
"gatewayToken",
|
|
12455
|
+
"caCertificate",
|
|
12456
|
+
"instanceId",
|
|
12457
|
+
"expiresAt"
|
|
12458
|
+
];
|
|
12459
|
+
const descriptor = {};
|
|
12460
|
+
for (const field of fields) descriptor[field] = record[field];
|
|
12461
|
+
activeIrohDescriptor = JSON.stringify(descriptor);
|
|
12462
|
+
irohCopyDescriptor.hidden = false;
|
|
12463
|
+
irohPairStatus.textContent = t("irohPairingReady", { code });
|
|
12464
|
+
});
|
|
12465
|
+
irohPair.addEventListener("click", () => {
|
|
12466
|
+
if (remoteProviderBusy) return;
|
|
12467
|
+
remoteProviderBusy = true;
|
|
12468
|
+
irohPair.disabled = true;
|
|
12469
|
+
irohPairStatus.textContent = t("loadingRemoteStatus");
|
|
12470
|
+
openIrohPairing().catch((error) => {
|
|
12471
|
+
remoteFailureText = remoteFailureTextFor(error, "requestFailed");
|
|
12472
|
+
irohPairStatus.textContent = remoteFailureText;
|
|
12473
|
+
}).finally(() => {
|
|
12474
|
+
remoteProviderBusy = false;
|
|
12475
|
+
loadRemote();
|
|
12476
|
+
});
|
|
12477
|
+
});
|
|
12478
|
+
irohCopyDescriptor.addEventListener("click", () => {
|
|
12479
|
+
if (activeIrohDescriptor === "") return;
|
|
12480
|
+
navigator.clipboard.writeText(activeIrohDescriptor).then(() => {
|
|
12481
|
+
irohPairStatus.textContent = t("linkCopied");
|
|
12482
|
+
}, () => {
|
|
12483
|
+
irohPairStatus.textContent = t("remoteQrReady");
|
|
12484
|
+
});
|
|
12485
|
+
});
|
|
11534
12486
|
cloudflaredInstall.addEventListener("click", () => {
|
|
11535
12487
|
if (remoteProviderBusy) return;
|
|
11536
12488
|
if (!window.confirm(t("installConfirmCloudflared", { size: cloudflaredDownloadSize }))) return;
|
|
@@ -12523,7 +13475,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
12523
13475
|
remoteReconnect.disabled = true;
|
|
12524
13476
|
remoteSetup.disabled = true;
|
|
12525
13477
|
remoteSetupRetry.disabled = true;
|
|
12526
|
-
remoteStatus.textContent = remoteProvider === "cpolar" ? t("reconnectingCpolar") : remoteProvider === "cloudflared" ? t("reconnectingCloudflared") : remoteProvider === "frp" ? t("reconnectingFrp") : remoteProvider === "origin" ? t("reconnectingOrigin") : t("reconnectingTailscale");
|
|
13478
|
+
remoteStatus.textContent = remoteProvider === "cpolar" ? t("reconnectingCpolar") : remoteProvider === "cloudflared" ? t("reconnectingCloudflared") : remoteProvider === "frp" ? t("reconnectingFrp") : remoteProvider === "origin" ? t("reconnectingOrigin") : remoteProvider === "iroh" ? t("reconnectingIroh") : t("reconnectingTailscale");
|
|
12527
13479
|
controlRequestJson("/api/mobile-access/remote/reconnect", {
|
|
12528
13480
|
method: "POST",
|
|
12529
13481
|
body: "{}"
|
|
@@ -12644,7 +13596,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
12644
13596
|
if (show) loadRemoteDevices();
|
|
12645
13597
|
});
|
|
12646
13598
|
remoteReset.addEventListener("click", () => {
|
|
12647
|
-
const prompt = remoteProvider === "cpolar" ? t("resetCpolarConfirm") : remoteProvider === "cloudflared" ? t("resetCloudflaredConfirm") : remoteProvider === "frp" ? t("resetFrpConfirm") : remoteProvider === "origin" ? t("resetOriginConfirm") : t("resetTailscaleConfirm");
|
|
13599
|
+
const prompt = remoteProvider === "cpolar" ? t("resetCpolarConfirm") : remoteProvider === "cloudflared" ? t("resetCloudflaredConfirm") : remoteProvider === "frp" ? t("resetFrpConfirm") : remoteProvider === "origin" ? t("resetOriginConfirm") : remoteProvider === "iroh" ? t("resetIrohConfirm") : t("resetTailscaleConfirm");
|
|
12648
13600
|
if (!window.confirm(prompt)) return;
|
|
12649
13601
|
controlRequestJson("/api/mobile-access/remote/reset", {
|
|
12650
13602
|
method: "POST",
|
|
@@ -13443,10 +14395,28 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
13443
14395
|
});
|
|
13444
14396
|
}
|
|
13445
14397
|
};
|
|
13446
|
-
/**
|
|
13447
|
-
|
|
14398
|
+
/**
|
|
14399
|
+
* Install authenticated customization refresh, retaining assets through temporary failures.
|
|
14400
|
+
* @param recovery - optional instance-owned authentication feedback and refresh registration.
|
|
14401
|
+
* @returns synchronous teardown for the customization UI and refresh admission.
|
|
14402
|
+
*/
|
|
14403
|
+
function installCustomAssets(recovery) {
|
|
13448
14404
|
const reporter = createMobileAssetLoadReporter(selectedMobileControlLocale);
|
|
14405
|
+
let refreshNow = () => {};
|
|
13449
14406
|
const customizationOwner = ownsMobileCustomization(document.querySelector("meta[name=\"dsh-mobile-gateway\"][content=\"authenticated\"]") !== null, window.__DSH_MOBILE_FRONTEND__);
|
|
14407
|
+
const removeNotice = customizationOwner ? installCustomizationNotice(document, {
|
|
14408
|
+
locale: () => {
|
|
14409
|
+
const locale = selectedMobileControlLocale();
|
|
14410
|
+
return locale === "zh" || locale === "it" ? locale : "en";
|
|
14411
|
+
},
|
|
14412
|
+
reconnect: () => {
|
|
14413
|
+
const attempted = recovery?.retry() ?? false;
|
|
14414
|
+
refreshNow();
|
|
14415
|
+
return attempted;
|
|
14416
|
+
},
|
|
14417
|
+
subscribe: (listener) => reporter.subscribe(listener),
|
|
14418
|
+
message: (reason) => reporter.message(reason)
|
|
14419
|
+
}) : () => {};
|
|
13450
14420
|
const legacyStyle = element("style");
|
|
13451
14421
|
legacyStyle.dataset.plugin = "dsh-mobile-custom";
|
|
13452
14422
|
document.head.append(legacyStyle);
|
|
@@ -13478,7 +14448,9 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
13478
14448
|
if (reason === "authentication_required") events?.setEnabled(false);
|
|
13479
14449
|
};
|
|
13480
14450
|
const unavailable = (response, signal, fallback) => {
|
|
13481
|
-
|
|
14451
|
+
const rejectedAuth = response.status === 401 || response.status === 403;
|
|
14452
|
+
failed(rejectedAuth ? "authentication_required" : fallback, signal);
|
|
14453
|
+
if (rejectedAuth && !disposed && !signal.aborted) recovery?.rejected(response.status);
|
|
13482
14454
|
response.body?.cancel().catch((error) => {});
|
|
13483
14455
|
};
|
|
13484
14456
|
let expectedDefinitionId;
|
|
@@ -14038,6 +15010,10 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
14038
15010
|
events?.setEnabled(true);
|
|
14039
15011
|
}
|
|
14040
15012
|
});
|
|
15013
|
+
refreshNow = () => {
|
|
15014
|
+
stopRefresh.refresh();
|
|
15015
|
+
};
|
|
15016
|
+
const unregisterRefresh = recovery?.registerRefresh(refreshNow);
|
|
14041
15017
|
const stopEvents = startExtensionChangeStream(() => {
|
|
14042
15018
|
stopRefresh.refresh();
|
|
14043
15019
|
}, void 0, (payload) => {
|
|
@@ -14058,6 +15034,8 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
14058
15034
|
events = stopEvents;
|
|
14059
15035
|
return () => {
|
|
14060
15036
|
disposed = true;
|
|
15037
|
+
unregisterRefresh?.();
|
|
15038
|
+
removeNotice();
|
|
14061
15039
|
reporter.dispose();
|
|
14062
15040
|
stopNativeBack();
|
|
14063
15041
|
stopEvents();
|
|
@@ -14128,12 +15106,12 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
14128
15106
|
/* The cloudflared setup section deliberately reuses this card, so
|
|
14129
15107
|
.dsh-mobile-control__cloudflared-setup is a DOM hook for targeting rather than a
|
|
14130
15108
|
style rule of its own. */
|
|
14131
|
-
.dsh-mobile-control__cpolar-setup{margin:0 0 12px;padding:12px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__cpolar-setup[hidden],.dsh-mobile-control__cpolar-account[hidden],.dsh-mobile-control__details[hidden],.dsh-mobile-control__view.is-remote .dsh-mobile-control__actions[hidden],.dsh-mobile-control__danger[hidden]{display:none}.dsh-mobile-control__component-status,.dsh-mobile-control__component-note{margin:0 0 10px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.55}.dsh-mobile-control__cpolar-setup>.dsh-mobile-control__primary{width:100%;min-height:44px;padding:9px 12px;border-radius:10px;font:600 12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__cpolar-account{margin-top:10px}.dsh-mobile-control__link-row{display:flex;flex-wrap:wrap;gap:6px 12px;margin:0 0 10px}.dsh-mobile-control__text-link{color:var(--dsw-alias-label-primary-bluish,#2563eb);font-size:11px;text-decoration:none}.dsh-mobile-control__text-link:hover{text-decoration:underline}.dsh-mobile-control__token-label{display:flex;flex-direction:column;gap:5px;margin:0 0 8px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px}.dsh-mobile-control__token{box-sizing:border-box;width:100%;min-height:44px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-bg-layer-2,#fff));color:var(--dsw-alias-label-primary,#16181d);font:16px/1.4 system-ui}.dsh-mobile-control__cpolar-connect{display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:100%;min-height:44px;padding:10px 14px;border-radius:12px;font:650 13px/1.2 system-ui;cursor:pointer;transition:background-color 160ms ease,border-color 160ms ease,opacity 160ms ease}.dsh-mobile-control__cpolar-connect:hover:not(:disabled){border-color:#1d4ed8;background:#1d4ed8}.dsh-mobile-control__cpolar-connect:active:not(:disabled){border-color:#1e40af;background:#1e40af}.dsh-mobile-control__cpolar-connect:disabled{cursor:wait;opacity:.55}.dsh-mobile-control__details{margin:10px 0 0;border-top:1px solid var(--dsw-alias-border-l2,#e1e5eb);padding-top:9px}.dsh-mobile-control__details>summary{min-height:36px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:30px;cursor:pointer}.dsh-mobile-control__details-body{display:flex;flex-wrap:wrap;align-items:center;gap:7px 12px;padding:4px 0}.dsh-mobile-control__details-body p{flex:1 0 100%;margin:0;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__storage{display:block;flex:1 0 100%;max-width:100%;overflow:hidden;padding:7px 8px;border-radius:8px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-secondary,#475569);font:11px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__danger{flex:1 0 100%;min-height:44px;margin-top:3px;padding:7px 10px;border:1px solid var(--dsw-alias-state-error-primary,#dc2626);border-radius:9px;background:transparent;color:var(--dsw-alias-label-primary,#dc2626);font:12px/1.3 system-ui;cursor:pointer}
|
|
15109
|
+
.dsh-mobile-control__cpolar-setup{margin:0 0 12px;padding:12px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__cpolar-setup[hidden],.dsh-mobile-control__cpolar-account[hidden],.dsh-mobile-control__details[hidden],.dsh-mobile-control__view.is-remote .dsh-mobile-control__actions[hidden],.dsh-mobile-control__danger[hidden]{display:none}.dsh-mobile-control__component-status,.dsh-mobile-control__component-note{margin:0 0 10px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.55}.dsh-mobile-control__progress{box-sizing:border-box;width:100%;height:6px;margin:0 0 10px}.dsh-mobile-control__progress[hidden]{display:none}.dsh-mobile-control__cpolar-setup>.dsh-mobile-control__primary{width:100%;min-height:44px;padding:9px 12px;border-radius:10px;font:600 12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__cpolar-account{margin-top:10px}.dsh-mobile-control__link-row{display:flex;flex-wrap:wrap;gap:6px 12px;margin:0 0 10px}.dsh-mobile-control__text-link{color:var(--dsw-alias-label-primary-bluish,#2563eb);font-size:11px;text-decoration:none}.dsh-mobile-control__text-link:hover{text-decoration:underline}.dsh-mobile-control__token-label{display:flex;flex-direction:column;gap:5px;margin:0 0 8px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px}.dsh-mobile-control__token{box-sizing:border-box;width:100%;min-height:44px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-bg-layer-2,#fff));color:var(--dsw-alias-label-primary,#16181d);font:16px/1.4 system-ui}.dsh-mobile-control__cpolar-connect{display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:100%;min-height:44px;padding:10px 14px;border-radius:12px;font:650 13px/1.2 system-ui;cursor:pointer;transition:background-color 160ms ease,border-color 160ms ease,opacity 160ms ease}.dsh-mobile-control__cpolar-connect:hover:not(:disabled){border-color:#1d4ed8;background:#1d4ed8}.dsh-mobile-control__cpolar-connect:active:not(:disabled){border-color:#1e40af;background:#1e40af}.dsh-mobile-control__cpolar-connect:disabled{cursor:wait;opacity:.55}.dsh-mobile-control__details{margin:10px 0 0;border-top:1px solid var(--dsw-alias-border-l2,#e1e5eb);padding-top:9px}.dsh-mobile-control__details>summary{min-height:36px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:30px;cursor:pointer}.dsh-mobile-control__details-body{display:flex;flex-wrap:wrap;align-items:center;gap:7px 12px;padding:4px 0}.dsh-mobile-control__details-body p{flex:1 0 100%;margin:0;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__storage{display:block;flex:1 0 100%;max-width:100%;overflow:hidden;padding:7px 8px;border-radius:8px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-secondary,#475569);font:11px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__danger{flex:1 0 100%;min-height:44px;margin-top:3px;padding:7px 10px;border:1px solid var(--dsw-alias-state-error-primary,#dc2626);border-radius:9px;background:transparent;color:var(--dsw-alias-label-primary,#dc2626);font:12px/1.3 system-ui;cursor:pointer}
|
|
14132
15110
|
.dsh-mobile-control__danger:hover:not(:disabled),.dsh-mobile-control__device-revoke:hover{background:var(--dsw-alias-interactive-bg-hover-danger,rgb(220 38 38 / 8%))}
|
|
14133
15111
|
.dsh-mobile-control__frp-setup{margin:0;padding:12px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__frp-setup[hidden]{display:none}.dsh-mobile-control__frp-step{padding:11px 0}.dsh-mobile-control__frp-step + .dsh-mobile-control__frp-step{border-top:1px solid var(--dsw-alias-border-l2,#e1e5eb)}.dsh-mobile-control__frp-step>strong{display:block;margin-bottom:3px;font-size:12px;line-height:1.4}.dsh-mobile-control__frp-step>p{margin:0 0 9px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__frp-changes{margin:0 0 9px;padding:0;list-style:none;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__frp-changes li{position:relative;margin:0;padding-left:12px}.dsh-mobile-control__frp-changes li+li{margin-top:5px}.dsh-mobile-control__frp-changes li::before{content:"•";position:absolute;left:2px;color:var(--dsw-alias-label-tertiary,#98a1ad)}.dsh-mobile-control__frp-step>.dsh-mobile-control__frp-requirement{padding:8px 9px;border-radius:9px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-primary,#384152);font-size:11px}.dsh-mobile-control__frp-fields{display:grid;grid-template-columns:minmax(0,1fr) 96px;gap:8px}.dsh-mobile-control__frp-fields>.dsh-mobile-control__field:nth-child(5),.dsh-mobile-control__frp-fields>.dsh-mobile-control__field:nth-child(6){grid-column:1/-1}.dsh-mobile-control__frp-fields>.dsh-mobile-control__field[hidden]{display:none}.dsh-mobile-control__frp-fields select{box-sizing:border-box;width:100%;min-height:44px;padding:9px 32px 9px 10px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);font:14px/1.4 system-ui}.dsh-mobile-control__frp-fields select:disabled{cursor:not-allowed;opacity:.55}.dsh-mobile-control__frp-hint{margin:0;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__field{display:flex;min-width:0;flex-direction:column;gap:5px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px}.dsh-mobile-control__field:nth-child(3),.dsh-mobile-control__field:nth-child(4){grid-column:1/-1}.dsh-mobile-control__field input{box-sizing:border-box;width:100%;min-height:44px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);font:16px/1.4 system-ui}.dsh-mobile-control__frp-action{box-sizing:border-box;width:100%;min-height:44px;padding:9px 12px;border-radius:10px;font:650 12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__frp-action:disabled{cursor:not-allowed;opacity:.5}.dsh-mobile-control__remote-workspace{margin:0;padding:12px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:15px;background:var(--dsw-alias-bg-layer-1,#f7f8fa)}.dsh-mobile-control__stage-header{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:9px}.dsh-mobile-control__stage-header .dsh-mobile-control__section-title{margin:0}.dsh-mobile-control__stage-meta{display:flex;min-width:0;align-items:center;justify-content:flex-end;gap:5px}.dsh-mobile-control__stage-value{max-width:115px;overflow:hidden;color:var(--dsw-alias-label-primary,#16181d);font:650 11px/1.3 system-ui;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__state-badge{flex:none;padding:3px 7px;border-radius:999px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-secondary,#606873);font:650 11px/1.25 system-ui}.dsh-mobile-control__state-badge.is-ready{background:var(--dsw-alias-state-success-tertiary,#e6f7f0);color:var(--dsw-alias-label-primary,#087454)}.dsh-mobile-control__state-badge.is-busy{background:var(--dsw-alias-state-business-tertiary,#e8f0ff);color:var(--dsw-alias-label-primary,#1d4ed8)}.dsh-mobile-control__state-badge.is-attention{background:var(--dsw-alias-state-warn-tertiary,#fff4dc);color:var(--dsw-alias-label-primary,#935100)}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__status{box-sizing:border-box;margin:0 0 10px;padding:9px 10px;border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);font-size:11px;line-height:1.45}.dsh-mobile-control__provider-setup-body{margin:0 0 10px}.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__cpolar-setup{margin:0}.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__cpolar-setup>.dsh-mobile-control__section-title,.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__frp-setup>.dsh-mobile-control__section-title{display:none}.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__details{margin:0;padding:9px 10px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__actions{margin-top:2px}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__qr{margin:10px 0 0}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__manage-row{margin-top:10px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l2,#e1e5eb)}
|
|
14134
15112
|
.dsh-mobile-control__frp-overview{display:grid;grid-template-columns:32px minmax(0,1fr);align-items:center;gap:10px;margin:0 0 10px;padding:10px;border:1px solid var(--dsw-alias-state-success-primary,#a9dfc9);border-radius:11px;background:var(--dsw-alias-state-success-tertiary,#edf9f4)}.dsh-mobile-control__frp-overview[hidden]{display:none}.dsh-mobile-control__frp-overview-mark{display:grid;width:32px;height:32px;place-items:center;border-radius:50%;background:#087454;color:#fff;font:700 15px/1 system-ui}.dsh-mobile-control__frp-overview-body{display:flex;min-width:0;flex-direction:column;gap:2px}.dsh-mobile-control__frp-overview-body strong{color:var(--dsw-alias-label-primary,#075d46);font-size:12px;line-height:1.35}.dsh-mobile-control__frp-overview-body span{overflow:hidden;color:var(--dsw-alias-label-secondary,#357061);font:11px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__frp-group{margin:8px 0 0;overflow:hidden;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__frp-group>summary{box-sizing:border-box;min-height:44px;padding:12px 34px 10px 12px;color:var(--dsw-alias-label-primary,#16181d);font:650 12px/1.4 system-ui;cursor:pointer}.dsh-mobile-control__frp-group[open]>summary{border-bottom:1px solid var(--dsw-alias-border-l2,#e1e5eb);background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__frp-group>.dsh-mobile-control__frp-step{padding:12px}.dsh-mobile-control__frp-group>.dsh-mobile-control__frp-step>strong:first-child{display:none}.dsh-mobile-control__frp-group .dsh-mobile-control__frp-step{border-top:0}.dsh-mobile-control__frp-setup>.dsh-mobile-control__frp-step{margin-bottom:8px;padding:10px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__tunnel{margin:0 0 10px}.dsh-mobile-control__tunnel[hidden]{display:none}
|
|
14135
15113
|
.dsh-mobile-control__lan-setup{box-sizing:border-box;margin:0 0 12px;padding:14px;border:1px solid #6f96db;border-radius:15px;background:var(--dsw-alias-bg-layer-1,#f5f8ff)}.dsh-mobile-control__lan-setup[hidden],.dsh-mobile-control__lan-setup-form[hidden]{display:none}.dsh-mobile-control__lan-setup>.dsh-mobile-control__section-title{margin-bottom:5px}.dsh-mobile-control__lan-setup>.dsh-mobile-control__intro{margin-bottom:12px}.dsh-mobile-control__lan-setup-form{display:grid;gap:9px}.dsh-mobile-control__lan-setup .dsh-mobile-control__field select{box-sizing:border-box;width:100%;min-height:44px;padding:9px 32px 9px 10px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);font:14px/1.4 system-ui}.dsh-mobile-control__lan-setup .dsh-mobile-control__component-note{margin:0}.dsh-mobile-control__lan-setup-actions{display:grid;grid-template-columns:1fr 1.35fr;gap:8px}.dsh-mobile-control__lan-setup-actions button{box-sizing:border-box;min-width:0;min-height:44px;padding:9px 10px;border-radius:10px;font:650 12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__lan-setup-actions button:disabled{cursor:wait;opacity:.55}.dsh-mobile-control__lan-setup>.dsh-mobile-control__status{margin:10px 0 0;line-height:1.5}.dsh-mobile-control__lan-setup>.dsh-mobile-control__status:empty{display:none}
|
|
14136
|
-
.dsh-mobile-control__access{display:flex;align-items:baseline;gap:6px;min-width:0;margin:0 0 12px}.dsh-mobile-control__access[hidden]{display:none}.dsh-mobile-control__access-label{flex:none;color:var(--dsw-alias-label-secondary,#606873);white-space:nowrap}.dsh-mobile-control__access-label::after{content:":"}.dsh-mobile-control__access-link{min-width:0;overflow:hidden;color:var(--dsw-alias-label-primary-bluish,#2563eb);text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__access-link:hover{text-decoration:underline}.dsh-mobile-control__qr{display:flex;justify-content:center;margin:0 0 12px}.dsh-mobile-control__qr[hidden]{display:none}.dsh-mobile-control__qr img{border-radius:12px;background:#fff;padding:8px}
|
|
15114
|
+
.dsh-mobile-control__access{display:flex;align-items:baseline;gap:6px;min-width:0;margin:0 0 12px}.dsh-mobile-control__access[hidden]{display:none}.dsh-mobile-control__access-label{flex:none;color:var(--dsw-alias-label-secondary,#606873);white-space:nowrap}.dsh-mobile-control__access-label::after{content:":"}.dsh-mobile-control__access-link{min-width:0;overflow:hidden;color:var(--dsw-alias-label-primary-bluish,#2563eb);text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__access-link:hover{text-decoration:underline}.dsh-mobile-control__qr{display:flex;justify-content:center;margin:0 0 12px}.dsh-mobile-control__qr[hidden]{display:none}.dsh-mobile-control__qr img{border-radius:12px;background:#fff;padding:8px}.dsh-mobile-control__qr.is-expandable{cursor:zoom-in}.dsh-mobile-control__qr.is-expandable:focus-visible{outline:3px solid var(--dsw-alias-state-business-primary,#2563eb);outline-offset:2px;border-radius:12px}.dsh-mobile-control__qr-zoom{position:fixed;inset:0;z-index:1001;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.72)}.dsh-mobile-control__qr-zoom[hidden]{display:none}.dsh-mobile-control__qr-zoom-image{width:min(84vw,360px);height:min(84vw,360px);border-radius:12px;background:#fff;padding:12px}.dsh-mobile-control__qr-zoom-close{position:absolute;top:12px;right:16px;min-width:44px;min-height:44px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);font:600 18px/1 system-ui;cursor:pointer}
|
|
14137
15115
|
.dsh-mobile-control__status{margin:0 0 14px;overflow-wrap:anywhere;color:var(--dsw-alias-label-secondary,#606873)}.dsh-mobile-control__status::before{display:inline-block;width:8px;height:8px;margin-right:7px;border-radius:50%;background:var(--dsw-alias-label-tertiary,#98a1ad);content:""}.dsh-mobile-control__status.is-running::before{background:var(--dsw-alias-state-success-primary,#16a36a)}.dsh-mobile-control__status.is-key{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:12px;word-break:break-all}
|
|
14138
15116
|
.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}
|
|
14139
15117
|
.dsh-mobile-control__extensions{margin:0 0 12px;color:var(--dsw-alias-label-secondary,#606873);font-size:12px}
|
|
@@ -14160,6 +15138,9 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
14160
15138
|
` + EXTENSION_RECOVERY_STYLES;
|
|
14161
15139
|
/** Mount the desktop control or mobile feature enhancements. */
|
|
14162
15140
|
function apply(ctx) {
|
|
15141
|
+
let gatewayRejectionSink;
|
|
15142
|
+
let retryRecovery;
|
|
15143
|
+
let refreshCustomization;
|
|
14163
15144
|
ctx.effect(() => installMobileUiLocalePersistence(document, window), "dsh-mobile: browser UI locale persistence");
|
|
14164
15145
|
ctx.effect(() => {
|
|
14165
15146
|
if (window.__DSH_MOBILE_FRONTEND__ !== "dedicated") return;
|
|
@@ -14168,7 +15149,26 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
14168
15149
|
}, "dsh-mobile: authenticated gateway client trust");
|
|
14169
15150
|
ctx.effect(() => {
|
|
14170
15151
|
if (!ownsMobileCustomization(document.querySelector("meta[name=\"dsh-mobile-gateway\"][content=\"authenticated\"]") !== null, window.__DSH_MOBILE_FRONTEND__)) return;
|
|
14171
|
-
|
|
15152
|
+
let ownedRejection;
|
|
15153
|
+
const recovery = installMobileConnectionRecovery(() => ctx.get("connection"), window, document, (rejected, retry) => {
|
|
15154
|
+
ownedRejection = rejected;
|
|
15155
|
+
gatewayRejectionSink = rejected;
|
|
15156
|
+
retryRecovery = retry;
|
|
15157
|
+
}, () => {
|
|
15158
|
+
refreshCustomization?.();
|
|
15159
|
+
});
|
|
15160
|
+
if (recovery === void 0) {
|
|
15161
|
+
gatewayRejectionSink = void 0;
|
|
15162
|
+
retryRecovery = void 0;
|
|
15163
|
+
return;
|
|
15164
|
+
}
|
|
15165
|
+
return () => {
|
|
15166
|
+
if (gatewayRejectionSink === ownedRejection) {
|
|
15167
|
+
gatewayRejectionSink = void 0;
|
|
15168
|
+
retryRecovery = void 0;
|
|
15169
|
+
}
|
|
15170
|
+
return recovery();
|
|
15171
|
+
};
|
|
14172
15172
|
}, "dsh-mobile: connection recovery");
|
|
14173
15173
|
ctx.effect(() => {
|
|
14174
15174
|
const desktopAdmin = isDesktopAdminSurface(location.hostname, location.search, window.__DSH_MOBILE_FRONTEND__, location.protocol);
|
|
@@ -14178,7 +15178,18 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
14178
15178
|
style.textContent = desktopAdmin ? CONTROL_STYLES : NATIVE_MOBILE_STYLES;
|
|
14179
15179
|
style.textContent += CLIENT_MODULE_STYLES;
|
|
14180
15180
|
document.head.append(style);
|
|
14181
|
-
const removeCustom = !desktopAdmin || gatewayCustomization ? installCustomAssets(
|
|
15181
|
+
const removeCustom = !desktopAdmin || gatewayCustomization ? installCustomAssets({
|
|
15182
|
+
rejected: (status) => {
|
|
15183
|
+
gatewayRejectionSink?.(status);
|
|
15184
|
+
},
|
|
15185
|
+
retry: () => retryRecovery?.() ?? false,
|
|
15186
|
+
registerRefresh: (refresh) => {
|
|
15187
|
+
refreshCustomization = refresh;
|
|
15188
|
+
return () => {
|
|
15189
|
+
if (refreshCustomization === refresh) refreshCustomization = void 0;
|
|
15190
|
+
};
|
|
15191
|
+
}
|
|
15192
|
+
}) : () => void 0;
|
|
14182
15193
|
const removeVoiceSession = installVoiceSession(!desktopAdmin);
|
|
14183
15194
|
const removeDraftTransfer = !desktopAdmin && document.querySelector("meta[name=\"dsh-mobile-gateway\"][content=\"authenticated\"]") !== null ? installDraftTransfer(ctx, window, document) : () => void 0;
|
|
14184
15195
|
const removeModuleSettings = ctx.slots.inject("settings.general.item", () => ctx.slots.register({
|
|
@@ -14227,7 +15238,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
14227
15238
|
getLayout: () => ctx.get("layout"),
|
|
14228
15239
|
getSidebarRight: () => ctx.get("sidebarRight")
|
|
14229
15240
|
}));
|
|
14230
|
-
const removeDirectoryPicker = window.__DSH_MOBILE_FRONTEND__ === "dedicated" ? installMobileDirectoryPicker(ctx, document) : () => Promise.resolve();
|
|
15241
|
+
const removeDirectoryPicker = window.__DSH_MOBILE_FRONTEND__ === "dedicated" && !hasUpstreamBrowsePicker(window) ? installMobileDirectoryPicker(ctx, document) : () => Promise.resolve();
|
|
14231
15242
|
return async () => {
|
|
14232
15243
|
const removal = removeDirectoryPicker();
|
|
14233
15244
|
try {
|