@linxin666/dsh-remote-web-ui 0.3.15 → 0.3.17

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/index.js CHANGED
@@ -2428,7 +2428,9 @@ function writeLanBind(host, port, profile, home = dshHome()) {
2428
2428
  const file = profilePatchFile(profile, home);
2429
2429
  const stripped = stripManagedBlock(readPatchContent(file));
2430
2430
  const orphanBegin = stripped.indexOf(LAN_BIND_BLOCK_BEGIN);
2431
- const content = `${(orphanBegin === -1 ? stripped : stripped.slice(0, orphanBegin)).trimEnd()}\n\n${managedBlock(host, port)}`;
2431
+ const base = (orphanBegin === -1 ? stripped : stripped.slice(0, orphanBegin)).trimEnd().replace(/\[\s*\]\s*$/, "").trimEnd();
2432
+ const block = managedBlock(host, port);
2433
+ const content = base.length > 0 ? `${base}\n\n${block}` : block;
2432
2434
  const mode = existsSync(file) ? statSync(file).mode & 511 : 384;
2433
2435
  mkdirSync(dirname(file), { recursive: true });
2434
2436
  const temp = `${file}.remote-web-ui-tmp-${process.pid.toString(36)}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -2753,7 +2755,9 @@ function createInnerAuth(launchUrl, fetchImpl = fetch) {
2753
2755
  //#region src/tunnel.ts
2754
2756
  /**
2755
2757
  * Auto-tunnel manager: spawns a Cloudflare tunnel through the `cloudflared`
2756
- * npm package — its postinstall downloads the platform binary, so no
2758
+ * npm package — its postinstall downloads the platform binary and the
2759
+ * readiness policy below validates it and re-fetches when it cannot run (the
2760
+ * desktop payload stages one tree for every shipped OS/arch), so no
2757
2761
  * user-side tooling is involved — surfaces the public URL, and restarts the
2758
2762
  * process after unexpected exits with exponential backoff. Two modes: the
2759
2763
  * accountless quick tunnel (`https://xxx.trycloudflare.com`, hostname minted
@@ -2808,10 +2812,59 @@ function namedTunnelHandle(inner, publicUrl) {
2808
2812
  };
2809
2813
  return handle;
2810
2814
  }
2811
- /** Default binary readiness: download the platform binary on first use. */
2815
+ /** Time box for the `--version` probe that validates a staged binary. */
2816
+ const BINARY_PROBE_TIMEOUT_MS = 1e4;
2817
+ /**
2818
+ * Probe whether the binary at `path` actually executes on this machine. The
2819
+ * desktop payload stages one dependency tree for every shipped OS/arch, so a
2820
+ * binary can exist with the wrong architecture (the arm64 darwin
2821
+ * `bin/cloudflared` on an x64 mac); existence alone must not skip the
2822
+ * reinstall.
2823
+ */
2824
+ function binaryRuns(executable) {
2825
+ return new Promise((resolvePromise) => {
2826
+ const child = spawn(executable, ["--version"], { stdio: "ignore" });
2827
+ const timer = setTimeout(() => {
2828
+ child.kill("SIGKILL");
2829
+ resolvePromise(false);
2830
+ }, BINARY_PROBE_TIMEOUT_MS);
2831
+ child.once("error", () => {
2832
+ clearTimeout(timer);
2833
+ resolvePromise(false);
2834
+ });
2835
+ child.once("exit", (code) => {
2836
+ clearTimeout(timer);
2837
+ resolvePromise(code === 0);
2838
+ });
2839
+ });
2840
+ }
2841
+ /**
2842
+ * Build the default readiness policy over a binary path: skip when the
2843
+ * binary has already proven runnable in this process, validate an existing
2844
+ * file before trusting it (a stale or wrong-arch binary must not disable
2845
+ * the reinstall), and re-fetch the current platform binary otherwise.
2846
+ */
2847
+ function createBinaryReadiness(executable, seams = {}) {
2848
+ const exists = seams.exists ?? existsSync;
2849
+ const runs = seams.runs ?? binaryRuns;
2850
+ const installBinary = seams.install ?? install;
2851
+ const validated = /* @__PURE__ */ new Set();
2852
+ return async () => {
2853
+ if (validated.has(executable)) return;
2854
+ if (exists(executable) && await runs(executable)) {
2855
+ validated.add(executable);
2856
+ return;
2857
+ }
2858
+ await installBinary(executable);
2859
+ if (!await runs(executable)) throw new Error(`the cloudflared binary at ${executable} still does not run after the reinstall`);
2860
+ validated.add(executable);
2861
+ };
2862
+ }
2863
+ /** The readiness policy the manager uses by default: the package's real binary path. */
2864
+ const defaultBinaryReadiness = createBinaryReadiness(bin);
2865
+ /** Default binary readiness: keep a working platform binary at `bin`. */
2812
2866
  async function defaultEnsureBinary() {
2813
- if (existsSync(bin)) return;
2814
- await install(bin);
2867
+ await defaultBinaryReadiness();
2815
2868
  }
