@livedesk/client 0.1.22 → 0.1.24

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
@@ -7,14 +7,14 @@ npx @livedesk/client
7
7
  npx @livedesk/client 3
8
8
  ```
9
9
 
10
- The default flow opens Google sign-in, waits for the active LiveDesk manager
11
- published by the same account, and connects locally. If the manager is not
10
+ The default flow opens Google sign-in, waits for the active LiveDesk Hub
11
+ published by the same account, and connects locally. If the Hub is not
12
12
  ready yet, the client keeps checking every 5 seconds instead of exiting. Omit the
13
13
  number for first-available placement, or pass `1` to `999` to pin this machine
14
14
  to a screen wall slot.
15
15
 
16
- On the manager computer, open the LiveDesk dashboard and sign in first. The
17
- dashboard keeps its local hub address and private pair token refreshed in the
16
+ On the Hub computer, open the LiveDesk dashboard and sign in first. The
17
+ dashboard keeps its local Hub address and private pair token refreshed in the
18
18
  LiveDesk Supabase registry for the signed-in account.
19
19
 
20
20
  Supabase Auth must allow the CLI callback URL:
@@ -27,11 +27,11 @@ If you use a custom port, run `npx @livedesk/client --auth-port 5200` and add
27
27
  the matching callback URL to Supabase Auth redirect URLs.
28
28
 
29
29
  The client registers the device, sends status heartbeats, can return
30
- manager-requested thumbnails, can stream a focused view-only live screen, and
30
+ Hub-requested thumbnails, can stream a focused view-only live screen, and
31
31
  can receive safe task-only instructions. The C# RemoteFast engine also supports
32
32
  keyboard/mouse control, file transfer, and Windows system-audio loopback
33
- playback. When file transfer is enabled by the manager, received files are saved
34
- to `Desktop/LiveDeskFiles` unless the manager sets another destination folder.
33
+ playback. When file transfer is enabled by the Hub, received files are saved
34
+ to `Desktop/LiveDeskFiles` unless the Hub sets another destination folder.
35
35
 
36
36
  By default, the launcher uses the packaged C# RemoteFast engine when supported.
37
37
  It falls back to the Node engine for AI assist or when a compatible RemoteFast
@@ -6,7 +6,7 @@ import path from 'path';
6
6
  import crypto from 'crypto';
7
7
  import { promises as fs, statfsSync } from 'fs';
8
8
 
9
- const AGENT_VERSION = '0.1.22-livedesk.1';
9
+ const AGENT_VERSION = '0.1.24-livedesk.1';
10
10
  const DEFAULT_MANAGER = '127.0.0.1:5197';
11
11
  const DEFAULT_HEARTBEAT_MS = 5000;
12
12
  const DEFAULT_RECONNECT_MS = 5000;
@@ -27,8 +27,8 @@ Usage:
27
27
  npx @livedesk/client connect --manager 127.0.0.1:5197 --pair <token>
28
28
 
29
29
  Options:
30
- --manager <host:port> RemoteHub address. Default: ${DEFAULT_MANAGER}
31
- --pair <token> RemoteHub pairing token. Can also use LIVEDESK_CLIENT_PAIR_TOKEN.
30
+ --manager <host:port> LiveDesk Hub address. Default: ${DEFAULT_MANAGER}
31
+ --pair <token> LiveDesk Hub pairing token. Can also use LIVEDESK_CLIENT_PAIR_TOKEN.
32
32
  --slot <number> Screen wall slot number for this computer.
33
33
  --name <name> Friendly device name. Default: OS hostname.
34
34
  --heartbeat <ms> Status heartbeat interval. Default: ${DEFAULT_HEARTBEAT_MS}
@@ -45,8 +45,8 @@ Options:
45
45
  --ai-model <model> OpenAI model for AI assist. Default: ${DEFAULT_AI_MODEL}
46
46
  --fake-ai Use deterministic AI assist responses for smoke tests.
47
47
  --fake-thumbnail Use generated thumbnail frames for smoke tests.
48
- --exit-on-disconnect Exit after manager disconnect. Useful for tests.
49
- --exit-on-invalid-pair Exit when the manager rejects the pair token.
48
+ --exit-on-disconnect Exit after Hub disconnect. Useful for tests.
49
+ --exit-on-invalid-pair Exit when the Hub rejects the pair token.
50
50
  --once Connect, send one status packet, and exit after welcome.
51
51
  --version Show the agent version.
52
52
  --help Show this help.
@@ -587,14 +587,14 @@ function buildAiPrompt(options, payload, instruction) {
587
587
  const title = String(payload?.title || 'Remote AI task').trim();
588
588
  return [
589
589
  'You are the LiveDesk client AI assistant running on a controlled remote computer.',
590
- 'Complete the manager task as a text-only assistant.',
590
+ 'Complete the LiveDesk Hub task as a text-only assistant.',
591
591
  'Do not claim you used shell commands, file writes, browser automation, keyboard input, or mouse input.',
592
592
  'If the task requires external side effects, explain what would be needed and stop.',
593
593
  '',
594
594
  `Device: ${options.name} (${os.hostname()}, ${os.platform()} ${os.release()}, ${os.arch()})`,
595
595
  `Task title: ${title}`,
596
596
  '',
597
- 'Manager instruction:',
597
+ 'Hub instruction:',
598
598
  instruction
599
599
  ].join('\n');
600
600
  }
@@ -1082,7 +1082,7 @@ function connectOnce(options, deviceId) {
1082
1082
 
1083
1083
  const message = JSON.parse(line);
1084
1084
  if (message.type === 'welcome') {
1085
- console.log(`Connected to RemoteHub as ${options.name} (${deviceId})`);
1085
+ console.log(`Connected to LiveDesk Hub as ${options.name} (${deviceId})`);
1086
1086
  writeJsonLine(socket, {
1087
1087
  type: 'status',
1088
1088
  status: getStatus(options)
@@ -1113,7 +1113,7 @@ function connectOnce(options, deviceId) {
1113
1113
  finish();
1114
1114
  return;
1115
1115
  } else if (message.type === 'error') {
1116
- const remoteError = new Error(message.error || 'RemoteHub rejected the connection.');
1116
+ const remoteError = new Error(message.error || 'LiveDesk Hub rejected the connection.');
1117
1117
  if (message.error === 'invalid-pair-token' && options.exitOnInvalidPair) {
1118
1118
  remoteError.exitCode = EXIT_INVALID_PAIR_TOKEN;
1119
1119
  }
@@ -1130,7 +1130,7 @@ function connectOnce(options, deviceId) {
1130
1130
 
1131
1131
  async function connectWithRetry(options) {
1132
1132
  if (!options.pair) {
1133
- throw new Error('Missing --pair token. Get it from the manager RemoteHub status.');
1133
+ throw new Error('Missing --pair token. Sign in with Google or get it from LiveDesk Hub status.');
1134
1134
  }
1135
1135
 
1136
1136
  const deviceId = await getDeviceId(options.deviceId);
@@ -1158,7 +1158,7 @@ async function connectWithRetry(options) {
1158
1158
  throw err;
1159
1159
  }
1160
1160
  const delayMs = DEFAULT_RECONNECT_MS;
1161
- console.error(`RemoteHub connection failed: ${err?.message || err}. Retrying in ${delayMs}ms.`);
1161
+ console.error(`LiveDesk Hub connection failed: ${err?.message || err}. Retrying in ${delayMs}ms.`);
1162
1162
  await wait(delayMs);
1163
1163
  }
1164
1164
  }
@@ -17,6 +17,7 @@ const DEFAULT_AUTH_CALLBACK_HOST = '127.0.0.1';
17
17
  const DEFAULT_AUTH_CALLBACK_PORT = 5198;
18
18
  const DISCOVERY_RETRY_MS = 5000;
19
19
  const EXIT_INVALID_PAIR_TOKEN = 23;
20
+ const SESSION_REFRESH_SKEW_SECONDS = 60;
20
21
  const SUPABASE_URL = process.env.LIVEDESK_SUPABASE_URL || 'https://otbyfkjxrkngvjziawki.supabase.co';
21
22
  const SUPABASE_PUBLISHABLE_KEY = process.env.LIVEDESK_SUPABASE_PUBLISHABLE_KEY || 'sb_publishable_NpUs0RDJH2YnllsqTKO6TQ_1jTdSsNQ';
22
23
  const CLIENT_STATE_DIR = join(os.homedir(), '.livedesk-client');
@@ -32,9 +33,9 @@ Usage:
32
33
  npx @livedesk/client 3
33
34
 
34
35
  Default flow:
35
- Opens Google sign-in, waits for the signed-in manager, and connects locally.
36
+ Opens Google sign-in, waits for the signed-in LiveDesk Hub, and connects locally.
36
37
  Omit the number for first-available placement, or pass 1-999 to pin a slot.
37
- If no manager is active yet, the client keeps checking every 5 seconds.
38
+ If no LiveDesk Hub is active yet, the client keeps checking every 5 seconds.
38
39
 
39
40
  Options:
40
41
  --slot <number> Screen wall slot number for this computer.
@@ -53,7 +54,7 @@ Options:
53
54
  --files-dir <path> Default folder for received files.
54
55
  --tasks Enable safe remote task inbox.
55
56
  --no-tasks Disable remote task dispatch capability.
56
- --login Sign in with Google and auto-discover manager.
57
+ --login Sign in with Google and auto-discover the Hub.
57
58
  --logout Forget saved Google session before connecting.
58
59
  --auth-port <port> Google sign-in callback port. Default: 5198.
59
60
  --version Show the agent version.
@@ -278,7 +279,7 @@ async function createSupabaseClient() {
278
279
  const { createClient } = await import('@supabase/supabase-js');
279
280
  return createClient(SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY, {
280
281
  auth: {
281
- autoRefreshToken: true,
282
+ autoRefreshToken: false,
282
283
  persistSession: true,
283
284
  detectSessionInUrl: false,
284
285
  flowType: 'pkce',
@@ -288,6 +289,62 @@ async function createSupabaseClient() {
288
289
  });
289
290
  }
290
291
 
292
+ function getNestedErrorCode(error) {
293
+ let cursor = error;
294
+ for (let i = 0; i < 5 && cursor; i++) {
295
+ if (typeof cursor.code === 'string' && cursor.code.trim()) {
296
+ return cursor.code.trim();
297
+ }
298
+ cursor = cursor.cause;
299
+ }
300
+ return '';
301
+ }
302
+
303
+ function getNestedErrorMessage(error) {
304
+ const messages = [];
305
+ let cursor = error;
306
+ for (let i = 0; i < 5 && cursor; i++) {
307
+ if (typeof cursor.message === 'string' && cursor.message.trim()) {
308
+ messages.push(cursor.message.trim());
309
+ }
310
+ cursor = cursor.cause;
311
+ }
312
+ return messages.join(' ');
313
+ }
314
+
315
+ function isTransientNetworkError(error) {
316
+ const code = getNestedErrorCode(error).toUpperCase();
317
+ if (['ENOTFOUND', 'EAI_AGAIN', 'ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT', 'ENETUNREACH', 'EHOSTUNREACH'].includes(code)) {
318
+ return true;
319
+ }
320
+ return /fetch failed|network|dns|socket|connection|timeout|getaddrinfo/i.test(getNestedErrorMessage(error));
321
+ }
322
+
323
+ function formatDiscoveryError(error) {
324
+ if (isTransientNetworkError(error)) {
325
+ const code = getNestedErrorCode(error);
326
+ return `Network is not ready yet${code ? ` (${code})` : ''}. Waiting for DNS/Wi-Fi after sleep.`;
327
+ }
328
+ return error instanceof Error ? error.message : String(error);
329
+ }
330
+
331
+ async function refreshSessionIfNeeded(supabase) {
332
+ const { data: existing } = await supabase.auth.getSession();
333
+ const session = existing?.session;
334
+ if (!session?.access_token) {
335
+ return null;
336
+ }
337
+ const expiresAt = Number(session.expires_at || 0);
338
+ if (expiresAt <= 0 || expiresAt - Math.floor(Date.now() / 1000) > SESSION_REFRESH_SKEW_SECONDS) {
339
+ return session;
340
+ }
341
+ const { data, error } = await supabase.auth.refreshSession(session);
342
+ if (error) {
343
+ throw error;
344
+ }
345
+ return data?.session || session;
346
+ }
347
+
291
348
  function openBrowser(url) {
292
349
  if (os.platform() === 'win32') {
293
350
  spawn('rundll32.exe', ['url.dll,FileProtocolHandler', url], { detached: true, stdio: 'ignore', windowsHide: true }).unref();
@@ -533,6 +590,7 @@ function normalizeEndpointCandidates(target) {
533
590
  }
534
591
 
535
592
  async function resolveManagerFromSupabase(supabase, options = {}) {
593
+ await refreshSessionIfNeeded(supabase);
536
594
  const { data, error } = await supabase
537
595
  .from('livedesk_remote_host_targets')
538
596
  .select('endpoint, endpoint_candidates, pair_token, active, expires_at, updated_at, manager_version')
@@ -542,21 +600,21 @@ async function resolveManagerFromSupabase(supabase, options = {}) {
542
600
  throw error;
543
601
  }
544
602
  if (!data?.active) {
545
- throw new Error('No active LiveDesk manager was found for this Google account. Start LiveDesk on the manager computer and sign in there first.');
603
+ throw new Error('No active LiveDesk Hub was found for this Google account. Start LiveDesk Hub on the screen wall computer and sign in there first.');
546
604
  }
547
605
  if (data.expires_at && Date.parse(data.expires_at) <= Date.now()) {
548
- throw new Error('The LiveDesk manager record is expired. Open the manager screen again while signed in.');
606
+ throw new Error('The LiveDesk Hub record is expired. Open the Hub screen again while signed in.');
549
607
  }
550
608
  if (!data.pair_token) {
551
- throw new Error('The LiveDesk manager record is missing its private connection token.');
609
+ throw new Error('The LiveDesk Hub record is missing its private connection token.');
552
610
  }
553
611
  const endpointCandidates = normalizeEndpointCandidates(data);
554
612
  if (endpointCandidates.length === 0) {
555
- throw new Error('The LiveDesk manager record does not contain a reachable local address.');
613
+ throw new Error('The LiveDesk Hub record does not contain a reachable local address.');
556
614
  }
557
615
  const manager = await chooseReachableEndpoint(endpointCandidates, { requireReachable: options.requireReachable === true });
558
616
  if (!manager) {
559
- throw new Error(`No reachable LiveDesk manager endpoint yet. Checked ${endpointCandidates.join(', ')}.`);
617
+ throw new Error(`No reachable LiveDesk Hub endpoint yet. Checked ${endpointCandidates.join(', ')}.`);
560
618
  }
561
619
  return {
562
620
  manager,
@@ -570,16 +628,16 @@ async function waitForManagerFromSupabase(supabase, options = {}) {
570
628
  const intervalSeconds = Math.max(1, Math.round(intervalMs / 1000));
571
629
  let attempts = 0;
572
630
  let lastMessage = '';
573
- console.log(`Waiting for a LiveDesk manager. This client will keep trying every ${intervalSeconds}s.`);
631
+ console.log(`Waiting for a LiveDesk Hub. This client will keep trying every ${intervalSeconds}s.`);
574
632
  while (true) {
575
633
  attempts += 1;
576
634
  try {
577
635
  return await resolveManagerFromSupabase(supabase, { requireReachable: true });
578
636
  } catch (err) {
579
- const message = err instanceof Error ? err.message : String(err);
637
+ const message = formatDiscoveryError(err);
580
638
  if (message !== lastMessage || attempts === 1 || attempts % 6 === 0) {
581
639
  const suffix = attempts === 1 ? '' : ` attempt ${attempts}`;
582
- console.log(`Still waiting for LiveDesk manager${suffix}: ${message}`);
640
+ console.log(`Still waiting for LiveDesk Hub${suffix}: ${message}`);
583
641
  lastMessage = message;
584
642
  }
585
643
  await sleep(intervalMs);
@@ -607,7 +665,7 @@ async function prepareLoginConnection(parsed) {
607
665
  const resolved = await waitForManagerFromSupabase(supabase);
608
666
  manager = resolved.manager;
609
667
  pair = resolved.pair;
610
- console.log(`Found LiveDesk manager at ${manager}.`);
668
+ console.log(`Found LiveDesk Hub at ${manager}.`);
611
669
  }
612
670
 
613
671
  const slot = normalizeSlotNumber(parsed.slot);
@@ -876,7 +934,7 @@ async function main() {
876
934
  return;
877
935
  }
878
936
  if (prepared.rediscoverOnInvalidPair && result?.code === EXIT_INVALID_PAIR_TOKEN) {
879
- console.error(`LiveDesk manager pair token changed. Refreshing manager discovery in ${DISCOVERY_RETRY_MS}ms.`);
937
+ console.error(`LiveDesk Hub pair token changed. Refreshing Hub discovery in ${DISCOVERY_RETRY_MS}ms.`);
880
938
  await sleep(DISCOVERY_RETRY_MS);
881
939
  continue;
882
940
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/client",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "LiveDesk local remote client",
5
5
  "type": "module",
6
6
  "bin": {