@livedesk/client 0.1.219 → 0.1.220

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.
Files changed (2) hide show
  1. package/bin/livedesk-client.js +98 -12
  2. package/package.json +52 -52
@@ -2791,6 +2791,8 @@ export async function resolveManagerFromPin(supabase, pin, options = {}) {
2791
2791
  endpointCandidates,
2792
2792
  directReachable: target.directReachable,
2793
2793
  connectionTransport: target.connectionTransport,
2794
+ directProbeSkipped: target.directProbeSkipped === true,
2795
+ directProbeSkipReason: String(target.directProbeSkipReason || ''),
2794
2796
  discoverySource: 'supabase'
2795
2797
  };
2796
2798
  }
@@ -3511,6 +3513,63 @@ function parseManagerEndpoint(value) {
3511
3513
  return { host, port };
3512
3514
  }
3513
3515
 
3516
+ function ipv4ToUnsignedInteger(address) {
3517
+ const octets = String(address || '').split('.').map(Number);
3518
+ if (octets.length !== 4
3519
+ || octets.some(octet => !Number.isInteger(octet) || octet < 0 || octet > 255)) {
3520
+ return null;
3521
+ }
3522
+ return (((octets[0] << 24) >>> 0)
3523
+ + (octets[1] << 16)
3524
+ + (octets[2] << 8)
3525
+ + octets[3]) >>> 0;
3526
+ }
3527
+
3528
+ function isAutomaticPrivateIpv4(address) {
3529
+ const value = ipv4ToUnsignedInteger(address);
3530
+ if (value === null) return false;
3531
+ const first = value >>> 24;
3532
+ const second = (value >>> 16) & 0xff;
3533
+ return first === 10
3534
+ || first === 127
3535
+ || (first === 100 && second >= 64 && second <= 127)
3536
+ || (first === 169 && second === 254)
3537
+ || (first === 172 && second >= 16 && second <= 31)
3538
+ || (first === 192 && second === 168);
3539
+ }
3540
+
3541
+ export function isEndpointOnLocalNetwork(endpoint, networkInterfaces = os.networkInterfaces()) {
3542
+ const parsedEndpoint = parseManagerEndpoint(endpoint);
3543
+ if (!parsedEndpoint || !net.isIPv4(parsedEndpoint.host)) return false;
3544
+ const target = ipv4ToUnsignedInteger(parsedEndpoint.host);
3545
+ if (target === null) return false;
3546
+ if ((target >>> 24) === 127) return true;
3547
+ for (const entries of Object.values(networkInterfaces || {})) {
3548
+ for (const entry of Array.isArray(entries) ? entries : []) {
3549
+ if (!entry || entry.internal || (entry.family !== 'IPv4' && entry.family !== 4)) continue;
3550
+ const address = ipv4ToUnsignedInteger(entry.address);
3551
+ const netmask = ipv4ToUnsignedInteger(entry.netmask);
3552
+ if (address === null || netmask === null) continue;
3553
+ if (address === target || ((address & netmask) >>> 0) === ((target & netmask) >>> 0)) {
3554
+ return true;
3555
+ }
3556
+ }
3557
+ }
3558
+ return false;
3559
+ }
3560
+
3561
+ export function shouldSkipAutomaticDirectProbe(endpoint, options = {}) {
3562
+ if (options.forcePrivateDirectProbe === true
3563
+ || /^(1|true|yes|on)$/i.test(String(process.env.LIVEDESK_FORCE_PRIVATE_DIRECT_PROBE || '').trim())) {
3564
+ return false;
3565
+ }
3566
+ const parsedEndpoint = parseManagerEndpoint(endpoint);
3567
+ return !!parsedEndpoint
3568
+ && net.isIPv4(parsedEndpoint.host)
3569
+ && isAutomaticPrivateIpv4(parsedEndpoint.host)
3570
+ && !isEndpointOnLocalNetwork(endpoint, options.networkInterfaces);
3571
+ }
3572
+
3514
3573
  function requestHubSlotAssignment({ manager, pairToken, deviceId, slotNumber, timeoutMs = 5000 }) {
3515
3574
  const endpoint = parseManagerEndpoint(manager);
3516
3575
  const normalizedPairToken = String(pairToken || '').trim();
@@ -3639,10 +3698,21 @@ export async function chooseManagerConnectionTarget(candidates, options = {}) {
3639
3698
  .filter(Boolean))];
