@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/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
- restartSupported: !!String(process.env.LIVEDESK_HUB_UPDATE_REQUEST_PATH || '').trim(),
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
+ }