dsh-mobile 0.8.2 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -1160,6 +1160,7 @@ window.__ModuleLoader__.load({
1160
1160
  enlargeQr: "Enlarge the QR code",
1161
1161
  closeEnlargedQr: "Close the enlarged QR code",
1162
1162
  irohPairingReady: "iroh pairing code {code} is live for five minutes. Scan it from the iroh pairing screen; opening another code replaces this one.",
1163
+ irohPairingCopyOnly: "The QR image could not be generated. Copy the pairing link and paste it into the App’s iroh pairing screen.",
1163
1164
  irohDescription: "Peer-to-peer encrypted link with relay fallback. No domain, port or account needed; requires the full Android App.",
1164
1165
  prepareIroh: "Prepare iroh",
1165
1166
  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.",
@@ -1194,6 +1195,7 @@ window.__ModuleLoader__.load({
1194
1195
  enlargeQr: "Ingrandisci il codice QR",
1195
1196
  closeEnlargedQr: "Chiudi il codice QR ingrandito",
1196
1197
  irohPairingReady: "Il codice iroh {code} è attivo per cinque minuti. Scansionalo dalla schermata di abbinamento iroh; aprirne un altro sostituisce questo.",
1198
+ irohPairingCopyOnly: "Impossibile generare il codice QR. Copia il link di abbinamento e incollalo nella schermata di abbinamento iroh dell’app.",
1197
1199
  irohDescription: "Collegamento cifrato peer-to-peer con ripiego su relay. Nessun dominio, porta o account richiesto; richiede l’App Android completa.",
1198
1200
  prepareIroh: "Prepara iroh",
1199
1201
  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.",
@@ -1228,6 +1230,7 @@ window.__ModuleLoader__.load({
1228
1230
  enlargeQr: "放大二维码",
1229
1231
  closeEnlargedQr: "关闭放大的二维码",
1230
1232
  irohPairingReady: "iroh 配对码 {code} 已生效,有效期 5 分钟。请在 App 的 iroh 配对页扫码;再生成一个会替换当前这个。",
1233
+ irohPairingCopyOnly: "未能生成二维码。可复制配对链接,粘贴到 App 的 iroh 配对页。",
1231
1234
  irohDescription: "点对点加密直连,跨网自动经中继。无需域名、端口或账号;需完整版 App。",
1232
1235
  prepareIroh: "准备 iroh",
1233
1236
  irohInstallNote: "点击安装会下载固定的 sidecar 发行版(dsh-mobile-iroh 0.1.0-alpha.1,Windows x64 约 15 MB,Linux x64 约 24 MB)到插件私有目录并校验 SHA-256;已安装的配套插件如有校验通过的副本会直接复用,不再下载。",
@@ -2042,6 +2045,13 @@ window.__ModuleLoader__.load({
2042
2045
  frpVhostHttpPortHint: "The loopback plaintext vhost port of your own frps (upstream default 7080).",
2043
2046
  frpPublicPort: "Public entry port",
2044
2047
  frpPublicPortHint: "Raw TCP proxy port used as the public HTTPS entry (default 33080, never 3080/3443/3444).",
2048
+ frpUser: "frpc user (optional)",
2049
+ frpUserPlaceholder: "myfrpuser",
2050
+ funnelListenerReady: "Funnel is listening. Verify public access from a network outside this tailnet.",
2051
+ macosFolderHelpTitle: "Before leaving this Mac",
2052
+ macosFolderHelpNote: "Open the workspace folder you plan to use in DSH on this Mac and respond to any macOS prompt. In System Settings → Privacy & Security → Files and Folders, allow only the locations you need. If opening a folder remotely times out, check this Mac for a pending prompt before changing the pairing.",
2053
+ frpUserHint: "Optional user field of shared frps services: the proxy registers as user.proxyName. Leave blank to omit it.",
2054
+ frpUserInvalid: "The user must be 1–128 letters, digits, dots, underscores, or hyphens.",
2045
2055
  frpAttachPlan: "Copy attach runbook",
2046
2056
  frpAttachPlanCopied: "Attach runbook copied: local frpc.toml plus the VPS checklist.",
2047
2057
  frpAttachPlanFailed: "Could not build the attach runbook: {error}",
@@ -2095,6 +2105,13 @@ window.__ModuleLoader__.load({
2095
2105
  frpVhostHttpPortHint: "Porta vhost in chiaro su loopback del tuo frps (predefinita 7080).",
2096
2106
  frpPublicPort: "Porta pubblica di ingresso",
2097
2107
  frpPublicPortHint: "Porta del proxy TCP grezzo usata come ingresso HTTPS pubblico (predefinita 33080, mai 3080/3443/3444).",
2108
+ frpUser: "Utente frpc (facoltativo)",
2109
+ frpUserPlaceholder: "myfrpuser",
2110
+ funnelListenerReady: "Funnel è in ascolto. Verifica l’accesso pubblico da una rete esterna alla tailnet.",
2111
+ macosFolderHelpTitle: "Prima di lasciare il Mac",
2112
+ macosFolderHelpNote: "Apri in DSH su questo Mac la cartella di lavoro che userai e rispondi alle eventuali richieste di macOS. In Impostazioni di Sistema → Privacy e sicurezza → File e cartelle, autorizza solo le posizioni necessarie. Se una cartella non si apre da remoto per un timeout, controlla se sul Mac c’è una richiesta in attesa prima di modificare l’abbinamento.",
2113
+ frpUserHint: "Campo utente facoltativo dei servizi frps condivisi: il proxy si registra come user.nomeproxy. Lascia vuoto per ometterlo.",
2114
+ frpUserInvalid: "L’utente deve essere di 1–128 lettere, cifre, punti, trattini bassi o trattini.",
2098
2115
  frpAttachPlan: "Copia il runbook attach",
2099
2116
  frpAttachPlanCopied: "Runbook attach copiato: frpc.toml locale più la checklist VPS.",
2100
2117
  frpAttachPlanFailed: "Impossibile creare il runbook attach: {error}",
@@ -2148,6 +2165,13 @@ window.__ModuleLoader__.load({
2148
2165
  frpVhostHttpPortHint: "你自己 frps 的回环明文 vhost 端口(上游默认 7080)。",
2149
2166
  frpPublicPort: "公网入口端口",
2150
2167
  frpPublicPortHint: "作为公网 HTTPS 入口的 TCP 透传端口(默认 33080,不得为 3080/3443/3444)。",
2168
+ frpUser: "用户名(可选)",
2169
+ frpUserPlaceholder: "myfrpuser",
2170
+ funnelListenerReady: "Funnel 已启动监听。请在此 tailnet 之外的网络验证公网访问。",
2171
+ macosFolderHelpTitle: "离开这台 Mac 前",
2172
+ macosFolderHelpNote: "先在这台 Mac 的 DSH 中打开远程会用到的工作区目录,处理可能出现的系统提示。在「系统设置 → 隐私与安全性 → 文件与文件夹」只授权需要的位置。手机打开文件夹超时时,先检查 Mac 上是否有待确认提示,不要急着重新配对。",
2173
+ frpUserHint: "共享 frp 服务的可选 user 字段:代理会以「用户名.代理名」注册。留空则不发送。",
2174
+ frpUserInvalid: "用户名只能包含 1–128 个字母、数字、点、下划线或连字符。",
2151
2175
  frpAttachPlan: "复制接入清单",
2152
2176
  frpAttachPlanCopied: "接入清单已复制:本机 frpc.toml 与 VPS 侧待办。",
2153
2177
  frpAttachPlanFailed: "生成接入清单失败:{error}",
@@ -2466,7 +2490,8 @@ window.__ModuleLoader__.load({
2466
2490
  "mode",
2467
2491
  "entryTls",
2468
2492
  "vhostHttpPort",
2469
- "publicPort"
2493
+ "publicPort",
2494
+ "user"
2470
2495
  ]);
2471
2496
  function hostname(value) {
2472
2497
  if (value.length > 253 || !value.includes(".")) return false;
@@ -2489,6 +2514,11 @@ window.__ModuleLoader__.load({
2489
2514
  if (typeof value !== "string" || value.length < 16 || value.length > 512 || /[\s\u0000-\u001f\u007f]/u.test(value)) throw new Error("frp_token_invalid");
2490
2515
  return value;
2491
2516
  }
2517
+ /** Validate the optional frpc `user` field; it must stay one safe TOML token. */
2518
+ function validateFrpUser(value) {
2519
+ if (typeof value !== "string" || value.length < 1 || value.length > 128 || !/^[A-Za-z0-9._-]+$/u.test(value)) throw new Error("frp_user_invalid");
2520
+ return value;
2521
+ }
2492
2522
  /** Validate the public HTTPS origin used by Caddy and Android pairing. */
2493
2523
  function validateFrpPublicOrigin(value) {
2494
2524
  if (typeof value !== "string" || value.length > 512) throw new Error("frp_public_origin_invalid");
@@ -2566,6 +2596,7 @@ window.__ModuleLoader__.load({
2566
2596
  const entryTls = record.entryTls === void 0 ? void 0 : validateFrpEntryTls(record.entryTls);
2567
2597
  const vhostHttpPort = record.vhostHttpPort === void 0 ? void 0 : validateFrpVhostHttpPort(record.vhostHttpPort);
2568
2598
  const publicPort = record.publicPort === void 0 ? void 0 : validateFrpPublicPort(record.publicPort);
2599
+ const user = record.user === void 0 || record.user === "" ? void 0 : validateFrpUser(record.user);
2569
2600
  const resolvedMode = mode ?? "deploy";
2570
2601
  const resolvedEntryTls = entryTls ?? "public-ip-cert";
2571
2602
  if (resolvedEntryTls === "self-signed" && resolvedMode !== "attach") throw new Error("frp_entry_tls_invalid");
@@ -2581,7 +2612,8 @@ window.__ModuleLoader__.load({
2581
2612
  ...mode === void 0 ? {} : { mode },
2582
2613
  ...entryTls === void 0 ? {} : { entryTls },
2583
2614
  ...vhostHttpPort === void 0 ? {} : { vhostHttpPort },
2584
- ...publicPort === void 0 ? {} : { publicPort }
2615
+ ...publicPort === void 0 ? {} : { publicPort },
2616
+ ...user === void 0 ? {} : { user }
2585
2617
  });
2586
2618
  }
2587
2619
  function tomlString(value) {
@@ -2627,6 +2659,7 @@ window.__ModuleLoader__.load({
2627
2659
  `serverPort = ${String(settings.serverPort)}`,
2628
2660
  "auth.method = \"token\"",
2629
2661
  `auth.token = ${tomlString(settings.token)}`,
2662
+ ...settings.user === void 0 ? [] : [`user = ${tomlString(settings.user)}`],
2630
2663
  "transport.tls.enable = true",
2631
2664
  "",
2632
2665
  "[[proxies]]",
@@ -2874,6 +2907,11 @@ window.__ModuleLoader__.load({
2874
2907
  code: "frp_public_origin_invalid",
2875
2908
  messageKey: "frpPublicOriginInvalid"
2876
2909
  },
2910
+ frp_user_invalid: {
2911
+ field: "user",
2912
+ code: "frp_user_invalid",
2913
+ messageKey: "frpUserInvalid"
2914
+ },
2877
2915
  frp_attach_mode_requires_vhost_port: {
2878
2916
  field: "vhostHttpPort",
2879
2917
  code: "frp_attach_mode_requires_vhost_port",
@@ -2907,6 +2945,7 @@ window.__ModuleLoader__.load({
2907
2945
  check("serverAddress", "frpServerAddressInvalid", () => validateFrpServerAddress(form.serverAddress));
2908
2946
  check("serverPort", "frpServerPortInvalid", () => validateFrpServerPort(form.serverPort));
2909
2947
  if (form.token !== "" || !allowSavedToken) check("token", "frpTokenInvalid", () => validateFrpToken(form.token));
2948
+ if (form.user !== void 0 && form.user !== "") check("user", "frpUserInvalid", () => validateFrpUser(form.user));
2910
2949
  check("publicOrigin", "frpPublicOriginInvalid", () => validateFrpPublicOrigin(form.publicOrigin));
2911
2950
  check("mode", "frpModeInvalid", () => validateFrpMode(form.mode));
2912
2951
  check("entryTls", "frpEntryTlsInvalid", () => validateFrpEntryTls(form.entryTls));
@@ -2929,45 +2968,173 @@ window.__ModuleLoader__.load({
2929
2968
  }
2930
2969
  return issues;
2931
2970
  }
2932
- // node_modules/@noble/hashes/esm/utils.js
2933
- /*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */
2934
- /** Checks if something is Uint8Array. Be careful: nodejs Buffer will return true. */
2971
+ // node_modules/@noble/hashes/_u64.js
2972
+ const fromNumH = (n) => n / 2 ** 32 | 0;
2973
+ const fromNumL = (n) => n >>> 0;
2974
+ function setU64FromNum(view, byteOffset, n, isLE) {
2975
+ const h = fromNumH(n);
2976
+ const l = fromNumL(n);
2977
+ view.setUint32(byteOffset, isLE ? l : h, isLE);
2978
+ view.setUint32(byteOffset + 4, isLE ? h : l, isLE);
2979
+ }
2980
+ // node_modules/@noble/hashes/utils.js
2981
+ /**
2982
+ * Checks if something is Uint8Array. Be careful: nodejs Buffer will return true.
2983
+ * @param a - value to test
2984
+ * @returns `true` when the value is a Uint8Array-compatible view.
2985
+ * @example
2986
+ * Check whether a value is a Uint8Array-compatible view.
2987
+ * ```ts
2988
+ * isBytes(new Uint8Array([1, 2, 3]));
2989
+ * ```
2990
+ */
2935
2991
  function isBytes(a) {
2936
- return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
2992
+ return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in a && a.BYTES_PER_ELEMENT === 1;
2937
2993
  }
2938
- /** Asserts something is Uint8Array. */
2939
- function abytes(b, ...lengths) {
2940
- if (!isBytes(b)) throw new Error("Uint8Array expected");
2941
- if (lengths.length > 0 && !lengths.includes(b.length)) throw new Error("Uint8Array expected of length " + lengths + ", got length=" + b.length);
2994
+ const atitle = (title) => title ? `"${title}" ` : "";
2995
+ /**
2996
+ * Asserts something is a non-negative integer.
2997
+ * @param n - number to validate
2998
+ * @param title - label included in thrown errors
2999
+ * @returns The validated number.
3000
+ * @throws On wrong argument types. {@link TypeError}
3001
+ * @throws On wrong argument ranges or values. {@link RangeError}
3002
+ * @example
3003
+ * Validate a non-negative integer option.
3004
+ * ```ts
3005
+ * anumber(32, 'length');
3006
+ * ```
3007
+ */
3008
+ function anumber(n, title = "") {
3009
+ if (typeof n !== "number") throw new TypeError(atitle(title) + "expected number, got " + typeof n);
3010
+ if (!Number.isSafeInteger(n) || n < 0) throw new RangeError(atitle(title) + "expected integer >= 0, got " + n);
3011
+ return n;
2942
3012
  }
2943
- /** Asserts a hash instance has not been destroyed / finished */
3013
+ /**
3014
+ * Asserts something is Uint8Array.
3015
+ * @param value - value to validate
3016
+ * @param length - optional exact length constraint
3017
+ * @param title - label included in thrown errors
3018
+ * @returns The validated byte array.
3019
+ * @throws On wrong argument types. {@link TypeError}
3020
+ * @throws On wrong argument ranges or values. {@link RangeError}
3021
+ * @example
3022
+ * Validate that a value is a byte array.
3023
+ * ```ts
3024
+ * abytes(new Uint8Array([1, 2, 3]));
3025
+ * ```
3026
+ */
3027
+ function abytes(value, length, title = "") {
3028
+ if (isBytes(value) && (length === void 0 || value.length === length)) return value;
3029
+ if (length !== void 0) anumber(length, "length");
3030
+ const bytes = isBytes(value);
3031
+ const ofLen = length !== void 0 ? ` of length ${length}` : "";
3032
+ const got = bytes ? `length=${value.length}` : `type=${typeof value}`;
3033
+ const message = atitle(title) + "expected Uint8Array" + ofLen + ", got " + got;
3034
+ if (!bytes) throw new TypeError(message);
3035
+ throw new RangeError(message);
3036
+ }
3037
+ const aobject = (value, label) => {
3038
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new TypeError((label === "object" ? "" : `"${label}" `) + "expected object, got type=" + typeof value);
3039
+ };
3040
+ const aopts = (value, label) => {
3041
+ aobject(value, label);
3042
+ const proto = Object.getPrototypeOf(value);
3043
+ if (proto !== Object.prototype && proto !== null) throw new TypeError(`"${label}" expected plain object`);
3044
+ if (Object.hasOwn(value, "__proto__")) throw new TypeError(`"${label}.__proto__" is not allowed`);
3045
+ };
3046
+ /**
3047
+ * Asserts a hash instance has not been destroyed or finished.
3048
+ * @param instance - hash instance to validate
3049
+ * @param checkFinished - whether to reject finalized instances
3050
+ * @throws If the hash instance has already been destroyed or finalized. {@link Error}
3051
+ * @example
3052
+ * Validate that a hash instance is still usable.
3053
+ * ```ts
3054
+ * import { aexists } from '@noble/hashes/utils.js';
3055
+ * import { sha256 } from '@noble/hashes/sha2.js';
3056
+ * const hash = sha256.create();
3057
+ * aexists(hash);
3058
+ * ```
3059
+ */
2944
3060
  function aexists(instance, checkFinished = true) {
2945
- if (instance.destroyed) throw new Error("Hash instance has been destroyed");
2946
- if (checkFinished && instance.finished) throw new Error("Hash#digest() has already been called");
3061
+ if (instance.destroyed) throw new Error("hash was destroyed");
3062
+ if (checkFinished && instance.finished) throw new Error("digest() was already called");
2947
3063
  }
2948
- /** Asserts output is properly-sized byte array */
3064
+ /**
3065
+ * Asserts output is a sufficiently-sized byte array.
3066
+ * @param out - destination buffer
3067
+ * @param instance - hash instance providing output length
3068
+ * Oversized buffers are allowed; downstream code only promises to fill the first `outputLen` bytes.
3069
+ * @throws On wrong argument types. {@link TypeError}
3070
+ * @throws On wrong argument ranges or values. {@link RangeError}
3071
+ * @example
3072
+ * Validate a caller-provided digest buffer.
3073
+ * ```ts
3074
+ * import { aoutput } from '@noble/hashes/utils.js';
3075
+ * import { sha256 } from '@noble/hashes/sha2.js';
3076
+ * const hash = sha256.create();
3077
+ * aoutput(new Uint8Array(hash.outputLen), hash);
3078
+ * ```
3079
+ */
2949
3080
  function aoutput(out, instance) {
2950
- abytes(out);
3081
+ abytes(out, void 0, "output");
2951
3082
  const min = instance.outputLen;
2952
- if (out.length < min) throw new Error("digestInto() expects output buffer of length at least " + min);
3083
+ if (!(out.length >= min)) throw new RangeError("\"output\" expected length >= " + min);
2953
3084
  }
2954
- /** Zeroize a byte array. Warning: JS provides no guarantees. */
3085
+ /**
3086
+ * Zeroizes typed arrays in place. Warning: JS provides no guarantees.
3087
+ * @param arrays - arrays to overwrite with zeros
3088
+ * @example
3089
+ * Zeroize sensitive buffers in place.
3090
+ * ```ts
3091
+ * clean(new Uint8Array([1, 2, 3]));
3092
+ * ```
3093
+ */
2955
3094
  function clean(...arrays) {
2956
3095
  for (let i = 0; i < arrays.length; i++) arrays[i].fill(0);
2957
3096
  }
2958
- /** Create DataView of an array for easy byte-level manipulation. */
3097
+ /**
3098
+ * Creates a DataView for byte-level manipulation.
3099
+ * @param arr - source typed array
3100
+ * @returns DataView over the same buffer region.
3101
+ * @example
3102
+ * Create a DataView over an existing buffer.
3103
+ * ```ts
3104
+ * createView(new Uint8Array(4));
3105
+ * ```
3106
+ */
2959
3107
  function createView(arr) {
2960
3108
  return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
2961
3109
  }
2962
- /** The rotate right (circular right shift) operation for uint32 */
3110
+ /**
3111
+ * Rotate-right operation for uint32 values.
3112
+ * @param word - source word
3113
+ * @param shift - shift amount in bits
3114
+ * @returns Rotated word.
3115
+ * @example
3116
+ * Rotate a 32-bit word to the right.
3117
+ * ```ts
3118
+ * rotr(0x12345678, 8);
3119
+ * ```
3120
+ */
2963
3121
  function rotr(word, shift) {
2964
3122
  return word << 32 - shift | word >>> shift;
2965
3123
  }
2966
3124
  const hasHexBuiltin = /* @__PURE__ */ (() => typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function")();
2967
3125
  const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
2968
3126
  /**
2969
- * Convert byte array to hex string. Uses built-in function, when available.
2970
- * @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
3127
+ * Convert byte array to hex string.
3128
+ * Uses the built-in function when available and assumes it matches the tested
3129
+ * fallback semantics.
3130
+ * @param bytes - bytes to encode
3131
+ * @returns Lowercase hexadecimal string.
3132
+ * @throws On wrong argument types. {@link TypeError}
3133
+ * @example
3134
+ * Convert bytes to lowercase hexadecimal.
3135
+ * ```ts
3136
+ * bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])); // 'cafe0123'
3137
+ * ```
2971
3138
  */
2972
3139
  function bytesToHex(bytes) {
2973
3140
  abytes(bytes);
@@ -2978,69 +3145,169 @@ window.__ModuleLoader__.load({
2978
3145
  }
2979
3146
  /**
2980
3147
  * Converts string to bytes using UTF8 encoding.
2981
- * @example utf8ToBytes('abc') // Uint8Array.from([97, 98, 99])
3148
+ * Built-in doesn't validate input to be string: we do the check.
3149
+ * Non-ASCII details are delegated to the platform `TextEncoder`.
3150
+ * @param str - string to encode
3151
+ * @returns UTF-8 encoded bytes.
3152
+ * @throws On wrong argument types. {@link TypeError}
3153
+ * @example
3154
+ * Encode a string as UTF-8 bytes.
3155
+ * ```ts
3156
+ * utf8ToBytes('abc'); // Uint8Array.from([97, 98, 99])
3157
+ * ```
2982
3158
  */
2983
3159
  function utf8ToBytes(str) {
2984
- if (typeof str !== "string") throw new Error("string expected");
2985
- return new Uint8Array(new TextEncoder().encode(str));
3160
+ if (typeof str !== "string") throw new TypeError("string expected");
3161
+ const encoded = new TextEncoder().encode(str);
3162
+ try {
3163
+ return new Uint8Array(encoded);
3164
+ } finally {
3165
+ clean(encoded);
3166
+ }
2986
3167
  }
2987
3168
  /**
2988
- * Normalizes (non-hex) string or Uint8Array to Uint8Array.
2989
- * Warning: when Uint8Array is passed, it would NOT get copied.
2990
- * Keep in mind for future mutable operations.
3169
+ * Merges default options and passed options.
3170
+ * @param defaults - base option object
3171
+ * @param opts - user overrides
3172
+ * @param title - label included in thrown override errors
3173
+ * @returns Fresh merged option object with a null prototype.
3174
+ * @throws On wrong argument types. {@link TypeError}
3175
+ * @example
3176
+ * Merge user overrides onto default options.
3177
+ * ```ts
3178
+ * checkOpts({ dkLen: 32 }, { asyncTick: 10 });
3179
+ * ```
2991
3180
  */
2992
- function toBytes(data) {
2993
- if (typeof data === "string") data = utf8ToBytes(data);
2994
- abytes(data);
2995
- return data;
2996
- }
2997
- /** For runtime check if class implements interface */
2998
- var Hash = class {};
2999
- /** Wraps hash function, creating an interface on top of it */
3000
- function createHasher(hashCons) {
3001
- const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
3002
- const tmp = hashCons();
3181
+ function checkOpts(defaults, opts, title = "opts") {
3182
+ aopts(defaults, "defaults");
3183
+ if (opts !== void 0) aopts(opts, title);
3184
+ return Object.assign(Object.create(null), defaults, opts);
3185
+ }
3186
+ /**
3187
+ * Creates a callable hash function from a stateful class constructor.
3188
+ * @param hashCons - hash constructor or factory
3189
+ * @param info - optional metadata such as DER OID
3190
+ * @returns Frozen callable hash wrapper with `.create()`.
3191
+ * Wrapper construction eagerly calls `hashCons(undefined)` once to read
3192
+ * `outputLen` / `blockLen`, so constructor side effects happen at module
3193
+ * init time.
3194
+ * @throws On wrong argument types. {@link TypeError}
3195
+ * @example
3196
+ * Wrap a stateful hash constructor into a callable helper.
3197
+ * ```ts
3198
+ * import { createHasher } from '@noble/hashes/utils.js';
3199
+ * import { sha256 } from '@noble/hashes/sha2.js';
3200
+ * const wrapped = createHasher(sha256.create, { oid: sha256.oid });
3201
+ * wrapped(new Uint8Array([1]));
3202
+ * ```
3203
+ */
3204
+ function createHasher(hashCons, info = {}) {
3205
+ if (typeof hashCons !== "function") throw new TypeError("\"hashCons\" expected function, got type=" + typeof hashCons);
3206
+ info = checkOpts({}, info, "info");
3207
+ const hashC = (msg, opts) => hashCons(opts).update(msg).digest();
3208
+ const tmp = hashCons(void 0);
3003
3209
  hashC.outputLen = tmp.outputLen;
3004
3210
  hashC.blockLen = tmp.blockLen;
3005
- hashC.create = () => hashCons();
3006
- return hashC;
3211
+ hashC.canXOF = tmp.canXOF;
3212
+ hashC.create = (opts) => hashCons(opts);
3213
+ Object.assign(hashC, info);
3214
+ return Object.freeze(hashC);
3007
3215
  }
3008
- // node_modules/@noble/hashes/esm/_md.js
3216
+ /**
3217
+ * Creates OID metadata for NIST hashes with prefix `06 09 60 86 48 01 65 03 04 02`.
3218
+ * @param suffix - final OID byte for the selected hash.
3219
+ * The helper accepts any byte even though only the documented NIST hash
3220
+ * suffixes are meaningful downstream.
3221
+ * @returns Object containing the DER-encoded OID.
3222
+ * @example
3223
+ * Build OID metadata for a NIST hash.
3224
+ * ```ts
3225
+ * oidNist(0x01);
3226
+ * ```
3227
+ */
3228
+ const oidNist = (suffix) => ({ oid: Uint8Array.from([
3229
+ 6,
3230
+ 9,
3231
+ 96,
3232
+ 134,
3233
+ 72,
3234
+ 1,
3235
+ 101,
3236
+ 3,
3237
+ 4,
3238
+ 2,
3239
+ suffix
3240
+ ]) });
3241
+ // node_modules/@noble/hashes/_md.js
3009
3242
  /**
3010
3243
  * Internal Merkle-Damgard hash utils.
3011
3244
  * @module
3012
3245
  */
3013
- /** Polyfill for Safari 14. https://caniuse.com/mdn-javascript_builtins_dataview_setbiguint64 */
3014
- function setBigUint64(view, byteOffset, value, isLE) {
3015
- if (typeof view.setBigUint64 === "function") return view.setBigUint64(byteOffset, value, isLE);
3016
- const _32n = BigInt(32);
3017
- const _u32_max = BigInt(4294967295);
3018
- const wh = Number(value >> _32n & _u32_max);
3019
- const wl = Number(value & _u32_max);
3020
- const h = isLE ? 4 : 0;
3021
- const l = isLE ? 0 : 4;
3022
- view.setUint32(byteOffset + h, wh, isLE);
3023
- view.setUint32(byteOffset + l, wl, isLE);
3024
- }
3025
- /** Choice: a ? b : c */
3246
+ /**
3247
+ * Shared 32-bit conditional boolean primitive reused by SHA-256, SHA-1, and MD5 `F`.
3248
+ * Returns bits from `b` when `a` is set, otherwise from `c`.
3249
+ * The XOR form is equivalent to MD5's `F(X,Y,Z) = XY v not(X)Z` because the masked terms never
3250
+ * set the same bit.
3251
+ * @param a - selector word
3252
+ * @param b - word chosen when selector bit is set
3253
+ * @param c - word chosen when selector bit is clear
3254
+ * @returns Mixed 32-bit word.
3255
+ * @example
3256
+ * Combine three words with the shared 32-bit choice primitive.
3257
+ * ```ts
3258
+ * Chi(0xffffffff, 0x12345678, 0x87654321);
3259
+ * ```
3260
+ */
3026
3261
  function Chi(a, b, c) {
3027
3262
  return a & b ^ ~a & c;
3028
3263
  }
3029
- /** Majority function, true if any two inputs is true. */
3264
+ /**
3265
+ * Shared 32-bit majority primitive reused by SHA-256 and SHA-1.
3266
+ * Returns bits shared by at least two inputs.
3267
+ * @param a - first input word
3268
+ * @param b - second input word
3269
+ * @param c - third input word
3270
+ * @returns Mixed 32-bit word.
3271
+ * @example
3272
+ * Combine three words with the shared 32-bit majority primitive.
3273
+ * ```ts
3274
+ * Maj(0xffffffff, 0x12345678, 0x87654321);
3275
+ * ```
3276
+ */
3030
3277
  function Maj(a, b, c) {
3031
3278
  return a & b ^ a & c ^ b & c;
3032
3279
  }
3033
3280
  /**
3034
3281
  * Merkle-Damgard hash construction base class.
3035
3282
  * Could be used to create MD5, RIPEMD, SHA1, SHA2.
3283
+ * Accepts only byte-aligned `Uint8Array` input, even when the underlying spec describes bit
3284
+ * strings with partial-byte tails.
3285
+ * @param blockLen - internal block size in bytes
3286
+ * @param outputLen - digest size in bytes
3287
+ * @param padOffset - trailing length field size in bytes
3288
+ * @param isLE - whether length and state words are encoded in little-endian
3289
+ * @example
3290
+ * Use a concrete subclass to get the shared Merkle-Damgard update/digest flow.
3291
+ * ```ts
3292
+ * import { _SHA1 } from '@noble/hashes/legacy.js';
3293
+ * const hash = new _SHA1();
3294
+ * hash.update(new Uint8Array([97, 98, 99]));
3295
+ * hash.digest();
3296
+ * ```
3036
3297
  */
3037
- var HashMD = class extends Hash {
3298
+ var HashMD = class {
3299
+ blockLen;
3300
+ outputLen;
3301
+ canXOF = false;
3302
+ padOffset;
3303
+ isLE;
3304
+ buffer;
3305
+ view;
3306
+ finished = false;
3307
+ length = 0;
3308
+ pos = 0;
3309
+ destroyed = false;
3038
3310
  constructor(blockLen, outputLen, padOffset, isLE) {
3039
- super();
3040
- this.finished = false;
3041
- this.length = 0;
3042
- this.pos = 0;
3043
- this.destroyed = false;
3044
3311
  this.blockLen = blockLen;
3045
3312
  this.outputLen = outputLen;
3046
3313
  this.padOffset = padOffset;
@@ -3050,27 +3317,29 @@ window.__ModuleLoader__.load({
3050
3317
  }
3051
3318
  update(data) {
3052
3319
  aexists(this);
3053
- data = toBytes(data);
3054
3320
  abytes(data);
3055
3321
  const { view, buffer, blockLen } = this;
3056
3322
  const len = data.length;
3323
+ let processed = false;
3057
3324
  for (let pos = 0; pos < len;) {
3058
3325
  const take = Math.min(blockLen - this.pos, len - pos);
3059
3326
  if (take === blockLen) {
3060
3327
  const dataView = createView(data);
3061
3328
  for (; blockLen <= len - pos; pos += blockLen) this.process(dataView, pos);
3329
+ processed = true;
3062
3330
  continue;
3063
3331
  }
3064
- buffer.set(data.subarray(pos, pos + take), this.pos);
3332
+ buffer.set(pos === 0 && take === len ? data : data.subarray(pos, pos + take), this.pos);
3065
3333
  this.pos += take;
3066
3334
  pos += take;
3067
3335
  if (this.pos === blockLen) {
3068
3336
  this.process(view, 0);
3069
3337
  this.pos = 0;
3338
+ processed = true;
3070
3339
  }
3071
3340
  }
3072
3341
  this.length += data.length;
3073
- this.roundClean();
3342
+ if (processed) this.roundClean();
3074
3343
  return this;
3075
3344
  }
3076
3345
  digestInto(out) {
@@ -3080,20 +3349,19 @@ window.__ModuleLoader__.load({
3080
3349
  const { buffer, view, blockLen, isLE } = this;
3081
3350
  let { pos } = this;
3082
3351
  buffer[pos++] = 128;
3083
- clean(this.buffer.subarray(pos));
3352
+ buffer.fill(0, pos);
3084
3353
  if (this.padOffset > blockLen - pos) {
3085
3354
  this.process(view, 0);
3086
- pos = 0;
3355
+ buffer.fill(0);
3087
3356
  }
3088
- for (let i = pos; i < blockLen; i++) buffer[i] = 0;
3089
- setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE);
3357
+ setU64FromNum(view, blockLen - 8, this.length * 8, isLE);
3090
3358
  this.process(view, 0);
3091
- const oview = createView(out);
3359
+ this.roundClean();
3360
+ const oview = out === buffer ? view : createView(out);
3092
3361
  const len = this.outputLen;
3093
- if (len % 4) throw new Error("_sha2: outputLen should be aligned to 32bit");
3094
3362
  const outLen = len / 4;
3095
3363
  const state = this.get();
3096
- if (outLen > state.length) throw new Error("_sha2: outputLen bigger than state");
3364
+ if (len % 4 || outLen > state.length) throw new Error("invalid outputLen");
3097
3365
  for (let i = 0; i < outLen; i++) oview.setUint32(4 * i, state[i], isLE);
3098
3366
  }
3099
3367
  digest() {
@@ -3103,15 +3371,13 @@ window.__ModuleLoader__.load({
3103
3371
  this.destroy();
3104
3372
  return res;
3105
3373
  }
3106
- _cloneInto(to) {
3107
- to || (to = new this.constructor());
3108
- to.set(...this.get());
3109
- const { blockLen, buffer, length, finished, destroyed, pos } = this;
3374
+ _cloneIntoMeta(to) {
3375
+ const { buffer, length, finished, destroyed, pos } = this;
3110
3376
  to.destroyed = destroyed;
3111
3377
  to.finished = finished;
3112
3378
  to.length = length;
3113
3379
  to.pos = pos;
3114
- if (length % blockLen) to.buffer.set(buffer);
3380
+ if (pos) to.buffer.set(buffer);
3115
3381
  return to;
3116
3382
  }
3117
3383
  clone() {
@@ -3122,7 +3388,9 @@ window.__ModuleLoader__.load({
3122
3388
  * Initial SHA-2 state: fractional parts of square roots of first 16 primes 2..53.
3123
3389
  * Check out `test/misc/sha2-gen-iv.js` for recomputation guide.
3124
3390
  */
3125
- /** Initial SHA256 state. Bits 0..32 of frac part of sqrt of primes 2..19 */
3391
+ /** Initial SHA256 state from RFC 6234 §6.1: the first 32 bits of the fractional parts of the
3392
+ * square roots of the first eight prime numbers. Exported as a shared table; callers must treat
3393
+ * it as read-only because constructors copy words from it by index. */
3126
3394
  const SHA256_IV = /* @__PURE__ */ Uint32Array.from([
3127
3395
  1779033703,
3128
3396
  3144134277,
@@ -3133,17 +3401,17 @@ window.__ModuleLoader__.load({
3133
3401
  528734635,
3134
3402
  1541459225
3135
3403
  ]);
3136
- // node_modules/@noble/hashes/esm/sha2.js
3404
+ // node_modules/@noble/hashes/sha2.js
3137
3405
  /**
3138
3406
  * SHA2 hash function. A.k.a. sha256, sha384, sha512, sha512_224, sha512_256.
3139
3407
  * SHA256 is the fastest hash implementable in JS, even faster than Blake3.
3140
- * Check out [RFC 4634](https://datatracker.ietf.org/doc/html/rfc4634) and
3141
- * [FIPS 180-4](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf).
3408
+ * Check out {@link https://www.rfc-editor.org/rfc/rfc4634 | RFC 4634} and
3409
+ * {@link https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf | FIPS 180-4}.
3142
3410
  * @module
3143
3411
  */
3144
3412
  /**
3145
- * Round constants:
3146
- * First 32 bits of fractional parts of the cube roots of the first 64 primes 2..311)
3413
+ * SHA-224 / SHA-256 round constants from RFC 6234 §5.1: the first 32 bits
3414
+ * of the cube roots of the first 64 primes (2..311).
3147
3415
  */
3148
3416
  const SHA256_K = /* @__PURE__ */ Uint32Array.from([
3149
3417
  1116352408,
@@ -3211,19 +3479,28 @@ window.__ModuleLoader__.load({
3211
3479
  3204031479,
3212
3480
  3329325298
3213
3481
  ]);
3214
- /** Reusable temporary buffer. "W" comes straight from spec. */
3482
+ /** Reusable SHA-224 / SHA-256 message schedule buffer `W_t` from RFC 6234 §6.2 step 1. */
3215
3483
  const SHA256_W = /* @__PURE__ */ new Uint32Array(64);
3216
- var SHA256 = class extends HashMD {
3217
- constructor(outputLen = 32) {
3484
+ /** Internal SHA-224 / SHA-256 compression engine from RFC 6234 §6.2. */
3485
+ var SHA2_32B = class extends HashMD {
3486
+ A = 0;
3487
+ B = 0;
3488
+ C = 0;
3489
+ D = 0;
3490
+ E = 0;
3491
+ F = 0;
3492
+ G = 0;
3493
+ H = 0;
3494
+ constructor(outputLen, IV) {
3218
3495
  super(64, outputLen, 8, false);
3219
- this.A = SHA256_IV[0] | 0;
3220
- this.B = SHA256_IV[1] | 0;
3221
- this.C = SHA256_IV[2] | 0;
3222
- this.D = SHA256_IV[3] | 0;
3223
- this.E = SHA256_IV[4] | 0;
3224
- this.F = SHA256_IV[5] | 0;
3225
- this.G = SHA256_IV[6] | 0;
3226
- this.H = SHA256_IV[7] | 0;
3496
+ this.A = IV[0] | 0;
3497
+ this.B = IV[1] | 0;
3498
+ this.C = IV[2] | 0;
3499
+ this.D = IV[3] | 0;
3500
+ this.E = IV[4] | 0;
3501
+ this.F = IV[5] | 0;
3502
+ this.G = IV[6] | 0;
3503
+ this.H = IV[7] | 0;
3227
3504
  }
3228
3505
  get() {
3229
3506
  const { A, B, C, D, E, F, G, H } = this;
@@ -3248,6 +3525,10 @@ window.__ModuleLoader__.load({
3248
3525
  this.G = G | 0;
3249
3526
  this.H = H | 0;
3250
3527
  }
3528
+ _cloneInto(to) {
3529
+ (to ||= new this.constructor()).set(...this.get());
3530
+ return this._cloneIntoMeta(to);
3531
+ }
3251
3532
  process(view, offset) {
3252
3533
  for (let i = 0; i < 16; i++, offset += 4) SHA256_W[i] = view.getUint32(offset, false);
3253
3534
  for (let i = 16; i < 64; i++) {
@@ -3285,23 +3566,34 @@ window.__ModuleLoader__.load({
3285
3566
  clean(SHA256_W);
3286
3567
  }
3287
3568
  destroy() {
3569
+ this.destroyed = true;
3288
3570
  this.set(0, 0, 0, 0, 0, 0, 0, 0);
3289
3571
  clean(this.buffer);
3290
3572
  }
3291
3573
  };
3292
- // node_modules/@noble/hashes/esm/sha256.js
3574
+ /** Internal SHA-256 hash class grounded in RFC 6234 §6.2. */
3575
+ var _SHA256 = class extends SHA2_32B {
3576
+ constructor() {
3577
+ super(32, SHA256_IV);
3578
+ }
3579
+ };
3293
3580
  /**
3294
- * SHA2-256 a.k.a. sha256. In JS, it is the fastest hash, even faster than Blake3.
3581
+ * SHA2-256 hash function from RFC 4634. In JS it's the fastest: even faster than Blake3. Some info:
3295
3582
  *
3296
- * To break sha256 using birthday attack, attackers need to try 2^128 hashes.
3297
- * BTC network is doing 2^70 hashes/sec (2^95 hashes/year) as per 2025.
3298
- *
3299
- * Check out [FIPS 180-4](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf).
3300
- * @module
3301
- * @deprecated
3583
+ * - Trying 2^128 hashes would get 50% chance of collision, using birthday attack.
3584
+ * - BTC network is doing 2^70 hashes/sec (2^95 hashes/year) as per 2025.
3585
+ * - Each sha256 hash is executing 2^18 bit operations.
3586
+ * - Good 2024 ASICs can do 200Th/sec with 3500 watts of power, corresponding to 2^36 hashes/joule.
3587
+ * @param msg - message bytes to hash
3588
+ * @param opts - Reserved hash options.
3589
+ * @returns Digest bytes.
3590
+ * @example
3591
+ * Hash a message with SHA2-256.
3592
+ * ```ts
3593
+ * sha256(new Uint8Array([97, 98, 99]));
3594
+ * ```
3302
3595
  */
3303
- /** @deprecated Use import from `noble/hashes/sha2` module */
3304
- const sha256 = /* @__PURE__ */ createHasher(() => new SHA256());
3596
+ const sha256 = /* @__PURE__ */ createHasher(() => new _SHA256(), /* @__PURE__ */ oidNist(1));
3305
3597
  Object.freeze({
3306
3598
  title: 80,
3307
3599
  body: 200,
@@ -3310,7 +3602,7 @@ window.__ModuleLoader__.load({
3310
3602
  const MAX_SEEN_QUESTION_KEYS = 20;
3311
3603
  /** A bounded native identifier derived from the complete, case-sensitive wire ID. */
3312
3604
  function notificationIdentity(value) {
3313
- return bytesToHex(sha256(JSON.stringify(value)).subarray(0, 16));
3605
+ return bytesToHex(sha256(utf8ToBytes(JSON.stringify(value))).subarray(0, 16));
3314
3606
  }
3315
3607
  /** Stable per-turn tag so concurrent sessions keep separate notifications. */
3316
3608
  function taskCompletionTag(sessionId, turn) {
@@ -3755,6 +4047,20 @@ window.__ModuleLoader__.load({
3755
4047
  }
3756
4048
  });
3757
4049
  }
4050
+ // src/mobile-layout-attributes.ts
4051
+ /**
4052
+ * Root marker the dedicated layout publishes for its wide/narrow band.
4053
+ *
4054
+ * The band depends on the device screen size, which a media query cannot read,
4055
+ * so the layout decides it in JS and writes this attribute on `.dshm-shell`:
4056
+ * `"1"` docks the sidebar, anything else — `"0"`, or no marker — is the overlay
4057
+ * drawer. The stylesheet and the sidebar scroll adapter both follow the marker,
4058
+ * so the band has exactly one owner.
4059
+ *
4060
+ * This module stays free of imports: the dedicated layout bundle and the native
4061
+ * client bundle both use it, and neither should pull the other in.
4062
+ */
4063
+ const WIDE_LAYOUT_ATTRIBUTE = "data-dshm-wide";
3758
4064
  // src/mobile-sidebar-scroll.ts
3759
4065
  function sidebarRestoreTop(snapshot, anchorTop) {
3760
4066
  return Math.max(0, anchorTop === void 0 || snapshot.offset === void 0 ? snapshot.top : anchorTop - snapshot.offset);
@@ -3785,7 +4091,16 @@ window.__ModuleLoader__.load({
3785
4091
  function installMobileSidebarScrollMemory() {
3786
4092
  const memories = /* @__PURE__ */ new Map();
3787
4093
  const stockViewport = window.matchMedia("(max-width:720px)");
3788
- const dedicatedViewport = window.matchMedia("(max-width:899px)");
4094
+ /**
4095
+ * Whether the dedicated layout currently renders the sidebar as an overlay
4096
+ * drawer. That is the layout's own decision on the shell — a landscape phone
4097
+ * is an overlay there although its viewport is wide — so a width query would
4098
+ * disable this memory exactly where the drawer is hardest to reach.
4099
+ */
4100
+ const drawerBand = () => {
4101
+ const shell = document.querySelector(".dshm-shell");
4102
+ return shell === null || shell.getAttribute("data-dshm-wide") !== "1";
4103
+ };
3789
4104
  let root;
3790
4105
  let open = false;
3791
4106
  let ports = /* @__PURE__ */ new Map();
@@ -3836,7 +4151,7 @@ window.__ModuleLoader__.load({
3836
4151
  const sync = () => {
3837
4152
  if (disposed) return;
3838
4153
  const next = document.querySelector("[data-dsh-mobile-sidebar-root]") ?? void 0;
3839
- const mobile = next?.closest("[data-pane=\"sidebar\"]") != null ? dedicatedViewport.matches : stockViewport.matches;
4154
+ const mobile = next?.closest("[data-pane=\"sidebar\"]") != null ? drawerBand() : stockViewport.matches;
3840
4155
  if (next !== root) {
3841
4156
  cancel();
3842
4157
  ports.clear();
@@ -3881,7 +4196,6 @@ window.__ModuleLoader__.load({
3881
4196
  remember();
3882
4197
  };
3883
4198
  stockViewport.addEventListener("change", sync);
3884
- dedicatedViewport.addEventListener("change", sync);
3885
4199
  document.addEventListener("scroll", onScroll, true);
3886
4200
  for (const type of [
3887
4201
  "pointerdown",
@@ -3896,7 +4210,6 @@ window.__ModuleLoader__.load({
3896
4210
  disposed = true;
3897
4211
  cancel();
3898
4212
  stockViewport.removeEventListener("change", sync);
3899
- dedicatedViewport.removeEventListener("change", sync);
3900
4213
  document.removeEventListener("scroll", onScroll, true);
3901
4214
  for (const type of [
3902
4215
  "pointerdown",
@@ -5696,7 +6009,8 @@ window.__ModuleLoader__.load({
5696
6009
  "readonly",
5697
6010
  "aria-busy",
5698
6011
  "aria-selected",
5699
- "data-dsh-mobile-session"
6012
+ "data-dsh-mobile-session",
6013
+ WIDE_LAYOUT_ATTRIBUTE
5700
6014
  ]
5701
6015
  });
5702
6016
  overlayQuery.addEventListener("change", schedule);
@@ -9516,11 +9830,15 @@ window.__ModuleLoader__.load({
9516
9830
  };
9517
9831
  return (0, react.createElement)("div", {
9518
9832
  className: "dsh-mobile-settings_row",
9519
- style: { flexWrap: "wrap" },
9833
+ style: {
9834
+ flexDirection: "column",
9835
+ alignItems: "stretch",
9836
+ gap: 12
9837
+ },
9520
9838
  lang: locale === "zh" ? "zh-CN" : locale
9521
9839
  }, (0, react.createElement)("div", {
9522
9840
  className: "dsh-mobile-settings_rowText",
9523
- style: { paddingRight: 8 }
9841
+ style: { paddingRight: 0 }
9524
9842
  }, (0, react.createElement)("div", { className: "dsh-mobile-settings_title" }, copy.title), (0, react.createElement)("div", {
9525
9843
  className: "dsh-mobile-settings_desc",
9526
9844
  "aria-live": "polite"
@@ -10789,6 +11107,23 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
10789
11107
  if (!escape.defaultPrevented) blocked();
10790
11108
  return;
10791
11109
  }
11110
+ if (top.getAttribute("data-shortcut-modal") === "sidebar" && top.closest("[data-sidebar-drawer]") !== null) {
11111
+ if (layers.some((layer) => layer !== top && !top.contains(layer))) {
11112
+ blocked();
11113
+ return;
11114
+ }
11115
+ const focused = page.activeElement;
11116
+ const target = focused !== null && top.contains(focused) ? focused : top;
11117
+ const escape = new KeyboardEvent("keydown", {
11118
+ key: "Escape",
11119
+ code: "Escape",
11120
+ bubbles: true,
11121
+ cancelable: true
11122
+ });
11123
+ target.dispatchEvent(escape);
11124
+ if (!escape.defaultPrevented) blocked();
11125
+ return;
11126
+ }
10792
11127
  if (top.getAttribute("role") === "dialog") {
10793
11128
  const wrapper = top.parentElement;
10794
11129
  const mask = wrapper?.firstElementChild;
@@ -11147,7 +11482,8 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
11147
11482
  mode: "attach",
11148
11483
  entryTls: form.entryTls,
11149
11484
  ...form.entryTls === "self-signed" || form.vhostHttpPort === void 0 ? {} : { vhostHttpPort: form.vhostHttpPort },
11150
- ...form.publicPort === void 0 ? {} : { publicPort: form.publicPort }
11485
+ ...form.publicPort === void 0 ? {} : { publicPort: form.publicPort },
11486
+ ...form.user === void 0 || form.user === "" ? {} : { user: form.user }
11151
11487
  });
11152
11488
  }
11153
11489
  /**
@@ -12071,6 +12407,16 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
12071
12407
  frpToken.autocomplete = "off";
12072
12408
  frpToken.spellcheck = false;
12073
12409
  frpToken.placeholder = t("frpTokenPlaceholder");
12410
+ const frpUserLabel = element("label", "dsh-mobile-control__field");
12411
+ frpUserLabel.textContent = t("frpUser");
12412
+ const frpUser = element("input");
12413
+ frpUser.type = "text";
12414
+ frpUser.autocomplete = "off";
12415
+ frpUser.spellcheck = false;
12416
+ frpUser.placeholder = t("frpUserPlaceholder");
12417
+ const frpUserHint = element("p", "dsh-mobile-control__frp-hint");
12418
+ frpUserHint.textContent = t("frpUserHint");
12419
+ frpUserLabel.append(frpUser, frpUserHint);
12074
12420
  const frpOriginLabel = element("label", "dsh-mobile-control__field");
12075
12421
  frpOriginLabel.textContent = t("frpPublicOrigin");
12076
12422
  const frpOrigin = element("input");
@@ -12133,6 +12479,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
12133
12479
  serverAddress: frpServer,
12134
12480
  serverPort: frpPort,
12135
12481
  token: frpToken,
12482
+ user: frpUser,
12136
12483
  publicOrigin: frpOrigin,
12137
12484
  mode: frpMode,
12138
12485
  entryTls: frpEntryTls,
@@ -12143,6 +12490,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
12143
12490
  serverAddress: frpServerLabel,
12144
12491
  serverPort: frpPortLabel,
12145
12492
  token: frpTokenLabel,
12493
+ user: frpUserLabel,
12146
12494
  publicOrigin: frpOriginLabel,
12147
12495
  mode: frpModeLabel,
12148
12496
  entryTls: frpEntryTlsLabel,
@@ -12153,6 +12501,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
12153
12501
  serverAddress: "frpServerAddress",
12154
12502
  serverPort: "frpServerPort",
12155
12503
  token: "frpToken",
12504
+ user: "frpUser",
12156
12505
  publicOrigin: "frpPublicOrigin",
12157
12506
  mode: "frpMode",
12158
12507
  entryTls: "frpEntryTls",
@@ -12175,7 +12524,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
12175
12524
  frpOriginHint.textContent = t("frpPublicOriginHint");
12176
12525
  frpOriginLabel.append(frpOriginHint);
12177
12526
  frpOrigin.setAttribute("aria-describedby", `${frpOriginHint.id} ${frpOrigin.getAttribute("aria-describedby") ?? ""}`);
12178
- frpFields.append(frpServerLabel, frpPortLabel, frpTokenLabel, frpOriginLabel, frpModeLabel, frpEntryTlsLabel, frpVhostLabel, frpPublicPortLabel);
12527
+ frpFields.append(frpServerLabel, frpPortLabel, frpTokenLabel, frpUserLabel, frpOriginLabel, frpModeLabel, frpEntryTlsLabel, frpVhostLabel, frpPublicPortLabel);
12179
12528
  frpStep1.append(frpStep1Title, frpStep1Text, frpFields);
12180
12529
  const frpStep2 = element("section", "dsh-mobile-control__frp-step");
12181
12530
  const frpStep2Title = element("strong");
@@ -12535,6 +12884,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
12535
12884
  const irohQr = element("div", "dsh-mobile-control__qr is-expandable");
12536
12885
  irohQr.hidden = true;
12537
12886
  const irohPairStatus = element("p", "dsh-mobile-control__component-status");
12887
+ irohPairStatus.setAttribute("aria-live", "polite");
12538
12888
  const irohCopyDescriptor = element("button", "dsh-mobile-control__secondary");
12539
12889
  irohCopyDescriptor.type = "button";
12540
12890
  irohCopyDescriptor.textContent = t("copyPairLink");
@@ -12864,6 +13214,16 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
12864
13214
  disclosure.hidden = true;
12865
13215
  setupDetailsRow.append(disclosure);
12866
13216
  }
13217
+ const macosFolderHelp = element("details", "dsh-mobile-control__details");
13218
+ macosFolderHelp.hidden = true;
13219
+ const macosFolderHelpSummary = element("summary");
13220
+ macosFolderHelpSummary.textContent = t("macosFolderHelpTitle");
13221
+ const macosFolderHelpBody = element("div", "dsh-mobile-control__details-body");
13222
+ const macosFolderHelpNote = element("p");
13223
+ macosFolderHelpNote.textContent = t("macosFolderHelpNote");
13224
+ macosFolderHelpBody.append(macosFolderHelpNote);
13225
+ macosFolderHelp.append(macosFolderHelpSummary, macosFolderHelpBody);
13226
+ setupDetailsRow.append(macosFolderHelp);
12867
13227
  remoteWorkspace.append(remoteStatus, remoteAccess, remoteGuide, providerSetupBody, remoteQr, pairingValidity, providerInstallRow, remoteActions, remotePairLink, setupDetailsRow);
12868
13228
  const diagnosticsView = element("div", "dsh-mobile-control__view is-diagnostics");
12869
13229
  diagnosticsView.hidden = true;
@@ -13013,6 +13373,8 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
13013
13373
  let configuredFrpOrigin = "";
13014
13374
  let configuredFrpVhostPort = 7080;
13015
13375
  let configuredFrpPublicPort = 33080;
13376
+ let configuredFrpUser = "";
13377
+ let frpUserDraft = false;
13016
13378
  /** Proxy name the host writes into its frpc.toml; the local clipboard copy must match. */
13017
13379
  let configuredFrpProxyName;
13018
13380
  let frpConfiguredMode = "deploy";
@@ -13390,6 +13752,17 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
13390
13752
  irohQr.hidden = false;
13391
13753
  irohQr.append(placeholder);
13392
13754
  };
13755
+ const clearIrohPairing = () => {
13756
+ activeIrohDescriptor = "";
13757
+ activeIrohPairExpiresAt = 0;
13758
+ activeIrohQrSvg = "";
13759
+ clearQrTarget(irohQr);
13760
+ irohQr.hidden = true;
13761
+ const wasZoomed = !irohQrZoom.hidden;
13762
+ setIrohQrZoomOpen(false);
13763
+ irohQrZoomImage.removeAttribute("src");
13764
+ if (wasZoomed && !irohPair.disabled && !irohPair.hidden) irohPair.focus();
13765
+ };
13393
13766
  const showRemoteQrPlaceholder = (text) => {
13394
13767
  clearQrTarget(remoteQr);
13395
13768
  const placeholder = element("span", "dsh-mobile-control__qr-placeholder");
@@ -13661,6 +14034,9 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
13661
14034
  if (frpConfigured && !frpEntryTlsDraft) frpEntryTls.value = configuredFrpEntryTls;
13662
14035
  if (typeof frpConfiguration.vhostHttpPort === "number") configuredFrpVhostPort = frpConfiguration.vhostHttpPort;
13663
14036
  if (typeof frpConfiguration.publicPort === "number") configuredFrpPublicPort = frpConfiguration.publicPort;
14037
+ configuredFrpUser = typeof frpConfiguration.user === "string" ? frpConfiguration.user : "";
14038
+ if (!frpUserDraft) frpUser.value = configuredFrpUser;
14039
+ else if (frpUser.value.trim() === configuredFrpUser) frpUserDraft = false;
13664
14040
  configuredFrpProxyName = typeof frpConfiguration.proxyName === "string" && frpConfiguration.proxyName !== "" ? frpConfiguration.proxyName : void 0;
13665
14041
  if (!frpVhostDraft) {
13666
14042
  if (frpMode.value === "attach" && configuredFrpMode === "attach" && configuredFrpEntryTls === "public-ip-cert") {
@@ -13825,6 +14201,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
13825
14201
  }[errorCode] ?? "";
13826
14202
  const needsFunnelSetup = state === "error" && remoteSetupUrl !== "";
13827
14203
  remoteReady = remoteRunning && state === "ready" && remoteOrigin !== "";
14204
+ macosFolderHelp.hidden = data.macosHost !== true || !remoteReady;
13828
14205
  frpChoiceBadge.textContent = frpConfigured && frpInstalled ? t("ready") : t("advanced");
13829
14206
  originChoiceBadge.textContent = managedOrigin ? t(remoteReady ? "ready" : "advanced") : originListening ? t("originBackendReady") : t("advanced");
13830
14207
  if (remoteStatus.style) remoteStatus.style.display = state === "off" && remoteFailureText === "" ? "none" : "";
@@ -13839,7 +14216,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
13839
14216
  starting: cpolar ? t("remoteStartingCpolar") : cloudflared ? t("remoteStartingCloudflared") : frp ? t("remoteStartingFrp") : origin ? t("originStarting") : iroh ? t("irohStarting") : t("remoteStartingTailscale"),
13840
14217
  "needs-login": t("remoteNeedsLogin"),
13841
14218
  connecting: cpolar ? t("remoteConnectingCpolar") : cloudflared ? t("remoteConnectingCloudflared") : frp ? t("remoteConnectingFrp") : iroh ? t("irohConnecting") : t("remoteConnectingTailscale"),
13842
- ready: origin ? t(managedOrigin ? "caddyVerified" : "originReady") : t("remoteReady"),
14219
+ ready: origin ? t(managedOrigin ? "caddyVerified" : "originReady") : tailscale ? t("funnelListenerReady") : t("remoteReady"),
13843
14220
  error: t("remoteError")
13844
14221
  };
13845
14222
  const errorLabels = translatedRemoteErrors();
@@ -13877,15 +14254,15 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
13877
14254
  irohPair.disabled = !remoteReady || !iroh || remoteProviderBusy;
13878
14255
  irohPair.hidden = !iroh;
13879
14256
  irohCopyDescriptor.hidden = !iroh;
13880
- if (iroh && irohInstalled && activeIrohQrSvg === "") showIrohQrPlaceholder();
13881
- else if (!iroh || !irohInstalled) irohQr.hidden = true;
13882
- if ((!iroh || !remoteReady) && activeIrohDescriptor !== "") {
13883
- activeIrohDescriptor = "";
13884
- activeIrohPairExpiresAt = 0;
13885
- activeIrohQrSvg = "";
13886
- showQr("", irohQr);
13887
- setIrohQrZoomOpen(false);
13888
- irohPairStatus.textContent = "";
14257
+ const irohPairingExpired = activeIrohPairExpiresAt > 0 && activeIrohPairExpiresAt <= Date.now();
14258
+ if (activeIrohDescriptor !== "" && (!iroh || !irohInstalled || !remoteReady || irohPairingExpired)) {
14259
+ clearIrohPairing();
14260
+ irohPairStatus.textContent = iroh && irohInstalled && remoteReady && irohPairingExpired ? t("pairingExpired") : "";
14261
+ }
14262
+ if (!iroh || !irohInstalled) irohQr.hidden = true;
14263
+ else if (activeIrohQrSvg === "") {
14264
+ if (activeIrohDescriptor === "") showIrohQrPlaceholder();
14265
+ else irohQr.hidden = true;
13889
14266
  }
13890
14267
  irohCopyDescriptor.disabled = !iroh || !remoteReady || remoteProviderBusy || activeIrohDescriptor === "" || activeIrohPairExpiresAt <= Date.now();
13891
14268
  if (origin) {
@@ -14074,8 +14451,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
14074
14451
  });
14075
14452
  const openIrohPairing = async () => {
14076
14453
  const generation = remotePairingGeneration;
14077
- activeIrohDescriptor = "";
14078
- activeIrohPairExpiresAt = 0;
14454
+ clearIrohPairing();
14079
14455
  irohCopyDescriptor.disabled = true;
14080
14456
  const data = await controlRequestJson("/api/mobile-access/remote/iroh/pairing/begin", {
14081
14457
  method: "POST",
@@ -14086,12 +14462,12 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
14086
14462
  const qrSvg = typeof record.qrSvg === "string" ? record.qrSvg : "";
14087
14463
  const code = typeof record.irohCode === "string" ? record.irohCode : "";
14088
14464
  const expiresAt = typeof record.expiresAt === "number" && Number.isFinite(record.expiresAt) ? record.expiresAt : 0;
14089
- showQr(qrSvg, irohQr);
14090
- if (qrSvg === "" || code === "" || expiresAt <= Date.now()) {
14091
- activeIrohQrSvg = "";
14465
+ if (!/^\d{6}$/u.test(code) || expiresAt <= Date.now()) {
14092
14466
  irohPairStatus.textContent = t("keyGenerationFailed");
14093
14467
  return;
14094
14468
  }
14469
+ if (qrSvg === "") irohQr.hidden = true;
14470
+ else showQr(qrSvg, irohQr);
14095
14471
  activeIrohQrSvg = qrSvg;
14096
14472
  const fields = [
14097
14473
  "version",
@@ -14109,7 +14485,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
14109
14485
  for (const field of fields) descriptor[field] = record[field];
14110
14486
  activeIrohDescriptor = JSON.stringify(descriptor);
14111
14487
  activeIrohPairExpiresAt = expiresAt;
14112
- irohPairStatus.textContent = t("irohPairingReady", { code });
14488
+ irohPairStatus.textContent = qrSvg === "" ? t("irohPairingCopyOnly") : t("irohPairingReady", { code });
14113
14489
  };
14114
14490
  irohPair.addEventListener("click", () => {
14115
14491
  if (remoteProviderBusy || !remoteReady || remoteProvider !== "iroh") return;
@@ -14427,6 +14803,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
14427
14803
  serverAddress: String(frpServer.value ?? "").trim(),
14428
14804
  serverPort: Number(frpPort.value),
14429
14805
  token: String(frpToken.value ?? ""),
14806
+ user: String(frpUser.value ?? "").trim(),
14430
14807
  publicOrigin: String(frpOrigin.value ?? "").trim(),
14431
14808
  mode,
14432
14809
  entryTls: frpEntryTls.value === "self-signed" ? "self-signed" : "public-ip-cert",
@@ -14445,7 +14822,8 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
14445
14822
  publicOrigin: form.publicOrigin,
14446
14823
  entryTls: form.entryTls,
14447
14824
  ...form.entryTls === "self-signed" || form.vhostHttpPort === void 0 ? {} : { vhostHttpPort: form.vhostHttpPort },
14448
- ...form.publicPort === void 0 ? {} : { publicPort: form.publicPort }
14825
+ ...form.publicPort === void 0 ? {} : { publicPort: form.publicPort },
14826
+ ...form.user === "" ? {} : { user: form.user }
14449
14827
  });
14450
14828
  const validFrpForm = (form) => {
14451
14829
  return validateFrpForm(form).length === 0;
@@ -14476,6 +14854,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
14476
14854
  return issues.length === 0;
14477
14855
  };
14478
14856
  for (const [name, input] of Object.entries(frpInputs)) input.addEventListener(name === "mode" || name === "entryTls" ? "change" : "input", () => {
14857
+ if (name === "user") frpUserDraft = true;
14479
14858
  if (frpValidationShown) renderFrpIssues(validateFrpForm(frpForm(), frpValidationAllowsSavedToken));
14480
14859
  });
14481
14860
  frpCopyTemplate.addEventListener("click", () => {
@@ -14658,7 +15037,8 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
14658
15037
  mode: form.mode,
14659
15038
  entryTls: form.entryTls,
14660
15039
  ...form.vhostHttpPort === void 0 ? {} : { vhostHttpPort: form.vhostHttpPort },
14661
- ...form.publicPort === void 0 ? {} : { publicPort: form.publicPort }
15040
+ ...form.publicPort === void 0 ? {} : { publicPort: form.publicPort },
15041
+ user: form.user
14662
15042
  };
14663
15043
  };
14664
15044
  const validVpsFrpForm = () => {
@@ -14898,9 +15278,10 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
14898
15278
  frpConfigure.addEventListener("click", () => {
14899
15279
  if (remoteProviderBusy || !frpInstalled) return;
14900
15280
  const form = frpForm();
14901
- const unchanged = frpConfigured && form.token === "" && form.serverAddress === configuredFrpServer && form.serverPort === configuredFrpPort && form.publicOrigin === configuredFrpOrigin && form.mode === (frpConfiguredMode === "attach" ? "attach" : "deploy") && form.entryTls === (frpConfiguredEntryTls === "self-signed" ? "self-signed" : "public-ip-cert") && (form.vhostHttpPort ?? configuredFrpVhostPort) === configuredFrpVhostPort && (form.publicPort ?? configuredFrpPublicPort) === configuredFrpPublicPort;
15281
+ const unchanged = frpConfigured && form.token === "" && form.serverAddress === configuredFrpServer && form.serverPort === configuredFrpPort && form.publicOrigin === configuredFrpOrigin && form.mode === (frpConfiguredMode === "attach" ? "attach" : "deploy") && form.entryTls === (frpConfiguredEntryTls === "self-signed" ? "self-signed" : "public-ip-cert") && (form.vhostHttpPort ?? configuredFrpVhostPort) === configuredFrpVhostPort && (form.publicPort ?? configuredFrpPublicPort) === configuredFrpPublicPort && form.user === configuredFrpUser;
14902
15282
  if (!unchanged) {
14903
- if (!checkFrpForm(form)) return;
15283
+ const sameServer = frpConfigured && form.serverAddress === configuredFrpServer && form.serverPort === configuredFrpPort;
15284
+ if (!checkFrpForm(form, sameServer)) return;
14904
15285
  }
14905
15286
  renderFrpIssues([]);
14906
15287
  remoteProviderBusy = true;
@@ -16919,7 +17300,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
16919
17300
  style rule of its own. */
16920
17301
  .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}
16921
17302
  .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%))}
16922
- .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[hidden]{display:none}.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)}
17303
+ .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[hidden]{display:none}.dsh-mobile-control__frp-fields>.dsh-mobile-control__field:nth-child(5),.dsh-mobile-control__frp-fields>.dsh-mobile-control__field:nth-child(6),.dsh-mobile-control__frp-fields>.dsh-mobile-control__field:nth-child(7){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)}
16923
17304
  .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}
16924
17305
  .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}
16925
17306
  .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}