3640
3699
  if (endpoints.length === 0) return null;
3641
3700
 
3642
- const manager = await chooseReachableEndpoint(endpoints, {
3643
- requireReachable: true,
3644
- probeEndpoint: options.probeEndpoint
3645
- });
3701
+ // A signed 192.168/10/172.16 endpoint from another site cannot normally be
3702
+ // reached from this Client. Skip that full TCP timeout only in automatic
3703
+ // relay-capable discovery. Direct-only mode and an explicit override still
3704
+ // probe it for routed VPN and port-forwarding deployments.
3705
+ const skippedPrivateEndpoints = options.allowRelayFallback === true
3706
+ ? endpoints.filter(endpoint => shouldSkipAutomaticDirectProbe(endpoint, options))
3707
+ : [];
3708
+ const skippedPrivateSet = new Set(skippedPrivateEndpoints);
3709
+ const directCandidates = endpoints.filter(endpoint => !skippedPrivateSet.has(endpoint));
3710
+ const manager = directCandidates.length > 0
3711
+ ? await chooseReachableEndpoint(directCandidates, {
3712
+ requireReachable: true,
3713
+ probeEndpoint: options.probeEndpoint
3714
+ })
3715
+ : '';
3646
3716
  if (manager) {
3647
3717
  return {
3648
3718
  manager,
@@ -3655,12 +3725,16 @@ export async function chooseManagerConnectionTarget(candidates, options = {}) {
3655
3725
  }
3656
3726
  return {
3657
3727
  // The account/PIN response is the authority for this retry candidate.
3658
- // RemoteFast gives it one bounded direct attempt, then uses the
3659
- // pair-token-authenticated encrypted relay rather than keeping the
3660
- // launcher in an indefinite TCP reachability loop.
3728
+ // RemoteFast uses relay control to negotiate encrypted UDP P2P video.
3661
3729
  manager: endpoints[0],
3662
3730
  directReachable: false,
3663
- connectionTransport: 'relay-fallback'
3731
+ connectionTransport: 'relay-fallback',
3732
+ ...(skippedPrivateEndpoints.length > 0
3733
+ ? {
3734
+ directProbeSkipped: true,
3735
+ directProbeSkipReason: 'private-endpoint-outside-local-subnet'
3736
+ }
3737
+ : {})
3664
3738
  };
3665
3739
  }
3666
3740
 
@@ -3929,7 +4003,9 @@ export async function resolveManagerFromSupabase(supabase, options = {}) {
3929
4003
  hubDeviceId: String(data.node_id || '').trim(),
3930
4004
  endpointCandidates,
3931
4005
  directReachable: target.directReachable,
3932
- connectionTransport: target.connectionTransport
4006
+ connectionTransport: target.connectionTransport,
4007
+ directProbeSkipped: target.directProbeSkipped === true,
4008
+ directProbeSkipReason: String(target.directProbeSkipReason || '')
3933
4009
  };
3934
4010
  }
3935
4011
 
@@ -4050,6 +4126,7 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null, get
4050
4126
  let discoverySource = '';
4051
4127
  let connectionTransport = '';
4052
4128
  let directReachable = false;
4129
+ let directProbeSkipReason = '';
4053
4130
  const allowRelayFallback = transportAllowsRelay(parsed.transport);
4054
4131
 
