livedesk 0.1.612 → 0.1.614
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 +28 -15
- package/client/bin/livedesk-client-node.js +65 -76
- package/client/bin/livedesk-client.js +29 -20
- package/client/package.json +6 -6
- package/hub/package.json +2 -2
- package/hub/src/remote-hub.js +98 -31
- package/package.json +6 -6
- package/runtime-core/package.json +2 -2
- package/runtime-core/src/direct-secure-transport.js +752 -0
- package/runtime-core/src/index.js +11 -1
- package/scripts/sync-web-dist.js +4 -2
- package/web/dist/app.webmanifest +1 -1
- package/web/dist/assets/LiveDeskApp-Boz0e1nW.js +179 -0
- package/web/dist/assets/{index-B1AoayYG.js → index-B0qBAUYN.js} +2 -2
- package/web/dist/index.html +1 -1
- package/web/dist/livedesk-build-evidence.json +16 -16
- package/web/dist/sw.js +1 -1
- package/web/dist/assets/LiveDeskApp-CNqqEwIj.js +0 -179
package/bin/livedesk.js
CHANGED
|
@@ -5,8 +5,9 @@ import net from 'node:net';
|
|
|
5
5
|
import { dirname, join, resolve } from 'node:path';
|
|
6
6
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
7
7
|
import { execFile, spawn } from 'node:child_process';
|
|
8
|
-
import {
|
|
9
|
-
|
|
8
|
+
import {
|
|
9
|
+
chmodSync,
|
|
10
|
+
createWriteStream,
|
|
10
11
|
existsSync,
|
|
11
12
|
mkdirSync,
|
|
12
13
|
readFileSync,
|
|
@@ -597,12 +598,18 @@ function normalizePort(value, fallback) {
|
|
|
597
598
|
return number;
|
|
598
599
|
}
|
|
599
600
|
|
|
600
|
-
function readStablePairToken() {
|
|
601
|
-
try {
|
|
602
|
-
const state = JSON.parse(readFileSync(MANAGER_STATE_PATH, 'utf8'));
|
|
603
|
-
const pairToken = String(state?.pairToken || '').trim();
|
|
604
|
-
if (pairToken.length >= 20) {
|
|
605
|
-
|
|
601
|
+
function readStablePairToken() {
|
|
602
|
+
try {
|
|
603
|
+
const state = JSON.parse(readFileSync(MANAGER_STATE_PATH, 'utf8'));
|
|
604
|
+
const pairToken = String(state?.pairToken || '').trim();
|
|
605
|
+
if (pairToken.length >= 20) {
|
|
606
|
+
try {
|
|
607
|
+
chmodSync(MANAGER_STATE_DIR, 0o700);
|
|
608
|
+
chmodSync(MANAGER_STATE_PATH, 0o600);
|
|
609
|
+
} catch {
|
|
610
|
+
// Windows and restricted filesystems may not expose POSIX modes.
|
|
611
|
+
}
|
|
612
|
+
return pairToken;
|
|
606
613
|
}
|
|
607
614
|
} catch {
|
|
608
615
|
// A missing or unreadable state file is repaired below.
|
|
@@ -615,13 +622,19 @@ function getStablePairToken() {
|
|
|
615
622
|
if (existing) {
|
|
616
623
|
return existing;
|
|
617
624
|
}
|
|
618
|
-
const pairToken = randomBytes(
|
|
619
|
-
try {
|
|
620
|
-
mkdirSync(MANAGER_STATE_DIR, { recursive: true });
|
|
621
|
-
writeFileSync(MANAGER_STATE_PATH, JSON.stringify({
|
|
622
|
-
pairToken,
|
|
623
|
-
createdAt: new Date().toISOString()
|
|
624
|
-
}, null, 2));
|
|
625
|
+
const pairToken = randomBytes(32).toString('hex');
|
|
626
|
+
try {
|
|
627
|
+
mkdirSync(MANAGER_STATE_DIR, { recursive: true, mode: 0o700 });
|
|
628
|
+
writeFileSync(MANAGER_STATE_PATH, JSON.stringify({
|
|
629
|
+
pairToken,
|
|
630
|
+
createdAt: new Date().toISOString()
|
|
631
|
+
}, null, 2), { encoding: 'utf8', mode: 0o600 });
|
|
632
|
+
try {
|
|
633
|
+
chmodSync(MANAGER_STATE_DIR, 0o700);
|
|
634
|
+
chmodSync(MANAGER_STATE_PATH, 0o600);
|
|
635
|
+
} catch {
|
|
636
|
+
// Windows and restricted filesystems may not expose POSIX modes.
|
|
637
|
+
}
|
|
625
638
|
} catch {
|
|
626
639
|
// If disk persistence is blocked, keep this manager process usable.
|
|
627
640
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import os from 'os';
|
|
3
|
+
import os from 'os';
|
|
5
4
|
import path from 'path';
|
|
6
5
|
import crypto from 'crypto';
|
|
7
6
|
import { existsSync, promises as fs, statfsSync } from 'fs';
|
|
8
7
|
import { spawn } from 'child_process';
|
|
9
8
|
import { createRequire } from 'node:module';
|
|
10
|
-
import { fileURLToPath } from 'node:url';
|
|
11
|
-
import { resolveAgentShellCommand } from '../src/runtime/agent-shell.js';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { resolveAgentShellCommand } from '../src/runtime/agent-shell.js';
|
|
11
|
+
import { connectSecureDirectSocket } from '../../runtime-core/src/direct-secure-transport.js';
|
|
12
12
|
|
|
13
13
|
const require = createRequire(import.meta.url);
|
|
14
14
|
const CLIENT_UPDATE_BOOTSTRAP_PATH = fileURLToPath(
|
|
@@ -488,21 +488,7 @@ function parseManagerAddress(value) {
|
|
|
488
488
|
};
|
|
489
489
|
}
|
|
490
490
|
|
|
491
|
-
function
|
|
492
|
-
const value = String(host || '').trim().toLowerCase().replace(/^\[|\]$/g, '');
|
|
493
|
-
if (!value || value === 'localhost' || value === '::1') return true;
|
|
494
|
-
if (net.isIPv4(value)) {
|
|
495
|
-
const octets = value.split('.').map(Number);
|
|
496
|
-
return octets[0] === 10
|
|
497
|
-
|| (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31)
|
|
498
|
-
|| (octets[0] === 192 && octets[1] === 168)
|
|
499
|
-
|| octets[0] === 127;
|
|
500
|
-
}
|
|
501
|
-
if (net.isIPv6(value)) return value.startsWith('fc') || value.startsWith('fd') || value.startsWith('fe80:');
|
|
502
|
-
return false;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
function normalizeDeviceId(value) {
|
|
491
|
+
function normalizeDeviceId(value) {
|
|
506
492
|
return String(value || '').trim().replace(/[^a-zA-Z0-9_.:-]/g, '-').slice(0, 128);
|
|
507
493
|
}
|
|
508
494
|
|
|
@@ -2005,18 +1991,24 @@ async function handleRemoteCommand(socket, options, message, nextFrameSeq, activ
|
|
|
2005
1991
|
});
|
|
2006
1992
|
}
|
|
2007
1993
|
|
|
2008
|
-
function connectOnce(options, deviceId) {
|
|
2009
|
-
const manager = parseManagerAddress(options.manager);
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
1994
|
+
async function connectOnce(options, deviceId) {
|
|
1995
|
+
const manager = parseManagerAddress(options.manager);
|
|
1996
|
+
let socket;
|
|
1997
|
+
try {
|
|
1998
|
+
socket = await connectSecureDirectSocket(
|
|
1999
|
+
manager,
|
|
2000
|
+
options.pair,
|
|
2001
|
+
'control');
|
|
2002
|
+
} catch (error) {
|
|
2003
|
+
if ((error?.code === 'LIVEDESK_INVALID_PAIR_TOKEN'
|
|
2004
|
+
|| error?.message === 'invalid-pair-token')
|
|
2005
|
+
&& options.exitOnInvalidPair) {
|
|
2006
|
+
error.exitCode = EXIT_INVALID_PAIR_TOKEN;
|
|
2007
|
+
}
|
|
2008
|
+
throw error;
|
|
2009
|
+
}
|
|
2010
|
+
return new Promise((resolve, reject) => {
|
|
2011
|
+
let buffer = '';
|
|
2020
2012
|
let heartbeatTimer = null;
|
|
2021
2013
|
let resolved = false;
|
|
2022
2014
|
let frameSeq = 0;
|
|
@@ -2050,47 +2042,7 @@ function connectOnce(options, deviceId) {
|
|
|
2050
2042
|
socket.setNoDelay(true);
|
|
2051
2043
|
socket.setKeepAlive(true, options.heartbeatMs);
|
|
2052
2044
|
|
|
2053
|
-
socket.
|
|
2054
|
-
writeJsonLine(socket, {
|
|
2055
|
-
type: 'hello',
|
|
2056
|
-
pairToken: options.pair,
|
|
2057
|
-
deviceId,
|
|
2058
|
-
deviceName: options.name,
|
|
2059
|
-
slotNumber: options.slotNumber || undefined,
|
|
2060
|
-
hostname: os.hostname(),
|
|
2061
|
-
platform: os.platform(),
|
|
2062
|
-
arch: os.arch(),
|
|
2063
|
-
pid: process.pid,
|
|
2064
|
-
agentVersion: AGENT_VERSION,
|
|
2065
|
-
productVersion: PRODUCT_VERSION,
|
|
2066
|
-
capabilities: {
|
|
2067
|
-
status: true,
|
|
2068
|
-
thumbnail: options.thumbnailEnabled,
|
|
2069
|
-
liveStream: options.liveEnabled,
|
|
2070
|
-
monitorSelection: true,
|
|
2071
|
-
screenCount: 1,
|
|
2072
|
-
monitorCount: 1,
|
|
2073
|
-
control: false,
|
|
2074
|
-
audio: false,
|
|
2075
|
-
remoteAudio: false,
|
|
2076
|
-
fileTransfer: true,
|
|
2077
|
-
remoteFiles: true,
|
|
2078
|
-
fileTransferMaxBytes: MAX_FILE_TRANSFER_BYTES,
|
|
2079
|
-
computerAgent: options.taskEnabled,
|
|
2080
|
-
taskDispatch: options.taskEnabled,
|
|
2081
|
-
clientUpdate: true,
|
|
2082
|
-
clientUpdateVerifiedProof: true,
|
|
2083
|
-
productVersion: PRODUCT_VERSION,
|
|
2084
|
-
agentApproval: options.taskEnabled,
|
|
2085
|
-
agentAudit: options.taskEnabled,
|
|
2086
|
-
agentTools: [...NODE_AGENT_OPERATIONS],
|
|
2087
|
-
elevation: typeof process.getuid === 'function' ? process.getuid() === 0 : false,
|
|
2088
|
-
externalEffects: options.taskEnabled
|
|
2089
|
-
}
|
|
2090
|
-
});
|
|
2091
|
-
});
|
|
2092
|
-
|
|
2093
|
-
socket.on('data', chunk => {
|
|
2045
|
+
socket.on('data', chunk => {
|
|
2094
2046
|
buffer += chunk;
|
|
2095
2047
|
let newlineIndex = buffer.indexOf('\n');
|
|
2096
2048
|
while (newlineIndex >= 0) {
|
|
@@ -2157,10 +2109,47 @@ function connectOnce(options, deviceId) {
|
|
|
2157
2109
|
}
|
|
2158
2110
|
});
|
|
2159
2111
|
|
|
2160
|
-
socket.once('error', err => finish(err));
|
|
2161
|
-
socket.once('close', () => finish());
|
|
2162
|
-
|
|
2163
|
-
|
|
2112
|
+
socket.once('error', err => finish(err));
|
|
2113
|
+
socket.once('close', () => finish());
|
|
2114
|
+
writeJsonLine(socket, {
|
|
2115
|
+
type: 'hello',
|
|
2116
|
+
pairToken: options.pair,
|
|
2117
|
+
deviceId,
|
|
2118
|
+
deviceName: options.name,
|
|
2119
|
+
slotNumber: options.slotNumber || undefined,
|
|
2120
|
+
hostname: os.hostname(),
|
|
2121
|
+
platform: os.platform(),
|
|
2122
|
+
arch: os.arch(),
|
|
2123
|
+
pid: process.pid,
|
|
2124
|
+
agentVersion: AGENT_VERSION,
|
|
2125
|
+
productVersion: PRODUCT_VERSION,
|
|
2126
|
+
capabilities: {
|
|
2127
|
+
status: true,
|
|
2128
|
+
thumbnail: options.thumbnailEnabled,
|
|
2129
|
+
liveStream: options.liveEnabled,
|
|
2130
|
+
monitorSelection: true,
|
|
2131
|
+
screenCount: 1,
|
|
2132
|
+
monitorCount: 1,
|
|
2133
|
+
control: false,
|
|
2134
|
+
audio: false,
|
|
2135
|
+
remoteAudio: false,
|
|
2136
|
+
fileTransfer: true,
|
|
2137
|
+
remoteFiles: true,
|
|
2138
|
+
fileTransferMaxBytes: MAX_FILE_TRANSFER_BYTES,
|
|
2139
|
+
computerAgent: options.taskEnabled,
|
|
2140
|
+
taskDispatch: options.taskEnabled,
|
|
2141
|
+
clientUpdate: true,
|
|
2142
|
+
clientUpdateVerifiedProof: true,
|
|
2143
|
+
productVersion: PRODUCT_VERSION,
|
|
2144
|
+
agentApproval: options.taskEnabled,
|
|
2145
|
+
agentAudit: options.taskEnabled,
|
|
2146
|
+
agentTools: [...NODE_AGENT_OPERATIONS],
|
|
2147
|
+
elevation: typeof process.getuid === 'function' ? process.getuid() === 0 : false,
|
|
2148
|
+
externalEffects: options.taskEnabled
|
|
2149
|
+
}
|
|
2150
|
+
});
|
|
2151
|
+
});
|
|
2152
|
+
}
|
|
2164
2153
|
|
|
2165
2154
|
async function connectWithRetry(options) {
|
|
2166
2155
|
if (!options.pair) {
|
|
@@ -25,8 +25,9 @@ import {
|
|
|
25
25
|
inspectLinuxVideoAcceleration,
|
|
26
26
|
installLinuxVideoAcceleration
|
|
27
27
|
} from '../src/runtime/linux-video-acceleration.js';
|
|
28
|
-
import { normalizeRuntimeAuthSession } from '../../runtime-core/src/auth-session.js';
|
|
29
|
-
import { startRoleTransitionSupervisor } from '../../runtime-core/src/role-transition-supervisor.js';
|
|
28
|
+
import { normalizeRuntimeAuthSession } from '../../runtime-core/src/auth-session.js';
|
|
29
|
+
import { startRoleTransitionSupervisor } from '../../runtime-core/src/role-transition-supervisor.js';
|
|
30
|
+
import { connectSecureDirectSocket } from '../../runtime-core/src/direct-secure-transport.js';
|
|
30
31
|
|
|
31
32
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
32
33
|
const require = createRequire(import.meta.url);
|
|
@@ -3674,7 +3675,7 @@ export function shouldSkipAutomaticDirectProbe(endpoint, options = {}) {
|
|
|
3674
3675
|
&& !isEndpointOnLocalNetwork(endpoint, options.networkInterfaces);
|
|
3675
3676
|
}
|
|
3676
3677
|
|
|
3677
|
-
function requestHubSlotAssignment({ manager, pairToken, deviceId, slotNumber, timeoutMs = 5000 }) {
|
|
3678
|
+
async function requestHubSlotAssignment({ manager, pairToken, deviceId, slotNumber, timeoutMs = 5000 }) {
|
|
3678
3679
|
const endpoint = parseManagerEndpoint(manager);
|
|
3679
3680
|
const normalizedPairToken = String(pairToken || '').trim();
|
|
3680
3681
|
const normalizedDeviceId = String(deviceId || '').trim();
|
|
@@ -3692,9 +3693,19 @@ function requestHubSlotAssignment({ manager, pairToken, deviceId, slotNumber, ti
|
|
|
3692
3693
|
return Promise.resolve({ ok: false, error: 'invalid-slot-number' });
|
|
3693
3694
|
}
|
|
3694
3695
|
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3696
|
+
let socket;
|
|
3697
|
+
try {
|
|
3698
|
+
socket = await connectSecureDirectSocket(
|
|
3699
|
+
endpoint,
|
|
3700
|
+
normalizedPairToken,
|
|
3701
|
+
'control',
|
|
3702
|
+
{ connectTimeoutMs: timeoutMs, handshakeTimeoutMs: timeoutMs });
|
|
3703
|
+
} catch (error) {
|
|
3704
|
+
return { ok: false, error: error?.message || 'hub-slot-secure-connection-failed' };
|
|
3705
|
+
}
|
|
3706
|
+
|
|
3707
|
+
return new Promise(resolveAssignment => {
|
|
3708
|
+
let settled = false;
|
|
3698
3709
|
let buffer = '';
|
|
3699
3710
|
const settle = result => {
|
|
3700
3711
|
if (settled) return;
|
|
@@ -3705,15 +3716,7 @@ function requestHubSlotAssignment({ manager, pairToken, deviceId, slotNumber, ti
|
|
|
3705
3716
|
};
|
|
3706
3717
|
socket.setEncoding('utf8');
|
|
3707
3718
|
socket.setTimeout(timeoutMs);
|
|
3708
|
-
socket.
|
|
3709
|
-
socket.write(`${JSON.stringify({
|
|
3710
|
-
type: 'slot.assign',
|
|
3711
|
-
pairToken: normalizedPairToken,
|
|
3712
|
-
deviceId: normalizedDeviceId,
|
|
3713
|
-
slotNumber: Number(normalizedSlot)
|
|
3714
|
-
})}\n`);
|
|
3715
|
-
});
|
|
3716
|
-
socket.on('data', chunk => {
|
|
3719
|
+
socket.on('data', chunk => {
|
|
3717
3720
|
buffer += chunk;
|
|
3718
3721
|
const newlineIndex = buffer.indexOf('\n');
|
|
3719
3722
|
if (newlineIndex < 0) return;
|
|
@@ -3730,11 +3733,17 @@ function requestHubSlotAssignment({ manager, pairToken, deviceId, slotNumber, ti
|
|
|
3730
3733
|
});
|
|
3731
3734
|
socket.once('timeout', () => settle({ ok: false, error: 'hub-slot-request-timeout' }));
|
|
3732
3735
|
socket.once('error', error => settle({ ok: false, error: error?.message || 'hub-slot-request-failed' }));
|
|
3733
|
-
socket.once('close', () => {
|
|
3734
|
-
if (!settled) settle({ ok: false, error: 'hub-slot-response-missing' });
|
|
3735
|
-
});
|
|
3736
|
-
|
|
3737
|
-
|
|
3736
|
+
socket.once('close', () => {
|
|
3737
|
+
if (!settled) settle({ ok: false, error: 'hub-slot-response-missing' });
|
|
3738
|
+
});
|
|
3739
|
+
socket.write(`${JSON.stringify({
|
|
3740
|
+
type: 'slot.assign',
|
|
3741
|
+
pairToken: normalizedPairToken,
|
|
3742
|
+
deviceId: normalizedDeviceId,
|
|
3743
|
+
slotNumber: Number(normalizedSlot)
|
|
3744
|
+
})}\n`);
|
|
3745
|
+
});
|
|
3746
|
+
}
|
|
3738
3747
|
|
|
3739
3748
|
export function canConnectToEndpoint(endpoint, timeoutMs = ENDPOINT_PROBE_TIMEOUT_MS) {
|
|
3740
3749
|
const parsedEndpoint = parseManagerEndpoint(endpoint);
|
package/client/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livedesk/client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.264",
|
|
4
4
|
"description": "VuvoDesk local remote client",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -36,16 +36,16 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
|
38
38
|
"ffmpeg-static": "^5.3.0",
|
|
39
|
-
"@livedesk/runtime-core": "0.1.
|
|
39
|
+
"@livedesk/runtime-core": "0.1.7",
|
|
40
40
|
"@supabase/supabase-js": "^2.110.0",
|
|
41
41
|
"node-screenshots": "^0.2.8",
|
|
42
42
|
"ws": "^8.18.3"
|
|
43
43
|
},
|
|
44
44
|
"optionalDependencies": {
|
|
45
|
-
"@livedesk/fast-linux-x64": "0.1.
|
|
46
|
-
"@livedesk/fast-osx-arm64": "0.1.
|
|
47
|
-
"@livedesk/fast-osx-x64": "0.1.
|
|
48
|
-
"@livedesk/fast-win-x64": "0.1.
|
|
45
|
+
"@livedesk/fast-linux-x64": "0.1.460",
|
|
46
|
+
"@livedesk/fast-osx-arm64": "0.1.460",
|
|
47
|
+
"@livedesk/fast-osx-x64": "0.1.460",
|
|
48
|
+
"@livedesk/fast-win-x64": "0.1.460"
|
|
49
49
|
},
|
|
50
50
|
"publishConfig": {
|
|
51
51
|
"access": "public"
|
package/hub/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livedesk/hub",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.55",
|
|
4
4
|
"description": "VuvoDesk local Hub API and browser frame bridge",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/server.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
|
19
|
-
"@livedesk/runtime-core": "0.1.
|
|
19
|
+
"@livedesk/runtime-core": "0.1.7",
|
|
20
20
|
"@openai/codex-sdk": "0.145.0",
|
|
21
21
|
"cors": "^2.8.5",
|
|
22
22
|
"express": "^4.21.2",
|
package/hub/src/remote-hub.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import net from 'net';
|
|
2
|
-
import os from 'os';
|
|
3
|
-
import crypto from 'crypto';
|
|
4
|
-
import {
|
|
1
|
+
import net from 'net';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import crypto from 'crypto';
|
|
4
|
+
import {
|
|
5
|
+
acceptSecureDirectSocket,
|
|
6
|
+
isDirectSecureChannel,
|
|
7
|
+
isLoopbackSocketAddress,
|
|
8
|
+
isPrivateSocketAddress,
|
|
9
|
+
isSecureDirectHandshakeStart
|
|
10
|
+
} from '../../runtime-core/src/direct-secure-transport.js';
|
|
11
|
+
import { createHubRelayControl } from './transport/relay-hub-control.js';
|
|
5
12
|
import { parseExactLiveStreamMonitorIndex } from './live-stream-monitor-contract.js';
|
|
6
13
|
import {
|
|
7
14
|
BoundedSegmentedBuffer,
|
|
@@ -1334,9 +1341,10 @@ function normalizeRemoteInputEvent(value = {}) {
|
|
|
1334
1341
|
// input must prove the exact monitor owned by its capture generation.
|
|
1335
1342
|
monitorIndex,
|
|
1336
1343
|
normalizedX: Number.isFinite(normalizedX) ? Math.max(0, Math.min(1, normalizedX)) : undefined,
|
|
1337
|
-
normalizedY: Number.isFinite(normalizedY) ? Math.max(0, Math.min(1, normalizedY)) : undefined,
|
|
1338
|
-
button: safeString(input.button || input.Button, 24),
|
|
1339
|
-
|
|
1344
|
+
normalizedY: Number.isFinite(normalizedY) ? Math.max(0, Math.min(1, normalizedY)) : undefined,
|
|
1345
|
+
button: safeString(input.button || input.Button, 24),
|
|
1346
|
+
inputSource: safeString(input.inputSource || input.InputSource, 48).toLowerCase(),
|
|
1347
|
+
deltaX: Number.isFinite(deltaX) ? Math.max(-4096, Math.min(4096, deltaX)) : 0,
|
|
1340
1348
|
deltaY: Number.isFinite(deltaY) ? Math.max(-4096, Math.min(4096, deltaY)) : 0,
|
|
1341
1349
|
key,
|
|
1342
1350
|
code,
|
|
@@ -2396,9 +2404,12 @@ export function createRemoteHub(options = {}) {
|
|
|
2396
2404
|
const hostInstanceId = safeString(options.hostInstanceId ?? env.LIVEDESK_HUB_INSTANCE_ID ?? env.MINDEXEC_BRIDGE_INSTANCE_ID ?? crypto.randomUUID(), 128) || crypto.randomUUID();
|
|
2397
2405
|
const publicEndpoint = safeString(env.LIVEDESK_REMOTE_PUBLIC_ENDPOINT || env.MINDEXEC_REMOTE_PUBLIC_ENDPOINT || env.REMOTE_HUB_PUBLIC_ENDPOINT, 256);
|
|
2398
2406
|
const publicHost = safeString(env.LIVEDESK_REMOTE_PUBLIC_HOST || env.MINDEXEC_REMOTE_PUBLIC_HOST || env.REMOTE_HUB_PUBLIC_HOST, 128);
|
|
2399
|
-
const pairToken = safeString(
|
|
2400
|
-
options.pairToken || env.REMOTE_HUB_PAIR_TOKEN || env.LIVEDESK_CLIENT_PAIR_TOKEN || env.MINDEXEC_REMOTE_PAIR_TOKEN || crypto.randomBytes(
|
|
2401
|
-
256);
|
|
2407
|
+
const pairToken = safeString(
|
|
2408
|
+
options.pairToken || env.REMOTE_HUB_PAIR_TOKEN || env.LIVEDESK_CLIENT_PAIR_TOKEN || env.MINDEXEC_REMOTE_PAIR_TOKEN || crypto.randomBytes(32).toString('hex'),
|
|
2409
|
+
256);
|
|
2410
|
+
const allowInsecurePrivateDirect = isEnabledValue(
|
|
2411
|
+
options.allowInsecureDirect ?? env.LIVEDESK_ALLOW_INSECURE_DIRECT,
|
|
2412
|
+
false);
|
|
2402
2413
|
const relayControl = options.relayControl && typeof options.relayControl === 'object'
|
|
2403
2414
|
? options.relayControl
|
|
2404
2415
|
: createHubRelayControl({
|
|
@@ -7948,10 +7959,24 @@ export function createRemoteHub(options = {}) {
|
|
|
7948
7959
|
function handleAgentMessage(socket, state, message) {
|
|
7949
7960
|
if (!message || typeof message !== 'object') {
|
|
7950
7961
|
return;
|
|
7951
|
-
}
|
|
7952
|
-
|
|
7953
|
-
if (!state.authenticated) {
|
|
7954
|
-
if (
|
|
7962
|
+
}
|
|
7963
|
+
|
|
7964
|
+
if (!state.authenticated) {
|
|
7965
|
+
if (socket.__liveDeskSecureDirect === true) {
|
|
7966
|
+
const applicationChannel = message.type === 'slot.assign'
|
|
7967
|
+
? 'control'
|
|
7968
|
+
: message.type === 'hello'
|
|
7969
|
+
? (safeString(message.channel || message.Channel, 40).toLowerCase() || 'control')
|
|
7970
|
+
: '';
|
|
7971
|
+
if (applicationChannel
|
|
7972
|
+
&& (!isDirectSecureChannel(applicationChannel)
|
|
7973
|
+
|| applicationChannel !== socket.__liveDeskSecureChannel)) {
|
|
7974
|
+
writeJsonLine(socket, { type: 'error', error: 'direct-secure-channel-mismatch' });
|
|
7975
|
+
socket.end();
|
|
7976
|
+
return;
|
|
7977
|
+
}
|
|
7978
|
+
}
|
|
7979
|
+
if (message.type === 'hello' && socket.__liveDeskRelayControl === true) {
|
|
7955
7980
|
message = {
|
|
7956
7981
|
...message,
|
|
7957
7982
|
capabilities: {
|
|
@@ -7987,7 +8012,7 @@ export function createRemoteHub(options = {}) {
|
|
|
7987
8012
|
return;
|
|
7988
8013
|
}
|
|
7989
8014
|
|
|
7990
|
-
const channel = safeString(message.channel || message.Channel, 40).toLowerCase();
|
|
8015
|
+
const channel = safeString(message.channel || message.Channel, 40).toLowerCase() || 'control';
|
|
7991
8016
|
if (channel === 'input') {
|
|
7992
8017
|
const device = attachInputSocket(socket, message);
|
|
7993
8018
|
if (!device) {
|
|
@@ -8537,18 +8562,60 @@ export function createRemoteHub(options = {}) {
|
|
|
8537
8562
|
if (firstChunk) {
|
|
8538
8563
|
processData(firstChunk);
|
|
8539
8564
|
}
|
|
8540
|
-
}
|
|
8541
|
-
|
|
8542
|
-
function handleSocket(socket) {
|
|
8543
|
-
|
|
8544
|
-
|
|
8545
|
-
if (
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
});
|
|
8565
|
+
}
|
|
8566
|
+
|
|
8567
|
+
function handleSocket(socket) {
|
|
8568
|
+
allSockets.add(socket);
|
|
8569
|
+
const prefaceTimer = setTimeout(() => {
|
|
8570
|
+
if (!socket.destroyed) socket.destroy();
|
|
8571
|
+
}, 10_000);
|
|
8572
|
+
prefaceTimer.unref?.();
|
|
8573
|
+
socket.once('close', () => {
|
|
8574
|
+
clearTimeout(prefaceTimer);
|
|
8575
|
+
allSockets.delete(socket);
|
|
8576
|
+
});
|
|
8577
|
+
socket.once('data', chunk => {
|
|
8578
|
+
clearTimeout(prefaceTimer);
|
|
8579
|
+
const firstChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
8580
|
+
if (isSecureDirectHandshakeStart(firstChunk)) {
|
|
8581
|
+
void acceptSecureDirectSocket(socket, firstChunk, pairToken)
|
|
8582
|
+
.then(secureSocket => {
|
|
8583
|
+
allSockets.delete(socket);
|
|
8584
|
+
handleTcpAgentSocket(secureSocket);
|
|
8585
|
+
})
|
|
8586
|
+
.catch(error => {
|
|
8587
|
+
if (socket.destroyed) return;
|
|
8588
|
+
if (error?.code === 'LIVEDESK_INVALID_PAIR_TOKEN_REJECTED') {
|
|
8589
|
+
const forcedCloseTimer = setTimeout(() => socket.destroy(), 1_000);
|
|
8590
|
+
forcedCloseTimer.unref?.();
|
|
8591
|
+
socket.once('close', () => clearTimeout(forcedCloseTimer));
|
|
8592
|
+
if (typeof socket.destroySoon === 'function') socket.destroySoon();
|
|
8593
|
+
else socket.end();
|
|
8594
|
+
return;
|
|
8595
|
+
}
|
|
8596
|
+
socket.destroy();
|
|
8597
|
+
});
|
|
8598
|
+
return;
|
|
8599
|
+
}
|
|
8600
|
+
|
|
8601
|
+
const remoteAddress = socket.remoteAddress;
|
|
8602
|
+
const allowLegacyPlaintext = isLoopbackSocketAddress(remoteAddress)
|
|
8603
|
+
|| (allowInsecurePrivateDirect && isPrivateSocketAddress(remoteAddress));
|
|
8604
|
+
if (isRemoteHubWebSocketUpgradeStart(firstChunk)) {
|
|
8605
|
+
if (!allowLegacyPlaintext) {
|
|
8606
|
+
socket.destroy();
|
|
8607
|
+
return;
|
|
8608
|
+
}
|
|
8609
|
+
handleWebSocketUpgradeSocket(socket, firstChunk);
|
|
8610
|
+
return;
|
|
8611
|
+
}
|
|
8612
|
+
|
|
8613
|
+
if (!allowLegacyPlaintext) {
|
|
8614
|
+
socket.destroy();
|
|
8615
|
+
return;
|
|
8616
|
+
}
|
|
8617
|
+
handleTcpAgentSocket(socket, firstChunk);
|
|
8618
|
+
});
|
|
8552
8619
|
|
|
8553
8620
|
socket.once('error', () => {
|
|
8554
8621
|
// The transport-specific handler owns logging after the first byte.
|
|
@@ -8570,10 +8637,10 @@ export function createRemoteHub(options = {}) {
|
|
|
8570
8637
|
started = true;
|
|
8571
8638
|
boundPort = candidateServer.address()?.port || port;
|
|
8572
8639
|
lastError = '';
|
|
8573
|
-
logEvent('remote', `VuvoDesk Hub client endpoint listening on tcp://${host}:${boundPort}`, 'success');
|
|
8574
|
-
if (host === '0.0.0.0' || host === '::') {
|
|
8575
|
-
logWarn('remote', 'VuvoDesk Hub client endpoint is externally reachable.
|
|
8576
|
-
}
|
|
8640
|
+
logEvent('remote', `VuvoDesk Hub client endpoint listening on tcp://${host}:${boundPort}`, 'success');
|
|
8641
|
+
if (host === '0.0.0.0' || host === '::') {
|
|
8642
|
+
logWarn('remote', 'VuvoDesk Hub client endpoint is externally reachable. Non-loopback Direct connections require authenticated encryption.');
|
|
8643
|
+
}
|
|
8577
8644
|
emitRemoteEvent('RemoteHubStarted', null);
|
|
8578
8645
|
resolve();
|
|
8579
8646
|
});
|
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.614",
|
|
4
|
+
"livedeskClientVersion": "0.1.264",
|
|
5
5
|
"buildFlavor": "production",
|
|
6
6
|
"description": "VuvoDesk Hub and client launcher",
|
|
7
7
|
"type": "module",
|
|
@@ -51,10 +51,10 @@
|
|
|
51
51
|
"ws": "^8.18.3"
|
|
52
52
|
},
|
|
53
53
|
"optionalDependencies": {
|
|
54
|
-
"@livedesk/fast-linux-x64": "0.1.
|
|
55
|
-
"@livedesk/fast-osx-arm64": "0.1.
|
|
56
|
-
"@livedesk/fast-osx-x64": "0.1.
|
|
57
|
-
"@livedesk/fast-win-x64": "0.1.
|
|
54
|
+
"@livedesk/fast-linux-x64": "0.1.460",
|
|
55
|
+
"@livedesk/fast-osx-arm64": "0.1.460",
|
|
56
|
+
"@livedesk/fast-osx-x64": "0.1.460",
|
|
57
|
+
"@livedesk/fast-win-x64": "0.1.460"
|
|
58
58
|
},
|
|
59
59
|
"publishConfig": {
|
|
60
60
|
"access": "public"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livedesk/runtime-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Shared VuvoDesk runtime role and lifecycle contracts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"src/"
|
|
9
9
|
],
|
|
10
10
|
"scripts": {
|
|
11
|
-
"check": "node --check src/index.js",
|
|
11
|
+
"check": "node --check src/index.js && node --check src/direct-secure-transport.js",
|
|
12
12
|
"prepublishOnly": "node ../../scripts/livedesk-release-git-gate.mjs"
|
|
13
13
|
},
|
|
14
14
|
"engines": {
|