2816
2869
  /** The flags every tunnel mode shares (see the quick factory comment). */
2817
2870
  const SHARED_TUNNEL_FLAGS = {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAA;AAelG,OAAO,EAAU,KAAK,SAAS,EAAE,MAAM,cAAc,CAAA;AAqBrD,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACrE,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AACnE,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC7C,YAAY,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAC/F,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAErE,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,0CAA0C;QAC1C,MAAM,EAAE,SAAS,CAAA;KAClB;IAED,UAAU,OAAO;QACf;;;;WAIG;QACH;;;;;WAKG;QACH,oBAAoB,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,4BAA4B,CAAA;SAAE,CAAA;KAC3F;CACF;AAED,4GAA4G;AAC5G,MAAM,WAAW,uBAAuB;IACtC,oEAAoE;IACpE,IAAI,EAAE,OAAO,CAAA;CACd;AAED,mEAAmE;AACnE,MAAM,WAAW,4BAA4B;IAC3C,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,KAAK,CAAA;CACjB;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf;;;;WAIG;QACH,aAAa,CAAC,EAAE;YAAE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;SAAE,CAAA;KAC1E;CACF;AAYD,wCAAwC;AACxC,eAAO,MAAM,MAAM,UAA+D,CAAA;AAElF;;;GAGG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAyQ9C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAA;AAelG,OAAO,EAAU,KAAK,SAAS,EAAE,MAAM,cAAc,CAAA;AAmBrD,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACrE,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AACnE,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC7C,YAAY,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAC/F,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAErE,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,0CAA0C;QAC1C,MAAM,EAAE,SAAS,CAAA;KAClB;IAED,UAAU,OAAO;QACf;;;;WAIG;QACH;;;;;WAKG;QACH,oBAAoB,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,4BAA4B,CAAA;SAAE,CAAA;KAC3F;CACF;AAED,4GAA4G;AAC5G,MAAM,WAAW,uBAAuB;IACtC,oEAAoE;IACpE,IAAI,EAAE,OAAO,CAAA;CACd;AAED,mEAAmE;AACnE,MAAM,WAAW,4BAA4B;IAC3C,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,KAAK,CAAA;CACjB;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf;;;;WAIG;QACH,aAAa,CAAC,EAAE;YAAE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;SAAE,CAAA;KAC1E;CACF;AAYD,wCAAwC;AACxC,eAAO,MAAM,MAAM,UAA+D,CAAA;AAElF;;;GAGG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAiR9C"}
@@ -3,8 +3,8 @@
3
3
  * sidebar-foot entry (phone trigger + pairing panel + update trigger), and
4
4
  * the pair boot flow (accept + presence heartbeats) plus the one-time
5
5
  * failed-pair notice. The portrait-touch adaptation of the official UI
6
- * starts at module scope (startMobileAdapt) so its focus guard is installed
7
- * before the app boots. Export discipline: packages/client/AGENTS.md — the
6
+ * starts under the plugin lifecycle (startMobileAdapt inside apply) and reverts
7
+ * on dispose, so disabled plugin entries stay inert. Export discipline: packages/client/AGENTS.md — the
8
8
  * /client surface carries only what cordis loading needs plus types.
9
9
  */
10
10
  import { createElement } from 'react';
@@ -19,11 +19,6 @@ import { channelTransition, installRemoteChannel, isLoopbackHostname, remoteChan
19
19
  import { FenceNotice } from "./FenceNotice.js";
20
20
  import { reportDailyHeartbeat } from "./telemetry.js";
21
21
  import { startMobileAdapt } from "./mobile-adapt.js";
22
- // Portrait-touch adaptation of the official UI: installed at module scope so
23
- // the composer focus guard exists before any app entry mounts React. The
24
- // layer self-evaluates and reverts with the viewport; the plugin apply below
25
- // wires its toggleSidebar onto the official layout service.
26
- startMobileAdapt();
27
22
  /** Dictionary namespace owned by this plugin. */
28
23
  const NS = 'remote';
29
24
  /** Settings namespace the remote-control card edits (the Host plugin registers it). */
@@ -37,6 +32,14 @@ export const inject = ['slots', 'locale', 'connection', 'settingsScope', 'remote
37
32
  * @param ctx - client root context.
38
33
  */
39
34
  export function apply(ctx) {
35
+ // Portrait-touch adaptation of the official UI: installed under the plugin
36
+ // lifecycle so disabling the plugin in cordis patch (disabled: true) never
37
+ // injects mobile CSS, gesture hooks, or the whale floating button.
38
+ startMobileAdapt();
39
+ ctx.effect(() => () => {
40
+ ;
41
+ window.__dshRemoteAdapt?.setEnabled?.(false);
42
+ }, 'remote-web-ui: mobile-adapt');
40
43
  // Anonymous install heartbeat (docs/telemetry.md): one beat per browser per
41
44
  // UTC day, package name only, silent failure.
42
45
  reportDailyHeartbeat([{ name: '@linxin666/dsh-remote-web-ui' }]);
@@ -1 +1 @@
1
- {"version":3,"file":"lan-bind.d.ts","sourceRoot":"","sources":["../../src/lan-bind.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AASH,eAAO,MAAM,oBAAoB,mEAAmE,CAAA;AACpG,eAAO,MAAM,kBAAkB,+CAA+C,CAAA;AAE9E,iDAAiD;AACjD,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,WAAW,CAAA;AAEjD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG,MAAM,CAUlF;AAYD,qEAAqE;AACrE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKzD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAcpE;AAED,8EAA8E;AAC9E,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,WAAW,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;IACjC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;CACzB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAW3E;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG;IAAE,YAAY,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAI/H;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG,IAAI,CAW7G"}
1
+ {"version":3,"file":"lan-bind.d.ts","sourceRoot":"","sources":["../../src/lan-bind.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AASH,eAAO,MAAM,oBAAoB,mEAAmE,CAAA;AACpG,eAAO,MAAM,kBAAkB,+CAA+C,CAAA;AAE9E,iDAAiD;AACjD,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,WAAW,CAAA;AAEjD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG,MAAM,CAUlF;AAYD,qEAAqE;AACrE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKzD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAcpE;AAED,8EAA8E;AAC9E,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,WAAW,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;IACjC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;CACzB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAW3E;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG;IAAE,YAAY,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAI/H;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG,IAAI,CAa7G"}
@@ -118,8 +118,10 @@ export function writeLanBind(host, port, profile, home = dshHome()) {
118
118
  const file = profilePatchFile(profile, home);
119
119
  const stripped = stripManagedBlock(readPatchContent(file));
120
120
  const orphanBegin = stripped.indexOf(LAN_BIND_BLOCK_BEGIN);
121
- const base = (orphanBegin === -1 ? stripped : stripped.slice(0, orphanBegin)).trimEnd();
122
- const content = `${base}\n\n${managedBlock(host, port)}`;
121
+ const rawBase = (orphanBegin === -1 ? stripped : stripped.slice(0, orphanBegin)).trimEnd();
122
+ const base = rawBase.replace(/\[\s*\]\s*$/, '').trimEnd();
123
+ const block = managedBlock(host, port);
124
+ const content = base.length > 0 ? `${base}\n\n${block}` : block;
123
125
  const mode = existsSync(file) ? statSync(file).mode & 0o777 : 0o600;
124
126
  mkdirSync(dirname(file), { recursive: true });
125
127
  const temp = `${file}.remote-web-ui-tmp-${process.pid.toString(36)}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Auto-tunnel manager: spawns a Cloudflare tunnel through the `cloudflared`
3
- * npm package — its postinstall downloads the platform binary, so no
3
+ * npm package — its postinstall downloads the platform binary and the
4
+ * readiness policy below validates it and re-fetches when it cannot run (the
5
+ * desktop payload stages one tree for every shipped OS/arch), so no
4
6
  * user-side tooling is involved — surfaces the public URL, and restarts the
5
7
  * process after unexpected exits with exponential backoff. Two modes: the
6
8
  * accountless quick tunnel (`https://xxx.trycloudflare.com`, hostname minted
@@ -84,6 +86,27 @@ export interface TunnelManagerOptions {
84
86
  clearTimeout(t: unknown): void;
85
87
  };
86
88
  }
89
+ /**
90
+ * Probe whether the binary at `path` actually executes on this machine. The
91
+ * desktop payload stages one dependency tree for every shipped OS/arch, so a
92
+ * binary can exist with the wrong architecture (the arm64 darwin
93
+ * `bin/cloudflared` on an x64 mac); existence alone must not skip the
94
+ * reinstall.
95
+ */
96
+ export declare function binaryRuns(executable: string): Promise<boolean>;
97
+ /** Injectable seams of the default binary readiness (tests swap them all). */
98
+ export interface BinaryReadinessSeams {
99
+ exists?: (path: string) => boolean;
100
+ runs?: (executable: string) => Promise<boolean>;
101
+ install?: (path: string) => Promise<unknown>;
102
+ }
103
+ /**
104
+ * Build the default readiness policy over a binary path: skip when the
105
+ * binary has already proven runnable in this process, validate an existing
106
+ * file before trusting it (a stale or wrong-arch binary must not disable
107
+ * the reinstall), and re-fetch the current platform binary otherwise.
108
+ */
109
+ export declare function createBinaryReadiness(executable: string, seams?: BinaryReadinessSeams): () => Promise<void>;
87
110
  /**
88
111
  * Spawn flags for one quick target. `originHostHeader` stamps the Host the
89
112
  * local webserver sees: the plugin's phone-facing fence trusts the configured
@@ -1 +1 @@
1
- {"version":3,"file":"tunnel.d.ts","sourceRoot":"","sources":["../../src/tunnel.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tunnel.d.ts","sourceRoot":"","sources":["../../src/tunnel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAOH,0EAA0E;AAC1E,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAA;AAEvE;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/D;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAA;AAQvD,6EAA6E;AAC7E,UAAU,kBAAkB;IAC1B,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAA;IACrD,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,KAAK,IAAI,GAAG,OAAO,CAAA;IAClG,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,OAAO,CAAA;IAC7D,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,GAAG,OAAO,CAAA;IAC/D,IAAI,IAAI,OAAO,CAAA;CAChB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,GAAG,YAAY,CA4B5F;AAED,+BAA+B;AAC/B,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,WAAW,CAAA;IAClB,yDAAyD;IACzD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,8EAA8E;AAC9E,MAAM,WAAW,YAAY;IAC3B,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,GAAG,OAAO,CAAA;IAC9D,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,GAAG,OAAO,CAAA;IAC/D,IAAI,IAAI,OAAO,CAAA;CAChB;AAED,kFAAkF;AAClF,MAAM,WAAW,oBAAoB;IACnC,gEAAgE;IAChE,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,YAAY,CAAA;IAChD,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAClC,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,wCAAwC;IACxC,KAAK,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;QAAC,YAAY,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAA;CAC5F;AAKD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAU/D;AAED,8EAA8E;AAC9E,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;IAClC,IAAI,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAC/C,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CAC7C;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,GAAE,oBAAyB,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAiB/G;AAaD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAI5F;AAqBD;;;GAGG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwC;IAChE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAClD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAQ;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAQ;IACtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAQ;IACrC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqF;IAE3G,OAAO,CAAC,KAAK,CAAyB;IACtC,OAAO,CAAC,GAAG,CAAoB;IAC/B,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,QAAQ,CAAqB;IACrC,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAI;IAGpB,OAAO,CAAC,UAAU,CAAI;IACtB,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAmC;IAChE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAwC;IAEvE;;OAEG;gBACS,OAAO,GAAE,oBAAyB;IAS9C,gCAAgC;IAChC,IAAI,IAAI,IAAI,UAAU,CAMrB;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,GAAG,IAAI;IAW1C,uDAAuD;IACvD,IAAI,IAAI,IAAI;IAOZ,wDAAwD;IACxD,OAAO,IAAI,IAAI;IAIf,4EAA4E;IAC5E,KAAK,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI;IAKlD,uCAAuC;IACvC,OAAO,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,GAAG,MAAM,IAAI;IAKzD,OAAO,CAAC,OAAO;IAqCf,OAAO,CAAC,SAAS;IAkBjB,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,IAAI;IAqBZ,iFAAiF;IACjF,OAAO,CAAC,QAAQ;IAgBhB,OAAO,CAAC,QAAQ;CAWjB"}
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Auto-tunnel manager: spawns a Cloudflare tunnel through the `cloudflared`
3
- * npm package — its postinstall downloads the platform binary, so no
3
+ * npm package — its postinstall downloads the platform binary and the
4
+ * readiness policy below validates it and re-fetches when it cannot run (the
5
+ * desktop payload stages one tree for every shipped OS/arch), so no
4
6
  * user-side tooling is involved — surfaces the public URL, and restarts the
5
7
  * process after unexpected exits with exponential backoff. Two modes: the
6
8
  * accountless quick tunnel (`https://xxx.trycloudflare.com`, hostname minted
@@ -15,6 +17,7 @@
15
17
  * a real binary or network.
16
18
  */
17
19
  import { existsSync } from 'node:fs';
20
+ import { spawn } from 'node:child_process';
18
21
  import { EventEmitter } from 'node:events';
19
22
  import { bin, install, Tunnel } from 'cloudflared';
20
23
  /** Compare two targets for the start idempotence check. */
@@ -62,11 +65,56 @@ export function namedTunnelHandle(inner, publicUrl) {
62
65
  };
63
66
  return handle;
64
67
  }
65
- /** Default binary readiness: download the platform binary on first use. */
68
+ /** Time box for the `--version` probe that validates a staged binary. */
69
+ const BINARY_PROBE_TIMEOUT_MS = 10_000;
70
+ /**
71
+ * Probe whether the binary at `path` actually executes on this machine. The
72
+ * desktop payload stages one dependency tree for every shipped OS/arch, so a
73
+ * binary can exist with the wrong architecture (the arm64 darwin
74
+ * `bin/cloudflared` on an x64 mac); existence alone must not skip the
75
+ * reinstall.
76
+ */
77
+ export function binaryRuns(executable) {
78
+ return new Promise((resolvePromise) => {
79
+ const child = spawn(executable, ['--version'], { stdio: 'ignore' });
80
+ const timer = setTimeout(() => {
81
+ child.kill('SIGKILL');
82
+ resolvePromise(false);
83
+ }, BINARY_PROBE_TIMEOUT_MS);
84
+ child.once('error', () => { clearTimeout(timer); resolvePromise(false); });
85
+ child.once('exit', (code) => { clearTimeout(timer); resolvePromise(code === 0); });
86
+ });
87
+ }
88
+ /**
89
+ * Build the default readiness policy over a binary path: skip when the
90
+ * binary has already proven runnable in this process, validate an existing
91
+ * file before trusting it (a stale or wrong-arch binary must not disable
92
+ * the reinstall), and re-fetch the current platform binary otherwise.
93
+ */
94
+ export function createBinaryReadiness(executable, seams = {}) {
95
+ const exists = seams.exists ?? existsSync;
96
+ const runs = seams.runs ?? binaryRuns;
97
+ const installBinary = seams.install ?? install;
98
+ const validated = new Set();
99
+ return async () => {
100
+ if (validated.has(executable))
101
+ return;
102
+ if (exists(executable) && await runs(executable)) {
103
+ validated.add(executable);
104
+ return;
105
+ }
106
+ await installBinary(executable);
107
+ if (!(await runs(executable))) {
108
+ throw new Error(`the cloudflared binary at ${executable} still does not run after the reinstall`);
109
+ }
110
+ validated.add(executable);
111
+ };
112
+ }
113
+ /** The readiness policy the manager uses by default: the package's real binary path. */
114
+ const defaultBinaryReadiness = createBinaryReadiness(bin);
115
+ /** Default binary readiness: keep a working platform binary at `bin`. */
66
116
  async function defaultEnsureBinary() {
67
- if (existsSync(bin))
68
- return;
69
- await install(bin);
117
+ await defaultBinaryReadiness();
70
118
  }
71
119
  /** The flags every tunnel mode shares (see the quick factory comment). */
72
120
  const SHARED_TUNNEL_FLAGS = { '--no-autoupdate': true, '--protocol': 'http2' };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@linxin666/dsh-remote-web-ui",
3
3
  "description": "Scan-to-pair remote access for the dsh web GUI that shares one official interface: a QR beside the settings button pairs phones and PCs into the same Web GUI (a portrait-touch adaptation layer for phones, full desktop on PCs) through one-time tokens and revocable device sessions, with a LAN bind toggle, optional Cloudflare tunnel, and one-click family self-update",
4
- "version": "0.3.15",
4
+ "version": "0.3.17",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": "^22.19.0 || >=24.0.0"
@@ -3,8 +3,8 @@
3
3
  * sidebar-foot entry (phone trigger + pairing panel + update trigger), and
4
4
  * the pair boot flow (accept + presence heartbeats) plus the one-time
5
5
  * failed-pair notice. The portrait-touch adaptation of the official UI
6
- * starts at module scope (startMobileAdapt) so its focus guard is installed
7
- * before the app boots. Export discipline: packages/client/AGENTS.md — the
6
+ * starts under the plugin lifecycle (startMobileAdapt inside apply) and reverts
7
+ * on dispose, so disabled plugin entries stay inert. Export discipline: packages/client/AGENTS.md — the
8
8
  * /client surface carries only what cordis loading needs plus types.
9
9
  */
10
10
  import { createElement } from 'react'
@@ -40,11 +40,9 @@ import { FenceNotice } from './FenceNotice.tsx'
40
40
  import { reportDailyHeartbeat } from './telemetry.ts'
41
41
  import { startMobileAdapt, type RemoteAdaptGlobal } from './mobile-adapt.ts'
42
42
 
43
- // Portrait-touch adaptation of the official UI: installed at module scope so
44
- // the composer focus guard exists before any app entry mounts React. The
45
- // layer self-evaluates and reverts with the viewport; the plugin apply below
46
- // wires its toggleSidebar onto the official layout service.
47
- startMobileAdapt()
43
+ // Portrait-touch adaptation of the official UI: installed under the plugin
44
+ // lifecycle (apply) so disabling the plugin in cordis patch (disabled: true)
45
+ // leaves the DOM untouched.
48
46
 
49
47
  export type { RemoteEntryProps } from './RemoteEntry.tsx'
50
48
  export type { PanelState, RemotePanelProps } from './RemotePanel.tsx'
@@ -117,6 +115,14 @@ export const inject = ['slots', 'locale', 'connection', 'settingsScope', 'remote
117
115
  * @param ctx - client root context.
118
116
  */
119
117
  export function apply(ctx: ClientContext): void {
118
+ // Portrait-touch adaptation of the official UI: installed under the plugin
119
+ // lifecycle so disabling the plugin in cordis patch (disabled: true) never
120
+ // injects mobile CSS, gesture hooks, or the whale floating button.
121
+ startMobileAdapt()
122
+ ctx.effect(() => () => {
123
+ ;(window as unknown as { __dshRemoteAdapt?: RemoteAdaptGlobal }).__dshRemoteAdapt?.setEnabled?.(false)
124
+ }, 'remote-web-ui: mobile-adapt')
125
+
120
126
  // Anonymous install heartbeat (docs/telemetry.md): one beat per browser per
121
127
  // UTC day, package name only, silent failure.
122
128
  reportDailyHeartbeat([{ name: '@linxin666/dsh-remote-web-ui' }])
package/src/lan-bind.ts CHANGED
@@ -135,8 +135,10 @@ export function writeLanBind(host: LanBindHost, port: number, profile: string, h
135
135
  const file = profilePatchFile(profile, home)
136
136
  const stripped = stripManagedBlock(readPatchContent(file))
137
137
  const orphanBegin = stripped.indexOf(LAN_BIND_BLOCK_BEGIN)
138
- const base = (orphanBegin === -1 ? stripped : stripped.slice(0, orphanBegin)).trimEnd()
139
- const content = `${base}\n\n${managedBlock(host, port)}`
138
+ const rawBase = (orphanBegin === -1 ? stripped : stripped.slice(0, orphanBegin)).trimEnd()
139
+ const base = rawBase.replace(/\[\s*\]\s*$/, '').trimEnd()
140
+ const block = managedBlock(host, port)
141
+ const content = base.length > 0 ? `${base}\n\n${block}` : block
140
142
  const mode = existsSync(file) ? statSync(file).mode & 0o777 : 0o600
141
143
  mkdirSync(dirname(file), { recursive: true })
142
144
  const temp = `${file}.remote-web-ui-tmp-${process.pid.toString(36)}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`
package/src/tunnel.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Auto-tunnel manager: spawns a Cloudflare tunnel through the `cloudflared`
3
- * npm package — its postinstall downloads the platform binary, so no
3
+ * npm package — its postinstall downloads the platform binary and the
4
+ * readiness policy below validates it and re-fetches when it cannot run (the
5
+ * desktop payload stages one tree for every shipped OS/arch), so no
4
6
  * user-side tooling is involved — surfaces the public URL, and restarts the
5
7
  * process after unexpected exits with exponential backoff. Two modes: the
6
8
  * accountless quick tunnel (`https://xxx.trycloudflare.com`, hostname minted
@@ -16,6 +18,7 @@
16
18
  */
17
19
 
18
20
  import { existsSync } from 'node:fs'
21
+ import { spawn } from 'node:child_process'
19
22
  import { EventEmitter } from 'node:events'
20
23
  import { bin, install, Tunnel } from 'cloudflared'
21
24
 
@@ -121,10 +124,66 @@ export interface TunnelManagerOptions {
121
124
  timer?: { setTimeout(fn: () => void, ms: number): unknown; clearTimeout(t: unknown): void }
122
125
  }
123
126
 
124
- /** Default binary readiness: download the platform binary on first use. */
127
+ /** Time box for the `--version` probe that validates a staged binary. */
128
+ const BINARY_PROBE_TIMEOUT_MS = 10_000
129
+
130
+ /**
131
+ * Probe whether the binary at `path` actually executes on this machine. The
132
+ * desktop payload stages one dependency tree for every shipped OS/arch, so a
133
+ * binary can exist with the wrong architecture (the arm64 darwin
134
+ * `bin/cloudflared` on an x64 mac); existence alone must not skip the
135
+ * reinstall.
136
+ */
137
+ export function binaryRuns(executable: string): Promise<boolean> {
138
+ return new Promise((resolvePromise) => {
139
+ const child = spawn(executable, ['--version'], { stdio: 'ignore' })
140
+ const timer = setTimeout(() => {
141
+ child.kill('SIGKILL')
142
+ resolvePromise(false)
143
+ }, BINARY_PROBE_TIMEOUT_MS)
144
+ child.once('error', () => { clearTimeout(timer); resolvePromise(false) })
145
+ child.once('exit', (code) => { clearTimeout(timer); resolvePromise(code === 0) })
146
+ })
147
+ }
148
+
149
+ /** Injectable seams of the default binary readiness (tests swap them all). */
150
+ export interface BinaryReadinessSeams {
151
+ exists?: (path: string) => boolean
152
+ runs?: (executable: string) => Promise<boolean>
153
+ install?: (path: string) => Promise<unknown>
154
+ }
155
+
156
+ /**
157
+ * Build the default readiness policy over a binary path: skip when the
158
+ * binary has already proven runnable in this process, validate an existing
159
+ * file before trusting it (a stale or wrong-arch binary must not disable
160
+ * the reinstall), and re-fetch the current platform binary otherwise.
161
+ */
162
+ export function createBinaryReadiness(executable: string, seams: BinaryReadinessSeams = {}): () => Promise<void> {
163
+ const exists = seams.exists ?? existsSync
164
+ const runs = seams.runs ?? binaryRuns
165
+ const installBinary = seams.install ?? install
166
+ const validated = new Set<string>()
167
+ return async () => {
168
+ if (validated.has(executable)) return
169
+ if (exists(executable) && await runs(executable)) {
170
+ validated.add(executable)
171
+ return
172
+ }
173
+ await installBinary(executable)
174
+ if (!(await runs(executable))) {
175
+ throw new Error(`the cloudflared binary at ${executable} still does not run after the reinstall`)
176
+ }
177
+ validated.add(executable)
178
+ }
179
+ }
180
+
181
+ /** The readiness policy the manager uses by default: the package's real binary path. */
182
+ const defaultBinaryReadiness = createBinaryReadiness(bin)
183
+
184
+ /** Default binary readiness: keep a working platform binary at `bin`. */
125
185
  async function defaultEnsureBinary(): Promise<void> {
126
- if (existsSync(bin)) return
127
- await install(bin)
186
+ await defaultBinaryReadiness()
128
187
  }
129
188
 
130
189
  /** The flags every tunnel mode shares (see the quick factory comment). */