@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/client.js +7 -4
- package/lib/client.js.map +1 -1
- package/lib/index.js +58 -5
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +10 -7
- package/lib/types/lan-bind.d.ts.map +1 -1
- package/lib/types/lan-bind.js +4 -2
- package/lib/types/tunnel.d.ts +24 -1
- package/lib/types/tunnel.d.ts.map +1 -1
- package/lib/types/tunnel.js +53 -5
- package/package.json +1 -1
- package/src/client/index.ts +13 -7
- package/src/lan-bind.ts +4 -2
- package/src/tunnel.ts +63 -4
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
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
|
|
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;
|
|
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
|
|
7
|
-
*
|
|
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,
|
|
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"}
|
package/lib/types/lan-bind.js
CHANGED
|
@@ -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
|
|
122
|
-
const
|
|
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)}`;
|
package/lib/types/tunnel.d.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
|
|
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":"AAAA
|
|
1
|
+
{"version":3,"file":"tunnel.d.ts","sourceRoot":"","sources":["../../src/tunnel.ts"],"names":[],"mappings":"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"}
|
package/lib/types/tunnel.js
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
|
|
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
|
-
/**
|
|
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
|
-
|
|
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.
|
|
4
|
+
"version": "0.3.17",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
package/src/client/index.ts
CHANGED
|
@@ -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
|
|
7
|
-
*
|
|
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
|
|
44
|
-
//
|
|
45
|
-
//
|
|
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
|
|
139
|
-
const
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
|
|
127
|
-
await install(bin)
|
|
186
|
+
await defaultBinaryReadiness()
|
|
128
187
|
}
|
|
129
188
|
|
|
130
189
|
/** The flags every tunnel mode shares (see the quick factory comment). */
|