livedesk 0.1.81 → 0.1.82

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.
@@ -1205,6 +1205,7 @@ export function createRemoteHub(options = {}) {
1205
1205
  const devices = new Map();
1206
1206
  const taskBatches = new Map();
1207
1207
  const sockets = new Map();
1208
+ const inputSockets = new Map();
1208
1209
  const allSockets = new Set();
1209
1210
  const duplicateDeviceLogAt = new Map();
1210
1211
  let server = null;
@@ -1738,6 +1739,7 @@ export function createRemoteHub(options = {}) {
1738
1739
 
1739
1740
  function closeExistingDeviceSocket(deviceId, nextSessionId) {
1740
1741
  const existing = devices.get(deviceId);
1742
+ closeInputSocket(existing, 'replaced-by-new-session');
1741
1743
  if (!existing?.socket || existing.socket.destroyed) {
1742
1744
  return;
1743
1745
  }
@@ -1752,6 +1754,43 @@ export function createRemoteHub(options = {}) {
1752
1754
  existing.socket.destroy();
1753
1755
  }
1754
1756
 
1757
+ function closeInputSocket(device, reason = 'input-socket-closed') {
1758
+ const socket = device?.inputSocket;
1759
+ if (!socket) {
1760
+ return;
1761
+ }
1762
+
1763
+ device.inputSocket = null;
1764
+ inputSockets.delete(socket);
1765
+ try {
1766
+ writeJsonLine(socket, { type: 'disconnect', reason });
1767
+ } catch {
1768
+ // Best-effort notice before closing the side channel.
1769
+ }
1770
+ try {
1771
+ socket.destroy?.();
1772
+ } catch {
1773
+ // Ignore close failures.
1774
+ }
1775
+ }
1776
+
1777
+ function detachInputSocket(socket, reason = 'input-socket-closed') {
1778
+ const deviceId = inputSockets.get(socket);
1779
+ inputSockets.delete(socket);
1780
+ if (!deviceId) {
1781
+ return;
1782
+ }
1783
+
1784
+ const device = devices.get(deviceId);
1785
+ if (!device || device.inputSocket !== socket) {
1786
+ return;
1787
+ }
1788
+
1789
+ device.inputSocket = null;
1790
+ device.inputLastSeenAt = new Date().toISOString();
1791
+ emitRemoteEvent('RemoteInputSocketDisconnected', device, { reason });
1792
+ }
1793
+
1755
1794
  function getDeviceActivityMs(device) {
1756
1795
  return Math.max(
1757
1796
  Date.parse(device?.lastSeenAt || '') || 0,
@@ -2136,6 +2175,7 @@ export function createRemoteHub(options = {}) {
2136
2175
 
2137
2176
  const device = {
2138
2177
  socket,
2178
+ inputSocket: null,
2139
2179
  deviceId,
2140
2180
  sessionId,
2141
2181
  deviceName: safeString(hello.deviceName || hello.hostname || deviceId, 120),
@@ -2152,6 +2192,8 @@ export function createRemoteHub(options = {}) {
2152
2192
  capabilities,
2153
2193
  connected: true,
2154
2194
  connectedAt: now,
2195
+ inputConnectedAt: '',
2196
+ inputLastSeenAt: '',
2155
2197
  disconnectedAt: '',
2156
2198
  lastSeenAt: now,
2157
2199
  lastStatusAt: '',
@@ -2195,6 +2237,37 @@ export function createRemoteHub(options = {}) {
2195
2237
  return device;
2196
2238
  }
2197
2239
 
2240
+ function attachInputSocket(socket, hello) {
2241
+ const deviceId = normalizeDeviceId(hello.deviceId);
2242
+ const device = devices.get(deviceId);
2243
+ if (!device || !device.connected || !device.socket || device.socket.destroyed) {
2244
+ writeJsonLine(socket, { type: 'error', error: 'device-not-connected' });
2245
+ socket.destroy();
2246
+ return null;
2247
+ }
2248
+
2249
+ closeInputSocket(device, 'replaced-by-new-input-socket');
2250
+
2251
+ const now = new Date().toISOString();
2252
+ device.inputSocket = socket;
2253
+ device.inputConnectedAt = now;
2254
+ device.inputLastSeenAt = now;
2255
+ inputSockets.set(socket, deviceId);
2256
+
2257
+ writeJsonLine(socket, {
2258
+ type: 'welcome',
2259
+ channel: 'input',
2260
+ protocol: REMOTE_AGENT_PROTOCOL,
2261
+ protocolVersion: REMOTE_PROTOCOL_VERSION,
2262
+ sessionId: device.sessionId,
2263
+ deviceId,
2264
+ serverTime: now
2265
+ });
2266
+
2267
+ emitRemoteEvent('RemoteInputSocketConnected', device);
2268
+ return device;
2269
+ }
2270
+
2198
2271
  function detachSocket(socket, reason = 'socket-closed') {
2199
2272
  const deviceId = sockets.get(socket);
2200
2273
  sockets.delete(socket);
@@ -2209,6 +2282,7 @@ export function createRemoteHub(options = {}) {
2209
2282
 
2210
2283
  device.connected = false;
2211
2284
  device.socket = null;
2285
+ closeInputSocket(device, reason);
2212
2286
  device.disconnectedAt = new Date().toISOString();
2213
2287
  device.lastDisconnectReason = reason;
2214
2288
  if (device.activeLiveStream) {
@@ -2973,6 +3047,12 @@ export function createRemoteHub(options = {}) {
2973
3047
  }
2974
3048
 
2975
3049
  const device = state.device;
3050
+ if (state.inputOnly) {
3051
+ writeJsonLine(socket, { type: 'error', error: 'input-channel-binary-not-supported' });
3052
+ socket.destroy();
3053
+ return;
3054
+ }
3055
+
2976
3056
  device.counters.messagesReceived += 1;
2977
3057
  device.lastSeenAt = new Date().toISOString();
2978
3058
 
@@ -3016,6 +3096,19 @@ export function createRemoteHub(options = {}) {
3016
3096
  return;
3017
3097
  }
3018
3098
 
3099
+ const channel = safeString(message.channel || message.Channel, 40).toLowerCase();
3100
+ if (channel === 'input') {
3101
+ const device = attachInputSocket(socket, message);
3102
+ if (!device) {
3103
+ return;
3104
+ }
3105
+
3106
+ state.authenticated = true;
3107
+ state.device = device;
3108
+ state.inputOnly = true;
3109
+ return;
3110
+ }
3111
+
3019
3112
  const device = attachDevice(socket, message);
3020
3113
  if (!device) {
3021
3114
  return;
@@ -3032,6 +3125,11 @@ export function createRemoteHub(options = {}) {
3032
3125
  return;
3033
3126
  }
3034
3127
 
3128
+ if (state.inputOnly) {
3129
+ device.inputLastSeenAt = new Date().toISOString();
3130
+ return;
3131
+ }
3132
+
3035
3133
  device.counters.messagesReceived += 1;
3036
3134
  device.lastSeenAt = new Date().toISOString();
3037
3135
 
@@ -3103,7 +3201,8 @@ export function createRemoteHub(options = {}) {
3103
3201
 
3104
3202
  const state = {
3105
3203
  authenticated: false,
3106
- device: null
3204
+ device: null,
3205
+ inputOnly: false
3107
3206
  };
3108
3207
 
3109
3208
  const helloTimer = setTimeout(() => {
@@ -3154,6 +3253,7 @@ export function createRemoteHub(options = {}) {
3154
3253
  const detach = reason => {
3155
3254
  allSockets.delete(socket);
3156
3255
  clearTimeout(helloTimer);
3256
+ detachInputSocket(socket, reason);
3157
3257
  detachSocket(socket, reason);
3158
3258
  };
3159
3259
 
@@ -3243,6 +3343,7 @@ export function createRemoteHub(options = {}) {
3243
3343
  const state = {
3244
3344
  authenticated: false,
3245
3345
  device: null,
3346
+ inputOnly: false,
3246
3347
  buffer: Buffer.alloc(0),
3247
3348
  pendingBinaryFrame: null
3248
3349
  };
@@ -3323,11 +3424,13 @@ export function createRemoteHub(options = {}) {
3323
3424
  socket.on('close', () => {
3324
3425
  allSockets.delete(socket);
3325
3426
  clearTimeout(helloTimer);
3427
+ detachInputSocket(socket);
3326
3428
  detachSocket(socket);
3327
3429
  });
3328
3430
  socket.on('error', err => {
3329
3431
  allSockets.delete(socket);
3330
3432
  clearTimeout(helloTimer);
3433
+ detachInputSocket(socket, err?.message || 'socket-error');
3331
3434
  detachSocket(socket, err?.message || 'socket-error');
3332
3435
  });
3333
3436
  }
@@ -3514,6 +3617,24 @@ export function createRemoteHub(options = {}) {
3514
3617
  return { ok: false, error: 'missing-input-type' };
3515
3618
  }
3516
3619
 
3620
+ const inputSocket = device.inputSocket;
3621
+ if (inputSocket && !inputSocket.destroyed) {
3622
+ const sent = writeJsonLine(inputSocket, {
3623
+ type: 'input.control',
3624
+ payload: {
3625
+ ...normalized,
3626
+ issuedAt: normalized.issuedAt || new Date().toISOString()
3627
+ }
3628
+ });
3629
+ if (sent) {
3630
+ device.counters.commandsSent += 1;
3631
+ device.inputLastSeenAt = new Date().toISOString();
3632
+ return { ok: true, inputSocket: true };
3633
+ }
3634
+
3635
+ detachInputSocket(inputSocket, 'input-socket-write-failed');
3636
+ }
3637
+
3517
3638
  return sendCommand(deviceId, {
3518
3639
  command: 'input.control',
3519
3640
  payload: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livedesk",
3
- "version": "0.1.81",
3
+ "version": "0.1.82",
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.61",
33
+ "@livedesk/client": "0.1.62",
34
34
  "cors": "^2.8.5",
35
35
  "express": "^4.21.2",
36
36
  "ws": "^8.18.3"