livedesk 0.1.436 → 0.1.437

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.
package/bin/livedesk.js CHANGED
@@ -1198,7 +1198,11 @@ async function runManager(args, resolvedRole = null) {
1198
1198
  LIVEDESK_CLIENT_PACKAGE_VERSION: readClientVersion(),
1199
1199
  LIVEDESK_HUB_UPDATE_REQUEST_PATH: updateRequestPath,
1200
1200
  LIVEDESK_HUB_UPDATE_RESULT_PATH: hubUpdateResultPath,
1201
- REMOTE_HUB_PORT: String(remotePort),
1201
+ LIVEDESK_UDP_ENABLED: String(process.env.LIVEDESK_UDP_ENABLED || '1'),
1202
+ LIVEDESK_UDP_PREFER_P2P: String(process.env.LIVEDESK_UDP_PREFER_P2P || '0'),
1203
+ LIVEDESK_UDP_RENDEZVOUS_HOST: String(process.env.LIVEDESK_UDP_RENDEZVOUS_HOST || 'www.gnsoftech.com'),
1204
+ LIVEDESK_UDP_RENDEZVOUS_PORT: String(process.env.LIVEDESK_UDP_RENDEZVOUS_PORT || '5199'),
1205
+ REMOTE_HUB_PORT: String(remotePort),
1202
1206
  REMOTE_HUB_PAIR_TOKEN: pairToken,
1203
1207
  LIVEDESK_WEB_DIST: existsSync(resolve(packagedWebDist, 'index.html')) ? packagedWebDist : (process.env.LIVEDESK_WEB_DIST || ''),
1204
1208
  LIVEDESK_RUNTIME_ROLE: 'hub',
@@ -3872,6 +3872,8 @@ function buildClientAgentEnvironment(prepared) {
3872
3872
  env.LIVEDESK_CLIENT_SLOT = String(prepared?.slot || env.LIVEDESK_CLIENT_SLOT || '');
3873
3873
  env.LIVEDESK_DEVICE_ID = String(prepared?.deviceId || env.LIVEDESK_DEVICE_ID || '');
3874
3874
  env.LIVEDESK_CLIENT_ENGINE = String(prepared?.engine || env.LIVEDESK_CLIENT_ENGINE || 'auto');
3875
+ env.LIVEDESK_CLIENT_TRANSPORT = String(env.LIVEDESK_CLIENT_TRANSPORT || 'auto');
3876
+ env.LIVEDESK_UDP_ENABLED = String(env.LIVEDESK_UDP_ENABLED || '1');
3875
3877
  env.LIVEDESK_CLIENT_UPDATE_STATE_PATH = CLIENT_UPDATE_STATE_PATH;
3876
3878
  env.LIVEDESK_CLIENT_UPDATE_ARGS_BASE64 = Buffer
3877
3879
  .from(JSON.stringify(buildClientUpdateRestartArgs(prepared)), 'utf8')
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/client",
3
- "version": "0.1.192",
3
+ "version": "0.1.193",
4
4
  "description": "LiveDesk local remote client",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,10 +41,10 @@
41
41
  "ws": "^8.18.3"
42
42
  },
43
43
  "optionalDependencies": {
44
- "@livedesk/fast-linux-x64": "0.1.399",
45
- "@livedesk/fast-osx-arm64": "0.1.399",
46
- "@livedesk/fast-osx-x64": "0.1.399",
47
- "@livedesk/fast-win-x64": "0.1.399"
44
+ "@livedesk/fast-linux-x64": "0.1.400",
45
+ "@livedesk/fast-osx-arm64": "0.1.400",
46
+ "@livedesk/fast-osx-x64": "0.1.400",
47
+ "@livedesk/fast-win-x64": "0.1.400"
48
48
  },
49
49
  "publishConfig": {
50
50
  "access": "public"
@@ -1456,7 +1456,9 @@ class RemoteHubWebSocketAgentSocket {
1456
1456
  }
1457
1457
 
1458
1458
  export function createRemoteHub(options = {}) {
1459
- const env = options.env || process.env;
1459
+ const env = options.env || process.env;
1460
+ const rejectFrameChannelForTest = isEnabledValue(env.LIVEDESK_TEST_MODE, false)
1461
+ && isEnabledValue(env.LIVEDESK_TEST_REJECT_FRAME_CHANNEL, false);
1460
1462
  const logEvent = options.logEvent || (() => {});
1461
1463
  const logWarn = options.logWarn || (() => {});
1462
1464
  const emitEvent = options.emitEvent || (() => {});
@@ -4497,9 +4499,14 @@ export function createRemoteHub(options = {}) {
4497
4499
  state.device = device;
4498
4500
  state.inputOnly = true;
4499
4501
  return;
4500
- }
4501
- if (channel === 'frame') {
4502
- const device = attachFrameSocket(socket, message);
4502
+ }
4503
+ if (channel === 'frame') {
4504
+ if (rejectFrameChannelForTest) {
4505
+ writeJsonLine(socket, { type: 'error', error: 'test-frame-channel-unavailable' });
4506
+ socket.end();
4507
+ return;
4508
+ }
4509
+ const device = attachFrameSocket(socket, message);
4503
4510
  if (!device) {
4504
4511
  return;
4505
4512
  }
@@ -30,16 +30,16 @@ function sameEndpoint(left, right) {
30
30
  return !!left && !!right && String(left.address) === String(right.address) && Number(left.port) === Number(right.port);
31
31
  }
32
32
 
33
- export function createHubUdpTransport({ env = process.env, logEvent = () => {}, logWarn = () => {} } = {}) {
34
- const enabled = isEnabled(env.LIVEDESK_UDP_ENABLED, false);
35
- const preferP2p = isEnabled(env.LIVEDESK_UDP_PREFER_P2P, true);
33
+ export function createHubUdpTransport({ env = process.env, logEvent = () => {}, logWarn = () => {} } = {}) {
34
+ const enabled = isEnabled(env.LIVEDESK_UDP_ENABLED, true);
35
+ const preferP2p = isEnabled(env.LIVEDESK_UDP_PREFER_P2P, false);
36
36
  const bindHost = safeText(env.LIVEDESK_UDP_BIND_HOST || '0.0.0.0', 128) || '0.0.0.0';
37
37
  const requestedPort = numberEnv(env.LIVEDESK_UDP_PORT, 0, 65535, 0);
38
38
  const advertiseHost = safeText(env.LIVEDESK_UDP_ADVERTISE_HOST || env.LIVEDESK_REMOTE_PUBLIC_HOST || '', 128);
39
- const rendezvousHost = safeText(env.LIVEDESK_UDP_RENDEZVOUS_HOST || '', 128);
39
+ const rendezvousHost = safeText(env.LIVEDESK_UDP_RENDEZVOUS_HOST || 'www.gnsoftech.com', 128);
40
40
  const rendezvousPort = numberEnv(env.LIVEDESK_UDP_RENDEZVOUS_PORT, 1, 65535, 5199);
41
41
  const punchTimeoutMs = numberEnv(env.LIVEDESK_UDP_PUNCH_TIMEOUT_MS, 500, 10_000, 3000);
42
- const frameTimeoutMs = numberEnv(env.LIVEDESK_UDP_FRAME_TIMEOUT_MS, 20, 2000, 80);
42
+ const frameTimeoutMs = numberEnv(env.LIVEDESK_UDP_FRAME_TIMEOUT_MS, 20, 2000, 500);
43
43
  const maxPendingFrames = numberEnv(env.LIVEDESK_UDP_MAX_PENDING_FRAMES, 1, 16, 2);
44
44
  const trace = isEnabled(env.LIVEDESK_UDP_TRACE, false);
45
45
  const socket = dgram.createSocket('udp4');
@@ -171,10 +171,16 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
171
171
  socket.once('error', onError);
172
172
  socket.once('listening', onListening);
173
173
  socket.bind(requestedPort, bindHost);
174
- });
175
- started = true;
176
- boundPort = socket.address().port;
177
- logEvent('udp', `UDP P2P socket listening on udp://${bindHost}:${boundPort}`);
174
+ });
175
+ started = true;
176
+ boundPort = socket.address().port;
177
+ try {
178
+ socket.setRecvBufferSize(8 * 1024 * 1024);
179
+ socket.setSendBufferSize(1024 * 1024);
180
+ } catch (error) {
181
+ logWarn('udp', `UDP socket buffer tuning was unavailable: ${error?.message || error}`);
182
+ }
183
+ logEvent('udp', `UDP P2P socket listening on udp://${bindHost}:${boundPort}`);
178
184
  return getStatus();
179
185
  }
180
186
 
@@ -1,4 +1,5 @@
1
- import crypto from 'node:crypto';
1
+ import crypto from 'node:crypto';
2
+ import { inflateRawSync } from 'node:zlib';
2
3
 
3
4
  export const UDP_P2P_PROTOCOL = 'livedesk.udp.p2p.v1';
4
5
  export const UDP_P2P_VERSION = 1;
@@ -17,8 +18,10 @@ const UDP_FIXED_HEADER_BYTES = 15;
17
18
  const UDP_NONCE_BYTES = 12;
18
19
  const UDP_TAG_BYTES = 16;
19
20
  const UDP_MAX_SESSION_ID_BYTES = 96;
20
- const UDP_MAX_HEADER_BYTES = 4096;
21
- const UDP_MAX_PAYLOAD_BYTES = UDP_MAX_DATAGRAM_BYTES - UDP_FIXED_HEADER_BYTES - UDP_NONCE_BYTES - UDP_TAG_BYTES - 1;
21
+ const UDP_MAX_HEADER_BYTES = 4096;
22
+ const UDP_MAX_PAYLOAD_BYTES = UDP_MAX_DATAGRAM_BYTES - UDP_FIXED_HEADER_BYTES - UDP_NONCE_BYTES - UDP_TAG_BYTES - 1;
23
+ const UDP_FRAME_PAYLOAD_MAGIC = Buffer.from('LDF1', 'ascii');
24
+ const UDP_MAX_FRAME_HEADER_BYTES = 64 * 1024;
22
25
 
23
26
  function asBuffer(value) {
24
27
  if (Buffer.isBuffer(value)) return value;
@@ -32,13 +35,53 @@ function normalizeKey(key) {
32
35
  return value.length === 32 ? value : crypto.createHash('sha256').update(value).digest();
33
36
  }
34
37
 
35
- function normalizeSessionId(value) {
38
+ function normalizeSessionId(value) {
36
39
  const sessionId = String(value || '').trim();
37
40
  if (!sessionId || Buffer.byteLength(sessionId, 'utf8') > UDP_MAX_SESSION_ID_BYTES) {
38
41
  throw new Error('invalid-udp-session-id');
39
42
  }
40
43
  return sessionId;
41
- }
44
+ }
45
+
46
+ function decodeFrameHeader(header) {
47
+ if (header?.frameHeader && typeof header.frameHeader === 'object') {
48
+ return header.frameHeader;
49
+ }
50
+ const encoded = String(header?.frameHeaderDeflate || '');
51
+ if (!encoded || encoded.length > 8192 || !/^[A-Za-z0-9+/]+={0,2}$/.test(encoded)) {
52
+ return null;
53
+ }
54
+ try {
55
+ const json = inflateRawSync(Buffer.from(encoded, 'base64'), { maxOutputLength: 64 * 1024 }).toString('utf8');
56
+ const value = JSON.parse(json);
57
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
58
+ } catch {
59
+ return null;
60
+ }
61
+ }
62
+
63
+ function decodeFramedPayload(payload, legacyHeader = null) {
64
+ const bytes = asBuffer(payload);
65
+ if (bytes.length < 8 || !bytes.subarray(0, 4).equals(UDP_FRAME_PAYLOAD_MAGIC)) {
66
+ return { header: legacyHeader || {}, payload: bytes };
67
+ }
68
+ const compressedHeaderLength = bytes.readUInt32BE(4);
69
+ if (compressedHeaderLength < 1 || compressedHeaderLength > UDP_MAX_FRAME_HEADER_BYTES
70
+ || 8 + compressedHeaderLength > bytes.length) return null;
71
+ try {
72
+ const json = inflateRawSync(bytes.subarray(8, 8 + compressedHeaderLength), {
73
+ maxOutputLength: UDP_MAX_FRAME_HEADER_BYTES
74
+ }).toString('utf8');
75
+ const header = JSON.parse(json);
76
+ if (!header || typeof header !== 'object' || Array.isArray(header)) return null;
77
+ return {
78
+ header,
79
+ payload: bytes.subarray(8 + compressedHeaderLength)
80
+ };
81
+ } catch {
82
+ return null;
83
+ }
84
+ }
42
85
 
43
86
  function packetTypeCode(type) {
44
87
  const code = typeof type === 'number' ? type : UDP_PACKET_TYPES[String(type || '').toLowerCase()];
@@ -147,10 +190,10 @@ export class UdpReplayWindow {
147
190
  }
148
191
  }
149
192
 
150
- export class UdpFrameReassembler {
151
- constructor({ maxPending = 2, timeoutMs = 80, maxFrameBytes = 8 * 1024 * 1024 } = {}) {
152
- this.maxPending = Math.max(1, Math.min(16, Math.floor(Number(maxPending) || 2)));
153
- this.timeoutMs = Math.max(20, Math.min(2000, Math.floor(Number(timeoutMs) || 80)));
193
+ export class UdpFrameReassembler {
194
+ constructor({ maxPending = 2, timeoutMs = 500, maxFrameBytes = 8 * 1024 * 1024 } = {}) {
195
+ this.maxPending = Math.max(1, Math.min(16, Math.floor(Number(maxPending) || 2)));
196
+ this.timeoutMs = Math.max(20, Math.min(2000, Math.floor(Number(timeoutMs) || 500)));
154
197
  this.maxFrameBytes = Math.max(64 * 1024, Math.min(16 * 1024 * 1024, Math.floor(Number(maxFrameBytes) || 8 * 1024 * 1024)));
155
198
  this.pending = new Map();
156
199
  }
@@ -179,19 +222,25 @@ export class UdpFrameReassembler {
179
222
  state.chunks[chunkIndex] = payload;
180
223
  state.received += 1;
181
224
  state.totalBytes += payload.length;
182
- if (header.frameHeader && typeof header.frameHeader === 'object') state.frameHeader = header.frameHeader;
225
+ const decodedFrameHeader = decodeFrameHeader(header);
226
+ if (decodedFrameHeader) state.frameHeader = decodedFrameHeader;
183
227
  if (state.totalBytes > this.maxFrameBytes) {
184
228
  this.pending.delete(frameId);
185
229
  return null;
186
- }
187
- if (state.received !== state.chunkCount) return null;
188
- this.pending.delete(frameId);
189
- return {
190
- frameId,
191
- header: state.frameHeader || header.frameHeader || {},
192
- payload: Buffer.concat(state.chunks, state.totalBytes)
193
- };
194
- }
230
+ }
231
+ if (state.received !== state.chunkCount) return null;
232
+ this.pending.delete(frameId);
233
+ const complete = decodeFramedPayload(
234
+ Buffer.concat(state.chunks, state.totalBytes),
235
+ state.frameHeader || header.frameHeader || {}
236
+ );
237
+ if (!complete) return null;
238
+ return {
239
+ frameId,
240
+ header: complete.header,
241
+ payload: complete.payload
242
+ };
243
+ }
195
244
 
196
245
  expire(now = Date.now()) {
197
246
  for (const [frameId, state] of this.pending) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "livedesk",
3
- "version": "0.1.436",
4
- "livedeskClientVersion": "0.1.192",
3
+ "version": "0.1.437",
4
+ "livedeskClientVersion": "0.1.193",
5
5
  "buildFlavor": "production",
6
6
  "description": "LiveDesk Hub and client launcher",
7
7
  "type": "module",
@@ -50,10 +50,10 @@
50
50
  "ws": "^8.18.3"
51
51
  },
52
52
  "optionalDependencies": {
53
- "@livedesk/fast-linux-x64": "0.1.399",
54
- "@livedesk/fast-osx-arm64": "0.1.399",
55
- "@livedesk/fast-osx-x64": "0.1.399",
56
- "@livedesk/fast-win-x64": "0.1.399"
53
+ "@livedesk/fast-linux-x64": "0.1.400",
54
+ "@livedesk/fast-osx-arm64": "0.1.400",
55
+ "@livedesk/fast-osx-x64": "0.1.400",
56
+ "@livedesk/fast-win-x64": "0.1.400"
57
57
  },
58
58
  "publishConfig": {
59
59
  "access": "public"