dsh-mobile 0.6.18 → 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 +22 -1
- package/CONTRIBUTORS.md +1 -1
- package/README.en.md +8 -6
- package/README.md +8 -6
- package/THIRD_PARTY_NOTICES.md +33 -0
- package/apps/mobile/README.md +28 -3
- package/apps/mobile/README.zh-CN.md +28 -3
- package/apps/mobile/release.json +3 -3
- package/docs/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 +8 -0
- package/docs/MOBILE_PLUGIN_ADAPTATION.md +8 -0
- package/docs/README.md +1 -0
- package/docs/SELF_HOSTED_ORIGIN.en.md +25 -0
- package/docs/SELF_HOSTED_ORIGIN.md +25 -0
- package/docs/SKIN_COMPATIBILITY.en.md +2 -0
- package/docs/SKIN_COMPATIBILITY.md +2 -0
- package/docs/SLOW_CONNECTIONS.en.md +8 -0
- package/docs/SLOW_CONNECTIONS.md +8 -0
- package/lib/cli.js.map +1 -1
- package/lib/client.js +661 -28
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +268 -2
- package/lib/index.mjs +1820 -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 +14 -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
|
|
@@ -9165,7 +9485,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
9165
9485
|
//#region src/client.ts
|
|
9166
9486
|
/** Map a host-reported provider onto the UI union, defaulting to the built-in one. */
|
|
9167
9487
|
function parseMobileRemoteProvider(value) {
|
|
9168
|
-
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";
|
|
9169
9489
|
}
|
|
9170
9490
|
/**
|
|
9171
9491
|
* Set a surface's visibility; opening a page or overlay brings it to the front.
|
|
@@ -9711,6 +10031,23 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
9711
10031
|
frp_discovery_invalid: "frpDiscoveryInvalid",
|
|
9712
10032
|
frp_stopped: "frpStopped",
|
|
9713
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",
|
|
9714
10051
|
frp_server_address_invalid: "frpServerAddressInvalid",
|
|
9715
10052
|
frp_server_port_invalid: "frpServerPortInvalid",
|
|
9716
10053
|
frp_token_invalid: "frpTokenInvalid",
|
|
@@ -9822,7 +10159,8 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
9822
10159
|
cpolar: "cpolar",
|
|
9823
10160
|
cloudflared: "cloudflared",
|
|
9824
10161
|
frp: t("frpName"),
|
|
9825
|
-
origin: t("originName")
|
|
10162
|
+
origin: t("originName"),
|
|
10163
|
+
iroh: t("irohName")
|
|
9826
10164
|
};
|
|
9827
10165
|
const providerId = typeof facts.provider === "string" ? facts.provider : "";
|
|
9828
10166
|
const controllerCode = typeof facts.controllerCode === "string" ? facts.controllerCode : "";
|
|
@@ -10355,7 +10693,19 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
10355
10693
|
originChoiceDescription.textContent = t("originDescription");
|
|
10356
10694
|
originChoiceTop.append(originChoiceName);
|
|
10357
10695
|
originChoice.append(originChoiceTop, originChoiceDescription);
|
|
10358
|
-
|
|
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);
|
|
10359
10709
|
selfHosted.append(selfHostedSummary, selfHostedBody);
|
|
10360
10710
|
providerSection.append(providerHeading, providerInfo, providerChoices, selfHosted);
|
|
10361
10711
|
const frpSetup = element("section", "dsh-mobile-control__frp-setup");
|
|
@@ -10839,7 +11189,77 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
10839
11189
|
providerSetupMeta.append(providerSetupName, remoteStateBadge);
|
|
10840
11190
|
providerSetupHeader.append(providerSetupHeading, providerSetupMeta);
|
|
10841
11191
|
const providerSetupBody = element("div", "dsh-mobile-control__provider-setup-body");
|
|
10842
|
-
|
|
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);
|
|
10843
11263
|
const remoteAccess = element("div", "dsh-mobile-control__access");
|
|
10844
11264
|
remoteAccess.hidden = true;
|
|
10845
11265
|
const remoteAccessLabel = element("span", "dsh-mobile-control__access-label");
|
|
@@ -10905,6 +11325,8 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
10905
11325
|
remotePairLink.hidden = true;
|
|
10906
11326
|
let activeRemotePairUrl = "";
|
|
10907
11327
|
let activeRemotePairExpiresAt = 0;
|
|
11328
|
+
let activeIrohDescriptor = "";
|
|
11329
|
+
let activeIrohQrSvg = "";
|
|
10908
11330
|
remoteActions.append(remoteToggle, remoteLogin, remoteReconnect, remotePair, remoteCopyLink);
|
|
10909
11331
|
const remoteManageRow = element("div", "dsh-mobile-control__manage-row");
|
|
10910
11332
|
const remoteDevices = element("button", "dsh-mobile-control__manage");
|
|
@@ -11201,6 +11623,9 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11201
11623
|
let latestRemoteState = {};
|
|
11202
11624
|
let frpInstalled = false;
|
|
11203
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;
|
|
11204
11629
|
let frpLayoutInitialized = false;
|
|
11205
11630
|
let frpDownloadSize = "14.0";
|
|
11206
11631
|
let configuredFrpServer = "";
|
|
@@ -11522,6 +11947,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11522
11947
|
const cloudflared = remoteProvider === "cloudflared";
|
|
11523
11948
|
const frp = remoteProvider === "frp";
|
|
11524
11949
|
const origin = remoteProvider === "origin";
|
|
11950
|
+
const iroh = remoteProvider === "iroh";
|
|
11525
11951
|
const managedOrigin = origin && data.originMode === "managed";
|
|
11526
11952
|
const tailscale = remoteProvider === "tailscale";
|
|
11527
11953
|
tailscaleChoice.classList.toggle("is-selected", tailscale);
|
|
@@ -11537,17 +11963,20 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11537
11963
|
cloudflaredChoice.tabIndex = cloudflared ? 0 : -1;
|
|
11538
11964
|
frpChoice.setAttribute("aria-pressed", String(frp));
|
|
11539
11965
|
originChoice.setAttribute("aria-pressed", String(origin));
|
|
11540
|
-
|
|
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";
|
|
11541
11968
|
tailscaleChoice.disabled = remoteProviderBusy;
|
|
11542
11969
|
cpolarChoice.disabled = remoteProviderBusy;
|
|
11543
11970
|
cloudflaredChoice.disabled = remoteProviderBusy;
|
|
11544
11971
|
frpChoice.disabled = remoteProviderBusy;
|
|
11545
11972
|
originChoice.disabled = remoteProviderBusy;
|
|
11973
|
+
irohChoice.disabled = remoteProviderBusy;
|
|
11546
11974
|
cpolarSetup.hidden = !cpolar;
|
|
11547
11975
|
cloudflaredSetup.hidden = !cloudflared;
|
|
11548
11976
|
frpSetup.hidden = !frp;
|
|
11549
11977
|
originSetup.hidden = !origin;
|
|
11550
|
-
|
|
11978
|
+
irohSetup.hidden = !iroh;
|
|
11979
|
+
if (frp || origin || iroh) selfHosted.open = true;
|
|
11551
11980
|
tailscaleInfo.hidden = !tailscale;
|
|
11552
11981
|
remoteReset.textContent = tailscale ? t("resetRemoteLogin") : t("resetRemoteDevices");
|
|
11553
11982
|
const providers = data.providers !== null && typeof data.providers === "object" ? data.providers : {};
|
|
@@ -11702,6 +12131,30 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11702
12131
|
frpVpsGroup.open = !frpConfigured;
|
|
11703
12132
|
frpLayoutInitialized = true;
|
|
11704
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;
|
|
11705
12158
|
const originProvider = providers.origin !== null && typeof providers.origin === "object" ? providers.origin : {};
|
|
11706
12159
|
const originConfiguration = originProvider.configuration !== null && typeof originProvider.configuration === "object" ? originProvider.configuration : {};
|
|
11707
12160
|
originConfigured = originConfiguration.configured === true;
|
|
@@ -11792,10 +12245,10 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11792
12245
|
remoteStatus.classList.toggle("is-running", remoteReady);
|
|
11793
12246
|
const labels = {
|
|
11794
12247
|
off: t("remoteOff"),
|
|
11795
|
-
unavailable: cpolar ? t("remoteUnavailableCpolar") : cloudflared ? t("remoteUnavailableCloudflared") : frp ? t("remoteUnavailableFrp") : origin ? t("originUnavailable") : t("remoteUnavailableTailscale"),
|
|
11796
|
-
starting: cpolar ? t("remoteStartingCpolar") : cloudflared ? t("remoteStartingCloudflared") : frp ? t("remoteStartingFrp") : origin ? t("originStarting") : t("remoteStartingTailscale"),
|
|
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"),
|
|
11797
12250
|
"needs-login": t("remoteNeedsLogin"),
|
|
11798
|
-
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"),
|
|
11799
12252
|
ready: origin ? t(managedOrigin ? "caddyVerified" : "originReady") : t("remoteReady"),
|
|
11800
12253
|
error: t("remoteError")
|
|
11801
12254
|
};
|
|
@@ -11810,7 +12263,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11810
12263
|
remoteSetupRetry.disabled = remoteReconnectBusy;
|
|
11811
12264
|
remoteToggle.textContent = remoteRunning ? t("disableRemote") : t("enableRemote");
|
|
11812
12265
|
const originPrepared = data.originMode === "managed" ? caddySupported && caddyInstalled && caddyConfiguration.configured === true : originConfigured && !caddyModeManagedSelected;
|
|
11813
|
-
const providerPrepared = cpolar ? cpolarInstalled && cpolarConfigured : cloudflared ? cloudflaredInstalled : frp ? frpInstalled && frpConfigured : origin ? originPrepared : true;
|
|
12266
|
+
const providerPrepared = cpolar ? cpolarInstalled && cpolarConfigured : cloudflared ? cloudflaredInstalled : frp ? frpInstalled && frpConfigured : origin ? originPrepared : iroh ? irohInstalled : true;
|
|
11814
12267
|
remoteToggle.disabled = remoteProviderBusy || !providerPrepared && !(origin && remoteRunning);
|
|
11815
12268
|
remoteLogin.hidden = !tailscale || state !== "needs-login" || remoteLoginUrl === "";
|
|
11816
12269
|
remoteReconnect.hidden = needsFunnelSetup || state !== "error" && state !== "unavailable" || !providerPrepared;
|
|
@@ -11818,6 +12271,21 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11818
12271
|
remotePair.disabled = !remoteReady || remoteProviderBusy;
|
|
11819
12272
|
remoteCopyLink.disabled = !remoteReady || remoteProviderBusy;
|
|
11820
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
|
+
}
|
|
11821
12289
|
if (origin) {
|
|
11822
12290
|
remoteReconnect.disabled = remoteProviderBusy || remoteReconnectBusy;
|
|
11823
12291
|
remoteReset.disabled = remoteProviderBusy;
|
|
@@ -11882,7 +12350,8 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11882
12350
|
cloudflaredChoice.disabled = true;
|
|
11883
12351
|
frpChoice.disabled = true;
|
|
11884
12352
|
originChoice.disabled = true;
|
|
11885
|
-
|
|
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");
|
|
11886
12355
|
controlRequestJson("/api/mobile-access/remote/provider", {
|
|
11887
12356
|
method: "POST",
|
|
11888
12357
|
body: JSON.stringify({ provider })
|
|
@@ -11909,6 +12378,111 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11909
12378
|
originChoice.addEventListener("click", () => {
|
|
11910
12379
|
chooseRemoteProvider("origin");
|
|
11911
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
|
+
});
|
|
11912
12486
|
cloudflaredInstall.addEventListener("click", () => {
|
|
11913
12487
|
if (remoteProviderBusy) return;
|
|
11914
12488
|
if (!window.confirm(t("installConfirmCloudflared", { size: cloudflaredDownloadSize }))) return;
|
|
@@ -12901,7 +13475,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
12901
13475
|
remoteReconnect.disabled = true;
|
|
12902
13476
|
remoteSetup.disabled = true;
|
|
12903
13477
|
remoteSetupRetry.disabled = true;
|
|
12904
|
-
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");
|
|
12905
13479
|
controlRequestJson("/api/mobile-access/remote/reconnect", {
|
|
12906
13480
|
method: "POST",
|
|
12907
13481
|
body: "{}"
|
|
@@ -13022,7 +13596,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
13022
13596
|
if (show) loadRemoteDevices();
|
|
13023
13597
|
});
|
|
13024
13598
|
remoteReset.addEventListener("click", () => {
|
|
13025
|
-
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");
|
|
13026
13600
|
if (!window.confirm(prompt)) return;
|
|
13027
13601
|
controlRequestJson("/api/mobile-access/remote/reset", {
|
|
13028
13602
|
method: "POST",
|
|
@@ -13821,10 +14395,28 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
13821
14395
|
});
|
|
13822
14396
|
}
|
|
13823
14397
|
};
|
|
13824
|
-
/**
|
|
13825
|
-
|
|
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) {
|
|
13826
14404
|
const reporter = createMobileAssetLoadReporter(selectedMobileControlLocale);
|
|
14405
|
+
let refreshNow = () => {};
|
|
13827
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
|
+
}) : () => {};
|
|
13828
14420
|
const legacyStyle = element("style");
|
|
13829
14421
|
legacyStyle.dataset.plugin = "dsh-mobile-custom";
|
|
13830
14422
|
document.head.append(legacyStyle);
|
|
@@ -13856,7 +14448,9 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
13856
14448
|
if (reason === "authentication_required") events?.setEnabled(false);
|
|
13857
14449
|
};
|
|
13858
14450
|
const unavailable = (response, signal, fallback) => {
|
|
13859
|
-
|
|
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);
|
|
13860
14454
|
response.body?.cancel().catch((error) => {});
|
|
13861
14455
|
};
|
|
13862
14456
|
let expectedDefinitionId;
|
|
@@ -14416,6 +15010,10 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
14416
15010
|
events?.setEnabled(true);
|
|
14417
15011
|
}
|
|
14418
15012
|
});
|
|
15013
|
+
refreshNow = () => {
|
|
15014
|
+
stopRefresh.refresh();
|
|
15015
|
+
};
|
|
15016
|
+
const unregisterRefresh = recovery?.registerRefresh(refreshNow);
|
|
14419
15017
|
const stopEvents = startExtensionChangeStream(() => {
|
|
14420
15018
|
stopRefresh.refresh();
|
|
14421
15019
|
}, void 0, (payload) => {
|
|
@@ -14436,6 +15034,8 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
14436
15034
|
events = stopEvents;
|
|
14437
15035
|
return () => {
|
|
14438
15036
|
disposed = true;
|
|
15037
|
+
unregisterRefresh?.();
|
|
15038
|
+
removeNotice();
|
|
14439
15039
|
reporter.dispose();
|
|
14440
15040
|
stopNativeBack();
|
|
14441
15041
|
stopEvents();
|
|
@@ -14506,12 +15106,12 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
14506
15106
|
/* The cloudflared setup section deliberately reuses this card, so
|
|
14507
15107
|
.dsh-mobile-control__cloudflared-setup is a DOM hook for targeting rather than a
|
|
14508
15108
|
style rule of its own. */
|
|
14509
|
-
.dsh-mobile-control__cpolar-setup{margin:0 0 12px;padding:12px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__cpolar-setup[hidden],.dsh-mobile-control__cpolar-account[hidden],.dsh-mobile-control__details[hidden],.dsh-mobile-control__view.is-remote .dsh-mobile-control__actions[hidden],.dsh-mobile-control__danger[hidden]{display:none}.dsh-mobile-control__component-status,.dsh-mobile-control__component-note{margin:0 0 10px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.55}.dsh-mobile-control__cpolar-setup>.dsh-mobile-control__primary{width:100%;min-height:44px;padding:9px 12px;border-radius:10px;font:600 12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__cpolar-account{margin-top:10px}.dsh-mobile-control__link-row{display:flex;flex-wrap:wrap;gap:6px 12px;margin:0 0 10px}.dsh-mobile-control__text-link{color:var(--dsw-alias-label-primary-bluish,#2563eb);font-size:11px;text-decoration:none}.dsh-mobile-control__text-link:hover{text-decoration:underline}.dsh-mobile-control__token-label{display:flex;flex-direction:column;gap:5px;margin:0 0 8px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px}.dsh-mobile-control__token{box-sizing:border-box;width:100%;min-height:44px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-bg-layer-2,#fff));color:var(--dsw-alias-label-primary,#16181d);font:16px/1.4 system-ui}.dsh-mobile-control__cpolar-connect{display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:100%;min-height:44px;padding:10px 14px;border-radius:12px;font:650 13px/1.2 system-ui;cursor:pointer;transition:background-color 160ms ease,border-color 160ms ease,opacity 160ms ease}.dsh-mobile-control__cpolar-connect:hover:not(:disabled){border-color:#1d4ed8;background:#1d4ed8}.dsh-mobile-control__cpolar-connect:active:not(:disabled){border-color:#1e40af;background:#1e40af}.dsh-mobile-control__cpolar-connect:disabled{cursor:wait;opacity:.55}.dsh-mobile-control__details{margin:10px 0 0;border-top:1px solid var(--dsw-alias-border-l2,#e1e5eb);padding-top:9px}.dsh-mobile-control__details>summary{min-height:36px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:30px;cursor:pointer}.dsh-mobile-control__details-body{display:flex;flex-wrap:wrap;align-items:center;gap:7px 12px;padding:4px 0}.dsh-mobile-control__details-body p{flex:1 0 100%;margin:0;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__storage{display:block;flex:1 0 100%;max-width:100%;overflow:hidden;padding:7px 8px;border-radius:8px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-secondary,#475569);font:11px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__danger{flex:1 0 100%;min-height:44px;margin-top:3px;padding:7px 10px;border:1px solid var(--dsw-alias-state-error-primary,#dc2626);border-radius:9px;background:transparent;color:var(--dsw-alias-label-primary,#dc2626);font:12px/1.3 system-ui;cursor:pointer}
|
|
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}
|
|
14510
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%))}
|
|
14511
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)}
|
|
14512
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}
|
|
14513
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}
|
|
14514
|
-
.dsh-mobile-control__access{display:flex;align-items:baseline;gap:6px;min-width:0;margin:0 0 12px}.dsh-mobile-control__access[hidden]{display:none}.dsh-mobile-control__access-label{flex:none;color:var(--dsw-alias-label-secondary,#606873);white-space:nowrap}.dsh-mobile-control__access-label::after{content:":"}.dsh-mobile-control__access-link{min-width:0;overflow:hidden;color:var(--dsw-alias-label-primary-bluish,#2563eb);text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__access-link:hover{text-decoration:underline}.dsh-mobile-control__qr{display:flex;justify-content:center;margin:0 0 12px}.dsh-mobile-control__qr[hidden]{display:none}.dsh-mobile-control__qr img{border-radius:12px;background:#fff;padding:8px}
|
|
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}
|
|
14515
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}
|
|
14516
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}
|
|
14517
15117
|
.dsh-mobile-control__extensions{margin:0 0 12px;color:var(--dsw-alias-label-secondary,#606873);font-size:12px}
|
|
@@ -14538,6 +15138,9 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
14538
15138
|
` + EXTENSION_RECOVERY_STYLES;
|
|
14539
15139
|
/** Mount the desktop control or mobile feature enhancements. */
|
|
14540
15140
|
function apply(ctx) {
|
|
15141
|
+
let gatewayRejectionSink;
|
|
15142
|
+
let retryRecovery;
|
|
15143
|
+
let refreshCustomization;
|
|
14541
15144
|
ctx.effect(() => installMobileUiLocalePersistence(document, window), "dsh-mobile: browser UI locale persistence");
|
|
14542
15145
|
ctx.effect(() => {
|
|
14543
15146
|
if (window.__DSH_MOBILE_FRONTEND__ !== "dedicated") return;
|
|
@@ -14546,7 +15149,26 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
14546
15149
|
}, "dsh-mobile: authenticated gateway client trust");
|
|
14547
15150
|
ctx.effect(() => {
|
|
14548
15151
|
if (!ownsMobileCustomization(document.querySelector("meta[name=\"dsh-mobile-gateway\"][content=\"authenticated\"]") !== null, window.__DSH_MOBILE_FRONTEND__)) return;
|
|
14549
|
-
|
|
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
|
+
};
|
|
14550
15172
|
}, "dsh-mobile: connection recovery");
|
|
14551
15173
|
ctx.effect(() => {
|
|
14552
15174
|
const desktopAdmin = isDesktopAdminSurface(location.hostname, location.search, window.__DSH_MOBILE_FRONTEND__, location.protocol);
|
|
@@ -14556,7 +15178,18 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
14556
15178
|
style.textContent = desktopAdmin ? CONTROL_STYLES : NATIVE_MOBILE_STYLES;
|
|
14557
15179
|
style.textContent += CLIENT_MODULE_STYLES;
|
|
14558
15180
|
document.head.append(style);
|
|
14559
|
-
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;
|
|
14560
15193
|
const removeVoiceSession = installVoiceSession(!desktopAdmin);
|
|
14561
15194
|
const removeDraftTransfer = !desktopAdmin && document.querySelector("meta[name=\"dsh-mobile-gateway\"][content=\"authenticated\"]") !== null ? installDraftTransfer(ctx, window, document) : () => void 0;
|
|
14562
15195
|
const removeModuleSettings = ctx.slots.inject("settings.general.item", () => ctx.slots.register({
|