@livedesk/client 0.1.240 → 0.1.241

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.md CHANGED
@@ -17,14 +17,14 @@ npx -y --prefer-online livedesk@latest client 3
17
17
  advanced diagnostics. The public command above keeps Hub and client versions
18
18
  aligned.
19
19
 
20
- The default flow lets the local Client runtime own Google authorization,
21
- callback exchange, and saved authentication as one path. The browser only
22
- shows progress; it does not maintain a second session that must be transferred
23
- back to the Client. LiveDesk then waits for the active Hub published by the
24
- same account and connects locally. If the Hub is not
25
- ready yet, the client waits for a Hub-online event and polls the registry at
26
- most every six seconds as a fallback. Missing a wake event can never leave a
27
- Client asleep for minutes. If a
20
+ The default flow lets the local Client runtime own Google authorization,
21
+ callback exchange, and saved authentication as one path. The browser only
22
+ shows progress; it does not maintain a second session that must be transferred
23
+ back to the Client. LiveDesk then waits for the active Hub published by the
24
+ same account and connects locally. If the Hub is not
25
+ ready yet, the client waits for a Hub-online event and polls the registry at
26
+ most every six seconds as a fallback. Missing a wake event can never leave a
27
+ Client asleep for minutes. If a
28
28
  connected Hub is replaced, the launcher also leaves the stale endpoint and
29
29
  discovers the newly published Hub without another Google login. Omit the number
30
30
  for first-available placement, or pass `1` to `999` to pin this machine to a
@@ -34,11 +34,11 @@ On Windows, check **Start with Windows** on the connection page to reconnect thi
34
34
  client automatically after reboot. The startup entry reuses the saved Google
35
35
  session and only opens the connection page again if sign-in is needed.
36
36
 
37
- On the Hub computer, open the LiveDesk dashboard, sign in, and run **Sync
38
- Server** once. The dashboard transfers the signed-in session to the local Hub;
39
- the Hub process then keeps its own local address and private pair token
40
- refreshed in the LiveDesk Supabase registry. The dashboard can be closed after
41
- that while the Hub remains running.
37
+ On the Hub computer, open the LiveDesk dashboard, sign in, and run **Sync
38
+ Server** once. The dashboard transfers the signed-in session to the local Hub;
39
+ the Hub process then keeps its own local address and private pair token
40
+ refreshed in the LiveDesk Supabase registry. The dashboard can be closed after
41
+ that while the Hub remains running.
42
42
 
43
43
  Supabase Auth must allow the CLI callback URL:
44
44
 
@@ -52,9 +52,9 @@ the matching callback URL to Supabase Auth redirect URLs.
52
52
  The client registers the device, sends status heartbeats, can return
53
53
  Hub-requested thumbnails, can stream a focused view-only live screen, and
54
54
  can receive safe task-only instructions. The C# RemoteFast engine also supports
55
- keyboard/mouse control on Windows, macOS, and Linux/X11, file transfer, and
56
- system-audio playback from Windows, macOS, and Linux. Linux captures the default
57
- output through PipeWire `pw-record` or PulseAudio `parec`. macOS control requires Accessibility
55
+ keyboard/mouse control on Windows, macOS, and Linux/X11, file transfer, and
56
+ system-audio playback from Windows, macOS, and Linux. Linux captures the default
57
+ output through PipeWire `pw-record` or PulseAudio `parec`. macOS control requires Accessibility
58
58
  permission for the terminal app that started LiveDesk; screen streaming and
59
59
  system-audio capture require Screen Recording permission. LiveDesk checks these
60
60
  permissions without opening repeated system dialogs and prints the matching
@@ -62,9 +62,9 @@ System Settings path when access is missing; after granting access, restart the
62
62
  client. When file transfer is enabled by the Hub, received files are saved
63
63
  to `Desktop/LiveDeskFiles` unless the Hub sets another destination folder.
64
64
 
