@livedesk/hub 0.1.41 → 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 +144 -144
- package/src/server.js +201 -200
- 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);
|
|
@@ -4430,32 +4431,32 @@ app.post('/api/auth/session', async (req, res) => {
|
|
|
4430
4431
|
expires_at: expiresAt,
|
|
4431
4432
|
user
|
|
4432
4433
|
});
|
|
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
|
-
}
|
|
4455
|
-
});
|
|
4456
|
-
if (runtimeRole === 'hub') {
|
|
4457
|
-
scheduleAuthenticatedHubHostTargetPublication('session-received');
|
|
4458
|
-
}
|
|
4434
|
+
const hostTarget = runtimeRole === 'hub'
|
|
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
|
+
}
|
|
4459
4460
|
} catch (error) {
|
|
4460
4461
|
const message = error instanceof Error ? error.message : String(error);
|
|
4461
4462
|
const status = authVerificationHttpStatus(message);
|
|
@@ -4741,36 +4742,36 @@ async function clearHubHostTarget(reason = 'shutdown') {
|
|
|
4741
4742
|
return { ok: !error, error, lease: getHubHostTargetLeaseStatus() };
|
|
4742
4743
|
}
|
|
4743
4744
|
|
|
4744
|
-
function startHubHostTargetLeaseRenewal() {
|
|
4745
|
+
function startHubHostTargetLeaseRenewal() {
|
|
4745
4746
|
if (hubHostTargetRenewTimer || runtimeRole !== 'hub') {
|
|
4746
4747
|
return;
|
|
4747
4748
|
}
|
|
4748
4749
|
hubHostTargetRenewTimer = setInterval(() => {
|
|
4749
4750
|
void publishHubHostTargetWithPendingRoleTakeover('renewal');
|
|
4750
4751
|
}, 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
|
-
}
|
|
4764
|
-
|
|
4765
|
-
app.delete('/api/auth/session', async (req, res) => {
|
|
4752
|
+
hubHostTargetRenewTimer.unref?.();
|
|
4753
|
+
}
|
|
4754
|
+
|
|
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) => {
|
|
4766
4767
|
noStore(res);
|
|
4767
4768
|
const hostTarget = runtimeRole === 'hub'
|
|
4768
4769
|
? await clearHubHostTarget('logout')
|
|
4769
4770
|
: { 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
|
-
});
|
|
4771
|
+
clearRuntimeSession();
|
|
4772
|
+
res.setHeader('Set-Cookie', clearHubUiSessionCookie({ secure: isSecureHubHttpRequest(req) }));
|
|
4773
|
+
res.json({ ok: true, authenticated: false, role: runtimeRole, hostTarget });
|
|
4774
|
+
});
|
|
4774
4775
|
|
|
4775
4776
|
app.get('/api/hub/status', (_req, res) => {
|
|
4776
4777
|
noStore(res);
|
|
@@ -5571,20 +5572,20 @@ if (existsSync(webIndexPath)) {
|
|
|
5571
5572
|
});
|
|
5572
5573
|
}
|
|
5573
5574
|
|
|
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 {
|
|
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 {
|
|
5588
5589
|
const parsed = new URL(req.url || '/', `http://${req.headers.host || '127.0.0.1'}`);
|
|
5589
5590
|
if (parsed.pathname === '/api/remote/frames/ws') {
|
|
5590
5591
|
frameWss.handleUpgrade(req, socket, head, ws => frameWss.emit('connection', ws, req));
|
|
@@ -5631,11 +5632,11 @@ frameWss.on('connection', (ws, req) => {
|
|
|
5631
5632
|
ws.on('message', data => {
|
|
5632
5633
|
try {
|
|
5633
5634
|
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') {
|
|
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') {
|
|
5639
5640
|
restartFrameSubscriptionLive(ws, payload);
|
|
5640
5641
|
}
|
|
5641
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
|
+
}
|