@livedesk/hub 0.1.36 → 0.1.38
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/package.json +32 -32
- package/src/agents/agent-audit-store.js +6 -16
- package/src/agents/agent-permissions.js +27 -33
- package/src/agents/agent-tool-registry.js +322 -340
- package/src/captures/capture-store.js +252 -299
- package/src/filesystem/shared-folders.js +181 -189
- package/src/filesystem/transfer-jobs.js +314 -345
- package/src/live-desk-update.js +615 -683
- package/src/remote-hub.js +85 -654
- package/src/server.js +154 -965
- package/src/settings/settings-schema.js +40 -32
- package/src/settings/settings-store.js +2 -7
- package/src/transport/relay-hub-control.js +1376 -1703
- package/src/transport/udp-hub-transport.js +520 -543
- package/src/transport/udp-rendezvous.js +408 -574
- package/src/security/device-credential-authority.js +0 -406
- package/src/security/security-audit-store.js +0 -260
- package/src/transport/secure-direct-acceptor.js +0 -543
package/src/remote-hub.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import net from 'net';
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import crypto from 'crypto';
|
|
4
|
-
import {
|
|
5
|
-
import { createDeviceCredentialAuthority } from './security/device-credential-authority.js';
|
|
6
|
-
import { createHubRelayControl, createSecureHubRelayControl } from './transport/relay-hub-control.js';
|
|
7
|
-
import { createSecureDirectAcceptor } from './transport/secure-direct-acceptor.js';
|
|
4
|
+
import { createHubRelayControl } from './transport/relay-hub-control.js';
|
|
8
5
|
import { parseExactLiveStreamMonitorIndex } from './live-stream-monitor-contract.js';
|
|
9
6
|
import {
|
|
10
7
|
BoundedSegmentedBuffer,
|
|
@@ -38,7 +35,6 @@ const MAX_AGENT_WEBSOCKET_MESSAGE_BYTES = 4
|
|
|
38
35
|
const MAX_AGENT_SOCKET_ACCUMULATOR_BYTES = MAX_LINE_CHARS
|
|
39
36
|
+ MAX_AGENT_TCP_BINARY_PACKET_BYTES
|
|
40
37
|
+ (256 * 1024);
|
|
41
|
-
const MAX_AGENT_MESSAGES_PER_SECOND = 240;
|
|
42
38
|
const HTTP_HEADER_TERMINATOR = Buffer.from('\r\n\r\n', 'ascii');
|
|
43
39
|
const MAX_AGENT_TASK_CHARS = 4000;
|
|
44
40
|
const MAX_AGENT_TASK_RESULT_CHARS = 3000;
|
|
@@ -47,7 +43,7 @@ const RECENT_TASK_LIMIT = 12;
|
|
|
47
43
|
const RECENT_TASK_BATCH_LIMIT = 16;
|
|
48
44
|
const RECENT_FRAME_CACHE_TTL_MS = 4000;
|
|
49
45
|
const RECENT_THUMBNAIL_FRAME_CACHE_LIMIT = 2;
|
|
50
|
-
const RECENT_LIVE_FRAME_CACHE_LIMIT =
|
|
46
|
+
const RECENT_LIVE_FRAME_CACHE_LIMIT = 1;
|
|
51
47
|
const RECENT_THUMBNAIL_FRAME_CACHE_MAX_BYTES = 2 * 1024 * 1024;
|
|
52
48
|
const RECENT_LIVE_FRAME_CACHE_MAX_BYTES = 8 * 1024 * 1024;
|
|
53
49
|
const LIVE_STREAM_PENDING_REUSE_MS = 5000;
|
|
@@ -837,53 +833,6 @@ function isPrivateNetworkAddress(value) {
|
|
|
837
833
|
|| address.startsWith('fd');
|
|
838
834
|
}
|
|
839
835
|
|
|
840
|
-
export function createRemoteMessageRateGate({
|
|
841
|
-
limit = MAX_AGENT_MESSAGES_PER_SECOND,
|
|
842
|
-
windowMs = 1_000,
|
|
843
|
-
now = () => Date.now()
|
|
844
|
-
} = {}) {
|
|
845
|
-
const boundedLimit = Math.max(1, Math.min(10_000, Math.floor(Number(limit) || MAX_AGENT_MESSAGES_PER_SECOND)));
|
|
846
|
-
const boundedWindowMs = Math.max(100, Math.min(60_000, Math.floor(Number(windowMs) || 1_000)));
|
|
847
|
-
let windowStartedAt = Number(now());
|
|
848
|
-
let count = 0;
|
|
849
|
-
return Object.freeze({
|
|
850
|
-
consume() {
|
|
851
|
-
const current = Number(now());
|
|
852
|
-
if (!Number.isFinite(current) || current - windowStartedAt >= boundedWindowMs || current < windowStartedAt) {
|
|
853
|
-
windowStartedAt = current;
|
|
854
|
-
count = 0;
|
|
855
|
-
}
|
|
856
|
-
count += 1;
|
|
857
|
-
return count <= boundedLimit;
|
|
858
|
-
},
|
|
859
|
-
getStatus: () => ({ count, limit: boundedLimit, windowMs: boundedWindowMs, windowStartedAt })
|
|
860
|
-
});
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
export function evaluateRemoteConnectionPolicy({
|
|
864
|
-
remoteAddress = '',
|
|
865
|
-
relayConnection = false,
|
|
866
|
-
encrypted = false,
|
|
867
|
-
allowLegacyPlaintextForTests = false,
|
|
868
|
-
policy = {}
|
|
869
|
-
} = {}) {
|
|
870
|
-
const address = String(remoteAddress || '').replace(/^::ffff:/i, '').trim().toLowerCase();
|
|
871
|
-
if (policy.requireEncryptedConnections === true
|
|
872
|
-
&& encrypted !== true
|
|
873
|
-
&& allowLegacyPlaintextForTests !== true) {
|
|
874
|
-
return 'encrypted-connection-required';
|
|
875
|
-
}
|
|
876
|
-
if (relayConnection === true || !isPrivateNetworkAddress(address)) {
|
|
877
|
-
return policy.allowInternetConnections === false
|
|
878
|
-
? 'internet-connections-blocked-by-settings'
|
|
879
|
-
: '';
|
|
880
|
-
}
|
|
881
|
-
if (!isLoopbackHost(address) && policy.allowLanConnections === false) {
|
|
882
|
-
return 'lan-connections-blocked-by-settings';
|
|
883
|
-
}
|
|
884
|
-
return '';
|
|
885
|
-
}
|
|
886
|
-
|
|
887
836
|
function readCpuTimes() {
|
|
888
837
|
let idle = 0;
|
|
889
838
|
let total = 0;
|
|
@@ -1170,9 +1119,19 @@ function getAccountRouteEndpointReason(endpoint, context = {}) {
|
|
|
1170
1119
|
return 'ok';
|
|
1171
1120
|
}
|
|
1172
1121
|
|
|
1173
|
-
function safeText(value, maxLength = 1000) {
|
|
1174
|
-
return String(value ?? '').replace(/\0/g, '').trim().slice(0, maxLength);
|
|
1175
|
-
}
|
|
1122
|
+
function safeText(value, maxLength = 1000) {
|
|
1123
|
+
return String(value ?? '').replace(/\0/g, '').trim().slice(0, maxLength);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
function normalizeRemoteKeyboardKey(value) {
|
|
1127
|
+
const raw = String(value ?? '').replace(/\0/g, '');
|
|
1128
|
+
return raw === ' ' ? ' ' : safeString(raw, 80);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
function normalizeRemoteKeyboardText(value) {
|
|
1132
|
+
const raw = String(value ?? '').replace(/\0/g, '');
|
|
1133
|
+
return raw === ' ' ? ' ' : safeText(raw, 256);
|
|
1134
|
+
}
|
|
1176
1135
|
|
|
1177
1136
|
function safeTaskData(value) {
|
|
1178
1137
|
if (value === undefined || value === null) return undefined;
|
|
@@ -1213,24 +1172,21 @@ const SUPPORTED_AGENT_OPERATIONS = new Set([
|
|
|
1213
1172
|
'process.list',
|
|
1214
1173
|
'service.status',
|
|
1215
1174
|
'diagnostics.collect',
|
|
1216
|
-
'file.read',
|
|
1217
|
-
'file.list',
|
|
1218
|
-
'network.status',
|
|
1219
|
-
'logs.collect'
|
|
1220
|
-
]);
|
|
1221
|
-
|
|
1222
|
-
const RETIRED_AGENT_MUTATING_OPERATIONS = new Set([
|
|
1223
1175
|
'process.control',
|
|
1224
1176
|
'service.control',
|
|
1225
1177
|
'application.launch',
|
|
1226
1178
|
'application.close',
|
|
1179
|
+
'file.read',
|
|
1227
1180
|
'file.write',
|
|
1228
1181
|
'file.delete',
|
|
1182
|
+
'file.list',
|
|
1229
1183
|
'command.run',
|
|
1230
1184
|
'script.run',
|
|
1231
1185
|
'software.install',
|
|
1186
|
+
'network.status',
|
|
1232
1187
|
'system.power',
|
|
1233
|
-
'system.configure'
|
|
1188
|
+
'system.configure',
|
|
1189
|
+
'logs.collect'
|
|
1234
1190
|
]);
|
|
1235
1191
|
|
|
1236
1192
|
function normalizeAgentOperation(value) {
|
|
@@ -1256,14 +1212,14 @@ function readRawRemoteInputMonitorIndex(input) {
|
|
|
1256
1212
|
return undefined;
|
|
1257
1213
|
}
|
|
1258
1214
|
|
|
1259
|
-
function normalizeRemoteInputEvent(value = {}) {
|
|
1215
|
+
function normalizeRemoteInputEvent(value = {}) {
|
|
1260
1216
|
const input = value && typeof value === 'object' ? value : {};
|
|
1261
1217
|
const type = safeString(input.type || input.Type, 48);
|
|
1262
1218
|
const normalizedX = Number(input.normalizedX ?? input.NormalizedX);
|
|
1263
1219
|
const normalizedY = Number(input.normalizedY ?? input.NormalizedY);
|
|
1264
1220
|
const deltaX = Number(input.deltaX ?? input.DeltaX);
|
|
1265
1221
|
const deltaY = Number(input.deltaY ?? input.DeltaY);
|
|
1266
|
-
const keyCode = Number(input.keyCode ?? input.KeyCode);
|
|
1222
|
+
const keyCode = Number(input.keyCode ?? input.KeyCode);
|
|
1267
1223
|
const location = Number(input.location ?? input.Location);
|
|
1268
1224
|
const monitorIndex = parseExactLiveStreamMonitorIndex(readRawRemoteInputMonitorIndex(input));
|
|
1269
1225
|
const inputSeq = Number(input.inputSeq ?? input.InputSeq);
|
|
@@ -1271,13 +1227,18 @@ function normalizeRemoteInputEvent(value = {}) {
|
|
|
1271
1227
|
const hubReceivedAtEpochMs = Number(input.hubReceivedAtEpochMs ?? input.HubReceivedAtEpochMs);
|
|
1272
1228
|
const captureGeneration = Number(input.captureGeneration ?? input.CaptureGeneration);
|
|
1273
1229
|
const rawPressedCodes = input.pressedCodes ?? input.PressedCodes;
|
|
1274
|
-
const pressedCodes = Array.isArray(rawPressedCodes)
|
|
1230
|
+
const pressedCodes = Array.isArray(rawPressedCodes)
|
|
1275
1231
|
? [...new Set(rawPressedCodes
|
|
1276
1232
|
.map(code => safeString(code, 80))
|
|
1277
1233
|
.filter(Boolean))]
|
|
1278
1234
|
.slice(0, 64)
|
|
1279
|
-
: null;
|
|
1280
|
-
|
|
1235
|
+
: null;
|
|
1236
|
+
const key = normalizeRemoteKeyboardKey(input.key ?? input.Key);
|
|
1237
|
+
let code = safeString(input.code ?? input.Code, 80);
|
|
1238
|
+
if ((!code || code === 'Unidentified') && (key === ' ' || key === 'Spacebar' || keyCode === 32)) {
|
|
1239
|
+
code = 'Space';
|
|
1240
|
+
}
|
|
1241
|
+
return {
|
|
1281
1242
|
type,
|
|
1282
1243
|
// A missing monitor is not equivalent to the primary display. Control
|
|
1283
1244
|
// input must prove the exact monitor owned by its capture generation.
|
|
@@ -1287,11 +1248,11 @@ function normalizeRemoteInputEvent(value = {}) {
|
|
|
1287
1248
|
button: safeString(input.button || input.Button, 24),
|
|
1288
1249
|
deltaX: Number.isFinite(deltaX) ? Math.max(-4096, Math.min(4096, deltaX)) : 0,
|
|
1289
1250
|
deltaY: Number.isFinite(deltaY) ? Math.max(-4096, Math.min(4096, deltaY)) : 0,
|
|
1290
|
-
key
|
|
1291
|
-
code
|
|
1251
|
+
key,
|
|
1252
|
+
code,
|
|
1292
1253
|
keyCode: Number.isFinite(keyCode) ? Math.max(0, Math.min(65535, Math.trunc(keyCode))) : 0,
|
|
1293
1254
|
location: Number.isFinite(location) ? Math.max(0, Math.min(255, Math.trunc(location))) : 0,
|
|
1294
|
-
text:
|
|
1255
|
+
text: normalizeRemoteKeyboardText(input.text ?? input.Text),
|
|
1295
1256
|
repeat: input.repeat === true || input.Repeat === true,
|
|
1296
1257
|
pressedCodes,
|
|
1297
1258
|
shiftKey: input.shiftKey === true || input.ShiftKey === true,
|
|
@@ -1402,7 +1363,7 @@ function buildRemoteFramePath(deviceId, frameKind, frame) {
|
|
|
1402
1363
|
return `/api/remote/devices/${encodeURIComponent(deviceId)}/${endpoint}?${params.toString()}`;
|
|
1403
1364
|
}
|
|
1404
1365
|
|
|
1405
|
-
function serializeRemoteFrame(frame, deviceId, frameKind, options = {}) {
|
|
1366
|
+
function serializeRemoteFrame(frame, deviceId, frameKind, options = {}) {
|
|
1406
1367
|
if (!frame) {
|
|
1407
1368
|
return null;
|
|
1408
1369
|
}
|
|
@@ -1424,27 +1385,27 @@ function serializeRemoteFrame(frame, deviceId, frameKind, options = {}) {
|
|
|
1424
1385
|
serialized.dataUrl = dataUrl || buildFrameDataUrl(payload, '', publicFrame.mimeType || publicFrame.format || 'image/jpeg');
|
|
1425
1386
|
}
|
|
1426
1387
|
|
|
1427
|
-
return serialized;
|
|
1428
|
-
}
|
|
1429
|
-
|
|
1430
|
-
function serializeActiveLiveStream(activeLiveStream, deviceId) {
|
|
1431
|
-
if (!activeLiveStream) {
|
|
1432
|
-
return null;
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
|
-
return {
|
|
1436
|
-
...activeLiveStream,
|
|
1437
|
-
// The active descriptor is status metadata.
|
|
1438
|
-
// owner
|
|
1439
|
-
//
|
|
1440
|
-
latestFrame: serializeRemoteFrame(
|
|
1441
|
-
activeLiveStream.latestFrame,
|
|
1442
|
-
deviceId,
|
|
1443
|
-
'live',
|
|
1444
|
-
{ includeDataUrl: false }
|
|
1445
|
-
)
|
|
1446
|
-
};
|
|
1447
|
-
}
|
|
1388
|
+
return serialized;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
function serializeActiveLiveStream(activeLiveStream, deviceId) {
|
|
1392
|
+
if (!activeLiveStream) {
|
|
1393
|
+
return null;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
return {
|
|
1397
|
+
...activeLiveStream,
|
|
1398
|
+
// The active descriptor is status metadata. Preserve the Hub's exact
|
|
1399
|
+
// internal frame owner for delivery, but never copy encoded bytes or
|
|
1400
|
+
// its access token into a browser status snapshot.
|
|
1401
|
+
latestFrame: serializeRemoteFrame(
|
|
1402
|
+
activeLiveStream.latestFrame,
|
|
1403
|
+
deviceId,
|
|
1404
|
+
'live',
|
|
1405
|
+
{ includeDataUrl: false }
|
|
1406
|
+
)
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1448
1409
|
|
|
1449
1410
|
function getRecentFrameCache(device, frameKind) {
|
|
1450
1411
|
if (!device) {
|
|
@@ -1478,7 +1439,7 @@ function getRecentFrameCacheMaxBytes(frameKind) {
|
|
|
1478
1439
|
: RECENT_LIVE_FRAME_CACHE_MAX_BYTES;
|
|
1479
1440
|
}
|
|
1480
1441
|
|
|
1481
|
-
|
|
1442
|
+
function pruneRecentFrameCache(cache, frameKind, nowMs = Date.now()) {
|
|
1482
1443
|
if (!Array.isArray(cache)) {
|
|
1483
1444
|
return;
|
|
1484
1445
|
}
|
|
@@ -1499,20 +1460,6 @@ export function pruneRecentFrameCache(cache, frameKind, nowMs = Date.now()) {
|
|
|
1499
1460
|
}
|
|
1500
1461
|
}
|
|
1501
1462
|
|
|
1502
|
-
if (frameKind !== 'thumbnail' && cache.length > 1) {
|
|
1503
|
-
// Keep the newest frame plus the newest independently decodable key
|
|
1504
|
-
// frame. A browser can subscribe after capture has already started;
|
|
1505
|
-
// retaining only the latest 30 fps delta makes that browser wait for a
|
|
1506
|
-
// future GOP even though the Hub accepted a current key frame moments
|
|
1507
|
-
// earlier. Exact stream-owner matching below prevents stale replay.
|
|
1508
|
-
const latest = cache[0];
|
|
1509
|
-
const keyFrame = cache.find(entry =>
|
|
1510
|
-
entry !== latest
|
|
1511
|
-
&& (entry?.frame?.isKeyFrame === true
|
|
1512
|
-
|| safeString(entry?.frame?.chunkType, 20).toLowerCase() === 'key'));
|
|
1513
|
-
cache.splice(0, cache.length, ...[latest, keyFrame].filter(Boolean));
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
1463
|
for (let index = 0; index < cache.length; index += 1) {
|
|
1517
1464
|
const entry = cache[index];
|
|
1518
1465
|
const byteLength = Number(entry?.byteLength || entry?.frame?.payload?.length || 0) || 0;
|
|
@@ -1524,7 +1471,7 @@ export function pruneRecentFrameCache(cache, frameKind, nowMs = Date.now()) {
|
|
|
1524
1471
|
}
|
|
1525
1472
|
}
|
|
1526
1473
|
|
|
1527
|
-
|
|
1474
|
+
function rememberRecentFramePayload(device, frameKind, frame) {
|
|
1528
1475
|
if (!device || !frame || !Buffer.isBuffer(frame.payload)) {
|
|
1529
1476
|
return;
|
|
1530
1477
|
}
|
|
@@ -1560,7 +1507,7 @@ export function rememberRecentFramePayload(device, frameKind, frame) {
|
|
|
1560
1507
|
pruneRecentFrameCache(cache, kind, nowMs);
|
|
1561
1508
|
}
|
|
1562
1509
|
|
|
1563
|
-
|
|
1510
|
+
function isFramePayloadRequestMatch(frame, options = {}) {
|
|
1564
1511
|
if (!frame || !Buffer.isBuffer(frame.payload)) {
|
|
1565
1512
|
return false;
|
|
1566
1513
|
}
|
|
@@ -1579,37 +1526,10 @@ export function isFramePayloadRequestMatch(frame, options = {}) {
|
|
|
1579
1526
|
return false;
|
|
1580
1527
|
}
|
|
1581
1528
|
|
|
1582
|
-
if (options.requireKeyFrame === true
|
|
1583
|
-
&& frame.isKeyFrame !== true
|
|
1584
|
-
&& safeString(frame.chunkType, 20).toLowerCase() !== 'key') {
|
|
1585
|
-
return false;
|
|
1586
|
-
}
|
|
1587
|
-
|
|
1588
|
-
for (const key of ['streamId', 'sessionId', 'commandId', 'streamPurpose']) {
|
|
1589
|
-
const expected = safeString(options[key], 160);
|
|
1590
|
-
if (expected && safeString(frame[key], 160) !== expected) {
|
|
1591
|
-
return false;
|
|
1592
|
-
}
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
const requestedCaptureGeneration = Number(options.captureGeneration);
|
|
1596
|
-
if (Number.isFinite(requestedCaptureGeneration)
|
|
1597
|
-
&& requestedCaptureGeneration > 0
|
|
1598
|
-
&& Number(frame.captureGeneration || 0) !== requestedCaptureGeneration) {
|
|
1599
|
-
return false;
|
|
1600
|
-
}
|
|
1601
|
-
|
|
1602
|
-
const requestedMonitorIndex = Number(options.monitorIndex);
|
|
1603
|
-
if (Number.isFinite(requestedMonitorIndex)
|
|
1604
|
-
&& requestedMonitorIndex >= 0
|
|
1605
|
-
&& Number(frame.monitorIndex ?? -1) !== requestedMonitorIndex) {
|
|
1606
|
-
return false;
|
|
1607
|
-
}
|
|
1608
|
-
|
|
1609
1529
|
return true;
|
|
1610
1530
|
}
|
|
1611
1531
|
|
|
1612
|
-
|
|
1532
|
+
function findRecentFramePayload(device, frameKind, options = {}) {
|
|
1613
1533
|
const kind = frameKind === 'thumbnail' ? 'thumbnail' : 'live';
|
|
1614
1534
|
const latest = kind === 'thumbnail'
|
|
1615
1535
|
? device?.latestThumbnail
|
|
@@ -1620,14 +1540,7 @@ export function findRecentFramePayload(device, frameKind, options = {}) {
|
|
|
1620
1540
|
|
|
1621
1541
|
const token = safeString(options.token, 128);
|
|
1622
1542
|
const requestedSeq = Number(options.frameSeq);
|
|
1623
|
-
|
|
1624
|
-
|| ['streamId', 'sessionId', 'commandId', 'streamPurpose']
|
|
1625
|
-
.some(key => safeString(options[key], 160))
|
|
1626
|
-
|| (Number.isFinite(Number(options.captureGeneration))
|
|
1627
|
-
&& Number(options.captureGeneration) > 0)
|
|
1628
|
-
|| (Number.isFinite(Number(options.monitorIndex))
|
|
1629
|
-
&& Number(options.monitorIndex) >= 0);
|
|
1630
|
-
if (!token && !Number.isFinite(requestedSeq) && !ownerQuery) {
|
|
1543
|
+
if (!token && !Number.isFinite(requestedSeq)) {
|
|
1631
1544
|
return null;
|
|
1632
1545
|
}
|
|
1633
1546
|
|
|
@@ -1683,7 +1596,7 @@ function serializeDevice(device, options = {}) {
|
|
|
1683
1596
|
},
|
|
1684
1597
|
latestThumbnail: serializeRemoteFrame(device.latestThumbnail, device.deviceId, 'thumbnail', options),
|
|
1685
1598
|
latestLiveFrame: serializeRemoteFrame(device.latestLiveFrame, device.deviceId, 'live', options),
|
|
1686
|
-
activeLiveStream: serializeActiveLiveStream(device.activeLiveStream, device.deviceId),
|
|
1599
|
+
activeLiveStream: serializeActiveLiveStream(device.activeLiveStream, device.deviceId),
|
|
1687
1600
|
liveCapturePause: device.liveCapturePause
|
|
1688
1601
|
? {
|
|
1689
1602
|
token: device.liveCapturePause.token,
|
|
@@ -2301,16 +2214,6 @@ export function createRemoteHub(options = {}) {
|
|
|
2301
2214
|
}
|
|
2302
2215
|
}
|
|
2303
2216
|
|
|
2304
|
-
function connectionPolicyError(socket, policy = {}) {
|
|
2305
|
-
return evaluateRemoteConnectionPolicy({
|
|
2306
|
-
remoteAddress: socket?.remoteAddress || '',
|
|
2307
|
-
relayConnection: socket?.__liveDeskRelayControl === true,
|
|
2308
|
-
encrypted: socket?.__liveDeskSecurityContext?.encrypted === true,
|
|
2309
|
-
allowLegacyPlaintextForTests,
|
|
2310
|
-
policy
|
|
2311
|
-
});
|
|
2312
|
-
}
|
|
2313
|
-
|
|
2314
2217
|
function policyError(device, permission = '', command = '') {
|
|
2315
2218
|
const policy = getDevicePolicy(device);
|
|
2316
2219
|
const commandName = safeString(command, 80);
|
|
@@ -2359,83 +2262,22 @@ export function createRemoteHub(options = {}) {
|
|
|
2359
2262
|
const hostInstanceId = safeString(options.hostInstanceId ?? env.LIVEDESK_HUB_INSTANCE_ID ?? env.MINDEXEC_BRIDGE_INSTANCE_ID ?? crypto.randomUUID(), 128) || crypto.randomUUID();
|
|
2360
2263
|
const publicEndpoint = safeString(env.LIVEDESK_REMOTE_PUBLIC_ENDPOINT || env.MINDEXEC_REMOTE_PUBLIC_ENDPOINT || env.REMOTE_HUB_PUBLIC_ENDPOINT, 256);
|
|
2361
2264
|
const publicHost = safeString(env.LIVEDESK_REMOTE_PUBLIC_HOST || env.MINDEXEC_REMOTE_PUBLIC_HOST || env.REMOTE_HUB_PUBLIC_HOST, 128);
|
|
2362
|
-
const
|
|
2363
|
-
options.pairToken || env.REMOTE_HUB_PAIR_TOKEN || env.LIVEDESK_CLIENT_PAIR_TOKEN || env.MINDEXEC_REMOTE_PAIR_TOKEN,
|
|
2265
|
+
const pairToken = safeString(
|
|
2266
|
+
options.pairToken || env.REMOTE_HUB_PAIR_TOKEN || env.LIVEDESK_CLIENT_PAIR_TOKEN || env.MINDEXEC_REMOTE_PAIR_TOKEN || crypto.randomBytes(6).toString('hex'),
|
|
2364
2267
|
256);
|
|
2365
|
-
const
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
let pairToken = allowLegacyPlaintextForTests && configuredEnrollmentToken
|
|
2374
|
-
? configuredEnrollmentToken
|
|
2375
|
-
: canonicalEnrollmentToken || crypto.randomBytes(32).toString('base64url');
|
|
2376
|
-
const deviceCredentialAuthority = options.deviceCredentialAuthority || createDeviceCredentialAuthority({
|
|
2377
|
-
dataDir: options.dataDir || env.LIVEDESK_DATA_DIR || undefined
|
|
2378
|
-
});
|
|
2379
|
-
const getSecurityIdentity = typeof options.getSecurityIdentity === 'function'
|
|
2380
|
-
? options.getSecurityIdentity
|
|
2381
|
-
: () => ({ accountId: safeString(options.accountId || env.LIVEDESK_ACCOUNT_ID, 128) });
|
|
2382
|
-
udpTransport?.setRendezvousProofIssuer?.(({ roomId, deviceId, role, ttlMs }) => {
|
|
2383
|
-
const identity = getSecurityIdentity() || {};
|
|
2384
|
-
return deviceCredentialAuthority.issueRendezvousProof({
|
|
2385
|
-
roomId,
|
|
2386
|
-
deviceId,
|
|
2387
|
-
role,
|
|
2388
|
-
ttlMs,
|
|
2389
|
-
accountId: safeString(identity.accountId, 128)
|
|
2268
|
+
const relayControl = options.relayControl && typeof options.relayControl === 'object'
|
|
2269
|
+
? options.relayControl
|
|
2270
|
+
: createHubRelayControl({
|
|
2271
|
+
env,
|
|
2272
|
+
pairToken,
|
|
2273
|
+
logEvent,
|
|
2274
|
+
logWarn,
|
|
2275
|
+
onPeerSocket: socket => handleTcpAgentSocket(socket)
|
|
2390
2276
|
});
|
|
2391
|
-
});
|
|
2392
2277
|
let pairingPin = /^\d{6}$/.test(String(options.pairingPin || env.LIVEDESK_PAIRING_PIN || '').trim())
|
|
2393
2278
|
? String(options.pairingPin || env.LIVEDESK_PAIRING_PIN).trim()
|
|
2394
2279
|
: generatePairingPin();
|
|
2395
2280
|
let pairingPinUpdatedAt = new Date().toISOString();
|
|
2396
|
-
const consumeEnrollmentToken = expected => {
|
|
2397
|
-
if (!timingSafeStringEqual(expected, pairToken)) return false;
|
|
2398
|
-
pairToken = crypto.randomBytes(32).toString('base64url');
|
|
2399
|
-
pairingPin = generatePairingPin();
|
|
2400
|
-
pairingPinUpdatedAt = new Date().toISOString();
|
|
2401
|
-
emitRemoteEvent('RemoteEnrollmentTokenRotated', null, {
|
|
2402
|
-
pairingPinUpdatedAt,
|
|
2403
|
-
hubId: deviceCredentialAuthority.hubId
|
|
2404
|
-
});
|
|
2405
|
-
return true;
|
|
2406
|
-
};
|
|
2407
|
-
const relayControl = options.relayControl && typeof options.relayControl === 'object'
|
|
2408
|
-
? options.relayControl
|
|
2409
|
-
: allowLegacyPlaintextForTests
|
|
2410
|
-
? createHubRelayControl({
|
|
2411
|
-
env,
|
|
2412
|
-
pairToken,
|
|
2413
|
-
logEvent,
|
|
2414
|
-
logWarn,
|
|
2415
|
-
onPeerSocket: socket => handleTcpAgentSocket(socket)
|
|
2416
|
-
})
|
|
2417
|
-
: createSecureHubRelayControl({
|
|
2418
|
-
env,
|
|
2419
|
-
authority: deviceCredentialAuthority,
|
|
2420
|
-
getIdentity: () => getSecurityIdentity() || {},
|
|
2421
|
-
getEnrollmentToken: () => pairToken,
|
|
2422
|
-
consumeEnrollmentToken,
|
|
2423
|
-
logEvent,
|
|
2424
|
-
logWarn,
|
|
2425
|
-
onPeerSocket: socket => handleTcpAgentSocket(socket)
|
|
2426
|
-
});
|
|
2427
|
-
const secureDirectAcceptor = createSecureDirectAcceptor({
|
|
2428
|
-
authority: deviceCredentialAuthority,
|
|
2429
|
-
getIdentity: () => getSecurityIdentity() || {},
|
|
2430
|
-
getEnrollmentToken: () => pairToken,
|
|
2431
|
-
consumeEnrollmentToken,
|
|
2432
|
-
onSecureSocket: socket => handleTcpAgentSocket(socket),
|
|
2433
|
-
onAudit: audit => emitEvent('RemoteSecurityHandshakeAudit', {
|
|
2434
|
-
...audit,
|
|
2435
|
-
remoteHub: getStatus({ includeSecrets: false })
|
|
2436
|
-
}),
|
|
2437
|
-
logWarn
|
|
2438
|
-
});
|
|
2439
2281
|
const duplicateDeviceLogThrottleMs = clampNumber(
|
|
2440
2282
|
env.MINDEXEC_REMOTE_DUPLICATE_DEVICE_LOG_MS || env.REMOTE_HUB_DUPLICATE_DEVICE_LOG_MS,
|
|
2441
2283
|
5000,
|
|
@@ -2461,7 +2303,6 @@ export function createRemoteHub(options = {}) {
|
|
|
2461
2303
|
const transportDiagnosticStartingDevices = new Set();
|
|
2462
2304
|
const duplicateDeviceLogAt = new Map();
|
|
2463
2305
|
let server = null;
|
|
2464
|
-
let controlIdleSweepTimer = null;
|
|
2465
2306
|
let started = false;
|
|
2466
2307
|
let boundPort = requestedPort;
|
|
2467
2308
|
let lastError = '';
|
|
@@ -2783,7 +2624,7 @@ export function createRemoteHub(options = {}) {
|
|
|
2783
2624
|
host,
|
|
2784
2625
|
agentHost: routeInfo.host || getAnnouncedHost(),
|
|
2785
2626
|
port: boundPort || requestedPort,
|
|
2786
|
-
protocol: '
|
|
2627
|
+
protocol: 'tcp-jsonl',
|
|
2787
2628
|
protocolVersion: REMOTE_PROTOCOL_VERSION,
|
|
2788
2629
|
agentProtocol: REMOTE_AGENT_PROTOCOL,
|
|
2789
2630
|
frameProtocol: buildRemoteFrameProtocolDescriptor(),
|
|
@@ -2803,15 +2644,6 @@ export function createRemoteHub(options = {}) {
|
|
|
2803
2644
|
pairTokenPreview: maskToken(pairToken),
|
|
2804
2645
|
pairingPin: includeSecrets ? pairingPin : '',
|
|
2805
2646
|
pairingPinUpdatedAt,
|
|
2806
|
-
security: {
|
|
2807
|
-
direct: secureDirectAcceptor.getStatus(),
|
|
2808
|
-
hubId: deviceCredentialAuthority.hubId,
|
|
2809
|
-
hubPublicKeyFingerprint: crypto.createHash('sha256')
|
|
2810
|
-
.update(Buffer.from(deviceCredentialAuthority.hubPublicKey, 'base64url'))
|
|
2811
|
-
.digest('hex'),
|
|
2812
|
-
enrolledDeviceCount: deviceCredentialAuthority.listDevices().filter(item => !item.revokedAt).length,
|
|
2813
|
-
legacyPlaintextForTests: allowLegacyPlaintextForTests
|
|
2814
|
-
},
|
|
2815
2647
|
deviceCount: devices.size,
|
|
2816
2648
|
connectedDeviceCount: connectedDevices,
|
|
2817
2649
|
canvasDeviceListMode: 'all-devices',
|
|
@@ -4078,14 +3910,6 @@ export function createRemoteHub(options = {}) {
|
|
|
4078
3910
|
}
|
|
4079
3911
|
|
|
4080
3912
|
const writeOwner = device.inputWriteOwner;
|
|
4081
|
-
if (device.controlOwnerConnectionId) {
|
|
4082
|
-
endControlSession(
|
|
4083
|
-
device,
|
|
4084
|
-
device.controlOwnerConnectionId,
|
|
4085
|
-
reason,
|
|
4086
|
-
writeOwner,
|
|
4087
|
-
writeOwner?.getBindingKey?.() || '');
|
|
4088
|
-
}
|
|
4089
3913
|
device.inputSocket = null;
|
|
4090
3914
|
device.inputWriteOwner = null;
|
|
4091
3915
|
device.inputSocketConnectionId = '';
|
|
@@ -4118,14 +3942,6 @@ export function createRemoteHub(options = {}) {
|
|
|
4118
3942
|
}
|
|
4119
3943
|
|
|
4120
3944
|
const writeOwner = device.inputWriteOwner;
|
|
4121
|
-
if (device.controlOwnerConnectionId) {
|
|
4122
|
-
endControlSession(
|
|
4123
|
-
device,
|
|
4124
|
-
device.controlOwnerConnectionId,
|
|
4125
|
-
reason,
|
|
4126
|
-
null,
|
|
4127
|
-
'');
|
|
4128
|
-
}
|
|
4129
3945
|
device.inputSocket = null;
|
|
4130
3946
|
device.inputWriteOwner = null;
|
|
4131
3947
|
device.inputSocketConnectionId = '';
|
|
@@ -4559,10 +4375,6 @@ export function createRemoteHub(options = {}) {
|
|
|
4559
4375
|
inputSocketConnectionId: '',
|
|
4560
4376
|
inputOwnerConnectionId: '',
|
|
4561
4377
|
inputOwnerBindingKey: '',
|
|
4562
|
-
controlOwnerSessionId: '',
|
|
4563
|
-
controlOwnerConnectionId: '',
|
|
4564
|
-
controlOwnerStartedAtMs: 0,
|
|
4565
|
-
controlOwnerLastInputAtMs: 0,
|
|
4566
4378
|
frameSocket: null,
|
|
4567
4379
|
audioSocket: null,
|
|
4568
4380
|
fileSocket: null,
|
|
@@ -7484,12 +7296,6 @@ export function createRemoteHub(options = {}) {
|
|
|
7484
7296
|
}
|
|
7485
7297
|
|
|
7486
7298
|
if (!state.authenticated) {
|
|
7487
|
-
const securityContext = socket.__liveDeskSecurityContext;
|
|
7488
|
-
const secureDeviceAuthenticated = securityContext?.authenticated === true
|
|
7489
|
-
&& securityContext?.encrypted === true
|
|
7490
|
-
&& safeString(securityContext.accountId, 128)
|
|
7491
|
-
&& safeString(securityContext.hubId, 128)
|
|
7492
|
-
&& safeString(securityContext.deviceId, 128);
|
|
7493
7299
|
if (message.type === 'hello' && socket.__liveDeskRelayControl === true) {
|
|
7494
7300
|
message = {
|
|
7495
7301
|
...message,
|
|
@@ -7502,9 +7308,8 @@ export function createRemoteHub(options = {}) {
|
|
|
7502
7308
|
};
|
|
7503
7309
|
}
|
|
7504
7310
|
if (message.type === 'slot.assign') {
|
|
7505
|
-
if (!
|
|
7506
|
-
|
|
7507
|
-
writeJsonLine(socket, { type: 'slot.assignment', ok: false, error: 'authenticated-device-credential-required' });
|
|
7311
|
+
if (!timingSafeStringEqual(message.pairToken, pairToken)) {
|
|
7312
|
+
writeJsonLine(socket, { type: 'slot.assignment', ok: false, error: 'invalid-pair-token' });
|
|
7508
7313
|
socket.end();
|
|
7509
7314
|
return;
|
|
7510
7315
|
}
|
|
@@ -7521,52 +7326,8 @@ export function createRemoteHub(options = {}) {
|
|
|
7521
7326
|
return;
|
|
7522
7327
|
}
|
|
7523
7328
|
|
|
7524
|
-
if (
|
|
7525
|
-
|
|
7526
|
-
writeJsonLine(socket, { type: 'error', error: 'device-credential-binding-invalid' });
|
|
7527
|
-
socket.destroy();
|
|
7528
|
-
return;
|
|
7529
|
-
}
|
|
7530
|
-
const helloChannel = safeString(message.channel || message.Channel || 'control', 40).toLowerCase() || 'control';
|
|
7531
|
-
if (helloChannel !== securityContext.channel) {
|
|
7532
|
-
writeJsonLine(socket, { type: 'error', error: 'secure-channel-binding-invalid' });
|
|
7533
|
-
socket.destroy();
|
|
7534
|
-
return;
|
|
7535
|
-
}
|
|
7536
|
-
} else {
|
|
7537
|
-
const legacyAllowed = socket.__liveDeskRelayControl === true
|
|
7538
|
-
|| socket.__liveDeskLegacyPlaintext === true && allowLegacyPlaintextForTests;
|
|
7539
|
-
if (!legacyAllowed || !timingSafeStringEqual(message.pairToken, pairToken)) {
|
|
7540
|
-
writeJsonLine(socket, { type: 'error', error: 'authenticated-device-credential-required' });
|
|
7541
|
-
socket.destroy();
|
|
7542
|
-
return;
|
|
7543
|
-
}
|
|
7544
|
-
}
|
|
7545
|
-
|
|
7546
|
-
const incomingDeviceId = safeString(message.deviceId || message.DeviceId, 128);
|
|
7547
|
-
const incomingCapabilities = message.capabilities && typeof message.capabilities === 'object'
|
|
7548
|
-
? message.capabilities
|
|
7549
|
-
: {};
|
|
7550
|
-
const incomingPolicy = getWelcomeDevicePolicy({
|
|
7551
|
-
deviceId: incomingDeviceId,
|
|
7552
|
-
capabilities: incomingCapabilities
|
|
7553
|
-
}) || {};
|
|
7554
|
-
const connectionDenied = connectionPolicyError(socket, incomingPolicy);
|
|
7555
|
-
if (connectionDenied) {
|
|
7556
|
-
const deniedTransport = socket.__liveDeskRelayControl === true ? 'relay' : 'direct-tcp';
|
|
7557
|
-
const deniedRemoteAddress = safeString(socket.remoteAddress, 256) || 'unknown';
|
|
7558
|
-
logWarn('security', 'Remote connection rejected reason=' + connectionDenied + ' transport=' + deniedTransport + ' remote=' + deniedRemoteAddress);
|
|
7559
|
-
writeJsonLine(socket, { type: 'error', error: connectionDenied });
|
|
7560
|
-
emitEvent('RemoteSecurityPolicyAudit', {
|
|
7561
|
-
result: 'rejected',
|
|
7562
|
-
reason: connectionDenied,
|
|
7563
|
-
deviceId: incomingDeviceId,
|
|
7564
|
-
sessionId: safeString(socket.__liveDeskSecurityContext?.sessionId, 160),
|
|
7565
|
-
accountId: safeString(socket.__liveDeskSecurityContext?.accountId, 128),
|
|
7566
|
-
hubId: safeString(socket.__liveDeskSecurityContext?.hubId, 128),
|
|
7567
|
-
transport: socket.__liveDeskRelayControl === true ? 'relay' : 'direct-tcp',
|
|
7568
|
-
remoteAddress: safeString(socket.remoteAddress, 256)
|
|
7569
|
-
});
|
|
7329
|
+
if (!timingSafeStringEqual(message.pairToken, pairToken)) {
|
|
7330
|
+
writeJsonLine(socket, { type: 'error', error: 'invalid-pair-token' });
|
|
7570
7331
|
socket.destroy();
|
|
7571
7332
|
return;
|
|
7572
7333
|
}
|
|
@@ -7738,26 +7499,6 @@ export function createRemoteHub(options = {}) {
|
|
|
7738
7499
|
}
|
|
7739
7500
|
}
|
|
7740
7501
|
|
|
7741
|
-
function enforceAgentMessageRate(socket, state, channel = 'control') {
|
|
7742
|
-
if (state.messageRateGate.consume()) return true;
|
|
7743
|
-
const device = state.device;
|
|
7744
|
-
emitEvent('RemoteAbuseDefense', {
|
|
7745
|
-
result: 'rejected',
|
|
7746
|
-
reason: 'authenticated-message-rate-exceeded',
|
|
7747
|
-
deviceId: device?.deviceId || '',
|
|
7748
|
-
sessionId: device?.sessionId || '',
|
|
7749
|
-
accountId: socket.__liveDeskSecurityContext?.accountId || '',
|
|
7750
|
-
hubId: socket.__liveDeskSecurityContext?.hubId || '',
|
|
7751
|
-
transport: socket.__liveDeskRelayControl === true ? 'relay' : 'direct-tcp',
|
|
7752
|
-
channel,
|
|
7753
|
-
remoteAddress: safeString(socket.remoteAddress, 256),
|
|
7754
|
-
rate: state.messageRateGate.getStatus()
|
|
7755
|
-
});
|
|
7756
|
-
writeJsonLine(socket, { type: 'error', error: 'message-rate-exceeded' });
|
|
7757
|
-
socket.destroy();
|
|
7758
|
-
return false;
|
|
7759
|
-
}
|
|
7760
|
-
|
|
7761
7502
|
function handleWebSocketAgentSocket(socket) {
|
|
7762
7503
|
allSockets.add(socket);
|
|
7763
7504
|
socket.setNoDelay(true);
|
|
@@ -7775,7 +7516,6 @@ export function createRemoteHub(options = {}) {
|
|
|
7775
7516
|
binaryIngress: null,
|
|
7776
7517
|
binaryQueueDrops: 0,
|
|
7777
7518
|
binaryQueueEpoch: crypto.randomUUID(),
|
|
7778
|
-
messageRateGate: createRemoteMessageRateGate(),
|
|
7779
7519
|
closed: false
|
|
7780
7520
|
};
|
|
7781
7521
|
attachAgentBinaryIngress(socket, state);
|
|
@@ -7788,7 +7528,6 @@ export function createRemoteHub(options = {}) {
|
|
|
7788
7528
|
}, 10000);
|
|
7789
7529
|
|
|
7790
7530
|
socket.onTextMessage = text => {
|
|
7791
|
-
if (!enforceAgentMessageRate(socket, state, 'websocket-text')) return;
|
|
7792
7531
|
try {
|
|
7793
7532
|
handleAgentMessage(socket, state, parseJsonLine(String(text || '')));
|
|
7794
7533
|
} catch (err) {
|
|
@@ -7798,7 +7537,6 @@ export function createRemoteHub(options = {}) {
|
|
|
7798
7537
|
};
|
|
7799
7538
|
|
|
7800
7539
|
socket.onBinaryMessage = payload => {
|
|
7801
|
-
if (!enforceAgentMessageRate(socket, state, 'websocket-binary')) return;
|
|
7802
7540
|
try {
|
|
7803
7541
|
const packet = parseRemoteHubWebSocketBinaryFrame(payload);
|
|
7804
7542
|
if (!packet?.header || !Buffer.isBuffer(packet.payload)) {
|
|
@@ -7957,7 +7695,6 @@ export function createRemoteHub(options = {}) {
|
|
|
7957
7695
|
binaryIngress: null,
|
|
7958
7696
|
binaryQueueDrops: 0,
|
|
7959
7697
|
binaryQueueEpoch: crypto.randomUUID(),
|
|
7960
|
-
messageRateGate: createRemoteMessageRateGate(),
|
|
7961
7698
|
closed: false
|
|
7962
7699
|
};
|
|
7963
7700
|
attachAgentBinaryIngress(socket, state);
|
|
@@ -8075,15 +7812,6 @@ export function createRemoteHub(options = {}) {
|
|
|
8075
7812
|
|
|
8076
7813
|
try {
|
|
8077
7814
|
const message = parseJsonLine(lineBuffer.toString('utf8'));
|
|
8078
|
-
if (!enforceAgentMessageRate(socket, state, state.frameOnly
|
|
8079
|
-
? 'frame'
|
|
8080
|
-
: state.audioOnly
|
|
8081
|
-
? 'audio'
|
|
8082
|
-
: state.inputOnly
|
|
8083
|
-
? 'input'
|
|
8084
|
-
: state.fileOnly ? 'file' : 'control')) {
|
|
8085
|
-
return;
|
|
8086
|
-
}
|
|
8087
7815
|
if (message?.type === 'frame.binary') {
|
|
8088
7816
|
const frameKind = safeString(message.frameKind || message.kind || message.frameType, 40).toLowerCase();
|
|
8089
7817
|
const maxBytes = frameKind === 'thumbnail'
|
|
@@ -8139,32 +7867,13 @@ export function createRemoteHub(options = {}) {
|
|
|
8139
7867
|
}
|
|
8140
7868
|
|
|
8141
7869
|
function handleSocket(socket) {
|
|
8142
|
-
if (!allowLegacyPlaintextForTests) {
|
|
8143
|
-
secureDirectAcceptor.accept(socket);
|
|
8144
|
-
return;
|
|
8145
|
-
}
|
|
8146
7870
|
socket.once('data', chunk => {
|
|
8147
7871
|
const firstChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
8148
|
-
if (isSecureHandshakeStart(firstChunk)) {
|
|
8149
|
-
secureDirectAcceptor.accept(socket, firstChunk);
|
|
8150
|
-
return;
|
|
8151
|
-
}
|
|
8152
7872
|
if (isRemoteHubWebSocketUpgradeStart(firstChunk)) {
|
|
8153
|
-
if (!allowLegacyPlaintextForTests) {
|
|
8154
|
-
socket.write('HTTP/1.1 426 Upgrade Required\r\nConnection: close\r\n\r\n');
|
|
8155
|
-
socket.destroy();
|
|
8156
|
-
return;
|
|
8157
|
-
}
|
|
8158
|
-
socket.__liveDeskLegacyPlaintext = true;
|
|
8159
7873
|
handleWebSocketUpgradeSocket(socket, firstChunk);
|
|
8160
7874
|
return;
|
|
8161
7875
|
}
|
|
8162
|
-
|
|
8163
|
-
writeJsonLine(socket, { type: 'error', error: 'secure-direct-required' });
|
|
8164
|
-
socket.destroy();
|
|
8165
|
-
return;
|
|
8166
|
-
}
|
|
8167
|
-
socket.__liveDeskLegacyPlaintext = true;
|
|
7876
|
+
|
|
8168
7877
|
handleTcpAgentSocket(socket, firstChunk);
|
|
8169
7878
|
});
|
|
8170
7879
|
|
|
@@ -8188,9 +7897,9 @@ export function createRemoteHub(options = {}) {
|
|
|
8188
7897
|
started = true;
|
|
8189
7898
|
boundPort = candidateServer.address()?.port || port;
|
|
8190
7899
|
lastError = '';
|
|
8191
|
-
logEvent('remote', `LiveDesk Hub client endpoint listening
|
|
7900
|
+
logEvent('remote', `LiveDesk Hub client endpoint listening on tcp://${host}:${boundPort}`, 'success');
|
|
8192
7901
|
if (host === '0.0.0.0' || host === '::') {
|
|
8193
|
-
logWarn('remote', 'LiveDesk Hub client endpoint is externally reachable
|
|
7902
|
+
logWarn('remote', 'LiveDesk Hub client endpoint is externally reachable. Use a strong pairing token and trusted network.');
|
|
8194
7903
|
}
|
|
8195
7904
|
emitRemoteEvent('RemoteHubStarted', null);
|
|
8196
7905
|
resolve();
|
|
@@ -8206,10 +7915,6 @@ export function createRemoteHub(options = {}) {
|
|
|
8206
7915
|
try {
|
|
8207
7916
|
await udpTransport?.start?.();
|
|
8208
7917
|
await listenOnPort(requestedPort);
|
|
8209
|
-
if (!controlIdleSweepTimer) {
|
|
8210
|
-
controlIdleSweepTimer = setInterval(sweepIdleControlSessions, 1_000);
|
|
8211
|
-
controlIdleSweepTimer.unref?.();
|
|
8212
|
-
}
|
|
8213
7918
|
try {
|
|
8214
7919
|
await relayControl?.start?.();
|
|
8215
7920
|
} catch {
|
|
@@ -8232,10 +7937,6 @@ export function createRemoteHub(options = {}) {
|
|
|
8232
7937
|
}
|
|
8233
7938
|
|
|
8234
7939
|
async function close() {
|
|
8235
|
-
if (controlIdleSweepTimer) {
|
|
8236
|
-
clearInterval(controlIdleSweepTimer);
|
|
8237
|
-
controlIdleSweepTimer = null;
|
|
8238
|
-
}
|
|
8239
7940
|
for (const state of [...agentBinaryIngressStates]) {
|
|
8240
7941
|
closeAgentBinaryIngress(state, 'hub-shutdown');
|
|
8241
7942
|
}
|
|
@@ -8442,60 +8143,6 @@ export function createRemoteHub(options = {}) {
|
|
|
8442
8143
|
return { ok: true, commandId };
|
|
8443
8144
|
}
|
|
8444
8145
|
|
|
8445
|
-
async function sendCommandAwaitResult(deviceId, command, options = {}) {
|
|
8446
|
-
const normalizedDeviceId = safeString(deviceId, 160);
|
|
8447
|
-
const device = devices.get(normalizedDeviceId);
|
|
8448
|
-
const commandId = safeString(command?.commandId, 128) || crypto.randomUUID();
|
|
8449
|
-
const commandWithId = { ...(command || {}), commandId };
|
|
8450
|
-
|
|
8451
|
-
if (device?.synthetic === true && device.connected) {
|
|
8452
|
-
const sent = sendCommand(normalizedDeviceId, commandWithId);
|
|
8453
|
-
return {
|
|
8454
|
-
...sent,
|
|
8455
|
-
queued: sent.ok === true,
|
|
8456
|
-
acknowledged: sent.ok === true,
|
|
8457
|
-
acknowledgement: sent.ok === true
|
|
8458
|
-
? { ok: true, commandId, result: { ok: true, synthetic: true }, error: '' }
|
|
8459
|
-
: { ok: false, commandId, result: null, error: sent.error || 'command-not-sent' }
|
|
8460
|
-
};
|
|
8461
|
-
}
|
|
8462
|
-
|
|
8463
|
-
if (!device?.socket || device.socket.destroyed || !device.connected) {
|
|
8464
|
-
const sent = sendCommand(normalizedDeviceId, commandWithId);
|
|
8465
|
-
return {
|
|
8466
|
-
...sent,
|
|
8467
|
-
queued: false,
|
|
8468
|
-
acknowledged: false,
|
|
8469
|
-
acknowledgement: {
|
|
8470
|
-
ok: false,
|
|
8471
|
-
commandId,
|
|
8472
|
-
result: null,
|
|
8473
|
-
error: sent.error || 'device-not-connected'
|
|
8474
|
-
}
|
|
8475
|
-
};
|
|
8476
|
-
}
|
|
8477
|
-
|
|
8478
|
-
const timeoutMs = clampNumber(options.timeoutMs, 1000, 120_000, 30_000);
|
|
8479
|
-
// Register before writing: a loopback Agent can return command.result in
|
|
8480
|
-
// the same event-loop turn as the command write.
|
|
8481
|
-
const acknowledgementPromise = waitForCommandResult(device, commandId, timeoutMs);
|
|
8482
|
-
const sent = sendCommand(normalizedDeviceId, commandWithId);
|
|
8483
|
-
if (!sent.ok) {
|
|
8484
|
-
failPendingCommandResultWaiter(device, commandId, sent.error || 'command-not-sent');
|
|
8485
|
-
}
|
|
8486
|
-
const acknowledgement = await acknowledgementPromise;
|
|
8487
|
-
return {
|
|
8488
|
-
...sent,
|
|
8489
|
-
ok: sent.ok === true && acknowledgement.ok === true,
|
|
8490
|
-
queued: sent.ok === true,
|
|
8491
|
-
acknowledged: acknowledgement.ok === true,
|
|
8492
|
-
acknowledgement,
|
|
8493
|
-
error: acknowledgement.ok === true
|
|
8494
|
-
? undefined
|
|
8495
|
-
: acknowledgement.error || sent.error || 'command-result-failed'
|
|
8496
|
-
};
|
|
8497
|
-
}
|
|
8498
|
-
|
|
8499
8146
|
function refreshDevicePolicies(deviceIds = undefined) {
|
|
8500
8147
|
const requestedIds = Array.isArray(deviceIds) && deviceIds.length > 0
|
|
8501
8148
|
? new Set(deviceIds.map(deviceId => safeString(deviceId, 160)).filter(Boolean))
|
|
@@ -8507,31 +8154,6 @@ export function createRemoteHub(options = {}) {
|
|
|
8507
8154
|
if (!device?.socket || device.socket.destroyed || !device.connected) continue;
|
|
8508
8155
|
total += 1;
|
|
8509
8156
|
const effectivePolicy = getDevicePolicy(device);
|
|
8510
|
-
const connectionDenied = connectionPolicyError(device.socket, effectivePolicy);
|
|
8511
|
-
if (connectionDenied) {
|
|
8512
|
-
const deniedTransport = device.controlTransport || 'direct-tcp';
|
|
8513
|
-
const deniedRemoteAddress = safeString(device.remoteAddress, 256) || 'unknown';
|
|
8514
|
-
logWarn('security', 'Connected device rejected by refreshed policy reason=' + connectionDenied + ' transport=' + deniedTransport + ' remote=' + deniedRemoteAddress + ' device=' + device.deviceId);
|
|
8515
|
-
emitEvent('RemoteSecurityPolicyAudit', {
|
|
8516
|
-
result: 'rejected',
|
|
8517
|
-
reason: connectionDenied,
|
|
8518
|
-
deviceId: device.deviceId,
|
|
8519
|
-
sessionId: device.sessionId,
|
|
8520
|
-
transport: device.controlTransport,
|
|
8521
|
-
remoteAddress: device.remoteAddress
|
|
8522
|
-
});
|
|
8523
|
-
disconnectDevice(device.deviceId, connectionDenied);
|
|
8524
|
-
continue;
|
|
8525
|
-
}
|
|
8526
|
-
if (effectivePolicy.allowControl !== true && device.controlOwnerConnectionId) {
|
|
8527
|
-
releaseInputOwner(
|
|
8528
|
-
device.deviceId,
|
|
8529
|
-
device.controlOwnerConnectionId,
|
|
8530
|
-
'control-disabled-by-policy');
|
|
8531
|
-
}
|
|
8532
|
-
if (effectivePolicy.allowRemoteAudio !== true && device.activeAudioStream) {
|
|
8533
|
-
stopAudioStream(device.deviceId, { reason: 'remote-audio-disabled-by-policy' });
|
|
8534
|
-
}
|
|
8535
8157
|
if (writeJsonLine(device.socket, {
|
|
8536
8158
|
type: 'policy.update',
|
|
8537
8159
|
effectivePolicy,
|
|
@@ -8636,112 +8258,6 @@ export function createRemoteHub(options = {}) {
|
|
|
8636
8258
|
};
|
|
8637
8259
|
}
|
|
8638
8260
|
|
|
8639
|
-
function controlSessionMessage(device, ownerConnectionId, type, reason = '') {
|
|
8640
|
-
const policy = getDevicePolicy(device);
|
|
8641
|
-
return {
|
|
8642
|
-
type,
|
|
8643
|
-
payload: {
|
|
8644
|
-
controlOwnerSessionId: safeString(device?.controlOwnerSessionId, 160),
|
|
8645
|
-
hubConnectionId: safeString(ownerConnectionId, 128),
|
|
8646
|
-
deviceId: safeString(device?.deviceId, 160),
|
|
8647
|
-
visibleIndicator: true,
|
|
8648
|
-
lockOnEnd: type === 'control.session.stop' && policy.lockOnControlEnd === true,
|
|
8649
|
-
idleControlMinutes: clampNumber(policy.idleControlMinutes, 5, 1440, 30),
|
|
8650
|
-
reason: safeString(reason, 160),
|
|
8651
|
-
issuedAt: new Date().toISOString()
|
|
8652
|
-
}
|
|
8653
|
-
};
|
|
8654
|
-
}
|
|
8655
|
-
|
|
8656
|
-
function sendControlSessionMessage(device, ownerConnectionId, type, reason, inputWriteOwner, bindingKey) {
|
|
8657
|
-
const message = controlSessionMessage(device, ownerConnectionId, type, reason);
|
|
8658
|
-
if (inputWriteOwner && bindingKey) {
|
|
8659
|
-
return inputWriteOwner.enqueue(message, bindingKey);
|
|
8660
|
-
}
|
|
8661
|
-
if (!device?.socket || device.socket.destroyed) {
|
|
8662
|
-
return { ok: false, error: 'device-not-connected' };
|
|
8663
|
-
}
|
|
8664
|
-
const sent = writeJsonLine(device.socket, {
|
|
8665
|
-
type: 'command',
|
|
8666
|
-
commandId: crypto.randomUUID(),
|
|
8667
|
-
command: type,
|
|
8668
|
-
payload: message.payload,
|
|
8669
|
-
issuedAt: new Date().toISOString()
|
|
8670
|
-
});
|
|
8671
|
-
return sent ? { ok: true, inputSocket: false } : { ok: false, error: 'device-not-connected' };
|
|
8672
|
-
}
|
|
8673
|
-
|
|
8674
|
-
function beginControlSession(device, ownerConnectionId, inputWriteOwner, bindingKey) {
|
|
8675
|
-
const owner = safeString(ownerConnectionId, 128);
|
|
8676
|
-
if (!device || !owner) return { ok: false, error: 'control-owner-required' };
|
|
8677
|
-
if (device.controlOwnerConnectionId === owner && device.controlOwnerSessionId) {
|
|
8678
|
-
return { ok: true, alreadyActive: true };
|
|
8679
|
-
}
|
|
8680
|
-
device.controlOwnerSessionId = crypto.randomUUID();
|
|
8681
|
-
device.controlOwnerConnectionId = owner;
|
|
8682
|
-
device.controlOwnerStartedAtMs = Date.now();
|
|
8683
|
-
device.controlOwnerLastInputAtMs = Date.now();
|
|
8684
|
-
const sent = sendControlSessionMessage(
|
|
8685
|
-
device,
|
|
8686
|
-
owner,
|
|
8687
|
-
'control.session.start',
|
|
8688
|
-
'browser-control-owner-active',
|
|
8689
|
-
inputWriteOwner,
|
|
8690
|
-
bindingKey);
|
|
8691
|
-
if (!sent.ok) {
|
|
8692
|
-
device.controlOwnerSessionId = '';
|
|
8693
|
-
device.controlOwnerConnectionId = '';
|
|
8694
|
-
device.controlOwnerStartedAtMs = 0;
|
|
8695
|
-
device.controlOwnerLastInputAtMs = 0;
|
|
8696
|
-
return sent;
|
|
8697
|
-
}
|
|
8698
|
-
emitRemoteEvent('RemoteControlSessionStarted', device, {
|
|
8699
|
-
controlOwnerSessionId: device.controlOwnerSessionId,
|
|
8700
|
-
hubConnectionId: owner
|
|
8701
|
-
});
|
|
8702
|
-
return sent;
|
|
8703
|
-
}
|
|
8704
|
-
|
|
8705
|
-
function endControlSession(device, ownerConnectionId, reason, inputWriteOwner, bindingKey) {
|
|
8706
|
-
const owner = safeString(ownerConnectionId, 128);
|
|
8707
|
-
if (!device?.controlOwnerSessionId
|
|
8708
|
-
|| !owner
|
|
8709
|
-
|| device.controlOwnerConnectionId !== owner) {
|
|
8710
|
-
return { ok: false, error: 'control-owner-not-current' };
|
|
8711
|
-
}
|
|
8712
|
-
const controlOwnerSessionId = device.controlOwnerSessionId;
|
|
8713
|
-
const sent = sendControlSessionMessage(
|
|
8714
|
-
device,
|
|
8715
|
-
owner,
|
|
8716
|
-
'control.session.stop',
|
|
8717
|
-
reason,
|
|
8718
|
-
inputWriteOwner,
|
|
8719
|
-
bindingKey);
|
|
8720
|
-
device.controlOwnerSessionId = '';
|
|
8721
|
-
device.controlOwnerConnectionId = '';
|
|
8722
|
-
device.controlOwnerStartedAtMs = 0;
|
|
8723
|
-
device.controlOwnerLastInputAtMs = 0;
|
|
8724
|
-
emitRemoteEvent('RemoteControlSessionEnded', device, {
|
|
8725
|
-
controlOwnerSessionId,
|
|
8726
|
-
hubConnectionId: owner,
|
|
8727
|
-
reason,
|
|
8728
|
-
delivered: sent.ok === true
|
|
8729
|
-
});
|
|
8730
|
-
return sent;
|
|
8731
|
-
}
|
|
8732
|
-
|
|
8733
|
-
function sweepIdleControlSessions() {
|
|
8734
|
-
const nowMs = Date.now();
|
|
8735
|
-
for (const device of devices.values()) {
|
|
8736
|
-
if (!device?.connected || !device.controlOwnerConnectionId) continue;
|
|
8737
|
-
const policy = getDevicePolicy(device);
|
|
8738
|
-
if (policy.disconnectIdleControlSessions !== true) continue;
|
|
8739
|
-
const idleMs = clampNumber(policy.idleControlMinutes, 5, 1440, 30) * 60 * 1000;
|
|
8740
|
-
if (nowMs - Number(device.controlOwnerLastInputAtMs || 0) < idleMs) continue;
|
|
8741
|
-
releaseInputOwner(device.deviceId, device.controlOwnerConnectionId, 'control-idle-timeout');
|
|
8742
|
-
}
|
|
8743
|
-
}
|
|
8744
|
-
|
|
8745
8261
|
function getCurrentInputWriteOwner(device) {
|
|
8746
8262
|
if (!device?.inputSocket
|
|
8747
8263
|
|| device.inputSocket.destroyed
|
|
@@ -8889,9 +8405,6 @@ export function createRemoteHub(options = {}) {
|
|
|
8889
8405
|
const bindingChanged = previousInputBindingKey
|
|
8890
8406
|
&& previousInputBindingKey !== activeInputBindingKey;
|
|
8891
8407
|
const writeBindingChanged = inputWriteOwner.getBindingKey() !== activeInputBindingKey;
|
|
8892
|
-
const startingControlOwner = !!normalized.hubConnectionId
|
|
8893
|
-
&& (!device.controlOwnerConnectionId
|
|
8894
|
-
|| device.controlOwnerConnectionId !== normalized.hubConnectionId);
|
|
8895
8408
|
if (ownerChanged || bindingChanged || writeBindingChanged) {
|
|
8896
8409
|
const resetSent = inputWriteOwner.replaceBinding(
|
|
8897
8410
|
activeInputBindingKey,
|
|
@@ -8909,25 +8422,6 @@ export function createRemoteHub(options = {}) {
|
|
|
8909
8422
|
closeInputSocket(device, resetSent.error || 'input-owner-reset-write-failed');
|
|
8910
8423
|
return { ok: false, error: 'INPUT_CHANNEL_RECONNECTING' };
|
|
8911
8424
|
}
|
|
8912
|
-
if (startingControlOwner) {
|
|
8913
|
-
if (device.controlOwnerConnectionId) {
|
|
8914
|
-
endControlSession(
|
|
8915
|
-
device,
|
|
8916
|
-
device.controlOwnerConnectionId,
|
|
8917
|
-
'browser-input-owner-changed',
|
|
8918
|
-
inputWriteOwner,
|
|
8919
|
-
activeInputBindingKey);
|
|
8920
|
-
}
|
|
8921
|
-
const started = beginControlSession(
|
|
8922
|
-
device,
|
|
8923
|
-
normalized.hubConnectionId,
|
|
8924
|
-
inputWriteOwner,
|
|
8925
|
-
activeInputBindingKey);
|
|
8926
|
-
if (!started.ok) {
|
|
8927
|
-
closeInputSocket(device, started.error || 'control-session-indicator-start-failed');
|
|
8928
|
-
return { ok: false, error: 'INPUT_CHANNEL_RECONNECTING' };
|
|
8929
|
-
}
|
|
8930
|
-
}
|
|
8931
8425
|
}
|
|
8932
8426
|
const sent = inputWriteOwner.enqueue({
|
|
8933
8427
|
type: 'input.control',
|
|
@@ -8942,7 +8436,6 @@ export function createRemoteHub(options = {}) {
|
|
|
8942
8436
|
device.inputOwnerConnectionId = normalized.hubConnectionId;
|
|
8943
8437
|
}
|
|
8944
8438
|
device.inputOwnerBindingKey = activeInputBindingKey;
|
|
8945
|
-
device.controlOwnerLastInputAtMs = Date.now();
|
|
8946
8439
|
device.counters.commandsSent += 1;
|
|
8947
8440
|
device.inputLastSeenAt = new Date().toISOString();
|
|
8948
8441
|
return {
|
|
@@ -8972,25 +8465,6 @@ export function createRemoteHub(options = {}) {
|
|
|
8972
8465
|
}
|
|
8973
8466
|
|
|
8974
8467
|
const hubForwardedAtEpochMs = Date.now();
|
|
8975
|
-
const startingFallbackOwner = !!normalized.hubConnectionId
|
|
8976
|
-
&& (!device.controlOwnerConnectionId
|
|
8977
|
-
|| device.controlOwnerConnectionId !== normalized.hubConnectionId);
|
|
8978
|
-
if (startingFallbackOwner) {
|
|
8979
|
-
if (device.controlOwnerConnectionId) {
|
|
8980
|
-
endControlSession(
|
|
8981
|
-
device,
|
|
8982
|
-
device.controlOwnerConnectionId,
|
|
8983
|
-
'browser-input-owner-changed',
|
|
8984
|
-
null,
|
|
8985
|
-
'');
|
|
8986
|
-
}
|
|
8987
|
-
const started = beginControlSession(
|
|
8988
|
-
device,
|
|
8989
|
-
normalized.hubConnectionId,
|
|
8990
|
-
null,
|
|
8991
|
-
'');
|
|
8992
|
-
if (!started.ok) return started;
|
|
8993
|
-
}
|
|
8994
8468
|
const fallback = sendCommand(deviceId, {
|
|
8995
8469
|
command: 'input.control',
|
|
8996
8470
|
payload: {
|
|
@@ -9014,22 +8488,6 @@ export function createRemoteHub(options = {}) {
|
|
|
9014
8488
|
timeoutTimer: null
|
|
9015
8489
|
});
|
|
9016
8490
|
}
|
|
9017
|
-
if (fallback.ok) {
|
|
9018
|
-
device.inputOwnerConnectionId = normalized.hubConnectionId;
|
|
9019
|
-
device.inputOwnerBindingKey = buildRemoteInputBindingKey(
|
|
9020
|
-
device,
|
|
9021
|
-
controlStream,
|
|
9022
|
-
normalized,
|
|
9023
|
-
activeMonitorIndex);
|
|
9024
|
-
device.controlOwnerLastInputAtMs = Date.now();
|
|
9025
|
-
} else if (startingFallbackOwner) {
|
|
9026
|
-
endControlSession(
|
|
9027
|
-
device,
|
|
9028
|
-
normalized.hubConnectionId,
|
|
9029
|
-
'input-control-delivery-failed',
|
|
9030
|
-
null,
|
|
9031
|
-
'');
|
|
9032
|
-
}
|
|
9033
8491
|
return fallback.ok
|
|
9034
8492
|
? {
|
|
9035
8493
|
...fallback,
|
|
@@ -9056,17 +8514,12 @@ export function createRemoteHub(options = {}) {
|
|
|
9056
8514
|
return { ok: false, error: 'input-owner-not-current' };
|
|
9057
8515
|
}
|
|
9058
8516
|
|
|
8517
|
+
device.inputOwnerConnectionId = '';
|
|
8518
|
+
device.inputOwnerBindingKey = '';
|
|
9059
8519
|
const inputSocket = device.inputSocket;
|
|
9060
8520
|
const inputWriteOwner = getCurrentInputWriteOwner(device);
|
|
9061
8521
|
if (!inputSocket || !inputWriteOwner) {
|
|
9062
|
-
|
|
9063
|
-
device.inputOwnerConnectionId = '';
|
|
9064
|
-
device.inputOwnerBindingKey = '';
|
|
9065
|
-
return {
|
|
9066
|
-
ok: ended.ok !== false,
|
|
9067
|
-
queued: false,
|
|
9068
|
-
controlSessionEnded: ended.ok === true
|
|
9069
|
-
};
|
|
8522
|
+
return { ok: true, queued: false };
|
|
9070
8523
|
}
|
|
9071
8524
|
const controlStream = getActiveControlStream(device);
|
|
9072
8525
|
const releaseBindingKey = buildRemoteInputReleaseBindingKey(device, owner);
|
|
@@ -9082,19 +8535,10 @@ export function createRemoteHub(options = {}) {
|
|
|
9082
8535
|
closeInputSocket(device, sent.error || 'input-owner-release-write-failed');
|
|
9083
8536
|
return { ok: false, error: 'INPUT_CHANNEL_RECONNECTING' };
|
|
9084
8537
|
}
|
|
9085
|
-
const ended = endControlSession(
|
|
9086
|
-
device,
|
|
9087
|
-
owner,
|
|
9088
|
-
reason,
|
|
9089
|
-
inputWriteOwner,
|
|
9090
|
-
releaseBindingKey);
|
|
9091
|
-
device.inputOwnerConnectionId = '';
|
|
9092
|
-
device.inputOwnerBindingKey = '';
|
|
9093
8538
|
return {
|
|
9094
|
-
ok:
|
|
8539
|
+
ok: true,
|
|
9095
8540
|
queued: true,
|
|
9096
|
-
backpressured: sent.backpressured === true
|
|
9097
|
-
controlSessionEnded: ended.ok === true
|
|
8541
|
+
backpressured: sent.backpressured === true
|
|
9098
8542
|
};
|
|
9099
8543
|
}
|
|
9100
8544
|
|
|
@@ -9137,9 +8581,6 @@ export function createRemoteHub(options = {}) {
|
|
|
9137
8581
|
}
|
|
9138
8582
|
const device = devices.get(String(deviceId || ''));
|
|
9139
8583
|
const requestedOperation = safeString(options.operation, 80);
|
|
9140
|
-
if (RETIRED_AGENT_MUTATING_OPERATIONS.has(requestedOperation.toLowerCase())) {
|
|
9141
|
-
return { ok: false, error: 'agent-mutating-tool-disabled' };
|
|
9142
|
-
}
|
|
9143
8584
|
const operation = normalizeAgentOperation(requestedOperation);
|
|
9144
8585
|
if (requestedOperation && !operation) {
|
|
9145
8586
|
return { ok: false, error: 'unsupported-agent-operation' };
|
|
@@ -11388,7 +10829,6 @@ export function createRemoteHub(options = {}) {
|
|
|
11388
10829
|
disconnectDevice,
|
|
11389
10830
|
assignDeviceSlot,
|
|
11390
10831
|
sendCommand,
|
|
11391
|
-
sendCommandAwaitResult,
|
|
11392
10832
|
refreshDevicePolicies,
|
|
11393
10833
|
sendLegacyClientUpdate,
|
|
11394
10834
|
sendInputControl,
|
|
@@ -11417,16 +10857,7 @@ export function createRemoteHub(options = {}) {
|
|
|
11417
10857
|
seedSyntheticFleet,
|
|
11418
10858
|
clearSyntheticFleet,
|
|
11419
10859
|
getPairToken: () => pairToken,
|
|
11420
|
-
getPairingPin: () => pairingPin
|
|
11421
|
-
getSecurityStatus: () => ({
|
|
11422
|
-
hubId: deviceCredentialAuthority.hubId,
|
|
11423
|
-
hubPublicKey: deviceCredentialAuthority.hubPublicKey,
|
|
11424
|
-
hubIssuerKeyId: deviceCredentialAuthority.hubIssuerKeyId,
|
|
11425
|
-
direct: secureDirectAcceptor.getStatus(),
|
|
11426
|
-
devices: deviceCredentialAuthority.listDevices()
|
|
11427
|
-
}),
|
|
11428
|
-
revokeDeviceCredential: (deviceId, reason) => deviceCredentialAuthority.revokeDevice(deviceId, reason),
|
|
11429
|
-
clearDeviceCredentials: () => deviceCredentialAuthority.clearDevices()
|
|
10860
|
+
getPairingPin: () => pairingPin
|
|
11430
10861
|
};
|
|
11431
10862
|
}
|
|
11432
10863
|
|