@livedesk/client 0.1.206 → 0.1.208

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.
@@ -47,8 +47,7 @@ const FRESH_REGISTRY_TIMEOUT_MAX_MS = 10000;
47
47
  // polling must stay interactive instead of backing off to multi-minute waits.
48
48
  const DISCOVERY_RETRY_SCHEDULE_MS = [750, 750, 750, 750, 750, 750, 750, 750, 5000];
49
49
  const DISCOVERY_RETRY_JITTER_RATIO = 0.2;
50
- const HUB_WAKE_QUERY_JITTER_MAX_MS = 2000;
51
- const EXIT_INVALID_PAIR_TOKEN = 23;
50
+ const EXIT_INVALID_PAIR_TOKEN = 23;
52
51
  const EXIT_CLIENT_UPDATE = 42;
53
52
  const SESSION_REFRESH_SKEW_SECONDS = 60;
54
53
  const HUB_TARGET_CACHE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;
@@ -2706,9 +2705,10 @@ export async function resolveManagerFromPin(supabase, pin, options = {}) {
2706
2705
  allowRelayFallback: options.allowRelayFallback === true,
2707
2706
  relayEndpoint: options.relayEndpoint,
2708
2707
  probeEndpoint: options.probeEndpoint,
2709
- resolveFreshTarget: async () => {
2708
+ resolveFreshTarget: async signal => {
2710
2709
  const { data, error } = await supabase.functions.invoke('livedesk-resolve-remote-host', {
2711
- body: { pin: normalizedPin }
2710
+ body: { pin: normalizedPin },
2711
+ signal
2712
2712
  });
2713
2713
  if (error) {
2714
2714
  throw await normalizePinResolverInvocationError(error);
@@ -3667,28 +3667,73 @@ export function resolveManagerFromCachedRelayFallback(ownerKey, options = {}) {
3667
3667
  };
3668
3668
  }
3669
3669
 
3670
+ function normalizeFreshRegistryTimeoutMs(value) {
3671
+ const configured = Number(value);
3672
+ return Number.isFinite(configured)
3673
+ ? Math.max(FRESH_REGISTRY_TIMEOUT_MIN_MS, Math.min(FRESH_REGISTRY_TIMEOUT_MAX_MS, configured))
3674
+ : FRESH_REGISTRY_TIMEOUT_MS;
3675
+ }
3676
+
3677
+ export async function runFreshHubRegistryLookup(resolveFreshTarget, options = {}) {
3678
+ if (typeof resolveFreshTarget !== 'function') {
3679
+ throw createHubDiscoveryError(
3680
+ 'fresh-registry-resolver-required',
3681
+ 'Fresh Hub registry resolver is required.'
3682
+ );
3683
+ }
3684
+ const timeoutMs = normalizeFreshRegistryTimeoutMs(options.timeoutMs);
3685
+ const controller = new AbortController();
3686
+ let timeoutHandle = null;
3687
+ const lookupOutcome = Promise.resolve()
3688
+ .then(() => resolveFreshTarget(controller.signal))
3689
+ .then(
3690
+ target => ({ type: 'target', target }),
3691
+ error => ({ type: 'error', error })
3692
+ );
3693
+ const timeoutOutcome = new Promise(resolveTimeout => {
3694
+ timeoutHandle = setTimeout(
3695
+ () => resolveTimeout({ type: 'timeout' }),
3696
+ timeoutMs
3697
+ );
3698
+ });
3699
+ const outcomes = [lookupOutcome, timeoutOutcome];
3700
+ if (options.wakePromise && typeof options.wakePromise.then === 'function') {
3701
+ outcomes.push(Promise.resolve(options.wakePromise).then(
3702
+ event => ({ type: 'hub-online', event }),
3703
+ () => new Promise(() => {})
3704
+ ));
3705
+ }
3706
+ const outcome = await Promise.race(outcomes).finally(() => {
3707
+ clearTimeout(timeoutHandle);
3708
+ });
3709
+ if (outcome.type === 'hub-online') {
3710
+ controller.abort('hub-online');
3711
+ return outcome;
3712
+ }
3713
+ if (outcome.type === 'timeout') {
3714
+ controller.abort('hub-registry-timeout');
3715
+ throw createHubDiscoveryError(
3716
+ 'hub-registry-timeout',
3717
+ `Fresh LiveDesk Hub registry lookup exceeded ${timeoutMs}ms.`
3718
+ );
3719
+ }
3720
+ if (outcome.type === 'error') {
3721
+ controller.abort('hub-registry-error');
3722
+ throw outcome.error;
3723
+ }
3724
+ return outcome;
3725
+ }
3726
+
3670
3727
  export async function resolveManagerWithCachedRecovery(ownerKey, options = {}) {
3671
3728
  const cached = Object.prototype.hasOwnProperty.call(options, 'cachedTarget')
3672
3729
  ? normalizeCachedHubTarget(options.cachedTarget, ownerKey, options.now)
3673
3730
  : readCachedHubTarget(ownerKey);
3674
3731
  try {
3675
- if (typeof options.resolveFreshTarget !== 'function') {
3676
- throw createHubDiscoveryError('fresh-registry-resolver-required', 'Fresh Hub registry resolver is required.');
3677
- }
3678
- const configuredFreshTimeoutMs = Number(options.freshTimeoutMs);
3679
- const freshTimeoutMs = Number.isFinite(configuredFreshTimeoutMs)
3680
- ? Math.max(FRESH_REGISTRY_TIMEOUT_MIN_MS, Math.min(FRESH_REGISTRY_TIMEOUT_MAX_MS, configuredFreshTimeoutMs))
3681
- : FRESH_REGISTRY_TIMEOUT_MS;
3682
- let timeoutHandle = null;
3683
- const fresh = await Promise.race([
3684
- Promise.resolve().then(() => options.resolveFreshTarget()),
3685
- new Promise((_, reject) => {
3686
- timeoutHandle = setTimeout(() => reject(createHubDiscoveryError(
3687
- 'hub-registry-timeout',
3688
- `Fresh LiveDesk Hub registry lookup exceeded ${freshTimeoutMs}ms.`
3689
- )), freshTimeoutMs);
3690
- })
3691
- ]).finally(() => clearTimeout(timeoutHandle));
3732
+ const freshOutcome = await runFreshHubRegistryLookup(
3733
+ options.resolveFreshTarget,
3734
+ { timeoutMs: options.freshTimeoutMs }
3735
+ );
3736
+ const fresh = freshOutcome.target;
3692
3737
  if (!fresh?.manager || !fresh?.pair) {
3693
3738
  throw createHubDiscoveryError('fresh-registry-target-invalid', 'Fresh Hub registry target is incomplete.');
3694
3739
  }
@@ -3770,12 +3815,21 @@ function normalizeEndpointCandidates(target) {
3770
3815
  }
3771
3816
 
3772
3817
  export async function resolveManagerFromSupabase(supabase, options = {}) {
3773
- await refreshSessionIfNeeded(supabase);
3774
- const { data, error } = await supabase
3775
- .from('livedesk_remote_host_targets')
3776
- .select('node_id, endpoint, endpoint_candidates, pair_token, active, expires_at, updated_at, manager_version')
3777
- .eq('product_key', 'livedesk')
3778
- .maybeSingle();
3818
+ if (options.signal?.aborted) {
3819
+ throw createHubDiscoveryError('hub-registry-aborted', 'LiveDesk Hub registry lookup was cancelled.');
3820
+ }
3821
+ await refreshSessionIfNeeded(supabase);
3822
+ if (options.signal?.aborted) {
3823
+ throw createHubDiscoveryError('hub-registry-aborted', 'LiveDesk Hub registry lookup was cancelled.');
3824
+ }
3825
+ let query = supabase
3826
+ .from('livedesk_remote_host_targets')
3827
+ .select('node_id, endpoint, endpoint_candidates, pair_token, active, expires_at, updated_at, manager_version')
3828
+ .eq('product_key', 'livedesk');
3829
+ if (options.signal && typeof query.abortSignal === 'function') {
3830
+ query = query.abortSignal(options.signal);
3831
+ }
3832
+ const { data, error } = await query.maybeSingle();
3779
3833
  if (error) {
3780
3834
  throw error;
3781
3835
  }
@@ -3849,9 +3903,12 @@ async function registerClientDeviceWithSupabase(supabase, options = {}) {
3849
3903
  }
3850
3904
  }
3851
3905
 
3852
- async function waitForManagerFromSupabase(supabase, options = {}) {
3906
+ export async function waitForManagerFromSupabase(supabase, options = {}) {
3853
3907
  const configuredIntervalMs = Number(options.intervalMs || 0);
3854
3908
  const shouldStop = typeof options.shouldStop === 'function' ? options.shouldStop : () => false;
3909
+ const createWakeListener = typeof options.createWakeListener === 'function'
3910
+ ? options.createWakeListener
3911
+ : createHubWakeListener;
3855
3912
  let attempts = 0;
3856
3913
  let lastMessage = '';
3857
3914
  let wakeListener = null;
@@ -3862,6 +3919,17 @@ async function waitForManagerFromSupabase(supabase, options = {}) {
3862
3919
  if (shouldStop()) {
3863
3920
  return null;
3864
3921
  }
3922
+ if (!wakeListener) {
3923
+ try {
3924
+ wakeListener = await createWakeListener({
3925
+ getAccessToken: async () => (await refreshSessionIfNeeded(supabase))?.access_token || ''
3926
+ });
3927
+ } catch (error) {
3928
+ if (attempts === 0) {
3929
+ console.warn(`[LiveDesk Client] Hub-online event channel unavailable; adaptive registry retries remain active: ${error?.message || error}`);
3930
+ }
3931
+ }
3932
+ }
3865
3933
  attempts += 1;
3866
3934
  try {
3867
3935
  if (initialError) {
@@ -3869,9 +3937,24 @@ async function waitForManagerFromSupabase(supabase, options = {}) {
3869
3937
  initialError = null;
3870
3938
  throw error;
3871
3939
  }
3872
- return await resolveManagerFromSupabase(supabase, {
3873
- allowRelayFallback: options.allowRelayFallback === true
3874
- });
3940
+ const outcome = await runFreshHubRegistryLookup(
3941
+ signal => resolveManagerFromSupabase(supabase, {
3942
+ allowRelayFallback: options.allowRelayFallback === true,
3943
+ probeEndpoint: options.probeEndpoint,
3944
+ signal
3945
+ }),
3946
+ {
3947
+ timeoutMs: options.freshTimeoutMs,
3948
+ wakePromise: wakeListener?.promise
3949
+ }
3950
+ );
3951
+ if (outcome.type === 'hub-online') {
3952
+ wakeListener?.close();
3953
+ wakeListener = null;
3954
+ console.log('[LiveDesk Client] Hub-online event received. Rechecking the registry now.');
3955
+ continue;
3956
+ }
3957
+ return outcome.target;
3875
3958
  } catch (err) {
3876
3959
  const message = formatDiscoveryError(err);
3877
3960
  if (message !== lastMessage || attempts === 1 || attempts % 6 === 0) {
@@ -3881,18 +3964,6 @@ async function waitForManagerFromSupabase(supabase, options = {}) {
3881
3964
  }
3882
3965
  }
3883
3966
 
3884
- if (!wakeListener) {
3885
- try {
3886
- wakeListener = await createHubWakeListener({
3887
- getAccessToken: async () => (await refreshSessionIfNeeded(supabase))?.access_token || ''
3888
- });
3889
- } catch (error) {
3890
- if (attempts === 1) {
3891
- console.warn(`[LiveDesk Client] Hub-online event channel unavailable; adaptive registry retries remain active: ${error?.message || error}`);
3892
- }
3893
- }
3894
- }
3895
-
3896
3967
  const trigger = await waitForDiscoveryTrigger(
3897
3968
  getDiscoveryRetryDelay(attempts, configuredIntervalMs),
3898
3969
  wakeListener?.promise,
@@ -3901,9 +3972,7 @@ async function waitForManagerFromSupabase(supabase, options = {}) {
3901
3972
  if (trigger.type === 'hub-online') {
3902
3973
  wakeListener?.close();
3903
3974
  wakeListener = null;
3904
- const jitterMs = Math.floor(Math.random() * (HUB_WAKE_QUERY_JITTER_MAX_MS + 1));
3905
- console.log(`[LiveDesk Client] Hub-online event received. Rechecking the registry in ${jitterMs}ms.`);
3906
- await waitForDiscoveryTrigger(jitterMs, null, shouldStop);
3975
+ console.log('[LiveDesk Client] Hub-online event received. Rechecking the registry now.');
3907
3976
  }
3908
3977
  }
3909
3978
  } finally {
@@ -3993,7 +4062,7 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
3993
4062
  resolved = await resolveManagerWithCachedRecovery(cacheOwnerKey, {
3994
4063
  allowRelayFallback,
3995
4064
  relayEndpoint: parsed.relay,
3996
- resolveFreshTarget: async () => {
4065
+ resolveFreshTarget: async signal => {
3997
4066
  // The signed registry is authoritative for Hub replacement
3998
4067
  // and pair-token rotation. Cache direct/relay recovery is
3999
4068
  // considered only after this one current lookup reports
@@ -4006,7 +4075,8 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
4006
4075
  console.warn('[LiveDesk Client] Another auth owner rotated the refresh token. Keeping the runtime alive while the saved session catches up.');
4007
4076
  }
4008
4077
  return await resolveManagerFromSupabase(supabase, {
4009
- allowRelayFallback
4078
+ allowRelayFallback,
4079
+ signal
4010
4080
  });
4011
4081
  }
4012
4082
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/client",
3
- "version": "0.1.206",
3
+ "version": "0.1.208",
4
4
  "description": "LiveDesk local remote client",
5
5
  "type": "module",
6
6
  "bin": {
@@ -40,10 +40,10 @@
40
40
  "ws": "^8.18.3"
41
41
  },
42
42
  "optionalDependencies": {
43
- "@livedesk/fast-linux-x64": "0.1.412",
44
- "@livedesk/fast-osx-arm64": "0.1.412",
45
- "@livedesk/fast-osx-x64": "0.1.412",
46
- "@livedesk/fast-win-x64": "0.1.412"
43
+ "@livedesk/fast-linux-x64": "0.1.413",
44
+ "@livedesk/fast-osx-arm64": "0.1.413",
45
+ "@livedesk/fast-osx-x64": "0.1.413",
46
+ "@livedesk/fast-win-x64": "0.1.413"
47
47
  },
48
48
  "publishConfig": {
49
49
  "access": "public"