livedesk 0.1.428 → 0.1.430

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/client/README.md CHANGED
@@ -22,8 +22,9 @@ callback exchange, and saved authentication as one path. The browser only
22
22
  shows progress; it does not maintain a second session that must be transferred
23
23
  back to the Client. LiveDesk then waits for the active Hub published by the
24
24
  same account and connects locally. If the Hub is not
25
- ready yet, the client waits for a Hub-online event and uses adaptive registry
26
- retries instead of exiting. If a
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
27
28
  connected Hub is replaced, the launcher also leaves the stale endpoint and
28
29
  discovers the newly published Hub without another Google login. Omit the number
29
30
  for first-available placement, or pass `1` to `999` to pin this machine to a
@@ -30,7 +30,10 @@ const DEFAULT_HUB_CLIENT_PORT = 5197;
30
30
  const DEFAULT_AUTH_CALLBACK_HOST = '127.0.0.1';
31
31
  const DEFAULT_AUTH_CALLBACK_PORT = 5179;
32
32
  const ENDPOINT_PROBE_TIMEOUT_MS = 900;
33
- const DISCOVERY_RETRY_SCHEDULE_MS = [1000, 2000, 5000, 10_000, 30_000, 60_000, 120_000, 300_000];
33
+ // Hub-online delivery is an optimization, not a correctness dependency. A
34
+ // browser, proxy, or suspended network can miss the wake event, so registry
35
+ // polling must stay interactive instead of backing off to multi-minute waits.
36
+ const DISCOVERY_RETRY_SCHEDULE_MS = [750, 750, 750, 750, 750, 750, 750, 750, 5000];
34
37
  const DISCOVERY_RETRY_JITTER_RATIO = 0.2;
35
38
  const HUB_WAKE_QUERY_JITTER_MAX_MS = 2000;
36
39
  const EXIT_INVALID_PAIR_TOKEN = 23;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/client",
3
- "version": "0.1.187",
3
+ "version": "0.1.188",
4
4
  "description": "LiveDesk local remote client",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,10 +41,10 @@
41
41
  "ws": "^8.18.3"
42
42
  },
43
43
  "optionalDependencies": {
44
- "@livedesk/fast-linux-x64": "0.1.394",
45
- "@livedesk/fast-osx-arm64": "0.1.394",
46
- "@livedesk/fast-osx-x64": "0.1.394",
47
- "@livedesk/fast-win-x64": "0.1.394"
44
+ "@livedesk/fast-linux-x64": "0.1.395",
45
+ "@livedesk/fast-osx-arm64": "0.1.395",
46
+ "@livedesk/fast-osx-x64": "0.1.395",
47
+ "@livedesk/fast-win-x64": "0.1.395"
48
48
  },
