livedesk 0.1.129 → 0.1.131

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.
@@ -858,6 +858,13 @@ function serializeDevice(device, options = {}) {
858
858
  lastDisconnectReason: device.lastDisconnectReason,
859
859
  remoteAddress: device.remoteAddress,
860
860
  remotePort: device.remotePort,
861
+ channels: {
862
+ control: !!device.socket && !device.socket.destroyed,
863
+ input: !!device.inputSocket && !device.inputSocket.destroyed,
864
+ frame: !!device.frameSocket && !device.frameSocket.destroyed,
865
+ audio: !!device.audioSocket && !device.audioSocket.destroyed,
866
+ file: !!device.fileSocket && !device.fileSocket.destroyed
867
+ },
861
868
  latestThumbnail: serializeRemoteFrame(device.latestThumbnail, device.deviceId, 'thumbnail', options),
862
869
  latestLiveFrame: serializeRemoteFrame(device.latestLiveFrame, device.deviceId, 'live', options),
863
870
  activeLiveStream: device.activeLiveStream ? { ...device.activeLiveStream } : null,
@@ -1338,6 +1345,8 @@ export function createRemoteHub(options = {}) {
1338
1345
  const sockets = new Map();
1339
1346
  const inputSockets = new Map();
1340
1347
  const frameSockets = new Map();
1348
+ const audioSockets = new Map();
1349
+ const fileSockets = new Map();
1341
1350
  const allSockets = new Set();
1342
1351
  const agentBinaryIngressLanes = [];
1343
1352
  const duplicateDeviceLogAt = new Map();
@@ -1875,6 +1884,8 @@ export function createRemoteHub(options = {}) {
1875
1884
  const existing = devices.get(deviceId);
1876
1885
  closeInputSocket(existing, 'replaced-by-new-session');
1877
1886
  closeFrameSocket(existing, 'replaced-by-new-session');
1887
+ closeAudioSocket(existing, 'replaced-by-new-session');
1888
+ closeFileSocket(existing, 'replaced-by-new-session');
1878
1889
  if (!existing?.socket || existing.socket.destroyed) {
1879
1890
  return;
1880
1891
  }
@@ -1909,6 +1920,46 @@ export function createRemoteHub(options = {}) {
1909
1920
  }
1910
1921
  }
1911
1922
 
1923
+ function closeAudioSocket(device, reason = 'audio-socket-closed') {
1924
+ const socket = device?.audioSocket;
1925
+ if (!socket) {
1926
+ return;
1927
+ }
1928
+
1929
+ device.audioSocket = null;
1930
+ audioSockets.delete(socket);
1931
+ try {
1932
+ writeJsonLine(socket, { type: 'disconnect', channel: 'audio', reason });
1933
+ } catch {
1934
+ // Best-effort notice before closing the side channel.
1935
+ }
1936
+ try {
1937
+ socket.destroy?.();
1938
+ } catch {
1939
+ // Ignore close failures.
1940
+ }
1941
+ }
1942
+
1943
+ function closeFileSocket(device, reason = 'file-socket-closed') {
1944
+ const socket = device?.fileSocket;
1945
+ if (!socket) {
1946
+ return;
1947
+ }
1948
+
1949
+ device.fileSocket = null;
1950
+ fileSockets.delete(socket);
1951
+ try {
1952
+ writeJsonLine(socket, { type: 'disconnect', channel: 'file', reason });
1953
+ } catch {
1954
+ // Best-effort notice before closing the side channel.
1955
+ }
1956
+ try {
1957
+ socket.destroy?.();
1958
+ } catch {
1959
+ // Ignore close failures.
1960
+ }
1961
+ }
1962
+
1912
1963
  function closeInputSocket(device, reason = 'input-socket-closed') {
1913
1964
  const socket = device?.inputSocket;
1914
1965
  if (!socket) {
@@ -1963,6 +2014,40 @@ export function createRemoteHub(options = {}) {
1963
2014
  emitRemoteEvent('RemoteFrameSocketDisconnected', device, { reason });
1964
2015
  }
1965
2016
 
2017
+ function detachAudioSocket(socket, reason = 'audio-socket-closed') {
2018
+ const deviceId = audioSockets.get(socket);
2019
+ audioSockets.delete(socket);
2020
+ if (!deviceId) {
2021
+ return;
2022
+ }
2023
+
2024
+ const device = devices.get(deviceId);
2025
+ if (!device || device.audioSocket !== socket) {
2026
+ return;
2027
+ }
2028
+
2029
+ device.audioSocket = null;
2030
+ device.audioLastSeenAt = new Date().toISOString();
2031
+ emitRemoteEvent('RemoteAudioSocketDisconnected', device, { reason });
2032
+ }
2033
+
2034
+ function detachFileSocket(socket, reason = 'file-socket-closed') {
2035
+ const deviceId = fileSockets.get(socket);
2036
+ fileSockets.delete(socket);
2037
+ if (!deviceId) {
2038
+ return;
2039
+ }
2040
+
2041
+ const device = devices.get(deviceId);
2042
+ if (!device || device.fileSocket !== socket) {
2043
+ return;
2044
+ }
2045
+
2046
+ device.fileSocket = null;
2047
+ device.fileLastSeenAt = new Date().toISOString();
2048
+ emitRemoteEvent('RemoteFileSocketDisconnected', device, { reason });
2049
+ }
2050
+
1966
2051
  function getDeviceActivityMs(device) {
1967
2052
  return Math.max(
1968
2053
  Date.parse(device?.lastSeenAt || '') || 0,
@@ -2349,6 +2434,8 @@ export function createRemoteHub(options = {}) {
2349
2434
  socket,
2350
2435
  inputSocket: null,
2351
2436
  frameSocket: null,
2437
+ audioSocket: null,
2438
+ fileSocket: null,
2352
2439
  deviceId,
2353
2440
  sessionId,
2354
2441
  deviceName: safeString(hello.deviceName || hello.hostname || deviceId, 120),
@@ -2369,6 +2456,10 @@ export function createRemoteHub(options = {}) {
2369
2456
  inputLastSeenAt: '',
2370
2457
  frameConnectedAt: '',
2371
2458
  frameLastSeenAt: '',
2459
+ audioConnectedAt: '',
2460
+ audioLastSeenAt: '',
2461
+ fileConnectedAt: '',
2462
+ fileLastSeenAt: '',
2372
2463
  disconnectedAt: '',
2373
2464
  lastSeenAt: now,
2374
2465
  lastStatusAt: '',
@@ -2476,6 +2567,62 @@ export function createRemoteHub(options = {}) {
2476
2567
  return device;
2477
2568
  }
2478
2569
 
2570
+ function attachAudioSocket(socket, hello) {
2571
+ const deviceId = normalizeDeviceId(hello.deviceId);
2572
+ const device = devices.get(deviceId);
2573
+ if (!device || !device.connected || !device.socket || device.socket.destroyed) {
2574
+ writeJsonLine(socket, { type: 'error', error: 'device-not-connected' });
2575
+ socket.destroy();
2576
+ return null;
2577
+ }
2578
+
2579
+ closeAudioSocket(device, 'replaced-by-new-audio-socket');
2580
+ const now = new Date().toISOString();
2581
+ device.audioSocket = socket;
2582
+ device.audioConnectedAt = now;
2583
+ device.audioLastSeenAt = now;
2584
+ audioSockets.set(socket, deviceId);
2585
+ writeJsonLine(socket, {
2586
+ type: 'welcome',
2587
+ channel: 'audio',
2588
+ protocol: REMOTE_AGENT_PROTOCOL,
2589
+ protocolVersion: REMOTE_PROTOCOL_VERSION,
2590
+ sessionId: device.sessionId,
2591
+ deviceId,
2592
+ serverTime: now
2593
+ });
2594
+ emitRemoteEvent('RemoteAudioSocketConnected', device);
2595
+ return device;
2596
+ }
2597
+
2598
+ function attachFileSocket(socket, hello) {
2599
+ const deviceId = normalizeDeviceId(hello.deviceId);
2600
+ const device = devices.get(deviceId);
2601
+ if (!device || !device.connected || !device.socket || device.socket.destroyed) {
2602
+ writeJsonLine(socket, { type: 'error', error: 'device-not-connected' });
2603
+ socket.destroy();
2604
+ return null;
2605
+ }
2606
+
2607
+ closeFileSocket(device, 'replaced-by-new-file-socket');
2608
+ const now = new Date().toISOString();
2609
+ device.fileSocket = socket;
2610
+ device.fileConnectedAt = now;
2611
+ device.fileLastSeenAt = now;
2612
+ fileSockets.set(socket, deviceId);
2613
+ writeJsonLine(socket, {
2614
+ type: 'welcome',
2615
+ channel: 'file',
2616
+ protocol: REMOTE_AGENT_PROTOCOL,
2617
+ protocolVersion: REMOTE_PROTOCOL_VERSION,
2618
+ sessionId: device.sessionId,
2619
+ deviceId,
2620
+ serverTime: now
2621
+ });
2622
+ emitRemoteEvent('RemoteFileSocketConnected', device);
2623
+ return device;
2624
+ }
2625
+
2479
2626
  function detachSocket(socket, reason = 'socket-closed') {
2480
2627
  const deviceId = sockets.get(socket);
2481
2628
  sockets.delete(socket);
@@ -3260,7 +3407,7 @@ export function createRemoteHub(options = {}) {
3260
3407
  }
3261
3408
 
3262
3409
  const device = state.device;
3263
- if (state.inputOnly) {
3410
+ if (state.inputOnly || state.fileOnly) {
3264
3411
  writeJsonLine(socket, { type: 'error', error: 'input-channel-binary-not-supported' });
3265
3412
  socket.destroy();
3266
3413
  return;
@@ -3271,6 +3418,9 @@ export function createRemoteHub(options = {}) {
3271
3418
  if (state.frameOnly) {
3272
3419
  device.frameLastSeenAt = device.lastSeenAt;
3273
3420
  }
3421
+ if (state.audioOnly) {
3422
+ device.audioLastSeenAt = device.lastSeenAt;
3423
+ }
3274
3424
 
3275
3425
  const frameKind = safeString(header.frameKind || header.kind || header.frameType || '', 40).toLowerCase();
3276
3426
  if (frameKind === 'thumbnail' || header.type === 'thumbnail.binary') {
@@ -3420,6 +3570,28 @@ export function createRemoteHub(options = {}) {
3420
3570
  state.frameOnly = true;
3421
3571
  return;
3422
3572
  }
3573
+ if (channel === 'audio') {
3574
+ const device = attachAudioSocket(socket, message);
3575
+ if (!device) {
3576
+ return;
3577
+ }
3578
+
3579
+ state.authenticated = true;
3580
+ state.device = device;
3581
+ state.audioOnly = true;
3582
+ return;
3583
+ }
3584
+ if (channel === 'file') {
3585
+ const device = attachFileSocket(socket, message);
3586
+ if (!device) {
3587
+ return;
3588
+ }
3589
+
3590
+ state.authenticated = true;
3591
+ state.device = device;
3592
+ state.fileOnly = true;
3593
+ return;
3594
+ }
3423
3595
 
3424
3596
  const device = attachDevice(socket, message);
3425
3597
  if (!device) {
@@ -3445,6 +3617,13 @@ export function createRemoteHub(options = {}) {
3445
3617
  device.frameLastSeenAt = new Date().toISOString();
3446
3618
  return;
3447
3619
  }
3620
+ if (state.audioOnly) {
3621
+ device.audioLastSeenAt = new Date().toISOString();
3622
+ return;
3623
+ }
3624
+ if (state.fileOnly) {
3625
+ device.fileLastSeenAt = new Date().toISOString();
3626
+ }
3448
3627
 
3449
3628
  device.counters.messagesReceived += 1;
3450
3629
  device.lastSeenAt = new Date().toISOString();
@@ -3520,6 +3699,8 @@ export function createRemoteHub(options = {}) {
3520
3699
  device: null,
3521
3700
  inputOnly: false,
3522
3701
  frameOnly: false,
3702
+ audioOnly: false,
3703
+ fileOnly: false,
3523
3704
  binaryQueue: [],
3524
3705
  binaryQueueDraining: false,
3525
3706
  binaryQueueScheduled: false,
@@ -3582,6 +3763,8 @@ export function createRemoteHub(options = {}) {
3582
3763
  clearTimeout(helloTimer);
3583
3764
  detachInputSocket(socket, reason);
3584
3765
  detachFrameSocket(socket, reason);
3766
+ detachAudioSocket(socket, reason);
3767
+ detachFileSocket(socket, reason);
3585
3768
  detachSocket(socket, reason);
3586
3769
  };
3587
3770
 
@@ -3673,6 +3856,8 @@ export function createRemoteHub(options = {}) {
3673
3856
  device: null,
3674
3857
  inputOnly: false,
3675
3858
  frameOnly: false,
3859
+ audioOnly: false,
3860
+ fileOnly: false,
3676
3861
  buffer: Buffer.alloc(0),
3677
3862
  pendingBinaryFrame: null,
3678
3863
  binaryQueue: [],
@@ -3710,7 +3895,7 @@ export function createRemoteHub(options = {}) {
3710
3895
  continue;
3711
3896
  }
3712
3897
 
3713
- if (state.authenticated && state.frameOnly && state.buffer.length > 0) {
3898
+ if (state.authenticated && (state.frameOnly || state.audioOnly) && state.buffer.length > 0) {
3714
3899
  const firstNonWhitespaceIndex = findFirstNonWhitespaceByte(state.buffer);
3715
3900
  if (firstNonWhitespaceIndex < 0) {
3716
3901
  state.buffer = Buffer.alloc(0);
@@ -3792,7 +3977,7 @@ export function createRemoteHub(options = {}) {
3792
3977
 
3793
3978
  handleAgentMessage(socket, state, message);
3794
3979
  } catch (err) {
3795
- if (state.authenticated && state.frameOnly) {
3980
+ if (state.authenticated && (state.frameOnly || state.audioOnly)) {
3796
3981
  continue;
3797
3982
  }
3798
3983
  writeJsonLine(socket, { type: 'error', error: 'invalid-json' });
@@ -3815,6 +4000,8 @@ export function createRemoteHub(options = {}) {
3815
4000
  clearTimeout(helloTimer);
3816
4001
  detachInputSocket(socket);
3817
4002
  detachFrameSocket(socket);
4003
+ detachAudioSocket(socket);
4004
+ detachFileSocket(socket);
3818
4005
  detachSocket(socket);
3819
4006
  });
3820
4007
  socket.on('error', err => {
@@ -3826,6 +4013,8 @@ export function createRemoteHub(options = {}) {
3826
4013
  clearTimeout(helloTimer);
3827
4014
  detachInputSocket(socket, err?.message || 'socket-error');
3828
4015
  detachFrameSocket(socket, err?.message || 'socket-error');
4016
+ detachAudioSocket(socket, err?.message || 'socket-error');
4017
+ detachFileSocket(socket, err?.message || 'socket-error');
3829
4018
  detachSocket(socket, err?.message || 'socket-error');
3830
4019
  });
3831
4020
  }
@@ -3976,19 +4165,26 @@ export function createRemoteHub(options = {}) {
3976
4165
  }
3977
4166
 
3978
4167
  const commandId = safeString(command?.commandId, 128) || crypto.randomUUID();
4168
+ const commandName = safeString(command?.command || 'ping', 80);
3979
4169
  const payload = {
3980
4170
  type: 'command',
3981
4171
  commandId,
3982
- command: safeString(command?.command || 'ping', 80),
4172
+ command: commandName,
3983
4173
  payload: command?.payload ?? null,
3984
4174
  issuedAt: new Date().toISOString()
3985
4175
  };
3986
4176
 
3987
- writeJsonLine(device.socket, payload);
4177
+ const dedicatedFileSocket = commandName === 'file.transfer'
4178
+ && device.fileSocket
4179
+ && !device.fileSocket.destroyed
4180
+ ? device.fileSocket
4181
+ : null;
4182
+ writeJsonLine(dedicatedFileSocket || device.socket, payload);
3988
4183
  device.counters.commandsSent += 1;
3989
4184
  emitRemoteEvent('RemoteCommandQueued', device, {
3990
4185
  commandId,
3991
- command: payload.command
4186
+ command: payload.command,
4187
+ channel: dedicatedFileSocket ? 'file' : 'control'
3992
4188
  });
3993
4189
  return { ok: true, commandId };
3994
4190
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livedesk",
3
- "version": "0.1.129",
3
+ "version": "0.1.131",
4
4
  "description": "LiveDesk Hub and client launcher",
5
5
  "type": "module",
6
6
  "bin": {
@@ -30,7 +30,7 @@
30
30
  "node": ">=20"
31
31
  },
32
32
  "dependencies": {
33
- "@livedesk/client": "0.1.88",
33
+ "@livedesk/client": "0.1.90",
34
34
  "cors": "^2.8.5",
35
35
  "express": "^4.21.2",
36
36
  "ws": "^8.18.3"