@livedesk/hub 0.1.54 → 0.1.55

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/package.json +2 -2
  2. package/src/remote-hub.js +94 -28
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/hub",
3
- "version": "0.1.54",
3
+ "version": "0.1.55",
4
4
  "description": "VuvoDesk local Hub API and browser frame bridge",
5
5
  "type": "module",
6
6
  "main": "src/server.js",
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "@ffmpeg-installer/ffmpeg": "^1.1.0",
19
- "@livedesk/runtime-core": "0.1.6",
19
+ "@livedesk/runtime-core": "0.1.7",
20
20
  "@openai/codex-sdk": "0.145.0",
21
21
  "cors": "^2.8.5",
22
22
  "express": "^4.21.2",
package/src/remote-hub.js CHANGED
@@ -1,7 +1,14 @@
1
- import net from 'net';
2
- import os from 'os';
3
- import crypto from 'crypto';
4
- import { createHubRelayControl } from './transport/relay-hub-control.js';
1
+ import net from 'net';
2
+ import os from 'os';
3
+ import crypto from 'crypto';
4
+ import {
5
+ acceptSecureDirectSocket,
6
+ isDirectSecureChannel,
7
+ isLoopbackSocketAddress,
8
+ isPrivateSocketAddress,
9
+ isSecureDirectHandshakeStart
10
+ } from '../../runtime-core/src/direct-secure-transport.js';
11
+ import { createHubRelayControl } from './transport/relay-hub-control.js';
5
12
  import { parseExactLiveStreamMonitorIndex } from './live-stream-monitor-contract.js';