4055
4132
  if (shouldLogin) {
@@ -4123,12 +4200,15 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null, get
4123
4200
  discoverySource = choice.discoverySource || 'supabase';
4124
4201
  connectionTransport = choice.connectionTransport || 'direct-tcp';
4125
4202
  directReachable = choice.directReachable !== false;
4203
+ directProbeSkipReason = String(choice.directProbeSkipReason || '');
4126
4204
  connectionPage?.update({
4127
4205
  endpointCandidates: choice.endpointCandidates || [],
4128
4206
  manager,
4129
4207
  assignedHubId: choice.hubDeviceId || '',
4130
4208
  message: connectionTransport === 'relay-fallback'
4131
- ? `LiveDesk Hub found at ${manager}; starting encrypted relay fallback.`
4209
+ ? directProbeSkipReason
4210
+ ? `LiveDesk Hub found at ${manager}; the private endpoint is outside this local subnet, so encrypted relay/P2P negotiation starts immediately.`
4211
+ : `LiveDesk Hub found at ${manager}; starting encrypted relay fallback.`
4132
4212
  : `Connected to LiveDesk Hub at ${manager}.`
4133
4213
  });
4134
4214
  console.log(connectionTransport === 'relay-fallback'
@@ -4210,17 +4290,22 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null, get
4210
4290
  pair = resolved.pair;
4211
4291
  connectionTransport = resolved.connectionTransport || 'direct-tcp';
4212
4292
  directReachable = resolved.directReachable !== false;
4293
+ directProbeSkipReason = String(resolved.directProbeSkipReason || '');
4213
4294
  connectionPage?.update({
4214
4295
  endpointCandidates: resolved.endpointCandidates || [],
4215
4296
  manager,
4216
4297
  assignedHubId: resolved.hubDeviceId || '',
4217
4298
  message: connectionTransport === 'relay-fallback'
4218
- ? `LiveDesk Hub found at ${manager}; starting encrypted relay fallback.`
4299
+ ? directProbeSkipReason
4300
+ ? `LiveDesk Hub found at ${manager}; the private endpoint is outside this local subnet, so encrypted relay/P2P negotiation starts immediately.`
4301
+ : `LiveDesk Hub found at ${manager}; starting encrypted relay fallback.`
4219
4302
  : `Connected to LiveDesk Hub at ${manager}.`
4220
4303
  });
4221
4304
  }
4222
4305
  console.log(connectionTransport === 'relay-fallback'
4223
- ? `Found LiveDesk Hub record at ${manager}; direct TCP did not answer, so RemoteFast will use the encrypted rendezvous relay.`
4306
+ ? directProbeSkipReason
4307
+ ? `Found LiveDesk Hub record at ${manager}; private endpoint is outside this local subnet, so Direct TCP was skipped and encrypted relay control will negotiate UDP P2P video.`
4308
+ : `Found LiveDesk Hub record at ${manager}; direct TCP did not answer, so RemoteFast will use encrypted relay control and prefer UDP P2P video.`
4224
4309
  : `Found LiveDesk Hub at ${manager}.`);
4225
4310
  }
4226
4311
  // Exact-package updates preserve the already paired Hub endpoint and pass
@@ -4285,6 +4370,7 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null, get
4285
4370
  forwarded,
4286
4371
  connectionTransport: connectionTransport || 'direct-tcp',
4287
4372
  directReachable: connectionTransport === 'relay-fallback' ? false : (directReachable || !shouldLogin),
4373
+ directProbeSkipReason,
4288
4374
  discoverySource,
4289
4375
  rediscoverOnDisconnect: shouldLogin,
4290
4376
  rediscoverOnInvalidPair: shouldLogin