49
49
  "publishConfig": {
50
50
  "access": "public"
@@ -34,10 +34,84 @@ const READ_ONLY_CLIENT_API_PATHS = new Set([
34
34
  '/api/auth/status'
35
35
  ]);
36
36
 
37
- function normalizeString(value, maxLength = 1024) {
38
- return String(value ?? '').replace(/[\r\n\t]/g, ' ').trim().slice(0, maxLength);
39
- }
40
-
37
+ function normalizeString(value, maxLength = 1024) {
38
+ return String(value ?? '').replace(/[\r\n\t]/g, ' ').trim().slice(0, maxLength);
39
+ }
40
+
41
+ function normalizeAccountAvatarUrl(value) {
42
+ const candidate = normalizeString(value, 2048);
43
+ if (!candidate) return '';
44
+ try {
45
+ const url = new URL(candidate);
46
+ return url.protocol === 'https:' || url.protocol === 'http:' ? url.toString() : '';
47
+ } catch {
48
+ return '';
49
+ }
50
+ }
51
+
52
+ function readClientAccountProfile(sessionOrUser) {
53
+ const user = sessionOrUser?.user && typeof sessionOrUser.user === 'object'
54
+ ? sessionOrUser.user
55
+ : sessionOrUser && typeof sessionOrUser === 'object'
56
+ ? sessionOrUser
57
+ : {};
58
+ const metadata = user.user_metadata && typeof user.user_metadata === 'object'
59
+ ? user.user_metadata
60
+ : {};
61
+ const email = normalizeString(user.email, 320);
62
+ const name = normalizeString(
63
+ metadata.full_name
64
+ || metadata.name
65
+ || metadata.preferred_username
66
+ || user.name
67
+ || email
68
+ || user.id,
69
+ 160
70
+ );
71
+ const avatarUrl = normalizeAccountAvatarUrl(
72
+ metadata.avatar_url
73
+ || metadata.picture
74
+ || user.avatar_url
75
+ || user.picture
76
+ );
77
+ return { email, name, avatarUrl };
78
+ }
79
+
80
+ function mergeClientAccountProfile(session, sourceUser) {
81
+ const profile = readClientAccountProfile(sourceUser);
82
+ const userMetadata = {
83
+ ...(profile.name ? { full_name: profile.name } : {}),
84
+ ...(profile.avatarUrl ? { avatar_url: profile.avatarUrl } : {})
85
+ };
86
+ return {
87
+ ...session,
88
+ user: {
89
+ ...(session?.user || {}),
90
+ ...(Object.keys(userMetadata).length > 0 ? { user_metadata: userMetadata } : {})
91
+ }
92
+ };
93
+ }
94
+
95
+ async function hydrateClientAccountProfile(session) {
96
+ const accessToken = normalizeString(session?.access_token, 8192);
97
+ if (!accessToken) return session;
98
+ try {
99
+ const response = await fetch(`${SUPABASE_URL.replace(/\/+$/, '')}/auth/v1/user`, {
100
+ headers: {
101
+ apikey: SUPABASE_PUBLISHABLE_KEY,
102
+ Authorization: `Bearer ${accessToken}`,
103
+ Accept: 'application/json'
104
+ },
105
+ signal: AbortSignal.timeout(5_000)
106
+ });
107
+ if (!response.ok) return session;
108
+ const verifiedUser = await response.json().catch(() => null);
109
+ return verifiedUser ? mergeClientAccountProfile(session, verifiedUser) : session;
110
+ } catch {
111
+ return session;
112
+ }
113
+ }
114
+
41
115
  function normalizePort(value, fallback = DEFAULT_PORT) {
42
116
  const port = Number(value);
43
117
  return Number.isInteger(port) && port >= 1 && port <= 65535 ? port : fallback;
@@ -538,6 +612,7 @@ export function createClientRuntimeServer(options = {}) {
538
612
  const appVersion = normalizeString(options.appVersion || process.env.LIVEDESK_NPM_LAUNCHER_VERSION, 80) || 'dev';
539
613
  const savedSession = options.savedSession?.refresh_token ? options.savedSession : readSavedSession();
540
614
  const savedSessionPersisted = Boolean(savedSession?.refresh_token);
615
+ const savedAccountProfile = readClientAccountProfile(savedSession);
541
616
  let previousCpuTotals = readCpuTotals();
542
617
  let hardwareSnapshot = { gpus: [], disks: [], collectedAt: '', collecting: true };
543
618
  let networkSnapshot = { interfaces: activeNetworkInterfaces(), receivedBytes: 0, sentBytes: 0, receiveBytesPerSecond: 0, sendBytesPerSecond: 0, sampledAt: '' };
@@ -570,7 +645,9 @@ export function createClientRuntimeServer(options = {}) {
570
645
  lastResult: '',
571
646
  lastError: '',
572
647
  persisted: savedSessionPersisted,
573
- email: normalizeString(savedSession?.user?.email, 320)
648
+ email: savedAccountProfile.email,
649
+ name: savedAccountProfile.name,
650
+ avatarUrl: savedAccountProfile.avatarUrl
574
651
  }
575
652
  };
576
653
  let completed = false;
@@ -586,8 +663,8 @@ export function createClientRuntimeServer(options = {}) {
586
663
  const runtime = createRuntimeManager({
587
664
  role: 'client',
588
665
  state: RuntimeState.WAITING_AUTH,
589
- authenticated: Boolean(options.savedSession?.access_token),
590
- userId: options.savedSession?.user?.id || '',
666
+ authenticated: Boolean(savedSession?.access_token),
667
+ userId: savedSession?.user?.id || '',
591
668
  deviceId,
592
669
  appVersion,
593
670
  startedAt: new Date().toISOString(),
@@ -673,6 +750,7 @@ export function createClientRuntimeServer(options = {}) {
673
750
  lastChoice = choice || lastChoice;
674
751
  if (completed) {
675
752
  if (choice?.session?.access_token) {
753
+ const accountProfile = readClientAccountProfile(choice.session);
676
754
  runtime.setAuthenticated(true, choice.session.user?.id || '');
677
755
  state.auth = {
678
756
  ...state.auth,
@@ -680,7 +758,9 @@ export function createClientRuntimeServer(options = {}) {
680
758
  lastResult: 'accepted',
681
759
  lastError: '',
682
760
  persisted: true,
683
- email: normalizeString(choice.session.user?.email, 320)
761
+ email: accountProfile.email || state.auth.email,
762
+ name: accountProfile.name || state.auth.name,
763
+ avatarUrl: accountProfile.avatarUrl || state.auth.avatarUrl
684
764
  };
685
765
  }
686
766
  return;
@@ -695,12 +775,15 @@ export function createClientRuntimeServer(options = {}) {
695
775
  : [];
696
776
  state.message = message;
697
777
  state.lastError = '';
778
+ const accountProfile = readClientAccountProfile(choice?.session);
698
779
  state.auth = {
699
780
  ...state.auth,
700
781
  lastAttemptAt: state.connectedAt,
701
782
  lastResult: 'accepted',
702
783
  lastError: '',
703
- email: normalizeString(choice?.session?.user?.email, 320) || state.auth.email
784
+ email: accountProfile.email || state.auth.email,
785
+ name: accountProfile.name || state.auth.name,
786
+ avatarUrl: accountProfile.avatarUrl || state.auth.avatarUrl
704
787
  };
705
788
  runtime.setAuthenticated(Boolean(choice?.session?.access_token), choice?.session?.user?.id || '');
706
789
  runtime.update({ state: RuntimeState.RESOLVING_ROLE }, 'credentials-accepted');
@@ -727,6 +810,8 @@ export function createClientRuntimeServer(options = {}) {
727
810
  authenticated: snapshot.authenticated,
728
811
  userId: snapshot.userId || '',
729
812
  userEmail: state.auth.email || '',
813
+ userName: state.auth.name || '',
814
+ userAvatarUrl: state.auth.avatarUrl || '',
730
815
  appVersion,
731
816
  runtimeId: `client-${snapshot.runtimePid}-${snapshot.startedAt}`,
732
817
  runtimePid: snapshot.runtimePid,
@@ -845,6 +930,8 @@ export function createClientRuntimeServer(options = {}) {
845
930
  authenticated: snapshot.authenticated,
846
931
  userId: snapshot.userId || null,
847
932
  userEmail: state.auth.email || null,
933
+ userName: state.auth.name || null,
934
+ userAvatarUrl: state.auth.avatarUrl || null,
848
935
  role: 'client'
849
936
  });
850
937
  return;
@@ -896,7 +983,10 @@ export function createClientRuntimeServer(options = {}) {
896
983
  const sessionCandidate = exchanged?.session || exchanged;
897
984
  const normalized = normalizeRuntimeAuthSession(sessionCandidate, { requireRefreshToken: true });
898
985
  if (!normalized.ok) throw new Error(normalized.error);
899
- const session = { ...sessionCandidate, ...normalized.session };
986
+ const session = {
987
+ ...sessionCandidate,
988
+ ...mergeClientAccountProfile(normalized.session, sessionCandidate?.user)
989
+ };
900
990
  if (!writeSavedSession(session)) throw new Error('refresh-token-required');
901
991
  state.auth.persisted = true;
902
992
  complete({ type: 'google', session }, 'Signed in. Finding the LiveDesk Hub.');
@@ -928,11 +1018,15 @@ export function createClientRuntimeServer(options = {}) {
928
1018
  respondJson(401, { ok: false, error: `supabase-user-verification-failed:${verifyResponse.status}` });
929
1019
  return;
930
1020
  }
931
- const verifiedUser = await verifyResponse.json().catch(() => ({}));
932
- const session = {
1021
+ const verifiedUser = await verifyResponse.json().catch(() => ({}));
1022
+ const verifiedSession = {
933
1023
  ...normalized.session,
934
- user: { id: normalizeString(verifiedUser?.id || body.user?.id || body.userId, 160), email: normalizeString(verifiedUser?.email || body.user?.email || body.email, 320) }
1024
+ user: {
1025
+ id: normalizeString(verifiedUser?.id || body.user?.id || body.userId, 160),
1026
+ email: normalizeString(verifiedUser?.email || body.user?.email || body.email, 320)
1027
+ }
935
1028
  };
1029
+ const session = mergeClientAccountProfile(verifiedSession, verifiedUser);
936
1030
  try {
937
1031
  if (!writeSavedSession(session)) throw new Error('refresh-token-required');
938
1032
  state.auth.persisted = true;
@@ -954,7 +1048,15 @@ export function createClientRuntimeServer(options = {}) {
954
1048
  state.manager = '';
955
1049
  state.message = 'Saved sign-in was cleared. Sign in again to start the Client.';
956
1050
  state.lastError = '';
957
- state.auth = { lastAttemptAt: new Date().toISOString(), lastResult: 'cleared', lastError: '', persisted: false, email: '' };
1051
+ state.auth = {
1052
+ lastAttemptAt: new Date().toISOString(),
1053
+ lastResult: 'cleared',
1054
+ lastError: '',
1055
+ persisted: false,
1056
+ email: '',
1057
+ name: '',
1058
+ avatarUrl: ''
1059
+ };
958
1060
  runtime.setAuthenticated(false);
959
1061
  respondJson(200, { ok: true, authenticated: false, role: 'client' });
960
1062
  return;
@@ -1168,16 +1270,20 @@ export function createClientRuntimeServer(options = {}) {
1168
1270
  complete(initialChoice, initialChoiceMessage);
1169
1271
  });
1170
1272
  } else {
1171
- const saved = normalizeRuntimeAuthSession(options.savedSession, { requireRefreshToken: true });
1273
+ const saved = normalizeRuntimeAuthSession(savedSession, { requireRefreshToken: true });
1172
1274
  if (saved.ok) {
1173
1275
  setImmediate(() => {
1174
- try {
1175
- if (!writeSavedSession(saved.session)) throw new Error('refresh-token-required');
1176
- state.auth.persisted = true;
1177
- complete({ type: 'google', session: saved.session }, 'Saved sign-in found. Finding the LiveDesk Hub.');
1178
- } catch (error) {
1179
- recordAuthAttempt('rejected', `session-persistence-failed:${normalizeString(error?.message || error, 160)}`);
1180
- }
1276
+ void (async () => {
1277
+ try {
1278
+ const normalizedSession = mergeClientAccountProfile(saved.session, savedSession?.user);
1279
+ const hydratedSession = await hydrateClientAccountProfile(normalizedSession);
1280
+ if (!writeSavedSession(hydratedSession)) throw new Error('refresh-token-required');
1281
+ state.auth.persisted = true;
1282
+ complete({ type: 'google', session: hydratedSession }, 'Saved sign-in found. Finding the LiveDesk Hub.');
1283
+ } catch (error) {
1284
+ recordAuthAttempt('rejected', `session-persistence-failed:${normalizeString(error?.message || error, 160)}`);
1285
+ }
1286
+ })();
1181
1287
  });
1182
1288
  }
1183
1289
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "livedesk",
3
- "version": "0.1.428",
4
- "livedeskClientVersion": "0.1.187",
3
+ "version": "0.1.430",
4
+ "livedeskClientVersion": "0.1.188",
5
5
  "buildFlavor": "production",
6
6
  "description": "LiveDesk Hub and client launcher",
7
7
  "type": "module",
@@ -50,10 +50,10 @@
50
50
  "ws": "^8.18.3"
51
51
  },
52
52
  "optionalDependencies": {
53
- "@livedesk/fast-linux-x64": "0.1.394",
54
- "@livedesk/fast-osx-arm64": "0.1.394",
55
- "@livedesk/fast-osx-x64": "0.1.394",
56
- "@livedesk/fast-win-x64": "0.1.394"
53
+ "@livedesk/fast-linux-x64": "0.1.395",
54
+ "@livedesk/fast-osx-arm64": "0.1.395",
55
+ "@livedesk/fast-osx-x64": "0.1.395",
56
+ "@livedesk/fast-win-x64": "0.1.395"
57
57
  },
58
58
  "publishConfig": {
59
59
  "access": "public"