65
- By default, the launcher uses the packaged C# RemoteFast engine when supported.
66
- It falls back to the Node engine only when a compatible RemoteFast runtime is
67
- unavailable. Windows, macOS, and Linux all try RemoteFast first so
65
+ By default, the launcher uses the packaged C# RemoteFast engine when supported.
66
+ It falls back to the Node engine only when a compatible RemoteFast runtime is
67
+ unavailable. Windows, macOS, and Linux all try RemoteFast first so
68
68
  the Hub can request the Mode 3 hardware video path when it is available.
69
69
  RemoteFast is shipped as a platform-specific self-contained .NET executable,
70
70
  so client computers do not need a separate .NET installation.
@@ -91,7 +91,7 @@ Frame pipeline roadmap:
91
91
 
92
92
  - Mode 1: `mode1-jpeg` - current test path using screen capture, resize, and JPEG binary frames.
93
93
  - Mode 2: `mode2-lzo` - cross-platform wall path using independent RGB565LE frames capped at 320x180 and compressed as LZO1X blocks. The wall defaults to 8 fps. macOS and Linux keep one persistent local capture helper per client process. Legacy `mode2-lz4` settings migrate to this mode.
94
- - Mode 3: `mode3-h264-hw` - OS-specific H.264 path. Windows tries Media Foundation/NVENC/QSV/AMF, macOS prefers ScreenCaptureKit plus VideoToolbox, and Linux tries NVENC/VAAPI/QSV before falling back to the bundled ffmpeg `libx264` encoder. Linux users do not need to install ffmpeg separately when starting LiveDesk through the published npm package. macOS emits one startup key frame and then an approximately half-second recovery GOP. The launcher uses bundled ffmpeg for fallback paths unless `LIVEDESK_FFMPEG` points to a custom binary. On macOS, set `LIVEDESK_FFMPEG_AVFOUNDATION_INPUT` only when the ScreenCaptureKit helper is unavailable and the AVFoundation fallback input is not `1:none`.
94
+ - Mode 3: `mode3-h264-hw` - OS-specific H.264 path. Windows tries Media Foundation/NVENC/QSV/AMF, macOS prefers ScreenCaptureKit plus VideoToolbox, and Linux tries NVENC/VAAPI/QSV before falling back to the bundled ffmpeg `libx264` encoder. Linux users do not need to install ffmpeg separately when starting LiveDesk through the published npm package. macOS emits one startup key frame and then an approximately half-second recovery GOP. The launcher uses bundled ffmpeg for fallback paths unless `LIVEDESK_FFMPEG` points to a custom binary. On macOS, set `LIVEDESK_FFMPEG_AVFOUNDATION_INPUT` only when the ScreenCaptureKit helper is unavailable and the AVFoundation fallback input is not `1:none`.
95
95
 
96
96
  Legacy mode names such as `remote-fast` and `remote-quality` are treated as
97
97
  Mode 1 aliases.