package/package.json CHANGED
@@ -1,52 +1,52 @@
1
- {
2
- "name": "@livedesk/client",
3
- "version": "0.1.219",
4
- "description": "LiveDesk local remote client",
5
- "type": "module",
6
- "bin": {
7
- "client": "bin/livedesk-client.js",
8
- "livedesk-client": "bin/livedesk-client.js",
9
- "livedesk-client-node": "bin/livedesk-client-node.js",
10
- "livedesk-client-fast": "bin/livedesk-client-fast.js"
11
- },
12
- "files": [
13
- "bin/",
14
- "src/",
15
- "tests/",
16
- "README.md",
17
- "THIRD_PARTY_NOTICES.md"
18
- ],
19
- "scripts": {
20
- "check": "node --check bin/client-version.js && node --check bin/livedesk-client.js && node --check bin/livedesk-client-node.js && node --check bin/livedesk-client-update-bootstrap.cjs && node --check bin/livedesk-client-fast.js",
21
- "test:version": "node --test tests/client-version.test.mjs",
22
- "pack:dry": "npm pack --dry-run"
23
- },
24
- "keywords": [
25
- "livedesk",
26
- "remote",
27
- "agent",
28
- "local",
29
- "desktop"
30
- ],
31
- "license": "MIT",
32
- "engines": {
33
- "node": ">=20"
34
- },
35
- "dependencies": {
36
- "@ffmpeg-installer/ffmpeg": "^1.1.0",
37
- "ffmpeg-static": "^5.3.0",
38
- "@livedesk/runtime-core": "0.1.1",
39
- "@supabase/supabase-js": "^2.110.0",
40
- "node-screenshots": "^0.2.8",
41
- "ws": "^8.18.3"
42
- },
43
- "optionalDependencies": {
44
- "@livedesk/fast-linux-x64": "0.1.424",
45
- "@livedesk/fast-osx-arm64": "0.1.424",
46
- "@livedesk/fast-osx-x64": "0.1.424",
47
- "@livedesk/fast-win-x64": "0.1.424"
48
- },
49
- "publishConfig": {
50
- "access": "public"
51
- }
52
- }
1
+ {
2
+ "name": "@livedesk/client",
3
+ "version": "0.1.220",
4
+ "description": "LiveDesk local remote client",
5
+ "type": "module",
6
+ "bin": {
7
+ "client": "bin/livedesk-client.js",
8
+ "livedesk-client": "bin/livedesk-client.js",
9
+ "livedesk-client-node": "bin/livedesk-client-node.js",
10
+ "livedesk-client-fast": "bin/livedesk-client-fast.js"
11
+ },
12
+ "files": [
13
+ "bin/",
14
+ "src/",
15
+ "tests/",
16
+ "README.md",
17
+ "THIRD_PARTY_NOTICES.md"
18
+ ],
19
+ "scripts": {
20
+ "check": "node --check bin/client-version.js && node --check bin/livedesk-client.js && node --check bin/livedesk-client-node.js && node --check bin/livedesk-client-update-bootstrap.cjs && node --check bin/livedesk-client-fast.js",
21
+ "test:version": "node --test tests/client-version.test.mjs",
22
+ "pack:dry": "npm pack --dry-run"
23
+ },
24
+ "keywords": [
25
+ "livedesk",
26
+ "remote",
27
+ "agent",
28
+ "local",
29
+ "desktop"
30
+ ],
31
+ "license": "MIT",
32
+ "engines": {
33
+ "node": ">=20"
34
+ },
35
+ "dependencies": {
36
+ "@ffmpeg-installer/ffmpeg": "^1.1.0",
37
+ "ffmpeg-static": "^5.3.0",
38
+ "@livedesk/runtime-core": "0.1.1",
39
+ "@supabase/supabase-js": "^2.110.0",
40
+ "node-screenshots": "^0.2.8",
41
+ "ws": "^8.18.3"
42
+ },
43
+ "optionalDependencies": {
44
+ "@livedesk/fast-linux-x64": "0.1.425",
45
+ "@livedesk/fast-osx-arm64": "0.1.425",
46
+ "@livedesk/fast-osx-x64": "0.1.425",
47
+ "@livedesk/fast-win-x64": "0.1.425"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ }
52
+ }