livedesk 0.1.124 → 0.1.126

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.
@@ -1024,6 +1024,59 @@ function startsWithTcpBinaryFrameMagic(buffer) {
1024
1024
  && buffer.subarray(0, TCP_BINARY_FRAME_MAGIC.length).equals(TCP_BINARY_FRAME_MAGIC);
1025
1025
  }
1026
1026
 
1027
+ function isJsonObjectStartByte(value) {
1028
+ return value === 0x7b;
1029
+ }
1030
+
1031
+ function isWhitespaceByte(value) {
1032
+ return value === 0x20 || value === 0x09 || value === 0x0d || value === 0x0a;
1033
+ }
1034
+
1035
+ function findFirstNonWhitespaceByte(buffer) {
1036
+ if (!Buffer.isBuffer(buffer)) {
1037
+ return -1;
1038
+ }
1039
+
1040
+ for (let index = 0; index < buffer.length; index += 1) {
1041
+ if (!isWhitespaceByte(buffer[index])) {
1042
+ return index;
1043
+ }
1044
+ }
1045
+ return -1;
1046
+ }
1047
+
1048
+ function findTcpFrameOnlySyncIndex(buffer, startIndex = 0) {
1049
+ if (!Buffer.isBuffer(buffer) || buffer.length <= 0) {
1050
+ return -1;
1051
+ }
1052
+
1053
+ const safeStart = Math.max(0, Math.min(buffer.length, Math.floor(Number(startIndex) || 0)));
1054
+ const magicIndex = buffer.indexOf(TCP_BINARY_FRAME_MAGIC, safeStart);
1055
+ const jsonIndex = buffer.indexOf(0x7b, safeStart);
1056
+ if (magicIndex < 0) {
1057
+ return jsonIndex;
1058
+ }
1059
+ if (jsonIndex < 0) {
1060
+ return magicIndex;
1061
+ }
1062
+ return Math.min(magicIndex, jsonIndex);
1063
+ }
1064
+
1065
+ function keepTcpFrameOnlySyncTail(buffer) {
1066
+ if (!Buffer.isBuffer(buffer) || buffer.length <= 0) {
1067
+ return Buffer.alloc(0);
1068
+ }
1069
+
1070
+ const maxTailLength = Math.min(buffer.length, TCP_BINARY_FRAME_MAGIC.length - 1);
1071
+ for (let tailLength = maxTailLength; tailLength > 0; tailLength -= 1) {
1072
+ const tail = buffer.subarray(buffer.length - tailLength);
1073
+ if (hasTcpBinaryFrameMagicPrefix(tail)) {
1074
+ return tail;
1075
+ }
1076
+ }
1077
+ return Buffer.alloc(0);
1078
+ }
1079
+
1027
1080
  function parseTcpBinaryFramePacket(buffer) {
1028
1081
  if (!Buffer.isBuffer(buffer) || buffer.length < TCP_BINARY_FRAME_HEADER_BYTES) {
1029
1082
  return { incomplete: true };
@@ -1286,7 +1339,9 @@ export function createRemoteHub(options = {}) {
1286
1339
  const inputSockets = new Map();
1287
1340
  const frameSockets = new Map();
1288
1341
  const allSockets = new Set();
1342
+ const agentBinaryIngressLanes = [];
1289
1343
  const duplicateDeviceLogAt = new Map();
1344
+ let agentBinaryIngressDraining = false;
1290
1345
  let server = null;
1291
1346
  let started = false;
1292
1347
  let boundPort = requestedPort;
@@ -3252,29 +3307,56 @@ export function createRemoteHub(options = {}) {
3252
3307
  state.binaryQueueDrops = Number(state.binaryQueueDrops || 0) + 1;
3253
3308
  }
3254
3309
 
3255
- function drainQueuedAgentBinaryFrames(socket, state) {
3256
- if (state.binaryQueueDraining || state.closed) {
3310
+ function scheduleAgentBinaryIngress(socket, state) {
3311
+ if (!state || state.closed || !socket || socket.destroyed) {
3257
3312
  return;
3258
3313
  }
3259
- state.binaryQueueDraining = true;
3260
- setImmediate(() => {
3261
- state.binaryQueueDraining = false;
3262
- if (state.closed || socket.destroyed) {
3263
- state.binaryQueue.length = 0;
3264
- return;
3265
- }
3266
3314
 
3315
+ state.binaryQueueSocket = socket;
3316
+ if (state.binaryQueueScheduled !== true) {
3317
+ state.binaryQueueScheduled = true;
3318
+ agentBinaryIngressLanes.push(state);
3319
+ }
3320
+ drainAgentBinaryIngressRoundRobin();
3321
+ }
3322
+
3323
+ function drainAgentBinaryIngressRoundRobin() {
3324
+ if (agentBinaryIngressDraining) {
3325
+ return;
3326
+ }
3327
+ agentBinaryIngressDraining = true;
3328
+ setImmediate(() => {
3329
+ agentBinaryIngressDraining = false;
3267
3330
  let processed = 0;
3268
- while (state.binaryQueue.length > 0 && processed < AGENT_BINARY_INGRESS_DRAIN_BUDGET_PACKETS) {
3331
+ while (agentBinaryIngressLanes.length > 0 && processed < AGENT_BINARY_INGRESS_DRAIN_BUDGET_PACKETS) {
3332
+ const state = agentBinaryIngressLanes.shift();
3333
+ if (!state) {
3334
+ continue;
3335
+ }
3336
+
3337
+ state.binaryQueueScheduled = false;
3338
+ const socket = state.binaryQueueSocket;
3339
+ if (state.closed || !socket || socket.destroyed) {
3340
+ if (Array.isArray(state.binaryQueue)) {
3341
+ state.binaryQueue.length = 0;
3342
+ }
3343
+ continue;
3344
+ }
3345
+
3269
3346
  const item = state.binaryQueue.shift();
3270
3347
  if (item) {
3271
3348
  handleAgentBinaryFrame(socket, state, item.header, item.payload);
3272
3349
  processed += 1;
3273
3350
  }
3351
+
3352
+ if (state.binaryQueue.length > 0 && !state.closed && !socket.destroyed) {
3353
+ state.binaryQueueScheduled = true;
3354
+ agentBinaryIngressLanes.push(state);
3355
+ }
3274
3356
  }
3275
3357
 
3276
- if (state.binaryQueue.length > 0 && !state.closed && !socket.destroyed) {
3277
- drainQueuedAgentBinaryFrames(socket, state);
3358
+ if (agentBinaryIngressLanes.length > 0) {
3359
+ drainAgentBinaryIngressRoundRobin();
3278
3360
  }
3279
3361
  });
3280
3362
  }
@@ -3290,7 +3372,7 @@ export function createRemoteHub(options = {}) {
3290
3372
  dropQueuedAgentBinaryFrame(state);
3291
3373
  }
3292
3374
  state.binaryQueue.push({ header, payload });
3293
- drainQueuedAgentBinaryFrames(socket, state);
3375
+ scheduleAgentBinaryIngress(socket, state);
3294
3376
  }
3295
3377
 
3296
3378
  function handleAgentMessage(socket, state, message) {
@@ -3436,6 +3518,8 @@ export function createRemoteHub(options = {}) {
3436
3518
  frameOnly: false,
3437
3519
  binaryQueue: [],
3438
3520
  binaryQueueDraining: false,
3521
+ binaryQueueScheduled: false,
3522
+ binaryQueueSocket: null,
3439
3523
  binaryQueueDrops: 0,
3440
3524
  closed: false
3441
3525
  };
@@ -3488,6 +3572,8 @@ export function createRemoteHub(options = {}) {
3488
3572
  const detach = reason => {
3489
3573
  state.closed = true;
3490
3574
  state.binaryQueue.length = 0;
3575
+ state.binaryQueueScheduled = false;
3576
+ state.binaryQueueSocket = null;
3491
3577
  allSockets.delete(socket);
3492
3578
  clearTimeout(helloTimer);
3493
3579
  detachInputSocket(socket, reason);
@@ -3587,6 +3673,8 @@ export function createRemoteHub(options = {}) {
3587
3673
  pendingBinaryFrame: null,
3588
3674
  binaryQueue: [],
3589
3675
  binaryQueueDraining: false,
3676
+ binaryQueueScheduled: false,
3677
+ binaryQueueSocket: null,
3590
3678
  binaryQueueDrops: 0,
3591
3679
  closed: false
3592
3680
  };
@@ -3619,6 +3707,15 @@ export function createRemoteHub(options = {}) {
3619
3707
  }
3620
3708
 
3621
3709
  if (state.authenticated && state.frameOnly && state.buffer.length > 0) {
3710
+ const firstNonWhitespaceIndex = findFirstNonWhitespaceByte(state.buffer);
3711
+ if (firstNonWhitespaceIndex < 0) {
3712
+ state.buffer = Buffer.alloc(0);
3713
+ return;
3714
+ }
3715
+ if (firstNonWhitespaceIndex > 0) {
3716
+ state.buffer = state.buffer.subarray(firstNonWhitespaceIndex);
3717
+ }
3718
+
3622
3719
  if (state.buffer.length < TCP_BINARY_FRAME_MAGIC.length && hasTcpBinaryFrameMagicPrefix(state.buffer)) {
3623
3720
  return;
3624
3721
  }
@@ -3629,9 +3726,13 @@ export function createRemoteHub(options = {}) {
3629
3726
  return;
3630
3727
  }
3631
3728
  if (!packet || packet.invalid) {
3632
- writeJsonLine(socket, { type: 'error', error: 'invalid-tcp-binary-frame' });
3633
- logWarn('remote', `invalid tcp binary frame from agent: ${packet?.reason || 'bad-magic'}`);
3634
- socket.destroy();
3729
+ const syncIndex = findTcpFrameOnlySyncIndex(state.buffer, 1);
3730
+ if (syncIndex > 0) {
3731
+ state.buffer = state.buffer.subarray(syncIndex);
3732
+ continue;
3733
+ }
3734
+
3735
+ state.buffer = keepTcpFrameOnlySyncTail(state.buffer);
3635
3736
  return;
3636
3737
  }
3637
3738
 
@@ -3639,6 +3740,17 @@ export function createRemoteHub(options = {}) {
3639
3740
  enqueueAgentBinaryFrame(socket, state, packet.header, packet.payload);
3640
3741
  continue;
3641
3742
  }
3743
+
3744
+ if (!isJsonObjectStartByte(state.buffer[0])) {
3745
+ const syncIndex = findTcpFrameOnlySyncIndex(state.buffer, 1);
3746
+ if (syncIndex > 0) {
3747
+ state.buffer = state.buffer.subarray(syncIndex);
3748
+ continue;
3749
+ }
3750
+
3751
+ state.buffer = keepTcpFrameOnlySyncTail(state.buffer);
3752
+ return;
3753
+ }
3642
3754
  }
3643
3755
 
3644
3756
  const newlineIndex = state.buffer.indexOf(0x0a);
@@ -3676,6 +3788,9 @@ export function createRemoteHub(options = {}) {
3676
3788
 
3677
3789
  handleAgentMessage(socket, state, message);
3678
3790
  } catch (err) {
3791
+ if (state.authenticated && state.frameOnly) {
3792
+ continue;
3793
+ }
3679
3794
  writeJsonLine(socket, { type: 'error', error: 'invalid-json' });
3680
3795
  logWarn('remote', `invalid agent message: ${err?.message || err}`);
3681
3796
  }
@@ -3690,6 +3805,8 @@ export function createRemoteHub(options = {}) {
3690
3805
  socket.on('close', () => {
3691
3806
  state.closed = true;
3692
3807
  state.binaryQueue.length = 0;
3808
+ state.binaryQueueScheduled = false;
3809
+ state.binaryQueueSocket = null;
3693
3810
  allSockets.delete(socket);
3694
3811
  clearTimeout(helloTimer);
3695
3812
  detachInputSocket(socket);
@@ -3699,6 +3816,8 @@ export function createRemoteHub(options = {}) {
3699
3816
  socket.on('error', err => {
3700
3817
  state.closed = true;
3701
3818
  state.binaryQueue.length = 0;
3819
+ state.binaryQueueScheduled = false;
3820
+ state.binaryQueueSocket = null;
3702
3821
  allSockets.delete(socket);
3703
3822
  clearTimeout(helloTimer);
3704
3823
  detachInputSocket(socket, err?.message || 'socket-error');
package/hub/src/server.js CHANGED
@@ -23,7 +23,7 @@ const httpHost = process.env.LIVEDESK_HUB_HTTP_HOST || '0.0.0.0';
23
23
  const httpPort = Number(process.env.LIVEDESK_HUB_HTTP_PORT || process.env.PORT || 5179);
24
24
  const frameBackpressureBytes = readPositiveIntegerEnv('LIVEDESK_FRAME_WS_BACKPRESSURE_BYTES', 16 * 1024 * 1024);
25
25
  const frameClientQueuePackets = readPositiveIntegerEnv('LIVEDESK_FRAME_WS_QUEUE_PACKETS', 12);
26
- const frameClientDrainBudgetPackets = readPositiveIntegerEnv('LIVEDESK_FRAME_WS_DRAIN_BUDGET_PACKETS', 96);
26
+ const frameClientDrainBudgetPackets = readPositiveIntegerEnv('LIVEDESK_FRAME_WS_DRAIN_BUDGET_PACKETS', 8);
27
27
  const frameClients = new Set();
28
28
  const frameClientsByDeviceId = new Map();
29
29
  const frameWildcardClients = new Set();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livedesk",
3
- "version": "0.1.124",
3
+ "version": "0.1.126",
4
4
  "description": "LiveDesk Hub and client launcher",
5
5
  "type": "module",
6
6
  "bin": {