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.
package/hub/src/remote-hub.js
CHANGED
|
@@ -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:
|
|
4172
|
+
command: commandName,
|
|
3983
4173
|
payload: command?.payload ?? null,
|
|
3984
4174
|
issuedAt: new Date().toISOString()
|
|
3985
4175
|
};
|
|
3986
4176
|
|
|
3987
|
-
|
|
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.
|
|
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.
|
|
33
|
+
"@livedesk/client": "0.1.90",
|
|
34
34
|
"cors": "^2.8.5",
|
|
35
35
|
"express": "^4.21.2",
|
|
36
36
|
"ws": "^8.18.3"
|