6
13
  import {
7
14
  BoundedSegmentedBuffer,
@@ -2397,9 +2404,12 @@ export function createRemoteHub(options = {}) {
2397
2404
  const hostInstanceId = safeString(options.hostInstanceId ?? env.LIVEDESK_HUB_INSTANCE_ID ?? env.MINDEXEC_BRIDGE_INSTANCE_ID ?? crypto.randomUUID(), 128) || crypto.randomUUID();
2398
2405
  const publicEndpoint = safeString(env.LIVEDESK_REMOTE_PUBLIC_ENDPOINT || env.MINDEXEC_REMOTE_PUBLIC_ENDPOINT || env.REMOTE_HUB_PUBLIC_ENDPOINT, 256);
2399
2406
  const publicHost = safeString(env.LIVEDESK_REMOTE_PUBLIC_HOST || env.MINDEXEC_REMOTE_PUBLIC_HOST || env.REMOTE_HUB_PUBLIC_HOST, 128);
2400
- const pairToken = safeString(
2401
- options.pairToken || env.REMOTE_HUB_PAIR_TOKEN || env.LIVEDESK_CLIENT_PAIR_TOKEN || env.MINDEXEC_REMOTE_PAIR_TOKEN || crypto.randomBytes(6).toString('hex'),
2402
- 256);
2407
+ const pairToken = safeString(
2408
+ options.pairToken || env.REMOTE_HUB_PAIR_TOKEN || env.LIVEDESK_CLIENT_PAIR_TOKEN || env.MINDEXEC_REMOTE_PAIR_TOKEN || crypto.randomBytes(32).toString('hex'),
2409
+ 256);
2410
+ const allowInsecurePrivateDirect = isEnabledValue(
2411
+ options.allowInsecureDirect ?? env.LIVEDESK_ALLOW_INSECURE_DIRECT,
2412
+ false);
2403
2413
  const relayControl = options.relayControl && typeof options.relayControl === 'object'
2404
2414
  ? options.relayControl
2405
2415
  : createHubRelayControl({
@@ -7949,10 +7959,24 @@ export function createRemoteHub(options = {}) {
7949
7959
  function handleAgentMessage(socket, state, message) {
7950
7960
  if (!message || typeof message !== 'object') {
7951
7961
  return;
7952
- }
7953
-
7954
- if (!state.authenticated) {
7955
- if (message.type === 'hello' && socket.__liveDeskRelayControl === true) {
7962
+ }
7963
+
7964
+ if (!state.authenticated) {
7965
+ if (socket.__liveDeskSecureDirect === true) {
7966
+ const applicationChannel = message.type === 'slot.assign'
7967
+ ? 'control'
7968
+ : message.type === 'hello'
7969
+ ? (safeString(message.channel || message.Channel, 40).toLowerCase() || 'control')
7970
+ : '';
7971
+ if (applicationChannel
7972
+ && (!isDirectSecureChannel(applicationChannel)
7973
+ || applicationChannel !== socket.__liveDeskSecureChannel)) {
7974
+ writeJsonLine(socket, { type: 'error', error: 'direct-secure-channel-mismatch' });
7975
+ socket.end();
7976
+ return;
7977
+ }
7978
+ }
7979
+ if (message.type === 'hello' && socket.__liveDeskRelayControl === true) {
7956
7980
  message = {
7957
7981
  ...message,
7958
7982
  capabilities: {
@@ -7988,7 +8012,7 @@ export function createRemoteHub(options = {}) {
7988
8012
  return;
7989
8013
  }
7990
8014
 
7991
- const channel = safeString(message.channel || message.Channel, 40).toLowerCase();
8015
+ const channel = safeString(message.channel || message.Channel, 40).toLowerCase() || 'control';
7992
8016
  if (channel === 'input') {
7993
8017
  const device = attachInputSocket(socket, message);
7994
8018
  if (!device) {
@@ -8538,18 +8562,60 @@ export function createRemoteHub(options = {}) {
8538
8562
  if (firstChunk) {
8539
8563
  processData(firstChunk);
8540
8564
  }
8541
- }
8542
-
8543
- function handleSocket(socket) {
8544
- socket.once('data', chunk => {
8545
- const firstChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
8546
- if (isRemoteHubWebSocketUpgradeStart(firstChunk)) {
8547
- handleWebSocketUpgradeSocket(socket, firstChunk);
8548
- return;
8549
- }
8550
-
8551
- handleTcpAgentSocket(socket, firstChunk);
8552
- });
8565
+ }
8566
+
8567
+ function handleSocket(socket) {
8568
+ allSockets.add(socket);
8569
+ const prefaceTimer = setTimeout(() => {
8570
+ if (!socket.destroyed) socket.destroy();
8571
+ }, 10_000);
8572
+ prefaceTimer.unref?.();
8573
+ socket.once('close', () => {
8574
+ clearTimeout(prefaceTimer);
8575
+ allSockets.delete(socket);
8576
+ });
8577
+ socket.once('data', chunk => {
8578
+ clearTimeout(prefaceTimer);
8579
+ const firstChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
8580
+ if (isSecureDirectHandshakeStart(firstChunk)) {
8581
+ void acceptSecureDirectSocket(socket, firstChunk, pairToken)
8582
+ .then(secureSocket => {
8583
+ allSockets.delete(socket);
8584
+ handleTcpAgentSocket(secureSocket);
8585
+ })
8586
+ .catch(error => {
8587
+ if (socket.destroyed) return;
8588
+ if (error?.code === 'LIVEDESK_INVALID_PAIR_TOKEN_REJECTED') {
8589
+ const forcedCloseTimer = setTimeout(() => socket.destroy(), 1_000);
8590
+ forcedCloseTimer.unref?.();
8591
+ socket.once('close', () => clearTimeout(forcedCloseTimer));
8592
+ if (typeof socket.destroySoon === 'function') socket.destroySoon();
8593
+ else socket.end();
8594
+ return;
8595
+ }
8596
+ socket.destroy();
8597
+ });
8598
+ return;
8599
+ }
8600
+
8601
+ const remoteAddress = socket.remoteAddress;
8602
+ const allowLegacyPlaintext = isLoopbackSocketAddress(remoteAddress)
8603
+ || (allowInsecurePrivateDirect && isPrivateSocketAddress(remoteAddress));
8604
+ if (isRemoteHubWebSocketUpgradeStart(firstChunk)) {
8605
+ if (!allowLegacyPlaintext) {
8606
+ socket.destroy();
8607
+ return;
8608
+ }
8609
+ handleWebSocketUpgradeSocket(socket, firstChunk);
8610
+ return;
8611
+ }
8612
+
8613
+ if (!allowLegacyPlaintext) {
8614
+ socket.destroy();
8615
+ return;
8616
+ }
8617
+ handleTcpAgentSocket(socket, firstChunk);
8618
+ });
8553
8619
 
8554
8620
  socket.once('error', () => {
8555
8621
  // The transport-specific handler owns logging after the first byte.
@@ -8571,10 +8637,10 @@ export function createRemoteHub(options = {}) {
8571
8637
  started = true;
8572
8638
  boundPort = candidateServer.address()?.port || port;
8573
8639
  lastError = '';
8574
- logEvent('remote', `VuvoDesk Hub client endpoint listening on tcp://${host}:${boundPort}`, 'success');
8575
- if (host === '0.0.0.0' || host === '::') {
8576
- logWarn('remote', 'VuvoDesk Hub client endpoint is externally reachable. Use a strong pairing token and trusted network.');
8577
- }
8640
+ logEvent('remote', `VuvoDesk Hub client endpoint listening on tcp://${host}:${boundPort}`, 'success');
8641
+ if (host === '0.0.0.0' || host === '::') {
8642
+ logWarn('remote', 'VuvoDesk Hub client endpoint is externally reachable. Non-loopback Direct connections require authenticated encryption.');
8643
+ }
8578
8644
  emitRemoteEvent('RemoteHubStarted', null);
8579
8645
  resolve();
8580
8646
  });