@@ -1,26 +1,26 @@
1
- # Third-Party Notices
2
-
3
- ## lzokay
4
-
5
- LiveDesk's LZO1X block encoder and decoder follow the instruction layout of
6
- [lzokay](https://github.com/AxioDL/lzokay), Copyright (c) 2018 Jack Andersen.
7
-
8
- The MIT License
9
-
10
- Permission is hereby granted, free of charge, to any person obtaining a copy of
11
- this software and associated documentation files (the "Software"), to deal in
12
- the Software without restriction, including without limitation the rights to
13
- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
14
- the Software, and to permit persons to whom the Software is furnished to do so,
15
- subject to the following conditions:
16
-
17
- The above copyright notice and this permission notice shall be included in all
18
- copies or substantial portions of the Software.
19
-
20
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
- SOFTWARE.
1
+ # Third-Party Notices
2
+
3
+ ## lzokay
4
+
5
+ LiveDesk's LZO1X block encoder and decoder follow the instruction layout of
6
+ [lzokay](https://github.com/AxioDL/lzokay), Copyright (c) 2018 Jack Andersen.
7
+
8
+ The MIT License
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
11
+ this software and associated documentation files (the "Software"), to deal in
12
+ the Software without restriction, including without limitation the rights to
13
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
14
+ the Software, and to permit persons to whom the Software is furnished to do so,
15
+ subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
@@ -1,23 +1,23 @@
1
- import { readFileSync } from 'node:fs';
2
- import { dirname, join, resolve } from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
-
5
- const __dirname = dirname(fileURLToPath(import.meta.url));
6
- const packageRoot = resolve(__dirname, '..');
7
-
8
- export function readClientPackageVersion() {
9
- try {
10
- const packageInfo = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'));
11
- return String(packageInfo.version || '0.0.0');
12
- } catch {
13
- return '0.0.0';
14
- }
15
- }
16
-
17
- export function formatClientVersionBanner(env = process.env) {
18
- const launcherName = String(env.LIVEDESK_NPM_LAUNCHER_NAME || '').trim();
19
- const launcherVersion = String(env.LIVEDESK_NPM_LAUNCHER_VERSION || '').trim();
20
- const productName = launcherName || 'livedesk';
21
- const productVersion = launcherVersion || readClientPackageVersion();
22
- return `[LiveDesk Client] ${productName}@${productVersion}`;
23
- }
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ const packageRoot = resolve(__dirname, '..');
7
+
8
+ export function readClientPackageVersion() {
9
+ try {
10
+ const packageInfo = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'));
11
+ return String(packageInfo.version || '0.0.0');
12
+ } catch {
13
+ return '0.0.0';
14
+ }
15
+ }
16
+
17
+ export function formatClientVersionBanner(env = process.env) {
18
+ const launcherName = String(env.LIVEDESK_NPM_LAUNCHER_NAME || '').trim();
19
+ const launcherVersion = String(env.LIVEDESK_NPM_LAUNCHER_VERSION || '').trim();
20
+ const productName = launcherName || 'livedesk';
21
+ const productVersion = launcherVersion || readClientPackageVersion();
22
+ return `[LiveDesk Client] ${productName}@${productVersion}`;
23
+ }
@@ -1,23 +1,23 @@
1
1
  #!/usr/bin/env node
2
-
3
- import { dirname, join } from 'node:path';
4
- import { fileURLToPath } from 'node:url';
5
- import { spawn } from 'node:child_process';
6
-
7
- const launcherPath = join(dirname(fileURLToPath(import.meta.url)), 'livedesk-client.js');
8
- const child = spawn(process.execPath, [launcherPath, '--fast', ...process.argv.slice(2)], {
9
- stdio: 'inherit',
10
- windowsHide: true
11
- });
12
-
13
- child.once('error', error => {
14
- console.error(`Failed to launch C# RemoteFast: ${error.message}`);
15
- process.exitCode = 1;
16
- });
17
- child.once('exit', (code, signal) => {
18
- if (signal) {
19
- process.kill(process.pid, signal);
20
- return;
21
- }
22
- process.exit(code ?? 0);
23
- });
2
+
3
+ import { dirname, join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { spawn } from 'node:child_process';
6
+
7
+ const launcherPath = join(dirname(fileURLToPath(import.meta.url)), 'livedesk-client.js');
8
+ const child = spawn(process.execPath, [launcherPath, '--fast', ...process.argv.slice(2)], {
9
+ stdio: 'inherit',
10
+ windowsHide: true
11
+ });
12
+
13
+ child.once('error', error => {
14
+ console.error(`Failed to launch C# RemoteFast: ${error.message}`);
15
+ process.exitCode = 1;
16
+ });
17
+ child.once('exit', (code, signal) => {
18
+ if (signal) {
19
+ process.kill(process.pid, signal);
20
+ return;
21
+ }
22
+ process.exit(code ?? 0);
23
+ });
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  import net from 'net';
4
4
  import os from 'os';
@@ -251,7 +251,7 @@ async function waitForClientUpdateShutdownSignal(
251
251
  return { ready: false, error };
252
252
  }
253
253
 
254
- function markClientUpdateConnected() {
254
+ function markClientUpdateConnected() {
255
255
  const operationId = String(process.env.LIVEDESK_CLIENT_UPDATE_OPERATION_ID || '').trim();
256
256
  if (!operationId) return;
257
257
  void writeClientUpdateState('connected', {
@@ -262,59 +262,59 @@ function markClientUpdateConnected() {
262
262
  error: ''
263
263
  }).catch(error => {
264
264
  console.error(`LiveDesk client update diagnostics could not be persisted: ${error?.message || error}`);
265
- });
266
- }
267
-
268
- async function publishClientUpdateSupervisorProof(socket) {
269
- const operationId = String(process.env.LIVEDESK_CLIENT_UPDATE_OPERATION_ID || '').trim();
270
- const attemptId = String(process.env.LIVEDESK_CLIENT_UPDATE_ATTEMPT_ID || '').trim();
271
- const expectedLauncherPid = Number(process.env.LIVEDESK_CLIENT_PARENT_PID || 0);
272
- if (!operationId || !attemptId || !Number.isInteger(expectedLauncherPid) || expectedLauncherPid <= 1) return;
273
- const deadline = Date.now() + 60_000;
274
- while (Date.now() < deadline && !socket.destroyed) {
275
- try {
276
- const state = JSON.parse(await fs.readFile(getClientUpdateStatePath(), 'utf8'));
277
- if (String(state?.operationId || '') !== operationId
278
- || String(state?.attemptId || '') !== attemptId) {
279
- await wait(100);
280
- continue;
281
- }
282
- if (['failed', 'restored', 'cancelled'].includes(String(state.stage || ''))) return;
283
- const launcherPid = Number(state.launcherPid || 0);
284
- const agentPid = Number(state.agentPid || 0);
285
- const supervisorPid = Number(state.supervisorPid || 0);
286
- const completedAt = String(state.completedAt || '').trim();
287
- const exactSupervisorCompletion = state.stage === 'connected'
288
- && state.restartVerified === true
289
- && Number.isFinite(Date.parse(completedAt))
290
- && launcherPid === expectedLauncherPid
291
- && agentPid === process.pid
292
- && Number.isInteger(supervisorPid)
293
- && supervisorPid > 1
294
- && String(state.productVersion || '') === PRODUCT_VERSION
295
- && String(state.agentVersion || '') === AGENT_VERSION
296
- && String(state.targetProductVersion || '') === PRODUCT_VERSION
297
- && String(state.targetAgentVersion || state.targetVersion || '') === AGENT_VERSION;
298
- if (exactSupervisorCompletion) {
299
- writeJsonLine(socket, {
300
- type: 'client.update.verified',
301
- operationId,
302
- attemptId,
303
- launcherPid,
304
- agentPid,
305
- supervisorPid,
306
- productVersion: PRODUCT_VERSION,
307
- agentVersion: AGENT_VERSION,
308
- completedAt
309
- });
310
- return;
311
- }
312
- } catch {
313
- // The exact package supervisor may be replacing the atomic state file.
314
- }
315
- await wait(100);
316
- }
317
- }
265
+ });
266
+ }
267
+
268
+ async function publishClientUpdateSupervisorProof(socket) {
269
+ const operationId = String(process.env.LIVEDESK_CLIENT_UPDATE_OPERATION_ID || '').trim();
270
+ const attemptId = String(process.env.LIVEDESK_CLIENT_UPDATE_ATTEMPT_ID || '').trim();
271
+ const expectedLauncherPid = Number(process.env.LIVEDESK_CLIENT_PARENT_PID || 0);
272
+ if (!operationId || !attemptId || !Number.isInteger(expectedLauncherPid) || expectedLauncherPid <= 1) return;
273
+ const deadline = Date.now() + 60_000;
274
+ while (Date.now() < deadline && !socket.destroyed) {
275
+ try {
276
+ const state = JSON.parse(await fs.readFile(getClientUpdateStatePath(), 'utf8'));
277
+ if (String(state?.operationId || '') !== operationId
278
+ || String(state?.attemptId || '') !== attemptId) {
279
+ await wait(100);
280
+ continue;
281
+ }
282
+ if (['failed', 'restored', 'cancelled'].includes(String(state.stage || ''))) return;
283
+ const launcherPid = Number(state.launcherPid || 0);
284
+ const agentPid = Number(state.agentPid || 0);
285
+ const supervisorPid = Number(state.supervisorPid || 0);
286
+ const completedAt = String(state.completedAt || '').trim();
287
+ const exactSupervisorCompletion = state.stage === 'connected'
288
+ && state.restartVerified === true
289
+ && Number.isFinite(Date.parse(completedAt))
290
+ && launcherPid === expectedLauncherPid
291
+ && agentPid === process.pid
292
+ && Number.isInteger(supervisorPid)
293
+ && supervisorPid > 1
294
+ && String(state.productVersion || '') === PRODUCT_VERSION
295
+ && String(state.agentVersion || '') === AGENT_VERSION
296
+ && String(state.targetProductVersion || '') === PRODUCT_VERSION
297
+ && String(state.targetAgentVersion || state.targetVersion || '') === AGENT_VERSION;
298
+ if (exactSupervisorCompletion) {
299
+ writeJsonLine(socket, {
300
+ type: 'client.update.verified',
301
+ operationId,
302
+ attemptId,
303
+ launcherPid,
304
+ agentPid,
305
+ supervisorPid,
306
+ productVersion: PRODUCT_VERSION,
307
+ agentVersion: AGENT_VERSION,
308
+ completedAt
309
+ });
310
+ return;
311
+ }
312
+ } catch {
313
+ // The exact package supervisor may be replacing the atomic state file.
314
+ }
315
+ await wait(100);
316
+ }
317
+ }
318
318
 
319
319
  function printHelp() {
320
320
  console.log(`
@@ -2023,10 +2023,10 @@ function connectOnce(options, deviceId) {
2023
2023
  remoteFiles: true,
2024
2024
  fileTransferMaxBytes: MAX_FILE_TRANSFER_BYTES,
2025
2025
  computerAgent: options.taskEnabled,
2026
- taskDispatch: options.taskEnabled,
2027
- clientUpdate: true,
2028
- clientUpdateVerifiedProof: true,
2029
- productVersion: PRODUCT_VERSION,
2026
+ taskDispatch: options.taskEnabled,
2027
+ clientUpdate: true,
2028
+ clientUpdateVerifiedProof: true,
2029
+ productVersion: PRODUCT_VERSION,
2030
2030
  agentApproval: options.taskEnabled,
2031
2031
  agentAudit: options.taskEnabled,
2032
2032
  agentTools: [...NODE_AGENT_OPERATIONS],
@@ -2052,13 +2052,13 @@ function connectOnce(options, deviceId) {
2052
2052
  options.effectivePolicy = message.effectivePolicy && typeof message.effectivePolicy === 'object'
2053
2053
  ? message.effectivePolicy
2054
2054
  : null;
2055
- console.log(`Connected to LiveDesk Hub as ${options.name} (${deviceId})`);
2056
- markClientUpdateConnected();
2057
- void publishClientUpdateSupervisorProof(socket).catch(error => {
2058
- if (!socket.destroyed) {
2059
- console.error(`LiveDesk client update verification proof failed: ${error?.message || error}`);
2060
- }
2061
- });
2055
+ console.log(`Connected to LiveDesk Hub as ${options.name} (${deviceId})`);
2056
+ markClientUpdateConnected();
2057
+ void publishClientUpdateSupervisorProof(socket).catch(error => {
2058
+ if (!socket.destroyed) {
2059
+ console.error(`LiveDesk client update verification proof failed: ${error?.message || error}`);
2060
+ }
2061
+ });
2062
2062
  writeJsonLine(socket, {
2063
2063
  type: 'status',
2064
2064
  status: getStatus(options)