@livedesk/hub 0.1.40 → 0.1.42
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 +2 -2
- 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/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 +140 -111
- package/src/server.js +196 -180
- package/src/settings/effective-device-policy.js +16 -16
- package/src/transport/udp-protocol.js +453 -453
package/src/server.js
CHANGED
|
@@ -51,13 +51,13 @@ import { normalizeRuntimeAuthSession, runtimeRoleError } from '../../runtime-cor
|
|
|
51
51
|
import { fetchAuthResponse, AUTH_REQUEST_TIMEOUT_MS } from '../../runtime-core/src/auth-http.js';
|
|
52
52
|
import { PRODUCTION_SUPABASE_PUBLISHABLE_KEY, PRODUCTION_SUPABASE_URL } from '../../runtime-core/src/auth-config.js';
|
|
53
53
|
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';
|
|
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';
|
|
61
61
|
|
|
62
62
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
63
63
|
const webDistCandidates = [
|
|
@@ -187,10 +187,10 @@ const persistentSessionGcToken = persistentSessionGcEnabled
|
|
|
187
187
|
? String(process.env.LIVEDESK_PERSISTENT_SESSION_TEST_TOKEN || '').trim()
|
|
188
188
|
: '';
|
|
189
189
|
let connectedDeviceCount = 0;
|
|
190
|
-
let runtimeAccessToken = '';
|
|
191
|
-
let runtimeRefreshToken = '';
|
|
192
|
-
let runtimeAccessTokenExpiresAt = 0;
|
|
193
|
-
const hubUiSessionAuthority = createHubUiSessionAuthority();
|
|
190
|
+
let runtimeAccessToken = '';
|
|
191
|
+
let runtimeRefreshToken = '';
|
|
192
|
+
let runtimeAccessTokenExpiresAt = 0;
|
|
193
|
+
const hubUiSessionAuthority = createHubUiSessionAuthority();
|
|
194
194
|
let roleWatchInFlight = false;
|
|
195
195
|
let verifiedLicense = {
|
|
196
196
|
userId: '',
|
|
@@ -491,12 +491,12 @@ function clearPersistedRuntimeSession() {
|
|
|
491
491
|
return writePrivateRuntimeAuthState(state);
|
|
492
492
|
}
|
|
493
493
|
|
|
494
|
-
function clearRuntimeSession() {
|
|
495
|
-
runtimeAccessToken = '';
|
|
496
|
-
runtimeRefreshToken = '';
|
|
497
|
-
runtimeAccessTokenExpiresAt = 0;
|
|
498
|
-
hubUiSessionAuthority.revokeAll();
|
|
499
|
-
runtimeManager.setAuthenticated(false);
|
|
494
|
+
function clearRuntimeSession() {
|
|
495
|
+
runtimeAccessToken = '';
|
|
496
|
+
runtimeRefreshToken = '';
|
|
497
|
+
runtimeAccessTokenExpiresAt = 0;
|
|
498
|
+
hubUiSessionAuthority.revokeAll();
|
|
499
|
+
runtimeManager.setAuthenticated(false);
|
|
500
500
|
try { clearPersistedRuntimeSession(); } catch { /* runtime auth is already invalid */ }
|
|
501
501
|
}
|
|
502
502
|
|
|
@@ -796,11 +796,12 @@ function getLiveDeskUpdateStatus() {
|
|
|
796
796
|
}
|
|
797
797
|
|
|
798
798
|
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
|
-
|
|
799
|
+
liveDeskUpdateManager = createLiveDeskUpdateManager({
|
|
800
|
+
remoteHub,
|
|
801
|
+
currentManagerVersion: process.env.LIVEDESK_MANAGER_VERSION || packageInfo.version,
|
|
802
|
+
currentClientVersion: process.env.LIVEDESK_CLIENT_PACKAGE_VERSION || '',
|
|
803
|
+
excludedDeviceIds: [runtimeDeviceId],
|
|
804
|
+
restartSupported: !!String(process.env.LIVEDESK_HUB_UPDATE_REQUEST_PATH || '').trim(),
|
|
804
805
|
requestHubRestart
|
|
805
806
|
});
|
|
806
807
|
}
|
|
@@ -1412,7 +1413,7 @@ function requestHostname(req) {
|
|
|
1412
1413
|
}
|
|
1413
1414
|
}
|
|
1414
1415
|
|
|
1415
|
-
function isTrustedBrowserRequest(req) {
|
|
1416
|
+
function isTrustedBrowserRequest(req) {
|
|
1416
1417
|
const origin = String(req.headers.origin || '').trim();
|
|
1417
1418
|
if (!origin) {
|
|
1418
1419
|
const fetchSite = String(req.headers['sec-fetch-site'] || '').toLowerCase();
|
|
@@ -1429,59 +1430,59 @@ function isTrustedBrowserRequest(req) {
|
|
|
1429
1430
|
const targetHost = requestHostname(req);
|
|
1430
1431
|
return (originHost === targetHost && isPrivateHubHostname(targetHost))
|
|
1431
1432
|
|| (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) => {
|
|
1433
|
+
} catch {
|
|
1434
|
+
return false;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
function isSecureHubHttpRequest(req) {
|
|
1439
|
+
return req.socket?.encrypted === true
|
|
1440
|
+
|| String(req.headers['x-forwarded-proto'] || '').trim().toLowerCase() === 'https';
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
function currentBoundHubUserId() {
|
|
1444
|
+
return String(
|
|
1445
|
+
runtimeManager.getSnapshot().userId
|
|
1446
|
+
|| readPersistedRuntimeSession()?.user?.id
|
|
1447
|
+
|| ''
|
|
1448
|
+
).trim();
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
function isPublicHubApiRequest(req) {
|
|
1452
|
+
const method = String(req.method || 'GET').toUpperCase();
|
|
1453
|
+
const path = String(req.path || '');
|
|
1454
|
+
return (method === 'GET' && (path === '/api/health' || path === '/api/runtime/status' || path === '/api/auth/status'))
|
|
1455
|
+
|| (method === 'POST' && path === '/api/auth/session');
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
function isTrustedNativeLoopbackRequest(req) {
|
|
1459
|
+
return isLoopbackAddress(req.socket?.remoteAddress)
|
|
1460
|
+
&& !String(req.headers.origin || '').trim()
|
|
1461
|
+
&& !String(req.headers['sec-fetch-site'] || '').trim();
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
function authorizeHubUiRequest(req) {
|
|
1465
|
+
if (process.env.LIVEDESK_TEST_MODE === '1') {
|
|
1466
|
+
return { ok: true, explicitTestMode: true };
|
|
1467
|
+
}
|
|
1468
|
+
if (isTrustedNativeLoopbackRequest(req)) {
|
|
1469
|
+
return { ok: true, nativeLoopback: true };
|
|
1470
|
+
}
|
|
1471
|
+
return hubUiSessionAuthority.authorize(req, currentBoundHubUserId());
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
app.use((req, res, next) => {
|
|
1474
1475
|
if (!isTrustedBrowserRequest(req)) {
|
|
1475
1476
|
res.status(403).json({ ok: false, error: 'untrusted-hub-origin' });
|
|
1476
1477
|
return;
|
|
1477
1478
|
}
|
|
1478
1479
|
const origin = String(req.headers.origin || '').trim();
|
|
1479
1480
|
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');
|
|
1481
|
+
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
1482
|
+
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
1483
|
+
res.setHeader('Access-Control-Allow-Private-Network', 'true');
|
|
1483
1484
|
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');
|
|
1485
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-LiveDesk-CSRF');
|
|
1485
1486
|
res.setHeader('Vary', 'Origin, Access-Control-Request-Headers, Access-Control-Request-Private-Network');
|
|
1486
1487
|
}
|
|
1487
1488
|
if (req.method === 'OPTIONS') {
|
|
@@ -1490,21 +1491,21 @@ app.use((req, res, next) => {
|
|
|
1490
1491
|
}
|
|
1491
1492
|
next();
|
|
1492
1493
|
});
|
|
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) => {
|
|
1494
|
+
app.use(express.json({ limit: '32mb' }));
|
|
1495
|
+
app.use((req, res, next) => {
|
|
1496
|
+
if (!req.path.startsWith('/api/') || isPublicHubApiRequest(req)) {
|
|
1497
|
+
next();
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
const authorization = authorizeHubUiRequest(req);
|
|
1501
|
+
if (!authorization.ok) {
|
|
1502
|
+
noStore(res);
|
|
1503
|
+
res.status(authorization.status).json({ ok: false, error: authorization.error });
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
1506
|
+
next();
|
|
1507
|
+
});
|
|
1508
|
+
app.use((req, res, next) => {
|
|
1508
1509
|
if (runtimeRole === 'client' && req.path.startsWith('/api/remote')) {
|
|
1509
1510
|
res.status(403).json(roleGuardResponse(runtimeRole, 'hub'));
|
|
1510
1511
|
return;
|
|
@@ -2767,7 +2768,7 @@ function enqueueFramePacketForClient(ws, packetOwner, meta) {
|
|
|
2767
2768
|
}
|
|
2768
2769
|
// FRAME_LANE_GENERATION_CONTRACT_END
|
|
2769
2770
|
|
|
2770
|
-
function updateFrameSubscription(ws, payload = {}) {
|
|
2771
|
+
function updateFrameSubscription(ws, payload = {}) {
|
|
2771
2772
|
const previousDeviceIds = ws.liveDeskDeviceIds instanceof Set
|
|
2772
2773
|
? new Set(ws.liveDeskDeviceIds)
|
|
2773
2774
|
: new Set();
|
|
@@ -2800,43 +2801,43 @@ function updateFrameSubscription(ws, payload = {}) {
|
|
|
2800
2801
|
const targetIds = newlySubscribedIds.length > 0 ? newlySubscribedIds : deviceIds;
|
|
2801
2802
|
for (const deviceId of targetIds.slice(0, 80)) {
|
|
2802
2803
|
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(
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
function refreshFrameSubscriptionLive(ws, payload = {}) {
|
|
2808
|
+
if (!ws || ws.readyState !== 1) {
|
|
2809
|
+
return;
|
|
2810
|
+
}
|
|
2811
|
+
const requestedIds = normalizeDeviceIds(payload.deviceIds ?? payload.devices ?? payload.deviceId);
|
|
2812
|
+
const subscribedIds = [...(ws.liveDeskDeviceIds || [])];
|
|
2813
|
+
const targetIds = requestedIds.length > 0
|
|
2814
|
+
? requestedIds.filter(deviceId => ws.liveDeskDeviceIds?.has?.(deviceId))
|
|
2815
|
+
: subscribedIds;
|
|
2816
|
+
if (targetIds.length === 0) {
|
|
2817
|
+
return;
|
|
2818
|
+
}
|
|
2819
|
+
ws.liveDeskAutoStart = /^(1|true|yes|on|live)$/i.test(String(
|
|
2820
|
+
payload.autoStartLive ?? payload.startLive ?? ws.liveDeskAutoStart ?? ''
|
|
2821
|
+
));
|
|
2822
|
+
ws.liveDeskLiveOptions = normalizeLiveOptions({
|
|
2823
|
+
...(ws.liveDeskLiveOptions || {}),
|
|
2824
|
+
...payload,
|
|
2825
|
+
forceRestart: false,
|
|
2826
|
+
reuseExisting: true,
|
|
2827
|
+
monitorSelections: {
|
|
2828
|
+
...(ws.liveDeskLiveOptions?.monitorSelections || {}),
|
|
2829
|
+
...(payload.monitorSelections || {})
|
|
2830
|
+
}
|
|
2831
|
+
});
|
|
2832
|
+
for (const deviceId of targetIds.slice(0, 80)) {
|
|
2833
|
+
startFrameSubscriptionLive(ws, 'watchdog', deviceId, {
|
|
2834
|
+
...ws.liveDeskLiveOptions,
|
|
2835
|
+
reuseExisting: true
|
|
2836
|
+
});
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2840
|
+
function startFrameSubscriptionLive(
|
|
2840
2841
|
ws,
|
|
2841
2842
|
reason = 'subscribe',
|
|
2842
2843
|
onlyDeviceId = '',
|
|
@@ -4408,19 +4409,19 @@ app.post('/api/auth/session', async (req, res) => {
|
|
|
4408
4409
|
res.status(400).json({ ok: false, error: normalized.error });
|
|
4409
4410
|
return;
|
|
4410
4411
|
}
|
|
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;
|
|
4412
|
+
const { access_token: accessToken, refresh_token: refreshToken, expires_at: expiresAt } = normalized.session;
|
|
4413
|
+
try {
|
|
4414
|
+
const user = await verifySupabaseUser(accessToken);
|
|
4415
|
+
const boundUserId = currentBoundHubUserId();
|
|
4416
|
+
if (boundUserId && boundUserId !== user.id) {
|
|
4417
|
+
res.status(403).json({ ok: false, error: 'hub-account-mismatch' });
|
|
4418
|
+
return;
|
|
4419
|
+
}
|
|
4420
|
+
if (!boundUserId && !isLoopbackAddress(req.socket?.remoteAddress)) {
|
|
4421
|
+
res.status(403).json({ ok: false, error: 'hub-first-account-binding-must-be-local' });
|
|
4422
|
+
return;
|
|
4423
|
+
}
|
|
4424
|
+
runtimeAccessToken = accessToken;
|
|
4424
4425
|
runtimeRefreshToken = refreshToken;
|
|
4425
4426
|
runtimeAccessTokenExpiresAt = normalizeSessionExpiryMs(expiresAt);
|
|
4426
4427
|
runtimeManager.setAuthenticated(true, user.id);
|
|
@@ -4431,27 +4432,31 @@ app.post('/api/auth/session', async (req, res) => {
|
|
|
4431
4432
|
user
|
|
4432
4433
|
});
|
|
4433
4434
|
const hostTarget = runtimeRole === 'hub'
|
|
4434
|
-
?
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4435
|
+
? {
|
|
4436
|
+
ok: true,
|
|
4437
|
+
pending: true,
|
|
4438
|
+
active: remoteHub.getStatus({ includeSecrets: false }).hostTargetActive === true
|
|
4439
|
+
}
|
|
4440
|
+
: { ok: true, pending: false, active: false };
|
|
4441
|
+
const uiSession = hubUiSessionAuthority.issue(user.id);
|
|
4442
|
+
res.setHeader('Set-Cookie', serializeHubUiSessionCookie(uiSession.sessionId, uiSession.expiresAt, {
|
|
4443
|
+
secure: isSecureHubHttpRequest(req)
|
|
4444
|
+
}));
|
|
4445
|
+
res.json({
|
|
4446
|
+
ok: true,
|
|
4447
|
+
authenticated: true,
|
|
4448
|
+
persisted,
|
|
4449
|
+
userId: user.id,
|
|
4450
|
+
role: runtimeRole,
|
|
4451
|
+
hostTarget,
|
|
4452
|
+
uiSession: {
|
|
4453
|
+
csrfToken: uiSession.csrfToken,
|
|
4454
|
+
expiresAt: uiSession.expiresAt
|
|
4455
|
+
}
|
|
4456
|
+
});
|
|
4457
|
+
if (runtimeRole === 'hub') {
|
|
4458
|
+
scheduleAuthenticatedHubHostTargetPublication('session-received');
|
|
4459
|
+
}
|
|
4455
4460
|
} catch (error) {
|
|
4456
4461
|
const message = error instanceof Error ? error.message : String(error);
|
|
4457
4462
|
const status = authVerificationHttpStatus(message);
|
|
@@ -4747,15 +4752,26 @@ function startHubHostTargetLeaseRenewal() {
|
|
|
4747
4752
|
hubHostTargetRenewTimer.unref?.();
|
|
4748
4753
|
}
|
|
4749
4754
|
|
|
4750
|
-
|
|
4755
|
+
function scheduleAuthenticatedHubHostTargetPublication(reason = 'session-received') {
|
|
4756
|
+
startHubHostTargetLeaseRenewal();
|
|
4757
|
+
setImmediate(() => {
|
|
4758
|
+
void publishHubHostTargetWithPendingRoleTakeover(reason).catch(error => {
|
|
4759
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4760
|
+
updateHubHostTargetLeaseState({ state: 'error', lastError: message });
|
|
4761
|
+
console.error(`[LiveDesk Hub] Host target ${reason} background publish failed: ${message}`);
|
|
4762
|
+
});
|
|
4763
|
+
});
|
|
4764
|
+
}
|
|
4765
|
+
|
|
4766
|
+
app.delete('/api/auth/session', async (req, res) => {
|
|
4751
4767
|
noStore(res);
|
|
4752
4768
|
const hostTarget = runtimeRole === 'hub'
|
|
4753
4769
|
? await clearHubHostTarget('logout')
|
|
4754
4770
|
: { ok: true, active: false };
|
|
4755
|
-
clearRuntimeSession();
|
|
4756
|
-
res.setHeader('Set-Cookie', clearHubUiSessionCookie({ secure: isSecureHubHttpRequest(req) }));
|
|
4757
|
-
res.json({ ok: true, authenticated: false, role: runtimeRole, hostTarget });
|
|
4758
|
-
});
|
|
4771
|
+
clearRuntimeSession();
|
|
4772
|
+
res.setHeader('Set-Cookie', clearHubUiSessionCookie({ secure: isSecureHubHttpRequest(req) }));
|
|
4773
|
+
res.json({ ok: true, authenticated: false, role: runtimeRole, hostTarget });
|
|
4774
|
+
});
|
|
4759
4775
|
|
|
4760
4776
|
app.get('/api/hub/status', (_req, res) => {
|
|
4761
4777
|
noStore(res);
|
|
@@ -5556,20 +5572,20 @@ if (existsSync(webIndexPath)) {
|
|
|
5556
5572
|
});
|
|
5557
5573
|
}
|
|
5558
5574
|
|
|
5559
|
-
httpServer.on('upgrade', (req, socket, head) => {
|
|
5560
|
-
if (!isTrustedBrowserRequest(req)) {
|
|
5561
|
-
socket.write('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n');
|
|
5562
|
-
socket.destroy();
|
|
5563
|
-
return;
|
|
5564
|
-
}
|
|
5565
|
-
const authorization = authorizeHubUiRequest(req);
|
|
5566
|
-
if (!authorization.ok) {
|
|
5567
|
-
const label = authorization.status === 403 ? 'Forbidden' : 'Unauthorized';
|
|
5568
|
-
socket.write(`HTTP/1.1 ${authorization.status} ${label}\r\nConnection: close\r\n\r\n`);
|
|
5569
|
-
socket.destroy();
|
|
5570
|
-
return;
|
|
5571
|
-
}
|
|
5572
|
-
try {
|
|
5575
|
+
httpServer.on('upgrade', (req, socket, head) => {
|
|
5576
|
+
if (!isTrustedBrowserRequest(req)) {
|
|
5577
|
+
socket.write('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n');
|
|
5578
|
+
socket.destroy();
|
|
5579
|
+
return;
|
|
5580
|
+
}
|
|
5581
|
+
const authorization = authorizeHubUiRequest(req);
|
|
5582
|
+
if (!authorization.ok) {
|
|
5583
|
+
const label = authorization.status === 403 ? 'Forbidden' : 'Unauthorized';
|
|
5584
|
+
socket.write(`HTTP/1.1 ${authorization.status} ${label}\r\nConnection: close\r\n\r\n`);
|
|
5585
|
+
socket.destroy();
|
|
5586
|
+
return;
|
|
5587
|
+
}
|
|
5588
|
+
try {
|
|
5573
5589
|
const parsed = new URL(req.url || '/', `http://${req.headers.host || '127.0.0.1'}`);
|
|
5574
5590
|
if (parsed.pathname === '/api/remote/frames/ws') {
|
|
5575
5591
|
frameWss.handleUpgrade(req, socket, head, ws => frameWss.emit('connection', ws, req));
|
|
@@ -5616,11 +5632,11 @@ frameWss.on('connection', (ws, req) => {
|
|
|
5616
5632
|
ws.on('message', data => {
|
|
5617
5633
|
try {
|
|
5618
5634
|
const payload = JSON.parse(Buffer.isBuffer(data) ? data.toString('utf8') : String(data || ''));
|
|
5619
|
-
if (payload?.type === 'subscribe') {
|
|
5620
|
-
updateFrameSubscription(ws, payload);
|
|
5621
|
-
} else if (payload?.type === 'refresh-live') {
|
|
5622
|
-
refreshFrameSubscriptionLive(ws, payload);
|
|
5623
|
-
} else if (payload?.type === 'restart-live') {
|
|
5635
|
+
if (payload?.type === 'subscribe') {
|
|
5636
|
+
updateFrameSubscription(ws, payload);
|
|
5637
|
+
} else if (payload?.type === 'refresh-live') {
|
|
5638
|
+
refreshFrameSubscriptionLive(ws, payload);
|
|
5639
|
+
} else if (payload?.type === 'restart-live') {
|
|
5624
5640
|
restartFrameSubscriptionLive(ws, payload);
|
|
5625
5641
|
}
|
|
5626
5642
|
} catch {
|
|
@@ -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
|
+
}
|