livedesk 0.1.435 → 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 +5 -1
- package/client/bin/livedesk-client.js +2 -0
- package/client/package.json +5 -5
- package/hub/src/remote-hub.js +11 -4
- package/hub/src/transport/udp-hub-transport.js +15 -9
- package/hub/src/transport/udp-protocol.js +68 -19
- package/package.json +6 -6
- package/web/dist/assets/{index-Dpc1qIak.css → index-Bd1_4ydL.css} +1 -1
- package/web/dist/assets/index-DxEkHN2-.js +25 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-BUnTKnuj.js +0 -25
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
|
-
|
|
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')
|
package/client/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livedesk/client",
|
|
3
|
-
"version": "0.1.
|
|
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.
|
|
45
|
-
"@livedesk/fast-osx-arm64": "0.1.
|
|
46
|
-
"@livedesk/fast-osx-x64": "0.1.
|
|
47
|
-
"@livedesk/fast-win-x64": "0.1.
|
|
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"
|
package/hub/src/remote-hub.js
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
35
|
-
const preferP2p = isEnabled(env.LIVEDESK_UDP_PREFER_P2P,
|
|
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,
|
|
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
|
-
|
|
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 =
|
|
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) ||
|
|
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
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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.
|
|
4
|
-
"livedeskClientVersion": "0.1.
|
|
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.
|
|
54
|
-
"@livedesk/fast-osx-arm64": "0.1.
|
|
55
|
-
"@livedesk/fast-osx-x64": "0.1.
|
|
56
|
-
"@livedesk/fast-win-x64": "0.1.
|
|
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"
|