@linxin666/dsh-remote-web-ui 0.1.12 → 0.1.14
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/README.i18n.yaml +6 -0
- package/README.md +17 -3
- package/README.zh.md +171 -0
- package/lib/client.js +171 -100
- package/lib/client.js.map +1 -1
- package/lib/index.js +338 -31
- package/lib/mobile.js +109 -33
- package/lib/mobile.js.map +1 -1
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +3 -2
- package/lib/types/client/RemoteSettingsCard.d.ts +4 -0
- package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
- package/lib/types/client/RemoteSettingsCard.js +3 -1
- package/lib/types/client/UpdateEntry.d.ts.map +1 -1
- package/lib/types/client/UpdateEntry.js +10 -2
- package/lib/types/client/deep-link.js +10 -5
- package/lib/types/client/locales.d.ts +8 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +10 -2
- package/lib/types/client/update-api.d.ts +10 -0
- package/lib/types/client/update-api.d.ts.map +1 -1
- package/lib/types/client/update-api.js +22 -2
- package/lib/types/index.d.ts +7 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +7 -1
- package/lib/types/mobile/api.d.ts +9 -2
- package/lib/types/mobile/api.d.ts.map +1 -1
- package/lib/types/mobile/api.js +7 -3
- package/lib/types/mobile/mobile-styles.d.ts +1 -1
- package/lib/types/mobile/mobile-styles.d.ts.map +1 -1
- package/lib/types/mobile/mobile-styles.js +3 -0
- package/lib/types/mobile/views/App.d.ts +3 -1
- package/lib/types/mobile/views/App.d.ts.map +1 -1
- package/lib/types/mobile/views/App.js +18 -3
- package/lib/types/mobile/views/ChatView.d.ts +6 -0
- package/lib/types/mobile/views/ChatView.d.ts.map +1 -1
- package/lib/types/mobile/views/ChatView.js +95 -7
- package/lib/types/mobile/views/ChatView.test.js +161 -3
- package/lib/types/mobile/views/WorkspaceView.d.ts +3 -1
- package/lib/types/mobile/views/WorkspaceView.d.ts.map +1 -1
- package/lib/types/mobile/views/WorkspaceView.js +9 -2
- package/lib/types/mobile/views/WorkspaceView.test.d.ts +2 -0
- package/lib/types/mobile/views/WorkspaceView.test.d.ts.map +1 -0
- package/lib/types/mobile/views/WorkspaceView.test.js +56 -0
- package/lib/types/mobile-api.d.ts +2 -0
- package/lib/types/mobile-api.d.ts.map +1 -1
- package/lib/types/mobile-api.js +17 -2
- package/lib/types/update.d.ts +5 -1
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +122 -26
- package/package.json +1 -11
- package/src/client/PluginSettingsCard.tsx +30 -5
- package/src/client/RemoteSettingsCard.tsx +18 -0
- package/src/client/UpdateEntry.tsx +10 -2
- package/src/client/deep-link.ts +10 -4
- package/src/client/locales.ts +10 -2
- package/src/client/settings-card.module.css +139 -144
- package/src/client/update-api.ts +22 -2
- package/src/index.ts +14 -1
- package/src/mobile/api.ts +14 -2
- package/src/mobile/mobile-styles.ts +3 -0
- package/src/mobile/views/App.tsx +22 -3
- package/src/mobile/views/ChatView.test.tsx +185 -3
- package/src/mobile/views/ChatView.tsx +102 -8
- package/src/mobile/views/WorkspaceView.test.tsx +66 -0
- package/src/mobile/views/WorkspaceView.tsx +11 -2
- package/src/mobile-api.ts +20 -2
- package/src/update.ts +116 -26
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { setInterval as setInterval$1 } from "node:timers";
|
|
3
|
-
import {
|
|
3
|
+
import { Service } from "@deepseek-ai/cordis";
|
|
4
4
|
import z from "schemastery";
|
|
5
5
|
import { randomBytes } from "node:crypto";
|
|
6
6
|
import { readFile } from "node:fs/promises";
|
|
@@ -11,6 +11,203 @@ import { networkInterfaces } from "node:os";
|
|
|
11
11
|
import { Tunnel, bin, install } from "cloudflared";
|
|
12
12
|
import { dirname, join } from "node:path";
|
|
13
13
|
import { spawn } from "node:child_process";
|
|
14
|
+
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-settings@0.1.0-rc.6_@deepseek-ai+cordis@4.0.1_@deepseek-ai+dsh-brand@0_52495af71738a1095726249ceff4fd7b/node_modules/@deepseek-ai/dsh-settings/lib/index.js
|
|
15
|
+
/**
|
|
16
|
+
* Structural secret redaction for settings values. `role('secret')` fields are
|
|
17
|
+
* removed from a value before it crosses a wire boundary; a sidecar records
|
|
18
|
+
* each schema-declared secret position and whether it currently holds a value,
|
|
19
|
+
* so a configuration surface can render a write-only input without ever
|
|
20
|
+
* receiving the secret itself.
|
|
21
|
+
* @module @deepseek-ai/dsh-settings/redact
|
|
22
|
+
*/
|
|
23
|
+
/** Whether a value is a plain data object the walker may recurse into. */
|
|
24
|
+
function isRecord(value) {
|
|
25
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
function walk(node, value, path, secrets) {
|
|
28
|
+
if (node === void 0) return value;
|
|
29
|
+
if (node.meta?.role === "secret") {
|
|
30
|
+
secrets.push({
|
|
31
|
+
path,
|
|
32
|
+
set: value !== void 0
|
|
33
|
+
});
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
switch (node.type) {
|
|
37
|
+
case "object": {
|
|
38
|
+
const properties = node.dict ?? {};
|
|
39
|
+
const source = isRecord(value) ? value : void 0;
|
|
40
|
+
const rebuilt = {};
|
|
41
|
+
if (source !== void 0) for (const [key, entry] of Object.entries(source)) {
|
|
42
|
+
if (key in properties) continue;
|
|
43
|
+
rebuilt[key] = entry;
|
|
44
|
+
}
|
|
45
|
+
for (const [key, child] of Object.entries(properties)) {
|
|
46
|
+
const stripped = walk(child, source?.[key], [...path, key], secrets);
|
|
47
|
+
if (stripped !== void 0) rebuilt[key] = stripped;
|
|
48
|
+
}
|
|
49
|
+
return source === void 0 && Object.keys(rebuilt).length === 0 ? value : rebuilt;
|
|
50
|
+
}
|
|
51
|
+
case "dict": {
|
|
52
|
+
if (!isRecord(value)) return value;
|
|
53
|
+
const rebuilt = {};
|
|
54
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
55
|
+
const stripped = walk(node.inner, entry, [...path, key], secrets);
|
|
56
|
+
if (stripped !== void 0) rebuilt[key] = stripped;
|
|
57
|
+
}
|
|
58
|
+
return rebuilt;
|
|
59
|
+
}
|
|
60
|
+
case "array":
|
|
61
|
+
if (!Array.isArray(value)) return value;
|
|
62
|
+
return value.map((entry, index) => walk(node.inner, entry, [...path, String(index)], secrets));
|
|
63
|
+
default: return value;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Service Definition for the user-settings capability seam (`ctx.settings`). Providers store one raw document of
|
|
68
|
+
* per-namespace sections; plugins register a namespace schema and read the
|
|
69
|
+
* resolved value, which layers schema defaults, the registrant's composition
|
|
70
|
+
* `base`, and the user document section, in that order.
|
|
71
|
+
* @module @deepseek-ai/dsh-settings
|
|
72
|
+
*/
|
|
73
|
+
const NAMESPACE_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
74
|
+
/**
|
|
75
|
+
* Brand a raw string as a {@link SettingsNamespace}.
|
|
76
|
+
* @param value - candidate namespace; lowercase kebab-case, as in plugin short names.
|
|
77
|
+
* @returns the branded namespace.
|
|
78
|
+
*/
|
|
79
|
+
function settingsNamespace(value) {
|
|
80
|
+
if (!NAMESPACE_PATTERN.test(value)) throw new TypeError(`settings namespace "${value}" must match ${String(NAMESPACE_PATTERN)}`);
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Deep equality over JSON-compatible data (objects, arrays, primitives) — the
|
|
85
|
+
* Service Definition's single change-detection predicate, exported so the invariant
|
|
86
|
+
* companion checks exactly the implementation's relation.
|
|
87
|
+
* @param a - one JSON-compatible value.
|
|
88
|
+
* @param b - the other JSON-compatible value.
|
|
89
|
+
* @returns whether the two values are structurally equal.
|
|
90
|
+
*/
|
|
91
|
+
function deepEqualJson(a, b) {
|
|
92
|
+
if (a === b) return true;
|
|
93
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
94
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
95
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
96
|
+
return a.every((entry, index) => deepEqualJson(entry, b[index]));
|
|
97
|
+
}
|
|
98
|
+
const left = a;
|
|
99
|
+
const right = b;
|
|
100
|
+
const keys = Object.keys(left);
|
|
101
|
+
if (keys.length !== Object.keys(right).length) return false;
|
|
102
|
+
return keys.every((key) => key in right && deepEqualJson(left[key], right[key]));
|
|
103
|
+
}
|
|
104
|
+
/** Whether a value is a plain data object (not an array, null, or class instance). */
|
|
105
|
+
function isPlainObject(value) {
|
|
106
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
107
|
+
const proto = Object.getPrototypeOf(value);
|
|
108
|
+
return proto === Object.prototype || proto === null;
|
|
109
|
+
}
|
|
110
|
+
/** Apply one path op to a detached section, returning the next section. */
|
|
111
|
+
function applyPathOp(section, op) {
|
|
112
|
+
const [head, ...rest] = op.path;
|
|
113
|
+
if (head === void 0) {
|
|
114
|
+
if (op.op === "unset") return {};
|
|
115
|
+
if (!isPlainObject(op.value)) throw new TypeError("settings mutate: setting the section root requires a plain object");
|
|
116
|
+
return { ...op.value };
|
|
117
|
+
}
|
|
118
|
+
if (rest.length === 0) {
|
|
119
|
+
if (op.op === "set") return {
|
|
120
|
+
...section,
|
|
121
|
+
[head]: op.value
|
|
122
|
+
};
|
|
123
|
+
const { [head]: _removed, ...kept } = section;
|
|
124
|
+
return kept;
|
|
125
|
+
}
|
|
126
|
+
const child = section[head];
|
|
127
|
+
if (!isPlainObject(child)) {
|
|
128
|
+
if (op.op === "unset") return section;
|
|
129
|
+
return {
|
|
130
|
+
...section,
|
|
131
|
+
[head]: applyPathOp({}, {
|
|
132
|
+
...op,
|
|
133
|
+
path: rest
|
|
134
|
+
})
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
...section,
|
|
139
|
+
[head]: applyPathOp(child, {
|
|
140
|
+
...op,
|
|
141
|
+
path: rest
|
|
142
|
+
})
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Layer `over` onto `under`: plain objects merge recursively, every other
|
|
147
|
+
* value (arrays included) replaces the lower layer wholesale. `over` never
|
|
148
|
+
* carries `undefined` entries — sections come from parsed documents and write
|
|
149
|
+
* snapshots pass {@link cloneJsonShaped}, which strips them so a sparse patch
|
|
150
|
+
* cannot erase lower keys.
|
|
151
|
+
*/
|
|
152
|
+
function mergeLayers(under, over) {
|
|
153
|
+
if (over === void 0) return under;
|
|
154
|
+
if (!isPlainObject(under) || !isPlainObject(over)) return over;
|
|
155
|
+
const merged = { ...under };
|
|
156
|
+
for (const [key, value] of Object.entries(over)) merged[key] = key in merged ? mergeLayers(merged[key], value) : value;
|
|
157
|
+
return merged;
|
|
158
|
+
}
|
|
159
|
+
/** Recursively freeze one resolved value so handed-out snapshots stay immutable. */
|
|
160
|
+
function deepFreeze(value) {
|
|
161
|
+
if (typeof value !== "object" || value === null || Object.isFrozen(value)) return value;
|
|
162
|
+
for (const entry of Object.values(value)) deepFreeze(entry);
|
|
163
|
+
return Object.freeze(value);
|
|
164
|
+
}
|
|
165
|
+
Service.init;
|
|
166
|
+
/**
|
|
167
|
+
* Value mirror of the `FiberState` members {@link isUnloading} compares
|
|
168
|
+
* against: a const enum has no runtime object to import, and the value is
|
|
169
|
+
* needed at runtime (same rationale as the CLI boot driver's mirror).
|
|
170
|
+
*/
|
|
171
|
+
const FIBER_DISPOSED = 4;
|
|
172
|
+
const FIBER_UNLOADING = 5;
|
|
173
|
+
/** Whether the consumer's own fiber is tearing down (not just losing the settings service). */
|
|
174
|
+
function isUnloading(ctx) {
|
|
175
|
+
const state = ctx.fiber.state;
|
|
176
|
+
return state === FIBER_UNLOADING || state === FIBER_DISPOSED;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Install the canonical optional-settings consumer wiring: while a settings
|
|
180
|
+
* service exists, register `ns` with the consumer's composition entry as the
|
|
181
|
+
* `base` layer and point the source thunk at the resolved scope; when the
|
|
182
|
+
* service goes away (disposal, provider reload), fall back to the entry so
|
|
183
|
+
* the consumer keeps working exactly as composed. The registration rides the
|
|
184
|
+
* scoped fiber, so no settings service ever mounted means none of this runs.
|
|
185
|
+
* @param ctx - consumer plugin context owning the wiring.
|
|
186
|
+
* @param ns - the consumer-owned settings namespace.
|
|
187
|
+
* @param schema - schema resolving the namespace (typically the plugin Config).
|
|
188
|
+
* @param entry - the consumer's composition entry config, used as `base`.
|
|
189
|
+
* @param hooks - source sink and change notification.
|
|
190
|
+
*/
|
|
191
|
+
function installSettingsSection(ctx, ns, schema, entry, hooks) {
|
|
192
|
+
ctx.inject(["settings"], (sctx) => {
|
|
193
|
+
const scope = sctx.settings.register(ns, schema, {
|
|
194
|
+
base: entry,
|
|
195
|
+
...hooks.validate === void 0 ? {} : { validate: hooks.validate }
|
|
196
|
+
});
|
|
197
|
+
hooks.setSource(() => scope.get());
|
|
198
|
+
sctx.effect(() => () => {
|
|
199
|
+
if (isUnloading(ctx)) return;
|
|
200
|
+
hooks.setSource(() => entry);
|
|
201
|
+
hooks.onChange();
|
|
202
|
+
});
|
|
203
|
+
hooks.onChange();
|
|
204
|
+
scope.watch(() => {
|
|
205
|
+
if (isUnloading(ctx)) return;
|
|
206
|
+
hooks.onChange();
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
//#endregion
|
|
14
211
|
//#region src/pairing.ts
|
|
15
212
|
/**
|
|
16
213
|
* Pairing state machine: one active one-time token, a device-session table,
|
|
@@ -803,6 +1000,12 @@ const MOBILE_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
|
803
1000
|
"session.selectModel",
|
|
804
1001
|
"session.rename"
|
|
805
1002
|
]);
|
|
1003
|
+
/**
|
|
1004
|
+
* Locally answered display-preference method (the phone's read-only
|
|
1005
|
+
* surface preferences; never proxied to the host ApiProxy and never a
|
|
1006
|
+
* settings-domain write).
|
|
1007
|
+
*/
|
|
1008
|
+
const MOBILE_PREFERENCES_METHOD = "mobile.preferences";
|
|
806
1009
|
/** One session.list page (thin phones load incrementally). */
|
|
807
1010
|
const SESSION_PAGE_SIZE = 20;
|
|
808
1011
|
/** Encode one list position as an opaque continuation cursor. */
|
|
@@ -836,7 +1039,7 @@ const MOBILE_API_METHOD_PREFIX = `${MOBILE_API_PREFIX}/`;
|
|
|
836
1039
|
* @returns the routes to register on webServer.
|
|
837
1040
|
*/
|
|
838
1041
|
function makeMobileApiRoutes(deps) {
|
|
839
|
-
const { service, apiProxy } = deps;
|
|
1042
|
+
const { service, apiProxy, mobileEnterToSend } = deps;
|
|
840
1043
|
/** The phone gate: a live paired-device cookie, or nothing else proceeds. */
|
|
841
1044
|
const gateOk = (req) => {
|
|
842
1045
|
const deviceId = readCookie(req.headers.cookie, service.config.cookieName);
|
|
@@ -874,7 +1077,8 @@ function makeMobileApiRoutes(deps) {
|
|
|
874
1077
|
return;
|
|
875
1078
|
}
|
|
876
1079
|
const method = pathname.slice(MOBILE_API_METHOD_PREFIX.length);
|
|
877
|
-
|
|
1080
|
+
const local = method === MOBILE_PREFERENCES_METHOD;
|
|
1081
|
+
if (!MOBILE_ALLOWLIST.has(method) && !local) {
|
|
878
1082
|
writeJson(res, 403, {
|
|
879
1083
|
ok: false,
|
|
880
1084
|
error: {
|
|
@@ -909,6 +1113,17 @@ function makeMobileApiRoutes(deps) {
|
|
|
909
1113
|
});
|
|
910
1114
|
return;
|
|
911
1115
|
}
|
|
1116
|
+
if (local) {
|
|
1117
|
+
writeJson(res, 200, {
|
|
1118
|
+
type: "server-response",
|
|
1119
|
+
rpcId,
|
|
1120
|
+
result: {
|
|
1121
|
+
ok: true,
|
|
1122
|
+
value: { mobileEnterToSend: mobileEnterToSend() }
|
|
1123
|
+
}
|
|
1124
|
+
});
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
912
1127
|
try {
|
|
913
1128
|
const response = await dispatch(apiProxy, method, parsed?.payload, rpcId);
|
|
914
1129
|
writeJson(res, 200, response);
|
|
@@ -1500,30 +1715,85 @@ async function checkUpdates(deps) {
|
|
|
1500
1715
|
/** Cap on captured pnpm output (keeps error payloads bounded). */
|
|
1501
1716
|
const OUTPUT_CAP = 16 * 1024;
|
|
1502
1717
|
/**
|
|
1503
|
-
*
|
|
1718
|
+
* Windows cmd command-not-found stderr. With shell:true a missing shim
|
|
1719
|
+
* exits with code 1 (cmd cannot report ENOENT), so the fallback chain
|
|
1720
|
+
* detects this message instead of the spawn error event.
|
|
1721
|
+
*/
|
|
1722
|
+
const WIN_CMD_MISSING_RE = /not recognized as an internal or external command/i;
|
|
1723
|
+
/**
|
|
1724
|
+
* Run the update inside the profile directory. Tries pnpm first, falls back
|
|
1725
|
+
* to corepack and then npx when the previous command is missing (ENOENT);
|
|
1726
|
+
* all candidates share one hard timeout and keep accumulating output.
|
|
1504
1727
|
* @param deps - profile dir, package list, and spawn/timeout seams.
|
|
1505
1728
|
* @returns the outcome with captured output.
|
|
1506
1729
|
*/
|
|
1507
1730
|
function runUpdate(deps) {
|
|
1508
1731
|
return new Promise((resolve) => {
|
|
1509
|
-
const
|
|
1732
|
+
const spawnImpl = deps.spawnImpl ?? spawn;
|
|
1733
|
+
const packages = deps.packages;
|
|
1734
|
+
const platform = deps.platform ?? process.platform;
|
|
1735
|
+
const spawnOptions = {
|
|
1510
1736
|
cwd: deps.profileDir,
|
|
1511
1737
|
stdio: [
|
|
1512
1738
|
"ignore",
|
|
1513
1739
|
"pipe",
|
|
1514
1740
|
"pipe"
|
|
1515
|
-
]
|
|
1516
|
-
|
|
1741
|
+
],
|
|
1742
|
+
...platform === "win32" ? { shell: true } : {}
|
|
1743
|
+
};
|
|
1744
|
+
const candidates = [
|
|
1745
|
+
{
|
|
1746
|
+
command: "pnpm",
|
|
1747
|
+
args: ["update", ...packages]
|
|
1748
|
+
},
|
|
1749
|
+
{
|
|
1750
|
+
command: "corepack",
|
|
1751
|
+
args: [
|
|
1752
|
+
"pnpm",
|
|
1753
|
+
"update",
|
|
1754
|
+
...packages
|
|
1755
|
+
]
|
|
1756
|
+
},
|
|
1757
|
+
{
|
|
1758
|
+
command: "npx",
|
|
1759
|
+
args: [
|
|
1760
|
+
"--yes",
|
|
1761
|
+
"pnpm",
|
|
1762
|
+
"update",
|
|
1763
|
+
...packages
|
|
1764
|
+
]
|
|
1765
|
+
}
|
|
1766
|
+
];
|
|
1517
1767
|
let output = "";
|
|
1768
|
+
let currentOutput = "";
|
|
1518
1769
|
const append = (chunk) => {
|
|
1519
1770
|
output += chunk.toString("utf8");
|
|
1520
1771
|
if (output.length > OUTPUT_CAP) output = output.slice(output.length - OUTPUT_CAP);
|
|
1772
|
+
currentOutput += chunk.toString("utf8");
|
|
1773
|
+
if (currentOutput.length > OUTPUT_CAP) currentOutput = currentOutput.slice(currentOutput.length - OUTPUT_CAP);
|
|
1774
|
+
};
|
|
1775
|
+
let currentChild;
|
|
1776
|
+
let finished = false;
|
|
1777
|
+
const finish = (result) => {
|
|
1778
|
+
if (finished) return;
|
|
1779
|
+
finished = true;
|
|
1780
|
+
clearTimeout(timer);
|
|
1781
|
+
resolve(result);
|
|
1521
1782
|
};
|
|
1522
|
-
child.stdout?.on("data", append);
|
|
1523
|
-
child.stderr?.on("data", append);
|
|
1524
1783
|
const timer = setTimeout(() => {
|
|
1525
|
-
|
|
1526
|
-
|
|
1784
|
+
if (finished) return;
|
|
1785
|
+
if (platform === "win32") {
|
|
1786
|
+
const pid = currentChild?.pid;
|
|
1787
|
+
if (pid !== void 0 && pid > 0) try {
|
|
1788
|
+
spawnImpl("taskkill", [
|
|
1789
|
+
"/pid",
|
|
1790
|
+
String(pid),
|
|
1791
|
+
"/t",
|
|
1792
|
+
"/f"
|
|
1793
|
+
], { stdio: "ignore" });
|
|
1794
|
+
} catch {}
|
|
1795
|
+
} else currentChild?.kill("SIGTERM");
|
|
1796
|
+
finish({
|
|
1527
1797
|
ok: false,
|
|
1528
1798
|
exitCode: null,
|
|
1529
1799
|
output,
|
|
@@ -1531,27 +1801,59 @@ function runUpdate(deps) {
|
|
|
1531
1801
|
errorCode: "timeout"
|
|
1532
1802
|
});
|
|
1533
1803
|
}, deps.timeoutMs ?? 10 * 6e4);
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1804
|
+
const runCandidate = (index) => {
|
|
1805
|
+
if (finished) return;
|
|
1806
|
+
if (index >= candidates.length) {
|
|
1807
|
+
finish({
|
|
1808
|
+
ok: false,
|
|
1809
|
+
exitCode: null,
|
|
1810
|
+
output,
|
|
1811
|
+
error: "pnpm not found on PATH (tried pnpm, corepack, npx); install pnpm and restart the app",
|
|
1812
|
+
errorCode: "pnpm-missing"
|
|
1813
|
+
});
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
const candidate = candidates[index];
|
|
1817
|
+
currentOutput = "";
|
|
1818
|
+
const child = spawnImpl(candidate.command, candidate.args, spawnOptions);
|
|
1819
|
+
currentChild = child;
|
|
1820
|
+
let settled = false;
|
|
1821
|
+
const once = (fn) => {
|
|
1822
|
+
if (settled) return;
|
|
1823
|
+
settled = true;
|
|
1824
|
+
fn();
|
|
1825
|
+
};
|
|
1826
|
+
child.stdout?.on("data", append);
|
|
1827
|
+
child.stderr?.on("data", append);
|
|
1828
|
+
child.on("error", (error) => {
|
|
1829
|
+
once(() => {
|
|
1830
|
+
if (error.code === "ENOENT") runCandidate(index + 1);
|
|
1831
|
+
else finish({
|
|
1832
|
+
ok: false,
|
|
1833
|
+
exitCode: null,
|
|
1834
|
+
output,
|
|
1835
|
+
error: error.message,
|
|
1836
|
+
errorCode: void 0
|
|
1837
|
+
});
|
|
1838
|
+
});
|
|
1543
1839
|
});
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1840
|
+
child.on("close", (code) => {
|
|
1841
|
+
if (settled || finished) return;
|
|
1842
|
+
if (platform === "win32" && code !== 0 && WIN_CMD_MISSING_RE.test(currentOutput)) {
|
|
1843
|
+
runCandidate(index + 1);
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1846
|
+
settled = true;
|
|
1847
|
+
finish({
|
|
1848
|
+
ok: code === 0,
|
|
1849
|
+
exitCode: code,
|
|
1850
|
+
output,
|
|
1851
|
+
error: code === 0 ? void 0 : "pnpm exited with code " + String(code),
|
|
1852
|
+
...code === 0 ? {} : { errorCode: "pnpm-failed" }
|
|
1853
|
+
});
|
|
1553
1854
|
});
|
|
1554
|
-
}
|
|
1855
|
+
};
|
|
1856
|
+
runCandidate(0);
|
|
1555
1857
|
});
|
|
1556
1858
|
}
|
|
1557
1859
|
//#endregion
|
|
@@ -1645,6 +1947,7 @@ const Config = z.object({
|
|
|
1645
1947
|
requirePairingForLan: z.boolean().default(true),
|
|
1646
1948
|
publicBaseUrl: z.string(),
|
|
1647
1949
|
autoTunnel: z.boolean().default(false),
|
|
1950
|
+
mobileEnterToSend: z.boolean().default(true),
|
|
1648
1951
|
enabled: z.boolean().default(true)
|
|
1649
1952
|
});
|
|
1650
1953
|
/** Presence sweep cadence (a stale device flips to disconnected within two sweeps). */
|
|
@@ -1658,6 +1961,7 @@ const DEFAULTS = {
|
|
|
1658
1961
|
requirePairingForLan: true,
|
|
1659
1962
|
publicBaseUrl: void 0,
|
|
1660
1963
|
autoTunnel: false,
|
|
1964
|
+
mobileEnterToSend: true,
|
|
1661
1965
|
enabled: true
|
|
1662
1966
|
};
|
|
1663
1967
|
/**
|
|
@@ -1674,6 +1978,7 @@ function apply(ctx, config) {
|
|
|
1674
1978
|
requirePairingForLan: config?.requirePairingForLan ?? DEFAULTS.requirePairingForLan,
|
|
1675
1979
|
publicBaseUrl: config?.publicBaseUrl,
|
|
1676
1980
|
autoTunnel: config?.autoTunnel ?? DEFAULTS.autoTunnel,
|
|
1981
|
+
mobileEnterToSend: config?.mobileEnterToSend ?? DEFAULTS.mobileEnterToSend,
|
|
1677
1982
|
enabled: config?.enabled ?? DEFAULTS.enabled
|
|
1678
1983
|
};
|
|
1679
1984
|
let current = () => config ?? {};
|
|
@@ -1687,6 +1992,7 @@ function apply(ctx, config) {
|
|
|
1687
1992
|
requirePairingForLan: value.requirePairingForLan ?? DEFAULTS.requirePairingForLan,
|
|
1688
1993
|
publicBaseUrl: value.publicBaseUrl,
|
|
1689
1994
|
autoTunnel: value.autoTunnel ?? DEFAULTS.autoTunnel,
|
|
1995
|
+
mobileEnterToSend: value.mobileEnterToSend ?? DEFAULTS.mobileEnterToSend,
|
|
1690
1996
|
enabled: value.enabled ?? DEFAULTS.enabled
|
|
1691
1997
|
};
|
|
1692
1998
|
};
|
|
@@ -1771,7 +2077,8 @@ function apply(ctx, config) {
|
|
|
1771
2077
|
...makeMobileRoutes(),
|
|
1772
2078
|
...apiProxy !== void 0 ? makeMobileApiRoutes({
|
|
1773
2079
|
service,
|
|
1774
|
-
apiProxy
|
|
2080
|
+
apiProxy,
|
|
2081
|
+
mobileEnterToSend: () => resolve().mobileEnterToSend
|
|
1775
2082
|
}) : [],
|
|
1776
2083
|
...updateRoutes
|
|
1777
2084
|
];
|