@livedesk/hub 0.1.41 → 0.1.43
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 +3 -3
- package/src/agents/agent-device-scope.js +29 -29
- package/src/agents/agent-manager.js +103 -103
- package/src/agents/agent-permission-store.js +78 -78
- package/src/agents/agent-runtime-error.js +9 -9
- package/src/agents/agent-settings.js +71 -71
- package/src/agents/codex-agent-runtime.js +594 -594
- package/src/agents/codex-mcp-server.js +100 -100
- package/src/console-relay.js +415 -0
- package/src/filesystem/directory-reader.js +187 -187
- package/src/filesystem/hub-filesystem.js +78 -78
- package/src/filesystem/path-registry.js +78 -78
- package/src/filesystem/roots.js +115 -115
- package/src/frame-packet-contract.mjs +427 -427
- package/src/http/hub-ui-session.js +119 -119
- package/src/live-capture-transition-retry.mjs +297 -297
- package/src/live-desk-update.js +167 -108
- package/src/live-stream-monitor-contract.js +38 -38
- package/src/lzo1x.js +109 -109
- package/src/mode4-atlas-pool.js +137 -137
- package/src/mode4-atlas-sizing.js +32 -32
- package/src/mode4-atlas-worker.js +599 -599
- package/src/mode4-atlas.js +306 -306
- package/src/remote-audio-subscription-contract.mjs +109 -109
- package/src/remote-audio-subscription-contract.test.mjs +72 -72
- package/src/remote-hub.js +144 -144
- package/src/server.js +244 -222
- package/src/settings/effective-device-policy.js +16 -16
- package/src/transport/udp-protocol.js +453 -453
package/src/server.js
CHANGED
|
@@ -7,8 +7,9 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, wri
|
|
|
7
7
|
import { dirname, resolve } from 'node:path';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import os from 'node:os';
|
|
10
|
-
import { WebSocketServer } from 'ws';
|
|
11
|
-
import { createRemoteHub } from './remote-hub.js';
|
|
10
|
+
import { WebSocketServer } from 'ws';
|
|
11
|
+
import { createRemoteHub } from './remote-hub.js';
|
|
12
|
+
import { createHubConsoleRelay } from './console-relay.js';
|
|
12
13
|
import {
|
|
13
14
|
buildImmutableRemoteFramePacket,
|
|
14
15
|
createRemoteFramePacketMetrics,
|
|
@@ -51,13 +52,13 @@ import { normalizeRuntimeAuthSession, runtimeRoleError } from '../../runtime-cor
|
|
|
51
52
|
import { fetchAuthResponse, AUTH_REQUEST_TIMEOUT_MS } from '../../runtime-core/src/auth-http.js';
|
|
52
53
|
import { PRODUCTION_SUPABASE_PUBLISHABLE_KEY, PRODUCTION_SUPABASE_URL } from '../../runtime-core/src/auth-config.js';
|
|
53
54
|
import { createHubRuntime } from './runtime/hub-runtime.js';
|
|
54
|
-
import { hubRuntimeStatus } from './runtime/hub-runtime-status.js';
|
|
55
|
-
import { roleGuardResponse } from './http/hub-role-guard.js';
|
|
56
|
-
import {
|
|
57
|
-
clearHubUiSessionCookie,
|
|
58
|
-
createHubUiSessionAuthority,
|
|
59
|
-
serializeHubUiSessionCookie
|
|
60
|
-
} from './http/hub-ui-session.js';
|
|
55
|
+
import { hubRuntimeStatus } from './runtime/hub-runtime-status.js';
|
|
56
|
+
import { roleGuardResponse } from './http/hub-role-guard.js';
|
|
57
|
+
import {
|
|
58
|
+
clearHubUiSessionCookie,
|
|
59
|
+
createHubUiSessionAuthority,
|
|
60
|
+
serializeHubUiSessionCookie
|
|
61
|
+
} from './http/hub-ui-session.js';
|
|
61
62
|
|
|
62
63
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
63
64
|
const webDistCandidates = [
|
|
@@ -187,10 +188,10 @@ const persistentSessionGcToken = persistentSessionGcEnabled
|
|
|
187
188
|
? String(process.env.LIVEDESK_PERSISTENT_SESSION_TEST_TOKEN || '').trim()
|
|
188
189
|
: '';
|
|
189
190
|
let connectedDeviceCount = 0;
|
|
190
|
-
let runtimeAccessToken = '';
|
|
191
|
-
let runtimeRefreshToken = '';
|
|
192
|
-
let runtimeAccessTokenExpiresAt = 0;
|
|
193
|
-
const hubUiSessionAuthority = createHubUiSessionAuthority();
|
|
191
|
+
let runtimeAccessToken = '';
|
|
192
|
+
let runtimeRefreshToken = '';
|
|
193
|
+
let runtimeAccessTokenExpiresAt = 0;
|
|
194
|
+
const hubUiSessionAuthority = createHubUiSessionAuthority();
|
|
194
195
|
let roleWatchInFlight = false;
|
|
195
196
|
let verifiedLicense = {
|
|
196
197
|
userId: '',
|
|
@@ -202,15 +203,22 @@ let verifiedLicense = {
|
|
|
202
203
|
let frameClientSeq = 0;
|
|
203
204
|
let inputClientSeq = 0;
|
|
204
205
|
let audioClientSeq = 0;
|
|
205
|
-
let liveDeskUpdateManager = null;
|
|
206
|
-
let hubTransferJobs = null;
|
|
206
|
+
let liveDeskUpdateManager = null;
|
|
207
|
+
let hubTransferJobs = null;
|
|
208
|
+
let hubConsoleRelay = null;
|
|
207
209
|
const HUB_HOST_TARGET_LEASE_MS = Math.max(5000, readPositiveIntegerEnv('LIVEDESK_HOST_TARGET_LEASE_MS', 60_000));
|
|
208
210
|
const HUB_HOST_TARGET_RENEW_MS = Math.max(1000, Math.min(
|
|
209
211
|
readPositiveIntegerEnv('LIVEDESK_HOST_TARGET_RENEW_MS', 20_000),
|
|
210
212
|
Math.max(1000, HUB_HOST_TARGET_LEASE_MS - 1000)
|
|
211
213
|
));
|
|
212
|
-
const HUB_ACCESS_TOKEN_REFRESH_SKEW_MS = 90_000;
|
|
213
|
-
const hubWakeBaseUrl = String(process.env.LIVEDESK_WAKE_URL || 'https://livedesk-wake.lovecrdm.workers.dev').trim();
|
|
214
|
+
const HUB_ACCESS_TOKEN_REFRESH_SKEW_MS = 90_000;
|
|
215
|
+
const hubWakeBaseUrl = String(process.env.LIVEDESK_WAKE_URL || 'https://livedesk-wake.lovecrdm.workers.dev').trim();
|
|
216
|
+
const hubConsoleRelayBaseUrl = String(
|
|
217
|
+
process.env.LIVEDESK_CONSOLE_RELAY_URL
|
|
218
|
+
|| (process.env.LIVEDESK_TEST_MODE === '1' || process.env.LIVEDESK_AUTH_TEST_MODE === '1'
|
|
219
|
+
? 'off'
|
|
220
|
+
: 'https://livedesk-wake.lovecrdm.workers.dev')
|
|
221
|
+
).trim();
|
|
214
222
|
const HUB_WAKE_NOTIFY_RETRY_MS = 60_000;
|
|
215
223
|
let hubHostTargetRenewTimer = null;
|
|
216
224
|
let hubHostTargetRenewInFlight = false;
|
|
@@ -491,12 +499,12 @@ function clearPersistedRuntimeSession() {
|
|
|
491
499
|
return writePrivateRuntimeAuthState(state);
|
|
492
500
|
}
|
|
493
501
|
|
|
494
|
-
function clearRuntimeSession() {
|
|
495
|
-
runtimeAccessToken = '';
|
|
496
|
-
runtimeRefreshToken = '';
|
|
497
|
-
runtimeAccessTokenExpiresAt = 0;
|
|
498
|
-
hubUiSessionAuthority.revokeAll();
|
|
499
|
-
runtimeManager.setAuthenticated(false);
|
|
502
|
+
function clearRuntimeSession() {
|
|
503
|
+
runtimeAccessToken = '';
|
|
504
|
+
runtimeRefreshToken = '';
|
|
505
|
+
runtimeAccessTokenExpiresAt = 0;
|
|
506
|
+
hubUiSessionAuthority.revokeAll();
|
|
507
|
+
runtimeManager.setAuthenticated(false);
|
|
500
508
|
try { clearPersistedRuntimeSession(); } catch { /* runtime auth is already invalid */ }
|
|
501
509
|
}
|
|
502
510
|
|
|
@@ -796,11 +804,12 @@ function getLiveDeskUpdateStatus() {
|
|
|
796
804
|
}
|
|
797
805
|
|
|
798
806
|
if (process.env.LIVEDESK_DESKTOP_HOST !== '1') {
|
|
799
|
-
liveDeskUpdateManager = createLiveDeskUpdateManager({
|
|
800
|
-
remoteHub,
|
|
801
|
-
currentManagerVersion: process.env.LIVEDESK_MANAGER_VERSION || packageInfo.version,
|
|
802
|
-
currentClientVersion: process.env.LIVEDESK_CLIENT_PACKAGE_VERSION || '',
|
|
803
|
-
|
|
807
|
+
liveDeskUpdateManager = createLiveDeskUpdateManager({
|
|
808
|
+
remoteHub,
|
|
809
|
+
currentManagerVersion: process.env.LIVEDESK_MANAGER_VERSION || packageInfo.version,
|
|
810
|
+
currentClientVersion: process.env.LIVEDESK_CLIENT_PACKAGE_VERSION || '',
|
|
811
|
+
excludedDeviceIds: [runtimeDeviceId],
|
|
812
|
+
restartSupported: !!String(process.env.LIVEDESK_HUB_UPDATE_REQUEST_PATH || '').trim(),
|
|
804
813
|
requestHubRestart
|
|
805
814
|
});
|
|
806
815
|
}
|
|
@@ -1332,9 +1341,16 @@ const hubSharedFolders = createHubSharedFolders({
|
|
|
1332
1341
|
dataDir: agentDataDir
|
|
1333
1342
|
});
|
|
1334
1343
|
|
|
1335
|
-
const app = express();
|
|
1336
|
-
const httpServer = createServer(app);
|
|
1337
|
-
|
|
1344
|
+
const app = express();
|
|
1345
|
+
const httpServer = createServer(app);
|
|
1346
|
+
hubConsoleRelay = createHubConsoleRelay({
|
|
1347
|
+
url: runtimeRole === 'hub' ? hubConsoleRelayBaseUrl : 'off',
|
|
1348
|
+
deviceId: runtimeDeviceId,
|
|
1349
|
+
httpBaseUrl: `http://127.0.0.1:${httpPort}`,
|
|
1350
|
+
getAccessToken: () => getRuntimeAccessToken(),
|
|
1351
|
+
logger: console
|
|
1352
|
+
});
|
|
1353
|
+
const httpConnections = new Set();
|
|
1338
1354
|
const frameWss = new WebSocketServer({ noServer: true, perMessageDeflate: false });
|
|
1339
1355
|
const atlasWss = new WebSocketServer({ noServer: true, perMessageDeflate: false });
|
|
1340
1356
|
const inputWss = new WebSocketServer({ noServer: true, perMessageDeflate: false });
|
|
@@ -1412,7 +1428,7 @@ function requestHostname(req) {
|
|
|
1412
1428
|
}
|
|
1413
1429
|
}
|
|
1414
1430
|
|
|
1415
|
-
function isTrustedBrowserRequest(req) {
|
|
1431
|
+
function isTrustedBrowserRequest(req) {
|
|
1416
1432
|
const origin = String(req.headers.origin || '').trim();
|
|
1417
1433
|
if (!origin) {
|
|
1418
1434
|
const fetchSite = String(req.headers['sec-fetch-site'] || '').toLowerCase();
|
|
@@ -1429,59 +1445,59 @@ function isTrustedBrowserRequest(req) {
|
|
|
1429
1445
|
const targetHost = requestHostname(req);
|
|
1430
1446
|
return (originHost === targetHost && isPrivateHubHostname(targetHost))
|
|
1431
1447
|
|| (isLoopbackHostname(originHost) && isLoopbackHostname(targetHost));
|
|
1432
|
-
} catch {
|
|
1433
|
-
return false;
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
function isSecureHubHttpRequest(req) {
|
|
1438
|
-
return req.socket?.encrypted === true
|
|
1439
|
-
|| String(req.headers['x-forwarded-proto'] || '').trim().toLowerCase() === 'https';
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
function currentBoundHubUserId() {
|
|
1443
|
-
return String(
|
|
1444
|
-
runtimeManager.getSnapshot().userId
|
|
1445
|
-
|| readPersistedRuntimeSession()?.user?.id
|
|
1446
|
-
|| ''
|
|
1447
|
-
).trim();
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
|
-
function isPublicHubApiRequest(req) {
|
|
1451
|
-
const method = String(req.method || 'GET').toUpperCase();
|
|
1452
|
-
const path = String(req.path || '');
|
|
1453
|
-
return (method === 'GET' && (path === '/api/health' || path === '/api/runtime/status' || path === '/api/auth/status'))
|
|
1454
|
-
|| (method === 'POST' && path === '/api/auth/session');
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
|
-
function isTrustedNativeLoopbackRequest(req) {
|
|
1458
|
-
return isLoopbackAddress(req.socket?.remoteAddress)
|
|
1459
|
-
&& !String(req.headers.origin || '').trim()
|
|
1460
|
-
&& !String(req.headers['sec-fetch-site'] || '').trim();
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
function authorizeHubUiRequest(req) {
|
|
1464
|
-
if (process.env.LIVEDESK_TEST_MODE === '1') {
|
|
1465
|
-
return { ok: true, explicitTestMode: true };
|
|
1466
|
-
}
|
|
1467
|
-
if (isTrustedNativeLoopbackRequest(req)) {
|
|
1468
|
-
return { ok: true, nativeLoopback: true };
|
|
1469
|
-
}
|
|
1470
|
-
return hubUiSessionAuthority.authorize(req, currentBoundHubUserId());
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
app.use((req, res, next) => {
|
|
1448
|
+
} catch {
|
|
1449
|
+
return false;
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
function isSecureHubHttpRequest(req) {
|
|
1454
|
+
return req.socket?.encrypted === true
|
|
1455
|
+
|| String(req.headers['x-forwarded-proto'] || '').trim().toLowerCase() === 'https';
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
function currentBoundHubUserId() {
|
|
1459
|
+
return String(
|
|
1460
|
+
runtimeManager.getSnapshot().userId
|
|
1461
|
+
|| readPersistedRuntimeSession()?.user?.id
|
|
1462
|
+
|| ''
|
|
1463
|
+
).trim();
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
function isPublicHubApiRequest(req) {
|
|
1467
|
+
const method = String(req.method || 'GET').toUpperCase();
|
|
1468
|
+
const path = String(req.path || '');
|
|
1469
|
+
return (method === 'GET' && (path === '/api/health' || path === '/api/runtime/status' || path === '/api/auth/status'))
|
|
1470
|
+
|| (method === 'POST' && path === '/api/auth/session');
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
function isTrustedNativeLoopbackRequest(req) {
|
|
1474
|
+
return isLoopbackAddress(req.socket?.remoteAddress)
|
|
1475
|
+
&& !String(req.headers.origin || '').trim()
|
|
1476
|
+
&& !String(req.headers['sec-fetch-site'] || '').trim();
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
function authorizeHubUiRequest(req) {
|
|
1480
|
+
if (process.env.LIVEDESK_TEST_MODE === '1') {
|
|
1481
|
+
return { ok: true, explicitTestMode: true };
|
|
1482
|
+
}
|
|
1483
|
+
if (isTrustedNativeLoopbackRequest(req)) {
|
|
1484
|
+
return { ok: true, nativeLoopback: true };
|
|
1485
|
+
}
|
|
1486
|
+
return hubUiSessionAuthority.authorize(req, currentBoundHubUserId());
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
app.use((req, res, next) => {
|
|
1474
1490
|
if (!isTrustedBrowserRequest(req)) {
|
|
1475
1491
|
res.status(403).json({ ok: false, error: 'untrusted-hub-origin' });
|
|
1476
1492
|
return;
|
|
1477
1493
|
}
|
|
1478
1494
|
const origin = String(req.headers.origin || '').trim();
|
|
1479
1495
|
if (origin) {
|
|
1480
|
-
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
1481
|
-
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
1482
|
-
res.setHeader('Access-Control-Allow-Private-Network', 'true');
|
|
1496
|
+
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
1497
|
+
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
1498
|
+
res.setHeader('Access-Control-Allow-Private-Network', 'true');
|
|
1483
1499
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
|
|
1484
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-LiveDesk-CSRF');
|
|
1500
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-LiveDesk-CSRF');
|
|
1485
1501
|
res.setHeader('Vary', 'Origin, Access-Control-Request-Headers, Access-Control-Request-Private-Network');
|
|
1486
1502
|
}
|
|
1487
1503
|
if (req.method === 'OPTIONS') {
|
|
@@ -1490,21 +1506,21 @@ app.use((req, res, next) => {
|
|
|
1490
1506
|
}
|
|
1491
1507
|
next();
|
|
1492
1508
|
});
|
|
1493
|
-
app.use(express.json({ limit: '32mb' }));
|
|
1494
|
-
app.use((req, res, next) => {
|
|
1495
|
-
if (!req.path.startsWith('/api/') || isPublicHubApiRequest(req)) {
|
|
1496
|
-
next();
|
|
1497
|
-
return;
|
|
1498
|
-
}
|
|
1499
|
-
const authorization = authorizeHubUiRequest(req);
|
|
1500
|
-
if (!authorization.ok) {
|
|
1501
|
-
noStore(res);
|
|
1502
|
-
res.status(authorization.status).json({ ok: false, error: authorization.error });
|
|
1503
|
-
return;
|
|
1504
|
-
}
|
|
1505
|
-
next();
|
|
1506
|
-
});
|
|
1507
|
-
app.use((req, res, next) => {
|
|
1509
|
+
app.use(express.json({ limit: '32mb' }));
|
|
1510
|
+
app.use((req, res, next) => {
|
|
1511
|
+
if (!req.path.startsWith('/api/') || isPublicHubApiRequest(req)) {
|
|
1512
|
+
next();
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
const authorization = authorizeHubUiRequest(req);
|
|
1516
|
+
if (!authorization.ok) {
|
|
1517
|
+
noStore(res);
|
|
1518
|
+
res.status(authorization.status).json({ ok: false, error: authorization.error });
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1521
|
+
next();
|
|
1522
|
+
});
|
|
1523
|
+
app.use((req, res, next) => {
|
|
1508
1524
|
if (runtimeRole === 'client' && req.path.startsWith('/api/remote')) {
|
|
1509
1525
|
res.status(403).json(roleGuardResponse(runtimeRole, 'hub'));
|
|
1510
1526
|
return;
|
|
@@ -2767,7 +2783,7 @@ function enqueueFramePacketForClient(ws, packetOwner, meta) {
|
|
|
2767
2783
|
}
|
|
2768
2784
|
// FRAME_LANE_GENERATION_CONTRACT_END
|
|
2769
2785
|
|
|
2770
|
-
function updateFrameSubscription(ws, payload = {}) {
|
|
2786
|
+
function updateFrameSubscription(ws, payload = {}) {
|
|
2771
2787
|
const previousDeviceIds = ws.liveDeskDeviceIds instanceof Set
|
|
2772
2788
|
? new Set(ws.liveDeskDeviceIds)
|
|
2773
2789
|
: new Set();
|
|
@@ -2800,43 +2816,43 @@ function updateFrameSubscription(ws, payload = {}) {
|
|
|
2800
2816
|
const targetIds = newlySubscribedIds.length > 0 ? newlySubscribedIds : deviceIds;
|
|
2801
2817
|
for (const deviceId of targetIds.slice(0, 80)) {
|
|
2802
2818
|
startFrameSubscriptionLive(ws, 'subscribe', deviceId, { reuseExisting: true });
|
|
2803
|
-
}
|
|
2804
|
-
}
|
|
2805
|
-
|
|
2806
|
-
function refreshFrameSubscriptionLive(ws, payload = {}) {
|
|
2807
|
-
if (!ws || ws.readyState !== 1) {
|
|
2808
|
-
return;
|
|
2809
|
-
}
|
|
2810
|
-
const requestedIds = normalizeDeviceIds(payload.deviceIds ?? payload.devices ?? payload.deviceId);
|
|
2811
|
-
const subscribedIds = [...(ws.liveDeskDeviceIds || [])];
|
|
2812
|
-
const targetIds = requestedIds.length > 0
|
|
2813
|
-
? requestedIds.filter(deviceId => ws.liveDeskDeviceIds?.has?.(deviceId))
|
|
2814
|
-
: subscribedIds;
|
|
2815
|
-
if (targetIds.length === 0) {
|
|
2816
|
-
return;
|
|
2817
|
-
}
|
|
2818
|
-
ws.liveDeskAutoStart = /^(1|true|yes|on|live)$/i.test(String(
|
|
2819
|
-
payload.autoStartLive ?? payload.startLive ?? ws.liveDeskAutoStart ?? ''
|
|
2820
|
-
));
|
|
2821
|
-
ws.liveDeskLiveOptions = normalizeLiveOptions({
|
|
2822
|
-
...(ws.liveDeskLiveOptions || {}),
|
|
2823
|
-
...payload,
|
|
2824
|
-
forceRestart: false,
|
|
2825
|
-
reuseExisting: true,
|
|
2826
|
-
monitorSelections: {
|
|
2827
|
-
...(ws.liveDeskLiveOptions?.monitorSelections || {}),
|
|
2828
|
-
...(payload.monitorSelections || {})
|
|
2829
|
-
}
|
|
2830
|
-
});
|
|
2831
|
-
for (const deviceId of targetIds.slice(0, 80)) {
|
|
2832
|
-
startFrameSubscriptionLive(ws, 'watchdog', deviceId, {
|
|
2833
|
-
...ws.liveDeskLiveOptions,
|
|
2834
|
-
reuseExisting: true
|
|
2835
|
-
});
|
|
2836
|
-
}
|
|
2837
|
-
}
|
|
2838
|
-
|
|
2839
|
-
function startFrameSubscriptionLive(
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2822
|
+
function refreshFrameSubscriptionLive(ws, payload = {}) {
|
|
2823
|
+
if (!ws || ws.readyState !== 1) {
|
|
2824
|
+
return;
|
|
2825
|
+
}
|
|
2826
|
+
const requestedIds = normalizeDeviceIds(payload.deviceIds ?? payload.devices ?? payload.deviceId);
|
|
2827
|
+
const subscribedIds = [...(ws.liveDeskDeviceIds || [])];
|
|
2828
|
+
const targetIds = requestedIds.length > 0
|
|
2829
|
+
? requestedIds.filter(deviceId => ws.liveDeskDeviceIds?.has?.(deviceId))
|
|
2830
|
+
: subscribedIds;
|
|
2831
|
+
if (targetIds.length === 0) {
|
|
2832
|
+
return;
|
|
2833
|
+
}
|
|
2834
|
+
ws.liveDeskAutoStart = /^(1|true|yes|on|live)$/i.test(String(
|
|
2835
|
+
payload.autoStartLive ?? payload.startLive ?? ws.liveDeskAutoStart ?? ''
|
|
2836
|
+
));
|
|
2837
|
+
ws.liveDeskLiveOptions = normalizeLiveOptions({
|
|
2838
|
+
...(ws.liveDeskLiveOptions || {}),
|
|
2839
|
+
...payload,
|
|
2840
|
+
forceRestart: false,
|
|
2841
|
+
reuseExisting: true,
|
|
2842
|
+
monitorSelections: {
|
|
2843
|
+
...(ws.liveDeskLiveOptions?.monitorSelections || {}),
|
|
2844
|
+
...(payload.monitorSelections || {})
|
|
2845
|
+
}
|
|
2846
|
+
});
|
|
2847
|
+
for (const deviceId of targetIds.slice(0, 80)) {
|
|
2848
|
+
startFrameSubscriptionLive(ws, 'watchdog', deviceId, {
|
|
2849
|
+
...ws.liveDeskLiveOptions,
|
|
2850
|
+
reuseExisting: true
|
|
2851
|
+
});
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
function startFrameSubscriptionLive(
|
|
2840
2856
|
ws,
|
|
2841
2857
|
reason = 'subscribe',
|
|
2842
2858
|
onlyDeviceId = '',
|
|
@@ -4218,9 +4234,10 @@ app.get('/api/remote/status', (_req, res) => {
|
|
|
4218
4234
|
runtimeRole,
|
|
4219
4235
|
deviceId: runtimeDeviceId,
|
|
4220
4236
|
deviceName: runtimeDeviceName,
|
|
4221
|
-
roleSource: runtimeRoleSource,
|
|
4222
|
-
agentPackage: '@livedesk/client',
|
|
4223
|
-
|
|
4237
|
+
roleSource: runtimeRoleSource,
|
|
4238
|
+
agentPackage: '@livedesk/client',
|
|
4239
|
+
consoleRelay: hubConsoleRelay?.inspect() || { enabled: false, state: 'starting' },
|
|
4240
|
+
frameLanes: snapshotFrameLaneResourceHealth(),
|
|
4224
4241
|
update: getLiveDeskUpdateStatus()
|
|
4225
4242
|
});
|
|
4226
4243
|
});
|
|
@@ -4408,19 +4425,19 @@ app.post('/api/auth/session', async (req, res) => {
|
|
|
4408
4425
|
res.status(400).json({ ok: false, error: normalized.error });
|
|
4409
4426
|
return;
|
|
4410
4427
|
}
|
|
4411
|
-
const { access_token: accessToken, refresh_token: refreshToken, expires_at: expiresAt } = normalized.session;
|
|
4412
|
-
try {
|
|
4413
|
-
const user = await verifySupabaseUser(accessToken);
|
|
4414
|
-
const boundUserId = currentBoundHubUserId();
|
|
4415
|
-
if (boundUserId && boundUserId !== user.id) {
|
|
4416
|
-
res.status(403).json({ ok: false, error: 'hub-account-mismatch' });
|
|
4417
|
-
return;
|
|
4418
|
-
}
|
|
4419
|
-
if (!boundUserId && !isLoopbackAddress(req.socket?.remoteAddress)) {
|
|
4420
|
-
res.status(403).json({ ok: false, error: 'hub-first-account-binding-must-be-local' });
|
|
4421
|
-
return;
|
|
4422
|
-
}
|
|
4423
|
-
runtimeAccessToken = accessToken;
|
|
4428
|
+
const { access_token: accessToken, refresh_token: refreshToken, expires_at: expiresAt } = normalized.session;
|
|
4429
|
+
try {
|
|
4430
|
+
const user = await verifySupabaseUser(accessToken);
|
|
4431
|
+
const boundUserId = currentBoundHubUserId();
|
|
4432
|
+
if (boundUserId && boundUserId !== user.id) {
|
|
4433
|
+
res.status(403).json({ ok: false, error: 'hub-account-mismatch' });
|
|
4434
|
+
return;
|
|
4435
|
+
}
|
|
4436
|
+
if (!boundUserId && !isLoopbackAddress(req.socket?.remoteAddress)) {
|
|
4437
|
+
res.status(403).json({ ok: false, error: 'hub-first-account-binding-must-be-local' });
|
|
4438
|
+
return;
|
|
4439
|
+
}
|
|
4440
|
+
runtimeAccessToken = accessToken;
|
|
4424
4441
|
runtimeRefreshToken = refreshToken;
|
|
4425
4442
|
runtimeAccessTokenExpiresAt = normalizeSessionExpiryMs(expiresAt);
|
|
4426
4443
|
runtimeManager.setAuthenticated(true, user.id);
|
|
@@ -4430,30 +4447,31 @@ app.post('/api/auth/session', async (req, res) => {
|
|
|
4430
4447
|
expires_at: expiresAt,
|
|
4431
4448
|
user
|
|
4432
4449
|
});
|
|
4433
|
-
const hostTarget = runtimeRole === 'hub'
|
|
4434
|
-
? {
|
|
4435
|
-
ok: true,
|
|
4436
|
-
pending: true,
|
|
4437
|
-
active: remoteHub.getStatus({ includeSecrets: false }).hostTargetActive === true
|
|
4438
|
-
}
|
|
4439
|
-
: { ok: true, pending: false, active: false };
|
|
4440
|
-
const uiSession = hubUiSessionAuthority.issue(user.id);
|
|
4441
|
-
res.setHeader('Set-Cookie', serializeHubUiSessionCookie(uiSession.sessionId, uiSession.expiresAt, {
|
|
4442
|
-
secure: isSecureHubHttpRequest(req)
|
|
4443
|
-
}));
|
|
4444
|
-
res.json({
|
|
4445
|
-
ok: true,
|
|
4446
|
-
authenticated: true,
|
|
4447
|
-
persisted,
|
|
4448
|
-
userId: user.id,
|
|
4449
|
-
role: runtimeRole,
|
|
4450
|
-
hostTarget,
|
|
4451
|
-
uiSession: {
|
|
4452
|
-
csrfToken: uiSession.csrfToken,
|
|
4453
|
-
expiresAt: uiSession.expiresAt
|
|
4454
|
-
}
|
|
4450
|
+
const hostTarget = runtimeRole === 'hub'
|
|
4451
|
+
? {
|
|
4452
|
+
ok: true,
|
|
4453
|
+
pending: true,
|
|
4454
|
+
active: remoteHub.getStatus({ includeSecrets: false }).hostTargetActive === true
|
|
4455
|
+
}
|
|
4456
|
+
: { ok: true, pending: false, active: false };
|
|
4457
|
+
const uiSession = hubUiSessionAuthority.issue(user.id);
|
|
4458
|
+
res.setHeader('Set-Cookie', serializeHubUiSessionCookie(uiSession.sessionId, uiSession.expiresAt, {
|
|
4459
|
+
secure: isSecureHubHttpRequest(req)
|
|
4460
|
+
}));
|
|
4461
|
+
res.json({
|
|
4462
|
+
ok: true,
|
|
4463
|
+
authenticated: true,
|
|
4464
|
+
persisted,
|
|
4465
|
+
userId: user.id,
|
|
4466
|
+
role: runtimeRole,
|
|
4467
|
+
hostTarget,
|
|
4468
|
+
uiSession: {
|
|
4469
|
+
csrfToken: uiSession.csrfToken,
|
|
4470
|
+
expiresAt: uiSession.expiresAt
|
|
4471
|
+
}
|
|
4455
4472
|
});
|
|
4456
4473
|
if (runtimeRole === 'hub') {
|
|
4474
|
+
hubConsoleRelay?.refresh();
|
|
4457
4475
|
scheduleAuthenticatedHubHostTargetPublication('session-received');
|
|
4458
4476
|
}
|
|
4459
4477
|
} catch (error) {
|
|
@@ -4475,10 +4493,11 @@ function getHubHostTargetLeaseStatus() {
|
|
|
4475
4493
|
...hubHostTargetLeaseState,
|
|
4476
4494
|
renewing: hubHostTargetRenewInFlight,
|
|
4477
4495
|
renewalIntervalMs: HUB_HOST_TARGET_RENEW_MS,
|
|
4478
|
-
leaseDurationMs: HUB_HOST_TARGET_LEASE_MS,
|
|
4479
|
-
authenticated: Boolean(runtimeAccessToken),
|
|
4480
|
-
timerActive: Boolean(hubHostTargetRenewTimer),
|
|
4481
|
-
wake: { ...hubWakeNotificationState }
|
|
4496
|
+
leaseDurationMs: HUB_HOST_TARGET_LEASE_MS,
|
|
4497
|
+
authenticated: Boolean(runtimeAccessToken),
|
|
4498
|
+
timerActive: Boolean(hubHostTargetRenewTimer),
|
|
4499
|
+
wake: { ...hubWakeNotificationState },
|
|
4500
|
+
consoleRelay: hubConsoleRelay?.inspect() || { enabled: false, state: 'starting' }
|
|
4482
4501
|
};
|
|
4483
4502
|
}
|
|
4484
4503
|
|
|
@@ -4741,36 +4760,36 @@ async function clearHubHostTarget(reason = 'shutdown') {
|
|
|
4741
4760
|
return { ok: !error, error, lease: getHubHostTargetLeaseStatus() };
|
|
4742
4761
|
}
|
|
4743
4762
|
|
|
4744
|
-
function startHubHostTargetLeaseRenewal() {
|
|
4763
|
+
function startHubHostTargetLeaseRenewal() {
|
|
4745
4764
|
if (hubHostTargetRenewTimer || runtimeRole !== 'hub') {
|
|
4746
4765
|
return;
|
|
4747
4766
|
}
|
|
4748
4767
|
hubHostTargetRenewTimer = setInterval(() => {
|
|
4749
4768
|
void publishHubHostTargetWithPendingRoleTakeover('renewal');
|
|
4750
4769
|
}, HUB_HOST_TARGET_RENEW_MS);
|
|
4751
|
-
hubHostTargetRenewTimer.unref?.();
|
|
4752
|
-
}
|
|
4753
|
-
|
|
4754
|
-
function scheduleAuthenticatedHubHostTargetPublication(reason = 'session-received') {
|
|
4755
|
-
startHubHostTargetLeaseRenewal();
|
|
4756
|
-
setImmediate(() => {
|
|
4757
|
-
void publishHubHostTargetWithPendingRoleTakeover(reason).catch(error => {
|
|
4758
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
4759
|
-
updateHubHostTargetLeaseState({ state: 'error', lastError: message });
|
|
4760
|
-
console.error(`[LiveDesk Hub] Host target ${reason} background publish failed: ${message}`);
|
|
4761
|
-
});
|
|
4762
|
-
});
|
|
4763
|
-
}
|
|
4770
|
+
hubHostTargetRenewTimer.unref?.();
|
|
4771
|
+
}
|
|
4764
4772
|
|
|
4765
|
-
|
|
4773
|
+
function scheduleAuthenticatedHubHostTargetPublication(reason = 'session-received') {
|
|
4774
|
+
startHubHostTargetLeaseRenewal();
|
|
4775
|
+
setImmediate(() => {
|
|
4776
|
+
void publishHubHostTargetWithPendingRoleTakeover(reason).catch(error => {
|
|
4777
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4778
|
+
updateHubHostTargetLeaseState({ state: 'error', lastError: message });
|
|
4779
|
+
console.error(`[LiveDesk Hub] Host target ${reason} background publish failed: ${message}`);
|
|
4780
|
+
});
|
|
4781
|
+
});
|
|
4782
|
+
}
|
|
4783
|
+
|
|
4784
|
+
app.delete('/api/auth/session', async (req, res) => {
|
|
4766
4785
|
noStore(res);
|
|
4767
4786
|
const hostTarget = runtimeRole === 'hub'
|
|
4768
4787
|
? await clearHubHostTarget('logout')
|
|
4769
4788
|
: { ok: true, active: false };
|
|
4770
|
-
clearRuntimeSession();
|
|
4771
|
-
res.setHeader('Set-Cookie', clearHubUiSessionCookie({ secure: isSecureHubHttpRequest(req) }));
|
|
4772
|
-
res.json({ ok: true, authenticated: false, role: runtimeRole, hostTarget });
|
|
4773
|
-
});
|
|
4789
|
+
clearRuntimeSession();
|
|
4790
|
+
res.setHeader('Set-Cookie', clearHubUiSessionCookie({ secure: isSecureHubHttpRequest(req) }));
|
|
4791
|
+
res.json({ ok: true, authenticated: false, role: runtimeRole, hostTarget });
|
|
4792
|
+
});
|
|
4774
4793
|
|
|
4775
4794
|
app.get('/api/hub/status', (_req, res) => {
|
|
4776
4795
|
noStore(res);
|
|
@@ -4782,10 +4801,11 @@ app.get('/api/hub/status', (_req, res) => {
|
|
|
4782
4801
|
...remoteHub.getStatus({ includeSecrets: false }),
|
|
4783
4802
|
role: 'hub',
|
|
4784
4803
|
deviceId: runtimeDeviceId,
|
|
4785
|
-
deviceName: runtimeDeviceName,
|
|
4786
|
-
roleSource: runtimeRoleSource,
|
|
4787
|
-
runtimeStarted: true,
|
|
4788
|
-
|
|
4804
|
+
deviceName: runtimeDeviceName,
|
|
4805
|
+
roleSource: runtimeRoleSource,
|
|
4806
|
+
runtimeStarted: true,
|
|
4807
|
+
consoleRelay: hubConsoleRelay?.inspect() || { enabled: false, state: 'starting' },
|
|
4808
|
+
hostTargetLease: getHubHostTargetLeaseStatus(),
|
|
4789
4809
|
update: getLiveDeskUpdateStatus()
|
|
4790
4810
|
});
|
|
4791
4811
|
});
|
|
@@ -5571,20 +5591,20 @@ if (existsSync(webIndexPath)) {
|
|
|
5571
5591
|
});
|
|
5572
5592
|
}
|
|
5573
5593
|
|
|
5574
|
-
httpServer.on('upgrade', (req, socket, head) => {
|
|
5575
|
-
if (!isTrustedBrowserRequest(req)) {
|
|
5576
|
-
socket.write('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n');
|
|
5577
|
-
socket.destroy();
|
|
5578
|
-
return;
|
|
5579
|
-
}
|
|
5580
|
-
const authorization = authorizeHubUiRequest(req);
|
|
5581
|
-
if (!authorization.ok) {
|
|
5582
|
-
const label = authorization.status === 403 ? 'Forbidden' : 'Unauthorized';
|
|
5583
|
-
socket.write(`HTTP/1.1 ${authorization.status} ${label}\r\nConnection: close\r\n\r\n`);
|
|
5584
|
-
socket.destroy();
|
|
5585
|
-
return;
|
|
5586
|
-
}
|
|
5587
|
-
try {
|
|
5594
|
+
httpServer.on('upgrade', (req, socket, head) => {
|
|
5595
|
+
if (!isTrustedBrowserRequest(req)) {
|
|
5596
|
+
socket.write('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n');
|
|
5597
|
+
socket.destroy();
|
|
5598
|
+
return;
|
|
5599
|
+
}
|
|
5600
|
+
const authorization = authorizeHubUiRequest(req);
|
|
5601
|
+
if (!authorization.ok) {
|
|
5602
|
+
const label = authorization.status === 403 ? 'Forbidden' : 'Unauthorized';
|
|
5603
|
+
socket.write(`HTTP/1.1 ${authorization.status} ${label}\r\nConnection: close\r\n\r\n`);
|
|
5604
|
+
socket.destroy();
|
|
5605
|
+
return;
|
|
5606
|
+
}
|
|
5607
|
+
try {
|
|
5588
5608
|
const parsed = new URL(req.url || '/', `http://${req.headers.host || '127.0.0.1'}`);
|
|
5589
5609
|
if (parsed.pathname === '/api/remote/frames/ws') {
|
|
5590
5610
|
frameWss.handleUpgrade(req, socket, head, ws => frameWss.emit('connection', ws, req));
|
|
@@ -5631,11 +5651,11 @@ frameWss.on('connection', (ws, req) => {
|
|
|
5631
5651
|
ws.on('message', data => {
|
|
5632
5652
|
try {
|
|
5633
5653
|
const payload = JSON.parse(Buffer.isBuffer(data) ? data.toString('utf8') : String(data || ''));
|
|
5634
|
-
if (payload?.type === 'subscribe') {
|
|
5635
|
-
updateFrameSubscription(ws, payload);
|
|
5636
|
-
} else if (payload?.type === 'refresh-live') {
|
|
5637
|
-
refreshFrameSubscriptionLive(ws, payload);
|
|
5638
|
-
} else if (payload?.type === 'restart-live') {
|
|
5654
|
+
if (payload?.type === 'subscribe') {
|
|
5655
|
+
updateFrameSubscription(ws, payload);
|
|
5656
|
+
} else if (payload?.type === 'refresh-live') {
|
|
5657
|
+
refreshFrameSubscriptionLive(ws, payload);
|
|
5658
|
+
} else if (payload?.type === 'restart-live') {
|
|
5639
5659
|
restartFrameSubscriptionLive(ws, payload);
|
|
5640
5660
|
}
|
|
5641
5661
|
} catch {
|
|
@@ -5792,13 +5812,14 @@ hubSharedFolders.startAutoSync(
|
|
|
5792
5812
|
() => remoteHub.listDevices({ includeDataUrl: false }).filter(device => device.connected).map(device => device.deviceId),
|
|
5793
5813
|
() => process.env.LIVEDESK_REMOTE_DIRECTORY || 'Desktop/LiveDeskFiles'
|
|
5794
5814
|
);
|
|
5795
|
-
httpServer.listen(httpPort, httpHost, () => {
|
|
5815
|
+
httpServer.listen(httpPort, httpHost, () => {
|
|
5796
5816
|
const status = remoteHub.getStatus({ includeSecrets: true });
|
|
5797
5817
|
const managerVersion = String(process.env.LIVEDESK_MANAGER_VERSION || packageInfo.version || 'dev');
|
|
5798
5818
|
console.log(`[LiveDesk Hub] Version ${managerVersion}`);
|
|
5799
|
-
console.log(`[LiveDesk Hub] HTTP API http://${httpHost}:${httpPort}`);
|
|
5800
|
-
console.log(`[LiveDesk Hub] Client endpoint ${status.agentEndpoint} pair=${status.pairTokenPreview}`);
|
|
5801
|
-
|
|
5819
|
+
console.log(`[LiveDesk Hub] HTTP API http://${httpHost}:${httpPort}`);
|
|
5820
|
+
console.log(`[LiveDesk Hub] Client endpoint ${status.agentEndpoint} pair=${status.pairTokenPreview}`);
|
|
5821
|
+
hubConsoleRelay?.start();
|
|
5822
|
+
});
|
|
5802
5823
|
|
|
5803
5824
|
const roleWatchTimer = runtimeRole === 'hub'
|
|
5804
5825
|
? setInterval(() => { void watchAuthoritativeRuntimeRole(); }, 5000)
|
|
@@ -5879,9 +5900,10 @@ function shutdownHub(signal) {
|
|
|
5879
5900
|
const startedAt = Date.now();
|
|
5880
5901
|
console.log(`[LiveDesk Hub] Shutdown started signal=${signal} grace=${hubShutdownGraceMs}ms timeout=${hubShutdownTimeoutMs}ms.`);
|
|
5881
5902
|
if (roleWatchTimer) clearInterval(roleWatchTimer);
|
|
5882
|
-
clearInterval(browserWebSocketHeartbeatTimer);
|
|
5883
|
-
runSynchronousShutdownStep('update manager close', () => liveDeskUpdateManager?.close());
|
|
5884
|
-
|
|
5903
|
+
clearInterval(browserWebSocketHeartbeatTimer);
|
|
5904
|
+
runSynchronousShutdownStep('update manager close', () => liveDeskUpdateManager?.close());
|
|
5905
|
+
runSynchronousShutdownStep('mobile console relay close', () => hubConsoleRelay?.close());
|
|
5906
|
+
atlasClients.clear();
|
|
5885
5907
|
runSynchronousShutdownStep('shared folder close', () => hubSharedFolders.close());
|
|
5886
5908
|
|
|
5887
5909
|
const httpClosed = new Promise(resolveClose => {
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { effectiveDevicePolicy } from './settings-schema.js';
|
|
2
|
-
|
|
3
|
-
export function buildEffectiveDevicePolicy(settings, { deviceId = '', capabilities = {} } = {}) {
|
|
4
|
-
const policy = effectiveDevicePolicy(settings);
|
|
5
|
-
return {
|
|
6
|
-
...policy,
|
|
7
|
-
deviceId: String(deviceId || ''),
|
|
8
|
-
supported: {
|
|
9
|
-
control: capabilities.control !== false,
|
|
10
|
-
clipboardText: capabilities.clipboardText !== false,
|
|
11
|
-
fileTransfer: capabilities.fileTransfer !== false,
|
|
12
|
-
remoteAudio: capabilities.audio === true || capabilities.remoteAudio === true,
|
|
13
|
-
agent: Array.isArray(capabilities.agentTools) && capabilities.agentTools.length > 0
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
}
|
|
1
|
+
import { effectiveDevicePolicy } from './settings-schema.js';
|
|
2
|
+
|
|
3
|
+
export function buildEffectiveDevicePolicy(settings, { deviceId = '', capabilities = {} } = {}) {
|
|
4
|
+
const policy = effectiveDevicePolicy(settings);
|
|
5
|
+
return {
|
|
6
|
+
...policy,
|
|
7
|
+
deviceId: String(deviceId || ''),
|
|
8
|
+
supported: {
|
|
9
|
+
control: capabilities.control !== false,
|
|
10
|
+
clipboardText: capabilities.clipboardText !== false,
|
|
11
|
+
fileTransfer: capabilities.fileTransfer !== false,
|
|
12
|
+
remoteAudio: capabilities.audio === true || capabilities.remoteAudio === true,
|
|
13
|
+
agent: Array.isArray(capabilities.agentTools) && capabilities.agentTools.length > 0
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|