@livedesk/hub 0.1.49 → 0.1.51
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/README.md +1 -1
- package/package.json +4 -4
- package/src/agents/agent-manager.js +2 -2
- package/src/agents/agent-permissions.js +2 -2
- package/src/agents/agent-tool-registry.js +36 -36
- package/src/agents/codex-agent-runtime.js +16 -16
- package/src/agents/codex-mcp-server.js +5 -5
- package/src/filesystem/roots.js +20 -20
- package/src/filesystem/shared-folders.js +2 -2
- package/src/live-desk-update.js +9 -9
- package/src/remote-clipboard-contract.mjs +482 -482
- package/src/remote-hub.js +1063 -1035
- package/src/server.js +242 -196
- package/src/settings/effective-device-policy.js +31 -31
- package/src/settings/settings-schema.js +2 -2
package/src/server.js
CHANGED
|
@@ -8,12 +8,12 @@ import { dirname, resolve } from 'node:path';
|
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import os from 'node:os';
|
|
10
10
|
import { WebSocketServer } from 'ws';
|
|
11
|
-
import { createRemoteHub } from './remote-hub.js';
|
|
12
|
-
import {
|
|
13
|
-
REMOTE_CLIPBOARD_INPUT_ACTIONS,
|
|
14
|
-
RemoteClipboardContractError,
|
|
15
|
-
normalizeRemoteClipboardRequest
|
|
16
|
-
} from './remote-clipboard-contract.mjs';
|
|
11
|
+
import { createRemoteHub } from './remote-hub.js';
|
|
12
|
+
import {
|
|
13
|
+
REMOTE_CLIPBOARD_INPUT_ACTIONS,
|
|
14
|
+
RemoteClipboardContractError,
|
|
15
|
+
normalizeRemoteClipboardRequest
|
|
16
|
+
} from './remote-clipboard-contract.mjs';
|
|
17
17
|
import { createHubConsoleRelay } from './console-relay.js';
|
|
18
18
|
import {
|
|
19
19
|
buildImmutableRemoteFramePacket,
|
|
@@ -151,7 +151,7 @@ const atlasPool = new Mode4AtlasPool({
|
|
|
151
151
|
onStatus: status => {
|
|
152
152
|
if (status?.type !== 'log') return;
|
|
153
153
|
const logger = status.level === 'warn' ? console.warn : console.log;
|
|
154
|
-
logger(`[
|
|
154
|
+
logger(`[VuvoDesk Hub] ${status.message}`);
|
|
155
155
|
}
|
|
156
156
|
});
|
|
157
157
|
const inputClients = new Set();
|
|
@@ -253,20 +253,40 @@ function readPositiveIntegerEnv(name, fallback) {
|
|
|
253
253
|
return Number.isFinite(value) && value > 0 ? Math.round(value) : fallback;
|
|
254
254
|
}
|
|
255
255
|
|
|
256
|
-
function secureExactTestTokenMatches(actual, expected) {
|
|
256
|
+
function secureExactTestTokenMatches(actual, expected) {
|
|
257
257
|
const actualBytes = Buffer.from(String(actual || ''), 'utf8');
|
|
258
258
|
const expectedBytes = Buffer.from(String(expected || ''), 'utf8');
|
|
259
259
|
return actualBytes.length > 0
|
|
260
260
|
&& actualBytes.length === expectedBytes.length
|
|
261
|
-
&& crypto.timingSafeEqual(actualBytes, expectedBytes);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function
|
|
261
|
+
&& crypto.timingSafeEqual(actualBytes, expectedBytes);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function sendRemoteInputRouteState(ws, deviceId, reason = '') {
|
|
265
|
+
const route = remoteHub.getInputRouteState(deviceId);
|
|
266
|
+
sendJson(ws, {
|
|
267
|
+
type: 'RemoteInputRouteState',
|
|
268
|
+
...route,
|
|
269
|
+
reason: String(reason || route.reason || ''),
|
|
270
|
+
timestamp: new Date().toISOString()
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function broadcastRemoteInputRouteState(deviceId, reason = '') {
|
|
275
|
+
const normalizedDeviceId = String(deviceId || '').trim();
|
|
276
|
+
if (!normalizedDeviceId) return;
|
|
277
|
+
for (const ws of inputClients) {
|
|
278
|
+
if (ws.readyState === 1 && ws.liveDeskInputDeviceIds?.has(normalizedDeviceId)) {
|
|
279
|
+
sendRemoteInputRouteState(ws, normalizedDeviceId, reason);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function handleRemoteHubEvent(type, event) {
|
|
265
285
|
liveDeskUpdateManager?.handleRemoteEvent(type, event);
|
|
266
286
|
hubTransferJobs?.handleRemoteEvent(type, event);
|
|
267
287
|
if (traceRemoteTestEventsEnabled
|
|
268
288
|
&& (type === 'RemoteFrameDropped' || type === 'RemoteFrameTransportProofAccepted')) {
|
|
269
|
-
console.warn(`[
|
|
289
|
+
console.warn(`[VuvoDesk Hub Test Event] ${type} ${JSON.stringify({
|
|
270
290
|
reason: event?.reason || '',
|
|
271
291
|
deviceId: event?.deviceId || event?.device?.deviceId || '',
|
|
272
292
|
proofOnly: event?.proofOnly === true,
|
|
@@ -297,7 +317,7 @@ function handleRemoteHubEvent(type, event) {
|
|
|
297
317
|
}
|
|
298
318
|
return;
|
|
299
319
|
}
|
|
300
|
-
if (type === 'RemoteInputError') {
|
|
320
|
+
if (type === 'RemoteInputError') {
|
|
301
321
|
const targetConnectionId = String(event?.hubConnectionId || '').trim();
|
|
302
322
|
for (const ws of inputClients) {
|
|
303
323
|
if (targetConnectionId && ws.liveDeskInputClientId !== targetConnectionId) {
|
|
@@ -308,11 +328,18 @@ function handleRemoteHubEvent(type, event) {
|
|
|
308
328
|
...event
|
|
309
329
|
});
|
|
310
330
|
}
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
if (type === '
|
|
314
|
-
|
|
315
|
-
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (type === 'RemoteInputSocketConnected' || type === 'RemoteInputSocketDisconnected') {
|
|
334
|
+
const deviceId = String(event?.deviceId || event?.device?.deviceId || '').trim();
|
|
335
|
+
broadcastRemoteInputRouteState(deviceId, event?.reason || type);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (type === 'RemoteDeviceConnected' || type === 'RemoteDeviceDisconnected') {
|
|
339
|
+
connectedDeviceCount = Number(remoteHub.getStatus({ includeSecrets: false }).connectedDeviceCount || 0);
|
|
340
|
+
const deviceId = String(event?.deviceId || event?.device?.deviceId || '').trim();
|
|
341
|
+
broadcastRemoteInputRouteState(deviceId, event?.reason || type);
|
|
342
|
+
}
|
|
316
343
|
if (type !== 'RemoteDeviceConnected') {
|
|
317
344
|
return;
|
|
318
345
|
}
|
|
@@ -572,7 +599,7 @@ async function getRuntimeAccessToken() {
|
|
|
572
599
|
expires_at: Math.floor(runtimeAccessTokenExpiresAt / 1000)
|
|
573
600
|
});
|
|
574
601
|
} catch (error) {
|
|
575
|
-
console.warn(`[
|
|
602
|
+
console.warn(`[VuvoDesk Hub] Refreshed session persistence failed: ${error?.message || error}`);
|
|
576
603
|
}
|
|
577
604
|
return runtimeAccessToken;
|
|
578
605
|
}
|
|
@@ -658,7 +685,7 @@ async function watchAuthoritativeRuntimeRole() {
|
|
|
658
685
|
try {
|
|
659
686
|
const role = await queryAuthoritativeRuntimeRole();
|
|
660
687
|
if (role === 'client') {
|
|
661
|
-
console.warn('[
|
|
688
|
+
console.warn('[VuvoDesk Hub] Supabase selected another Sync Server. Transitioning this runtime to Client.');
|
|
662
689
|
void clearHubHostTarget('role-demoted').finally(() => {
|
|
663
690
|
setTimeout(() => process.exit(ROLE_TRANSITION_EXIT_CODE), 150);
|
|
664
691
|
});
|
|
@@ -692,16 +719,16 @@ const agentDataDir = process.env.LIVEDESK_DATA_DIR || undefined;
|
|
|
692
719
|
const liveDeskSettingsStore = new LiveDeskSettingsStore({ dataDir: agentDataDir });
|
|
693
720
|
const captureStore = new CaptureStore({ dataDir: agentDataDir });
|
|
694
721
|
void captureStore.initialize().catch(error => {
|
|
695
|
-
console.warn(`[
|
|
722
|
+
console.warn(`[VuvoDesk Hub] capture store initialization failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
696
723
|
});
|
|
697
724
|
void liveDeskSettingsStore.getRecord().catch(error => {
|
|
698
|
-
console.warn(`[
|
|
725
|
+
console.warn(`[VuvoDesk Hub] settings load failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
699
726
|
});
|
|
700
727
|
|
|
701
728
|
const udpTransport = createHubUdpTransport({
|
|
702
729
|
env: process.env,
|
|
703
|
-
logEvent: (_scope, message) => console.log(`[
|
|
704
|
-
logWarn: (_scope, message) => console.warn(`[
|
|
730
|
+
logEvent: (_scope, message) => console.log(`[VuvoDesk Hub] ${message}`),
|
|
731
|
+
logWarn: (_scope, message) => console.warn(`[VuvoDesk Hub] ${message}`)
|
|
705
732
|
});
|
|
706
733
|
|
|
707
734
|
const remoteHub = createRemoteHub({
|
|
@@ -713,8 +740,8 @@ const remoteHub = createRemoteHub({
|
|
|
713
740
|
MINDEXEC_MANAGER_VERSION: packageInfo.version
|
|
714
741
|
},
|
|
715
742
|
udpTransport,
|
|
716
|
-
logEvent: (_scope, message) => console.log(`[
|
|
717
|
-
logWarn: (_scope, message) => console.warn(`[
|
|
743
|
+
logEvent: (_scope, message) => console.log(`[VuvoDesk Hub] ${message}`),
|
|
744
|
+
logWarn: (_scope, message) => console.warn(`[VuvoDesk Hub] ${message}`),
|
|
718
745
|
emitEvent: handleRemoteHubEvent,
|
|
719
746
|
emitFrame: broadcastRemoteBinaryFrame,
|
|
720
747
|
emitAudio: broadcastRemoteBinaryAudio,
|
|
@@ -817,16 +844,18 @@ function getLiveDeskUpdateStatus() {
|
|
|
817
844
|
};
|
|
818
845
|
}
|
|
819
846
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
847
|
+
const electronDesktopOwnsSelfUpdate = process.env.LIVEDESK_DESKTOP_HOST === '1';
|
|
848
|
+
liveDeskUpdateManager = createLiveDeskUpdateManager({
|
|
849
|
+
remoteHub,
|
|
850
|
+
currentManagerVersion: process.env.LIVEDESK_MANAGER_VERSION || packageInfo.version,
|
|
851
|
+
currentClientVersion: process.env.LIVEDESK_CLIENT_PACKAGE_VERSION || '',
|
|
852
|
+
excludedDeviceIds: [runtimeDeviceId],
|
|
853
|
+
// electron-updater owns replacement of the installed desktop application.
|
|
854
|
+
// The same Hub still needs the fleet manager for connected outdated Clients.
|
|
855
|
+
restartSupported: !electronDesktopOwnsSelfUpdate
|
|
856
|
+
&& !!String(process.env.LIVEDESK_HUB_UPDATE_REQUEST_PATH || '').trim(),
|
|
857
|
+
requestHubRestart: electronDesktopOwnsSelfUpdate ? undefined : requestHubRestart
|
|
858
|
+
});
|
|
830
859
|
|
|
831
860
|
if (process.env.LIVEDESK_UPDATE_CONTINUE === '1') {
|
|
832
861
|
let continueUpdateInFlight = false;
|
|
@@ -870,7 +899,7 @@ if (process.env.LIVEDESK_UPDATE_CONTINUE === '1') {
|
|
|
870
899
|
clearInterval(continueUpdateTimer);
|
|
871
900
|
}
|
|
872
901
|
} catch (error) {
|
|
873
|
-
console.warn(`[
|
|
902
|
+
console.warn(`[VuvoDesk Hub] automatic client update continuation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
874
903
|
clearInterval(continueUpdateTimer);
|
|
875
904
|
} finally {
|
|
876
905
|
continueUpdateInFlight = false;
|
|
@@ -1167,11 +1196,11 @@ async function dispatchAgentMcpTool(session, name, args = {}) {
|
|
|
1167
1196
|
'livedesk.close_application': 'application.close',
|
|
1168
1197
|
'livedesk.read_file': 'file.read',
|
|
1169
1198
|
'livedesk.write_file': 'file.write',
|
|
1170
|
-
'livedesk.delete_file': 'file.delete',
|
|
1171
|
-
'livedesk.list_directory': 'file.list',
|
|
1172
|
-
'livedesk.search_files': 'file.search',
|
|
1173
|
-
'livedesk.create_directory': 'directory.create',
|
|
1174
|
-
'livedesk.run_command': 'command.run',
|
|
1199
|
+
'livedesk.delete_file': 'file.delete',
|
|
1200
|
+
'livedesk.list_directory': 'file.list',
|
|
1201
|
+
'livedesk.search_files': 'file.search',
|
|
1202
|
+
'livedesk.create_directory': 'directory.create',
|
|
1203
|
+
'livedesk.run_command': 'command.run',
|
|
1175
1204
|
'livedesk.run_script': 'script.run',
|
|
1176
1205
|
'livedesk.install_software': 'software.install',
|
|
1177
1206
|
'livedesk.get_network_status': 'network.status',
|
|
@@ -1255,8 +1284,8 @@ async function dispatchAgentMcpTool(session, name, args = {}) {
|
|
|
1255
1284
|
? String(args.serviceName || '').replace(/[\0\r\n]/g, ' ').trim().slice(0, 120)
|
|
1256
1285
|
: '';
|
|
1257
1286
|
const result = remoteHub.requestAgentTaskBatch(targetIds, {
|
|
1258
|
-
instruction: `${tool.readOnly ? `
|
|
1259
|
-
title: `
|
|
1287
|
+
instruction: `${tool.readOnly ? `VuvoDesk read-only ${operation}` : `VuvoDesk ${name}`}${targetQuery ? ` for ${targetQuery}` : ''}. Return only the collected result.`,
|
|
1288
|
+
title: `VuvoDesk ${operation}`,
|
|
1260
1289
|
operation,
|
|
1261
1290
|
targetQuery,
|
|
1262
1291
|
approvalLevel: tool.readOnly ? 'read-only' : 'task-only',
|
|
@@ -1330,7 +1359,7 @@ const codexRuntime = createCodexAgentRuntime({
|
|
|
1330
1359
|
const detail = ['failed', 'recovering'].includes(progress.stage) && progress.message
|
|
1331
1360
|
? ` detail=${JSON.stringify(String(progress.message).slice(0, 800))}`
|
|
1332
1361
|
: '';
|
|
1333
|
-
console.log(`[
|
|
1362
|
+
console.log(`[VuvoDesk Hub] Agent ${progress.stage} run=${progress.runId}${tool} clients=${delivery.sent}/${delivery.total}${detail}`);
|
|
1334
1363
|
}
|
|
1335
1364
|
});
|
|
1336
1365
|
const agentManager = createAgentManager({
|
|
@@ -1761,40 +1790,40 @@ function normalizeLiveOptions(payload = {}) {
|
|
|
1761
1790
|
};
|
|
1762
1791
|
}
|
|
1763
1792
|
|
|
1764
|
-
function sendJson(ws, payload) {
|
|
1793
|
+
function sendJson(ws, payload) {
|
|
1765
1794
|
if (!ws || ws.readyState !== 1) {
|
|
1766
1795
|
return false;
|
|
1767
1796
|
}
|
|
1768
1797
|
return safeWebSocketSend(ws, JSON.stringify(payload));
|
|
1769
|
-
}
|
|
1770
|
-
|
|
1771
|
-
function findLiveRemoteInputClient(hubConnectionId) {
|
|
1772
|
-
const requestedId = String(hubConnectionId || '').trim();
|
|
1773
|
-
if (!requestedId) {
|
|
1774
|
-
return null;
|
|
1775
|
-
}
|
|
1776
|
-
for (const ws of inputClients) {
|
|
1777
|
-
if (ws.readyState === 1 && ws.liveDeskInputClientId === requestedId) {
|
|
1778
|
-
return ws;
|
|
1779
|
-
}
|
|
1780
|
-
}
|
|
1781
|
-
return null;
|
|
1782
|
-
}
|
|
1783
|
-
|
|
1784
|
-
function clipboardHttpStatus(error) {
|
|
1785
|
-
const code = String(error || '');
|
|
1786
|
-
if (code.includes('blocked-by-settings') || code === 'remote-access-blocked-by-settings') return 403;
|
|
1787
|
-
if (code.includes('too-large')) return 413;
|
|
1788
|
-
if (code.includes('unavailable') || code === 'device-not-connected') return 503;
|
|
1789
|
-
if (code.startsWith('STALE_')
|
|
1790
|
-
|| code.includes('owner')
|
|
1791
|
-
|| code.includes('not-ready')
|
|
1792
|
-
|| code.includes('not-found')
|
|
1793
|
-
|| code.includes('already-exists')
|
|
1794
|
-
|| code.includes('busy')
|
|
1795
|
-
|| code.includes('capacity')) return 409;
|
|
1796
|
-
return 400;
|
|
1797
|
-
}
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
function findLiveRemoteInputClient(hubConnectionId) {
|
|
1801
|
+
const requestedId = String(hubConnectionId || '').trim();
|
|
1802
|
+
if (!requestedId) {
|
|
1803
|
+
return null;
|
|
1804
|
+
}
|
|
1805
|
+
for (const ws of inputClients) {
|
|
1806
|
+
if (ws.readyState === 1 && ws.liveDeskInputClientId === requestedId) {
|
|
1807
|
+
return ws;
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
return null;
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
function clipboardHttpStatus(error) {
|
|
1814
|
+
const code = String(error || '');
|
|
1815
|
+
if (code.includes('blocked-by-settings') || code === 'remote-access-blocked-by-settings') return 403;
|
|
1816
|
+
if (code.includes('too-large')) return 413;
|
|
1817
|
+
if (code.includes('unavailable') || code === 'device-not-connected') return 503;
|
|
1818
|
+
if (code.startsWith('STALE_')
|
|
1819
|
+
|| code.includes('owner')
|
|
1820
|
+
|| code.includes('not-ready')
|
|
1821
|
+
|| code.includes('not-found')
|
|
1822
|
+
|| code.includes('already-exists')
|
|
1823
|
+
|| code.includes('busy')
|
|
1824
|
+
|| code.includes('capacity')) return 409;
|
|
1825
|
+
return 400;
|
|
1826
|
+
}
|
|
1798
1827
|
|
|
1799
1828
|
function forgetWebSocketClient(ws) {
|
|
1800
1829
|
unregisterFrameClient(ws);
|
|
@@ -1931,12 +1960,12 @@ function scheduleFrameStreamStop(deviceId, streamId, streamPurpose) {
|
|
|
1931
1960
|
if (result?.stopPromise) {
|
|
1932
1961
|
void result.stopPromise.then(confirmation => {
|
|
1933
1962
|
if (confirmation?.captureStopConfirmed === true) {
|
|
1934
|
-
console.log(`[
|
|
1963
|
+
console.log(`[VuvoDesk Hub] confirmed ${streamPurpose} capture stopped after frame client closed device=${deviceId} stream=${streamId}`);
|
|
1935
1964
|
return;
|
|
1936
1965
|
}
|
|
1937
1966
|
if (!hasOtherFrameStreamOwner(null, deviceId, streamId, streamPurpose)) {
|
|
1938
1967
|
console.warn(
|
|
1939
|
-
`[
|
|
1968
|
+
`[VuvoDesk Hub] ${streamPurpose} capture stop was not confirmed device=${deviceId} `
|
|
1940
1969
|
+ `stream=${streamId}: ${confirmation?.error || 'capture-stop-unconfirmed'}. `
|
|
1941
1970
|
+ 'Disconnecting the Agent so its final capture drain runs before reconnect.'
|
|
1942
1971
|
);
|
|
@@ -1944,12 +1973,12 @@ function scheduleFrameStreamStop(deviceId, streamId, streamPurpose) {
|
|
|
1944
1973
|
}
|
|
1945
1974
|
}).catch(error => {
|
|
1946
1975
|
if (!hasOtherFrameStreamOwner(null, deviceId, streamId, streamPurpose)) {
|
|
1947
|
-
console.warn(`[
|
|
1976
|
+
console.warn(`[VuvoDesk Hub] ${streamPurpose} capture stop confirmation failed device=${deviceId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1948
1977
|
remoteHub.disconnectDevice(deviceId, 'frame-capture-stop-confirmation-failed');
|
|
1949
1978
|
}
|
|
1950
1979
|
});
|
|
1951
1980
|
} else if (result?.ok && !result.alreadyStopped) {
|
|
1952
|
-
console.log(`[
|
|
1981
|
+
console.log(`[VuvoDesk Hub] queued ${streamPurpose} capture stop after frame client closed device=${deviceId} stream=${streamId}`);
|
|
1953
1982
|
}
|
|
1954
1983
|
}, frameStreamStopGraceMs);
|
|
1955
1984
|
pendingFrameStreamStops.set(key, { timer });
|
|
@@ -3748,11 +3777,11 @@ function stopUnsubscribedAudioOwners(owners, reason) {
|
|
|
3748
3777
|
disconnect: (targetDeviceId, disconnectReason) => (
|
|
3749
3778
|
remoteHub.disconnectDevice(targetDeviceId, disconnectReason)
|
|
3750
3779
|
),
|
|
3751
|
-
warn: message => console.warn(`[
|
|
3780
|
+
warn: message => console.warn(`[VuvoDesk Hub] ${message}. Disconnecting the Agent for a final drain.`)
|
|
3752
3781
|
});
|
|
3753
3782
|
if (result?.ok === false
|
|
3754
3783
|
&& !['device-not-found', 'device-not-connected', 'device-audio-unavailable'].includes(String(result.error || ''))) {
|
|
3755
|
-
console.warn(`[
|
|
3784
|
+
console.warn(`[VuvoDesk Hub] Could not stop unused remote audio capture device=${deviceId} reason=${reason}: ${result.error || 'unknown-error'}`);
|
|
3756
3785
|
}
|
|
3757
3786
|
}
|
|
3758
3787
|
}
|
|
@@ -4653,7 +4682,7 @@ async function notifyHubOnline({ accessToken, target, remoteStatus, endpointCand
|
|
|
4653
4682
|
lastEventId: eventId,
|
|
4654
4683
|
nextAttemptAt: ''
|
|
4655
4684
|
};
|
|
4656
|
-
console.log(`[
|
|
4685
|
+
console.log(`[VuvoDesk Hub] Hub-online wake signal sent event=${eventId.slice(0, 8)} delivered=${Number(data?.delivered || 0)}.`);
|
|
4657
4686
|
return { ok: true, eventId, delivered: Number(data?.delivered || 0) };
|
|
4658
4687
|
} catch (error) {
|
|
4659
4688
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -4664,7 +4693,7 @@ async function notifyHubOnline({ accessToken, target, remoteStatus, endpointCand
|
|
|
4664
4693
|
lastError: message,
|
|
4665
4694
|
nextAttemptAt: new Date(hubWakeNextAttemptAtMs).toISOString()
|
|
4666
4695
|
};
|
|
4667
|
-
console.warn(`[
|
|
4696
|
+
console.warn(`[VuvoDesk Hub] Hub-online wake signal failed; Supabase target remains authoritative: ${message}`);
|
|
4668
4697
|
return { ok: false, error: message, eventId };
|
|
4669
4698
|
}
|
|
4670
4699
|
}
|
|
@@ -4714,7 +4743,7 @@ async function promoteCurrentRuntimeToHub() {
|
|
|
4714
4743
|
p_assigned_hub_id: null,
|
|
4715
4744
|
p_expected_role_version: null
|
|
4716
4745
|
}, accessToken);
|
|
4717
|
-
console.log(`[
|
|
4746
|
+
console.log(`[VuvoDesk Hub] Sync Server role confirmed for ${runtimeDeviceId}.`);
|
|
4718
4747
|
return result;
|
|
4719
4748
|
}
|
|
4720
4749
|
|
|
@@ -4784,12 +4813,12 @@ async function publishHubHostTarget({ takeover = false, reason = 'renewal' } = {
|
|
|
4784
4813
|
lastPublishedEndpoint: target.endpoint || remoteStatus.agentEndpoint || '',
|
|
4785
4814
|
lastExpiresAt: target.expiresAt || ''
|
|
4786
4815
|
});
|
|
4787
|
-
console.log(`[
|
|
4816
|
+
console.log(`[VuvoDesk Hub] Host target ${reason} published endpoint=${target.endpoint || remoteStatus.agentEndpoint} expires=${target.expiresAt || ''}.`);
|
|
4788
4817
|
return { ok: true, active: true, hostTarget: target, lease: getHubHostTargetLeaseStatus() };
|
|
4789
4818
|
} catch (error) {
|
|
4790
4819
|
const message = error instanceof Error ? error.message : String(error);
|
|
4791
4820
|
updateHubHostTargetLeaseState({ state: 'error', lastError: message });
|
|
4792
|
-
console.error(`[
|
|
4821
|
+
console.error(`[VuvoDesk Hub] Host target ${reason} publish failed: ${message}`);
|
|
4793
4822
|
return { ok: false, active: false, error: message, lease: getHubHostTargetLeaseStatus() };
|
|
4794
4823
|
} finally {
|
|
4795
4824
|
hubHostTargetRenewInFlight = false;
|
|
@@ -4833,7 +4862,7 @@ async function clearHubHostTarget(reason = 'shutdown') {
|
|
|
4833
4862
|
}
|
|
4834
4863
|
} catch (cause) {
|
|
4835
4864
|
error = cause instanceof Error ? cause.message : String(cause);
|
|
4836
|
-
console.error(`[
|
|
4865
|
+
console.error(`[VuvoDesk Hub] Host target ${reason} clear failed: ${error}`);
|
|
4837
4866
|
}
|
|
4838
4867
|
try {
|
|
4839
4868
|
await remoteHub.setHostTarget({ enabled: false, nodeId: targetNodeId });
|
|
@@ -4848,7 +4877,7 @@ async function clearHubHostTarget(reason = 'shutdown') {
|
|
|
4848
4877
|
lastExpiresAt: ''
|
|
4849
4878
|
});
|
|
4850
4879
|
if (!error) {
|
|
4851
|
-
console.log(`[
|
|
4880
|
+
console.log(`[VuvoDesk Hub] Host target cleared (${reason}).`);
|
|
4852
4881
|
}
|
|
4853
4882
|
return { ok: !error, error, lease: getHubHostTargetLeaseStatus() };
|
|
4854
4883
|
}
|
|
@@ -4869,7 +4898,7 @@ function scheduleAuthenticatedHubHostTargetPublication(reason = 'session-receive
|
|
|
4869
4898
|
void publishHubHostTargetWithPendingRoleTakeover(reason).catch(error => {
|
|
4870
4899
|
const message = error instanceof Error ? error.message : String(error);
|
|
4871
4900
|
updateHubHostTargetLeaseState({ state: 'error', lastError: message });
|
|
4872
|
-
console.error(`[
|
|
4901
|
+
console.error(`[VuvoDesk Hub] Host target ${reason} background publish failed: ${message}`);
|
|
4873
4902
|
});
|
|
4874
4903
|
});
|
|
4875
4904
|
}
|
|
@@ -4923,7 +4952,7 @@ app.post('/api/hub/sync', async (req, res) => {
|
|
|
4923
4952
|
} catch (error) {
|
|
4924
4953
|
const message = error instanceof Error ? error.message : String(error);
|
|
4925
4954
|
updateHubHostTargetLeaseState({ state: 'error', lastError: message, lastReason: 'sync-server' });
|
|
4926
|
-
console.error(`[
|
|
4955
|
+
console.error(`[VuvoDesk Hub] Sync Server failed: ${message}`);
|
|
4927
4956
|
res.status(409).json({ ok: false, error: message, lease: getHubHostTargetLeaseStatus() });
|
|
4928
4957
|
}
|
|
4929
4958
|
});
|
|
@@ -5010,31 +5039,30 @@ app.post('/api/runtime/role', async (req, res) => {
|
|
|
5010
5039
|
}
|
|
5011
5040
|
});
|
|
5012
5041
|
|
|
5013
|
-
app.get('/api/update/status', (_req, res) => {
|
|
5014
|
-
noStore(res);
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
liveDeskUpdateManager?.
|
|
5036
|
-
|
|
5037
|
-
res.status(result?.ok === false ? 409 : 200).json(result || { ok: false, error: 'update-manager-unavailable' });
|
|
5042
|
+
app.get('/api/update/status', (_req, res) => {
|
|
5043
|
+
noStore(res);
|
|
5044
|
+
res.json({
|
|
5045
|
+
...getLiveDeskUpdateStatus(),
|
|
5046
|
+
selfUpdateOwner: electronDesktopOwnsSelfUpdate ? 'electron-updater' : 'npm-launcher'
|
|
5047
|
+
});
|
|
5048
|
+
});
|
|
5049
|
+
|
|
5050
|
+
app.post('/api/update/apply', async (_req, res) => {
|
|
5051
|
+
noStore(res);
|
|
5052
|
+
try {
|
|
5053
|
+
liveDeskUpdateManager?.reconcileHubRestartResult(readHubRestartResult());
|
|
5054
|
+
const updateStatus = liveDeskUpdateManager?.getStatus();
|
|
5055
|
+
if (electronDesktopOwnsSelfUpdate
|
|
5056
|
+
&& (updateStatus?.managerUpdateAvailable || updateStatus?.clientPackageUpdateAvailable)) {
|
|
5057
|
+
res.status(409).json({
|
|
5058
|
+
ok: false,
|
|
5059
|
+
error: 'electron-updater-managed',
|
|
5060
|
+
selfUpdateOwner: 'electron-updater'
|
|
5061
|
+
});
|
|
5062
|
+
return;
|
|
5063
|
+
}
|
|
5064
|
+
const result = await liveDeskUpdateManager?.startUpdate();
|
|
5065
|
+
res.status(result?.ok === false ? 409 : 200).json(result || { ok: false, error: 'update-manager-unavailable' });
|
|
5038
5066
|
} catch (error) {
|
|
5039
5067
|
res.status(500).json({ ok: false, error: error instanceof Error ? error.message : String(error) });
|
|
5040
5068
|
}
|
|
@@ -5184,64 +5212,64 @@ app.post('/api/remote/devices/:deviceId/status-diagnostic', (req, res) => {
|
|
|
5184
5212
|
}));
|
|
5185
5213
|
});
|
|
5186
5214
|
|
|
5187
|
-
app.post('/api/remote/devices/:deviceId/input', requireHubFeatureAccess, (req, res) => {
|
|
5188
|
-
noStore(res);
|
|
5189
|
-
res.json(remoteHub.sendInputControl(req.params.deviceId, req.body || {}));
|
|
5190
|
-
});
|
|
5191
|
-
|
|
5192
|
-
app.post('/api/remote/devices/:deviceId/clipboard', requireHubFeatureAccess, async (req, res) => {
|
|
5193
|
-
noStore(res);
|
|
5194
|
-
try {
|
|
5195
|
-
const request = normalizeRemoteClipboardRequest(req.body || {});
|
|
5196
|
-
const inputClient = findLiveRemoteInputClient(request.hubConnectionId);
|
|
5197
|
-
if (!inputClient) {
|
|
5198
|
-
res.status(409).json({ ok: false, error: 'clipboard-input-owner-not-live' });
|
|
5199
|
-
return;
|
|
5200
|
-
}
|
|
5201
|
-
const deviceId = String(req.params.deviceId || '').trim();
|
|
5202
|
-
inputClient.liveDeskInputDeviceIds?.add?.(deviceId);
|
|
5203
|
-
|
|
5204
|
-
if (REMOTE_CLIPBOARD_INPUT_ACTIONS.has(request.action)) {
|
|
5205
|
-
const result = remoteHub.sendInputControl(deviceId, {
|
|
5206
|
-
type: request.action,
|
|
5207
|
-
clipboardOperationId: request.operationId,
|
|
5208
|
-
operationId: request.operationId,
|
|
5209
|
-
clipboardDirection: request.clipboardDirection,
|
|
5210
|
-
manifest: request.payload.manifest || null,
|
|
5211
|
-
contentKind: request.payload.contentKind || request.payload.manifest?.contentKind || '',
|
|
5212
|
-
controlSessionId: request.binding.controlSessionId,
|
|
5213
|
-
controlCommandId: request.binding.controlCommandId,
|
|
5214
|
-
captureGeneration: request.binding.captureGeneration,
|
|
5215
|
-
monitorIndex: request.binding.monitorIndex,
|
|
5216
|
-
hubConnectionId: request.hubConnectionId,
|
|
5217
|
-
requestAck: true
|
|
5218
|
-
});
|
|
5219
|
-
res.status(result.ok ? 200 : clipboardHttpStatus(result.error)).json({
|
|
5220
|
-
ok: result.ok === true,
|
|
5221
|
-
action: request.action,
|
|
5222
|
-
operationId: request.operationId,
|
|
5223
|
-
input: result,
|
|
5224
|
-
...(result.ok ? {} : { error: result.error || 'clipboard-input-failed' })
|
|
5225
|
-
});
|
|
5226
|
-
return;
|
|
5227
|
-
}
|
|
5228
|
-
|
|
5229
|
-
const result = await remoteHub.sendClipboardCommand(deviceId, request);
|
|
5230
|
-
res.status(result.ok ? 200 : clipboardHttpStatus(result.error)).json(result);
|
|
5231
|
-
} catch (error) {
|
|
5232
|
-
const status = error instanceof RemoteClipboardContractError
|
|
5233
|
-
? error.status
|
|
5234
|
-
: 500;
|
|
5235
|
-
res.status(status).json({
|
|
5236
|
-
ok: false,
|
|
5237
|
-
error: error instanceof RemoteClipboardContractError
|
|
5238
|
-
? error.code
|
|
5239
|
-
: 'clipboard-request-failed'
|
|
5240
|
-
});
|
|
5241
|
-
}
|
|
5242
|
-
});
|
|
5243
|
-
|
|
5244
|
-
app.get('/api/remote/filesystem/roots', requireHubFeatureAccess, async (req, res) => {
|
|
5215
|
+
app.post('/api/remote/devices/:deviceId/input', requireHubFeatureAccess, (req, res) => {
|
|
5216
|
+
noStore(res);
|
|
5217
|
+
res.json(remoteHub.sendInputControl(req.params.deviceId, req.body || {}));
|
|
5218
|
+
});
|
|
5219
|
+
|
|
5220
|
+
app.post('/api/remote/devices/:deviceId/clipboard', requireHubFeatureAccess, async (req, res) => {
|
|
5221
|
+
noStore(res);
|
|
5222
|
+
try {
|
|
5223
|
+
const request = normalizeRemoteClipboardRequest(req.body || {});
|
|
5224
|
+
const inputClient = findLiveRemoteInputClient(request.hubConnectionId);
|
|
5225
|
+
if (!inputClient) {
|
|
5226
|
+
res.status(409).json({ ok: false, error: 'clipboard-input-owner-not-live' });
|
|
5227
|
+
return;
|
|
5228
|
+
}
|
|
5229
|
+
const deviceId = String(req.params.deviceId || '').trim();
|
|
5230
|
+
inputClient.liveDeskInputDeviceIds?.add?.(deviceId);
|
|
5231
|
+
|
|
5232
|
+
if (REMOTE_CLIPBOARD_INPUT_ACTIONS.has(request.action)) {
|
|
5233
|
+
const result = remoteHub.sendInputControl(deviceId, {
|
|
5234
|
+
type: request.action,
|
|
5235
|
+
clipboardOperationId: request.operationId,
|
|
5236
|
+
operationId: request.operationId,
|
|
5237
|
+
clipboardDirection: request.clipboardDirection,
|
|
5238
|
+
manifest: request.payload.manifest || null,
|
|
5239
|
+
contentKind: request.payload.contentKind || request.payload.manifest?.contentKind || '',
|
|
5240
|
+
controlSessionId: request.binding.controlSessionId,
|
|
5241
|
+
controlCommandId: request.binding.controlCommandId,
|
|
5242
|
+
captureGeneration: request.binding.captureGeneration,
|
|
5243
|
+
monitorIndex: request.binding.monitorIndex,
|
|
5244
|
+
hubConnectionId: request.hubConnectionId,
|
|
5245
|
+
requestAck: true
|
|
5246
|
+
});
|
|
5247
|
+
res.status(result.ok ? 200 : clipboardHttpStatus(result.error)).json({
|
|
5248
|
+
ok: result.ok === true,
|
|
5249
|
+
action: request.action,
|
|
5250
|
+
operationId: request.operationId,
|
|
5251
|
+
input: result,
|
|
5252
|
+
...(result.ok ? {} : { error: result.error || 'clipboard-input-failed' })
|
|
5253
|
+
});
|
|
5254
|
+
return;
|
|
5255
|
+
}
|
|
5256
|
+
|
|
5257
|
+
const result = await remoteHub.sendClipboardCommand(deviceId, request);
|
|
5258
|
+
res.status(result.ok ? 200 : clipboardHttpStatus(result.error)).json(result);
|
|
5259
|
+
} catch (error) {
|
|
5260
|
+
const status = error instanceof RemoteClipboardContractError
|
|
5261
|
+
? error.status
|
|
5262
|
+
: 500;
|
|
5263
|
+
res.status(status).json({
|
|
5264
|
+
ok: false,
|
|
5265
|
+
error: error instanceof RemoteClipboardContractError
|
|
5266
|
+
? error.code
|
|
5267
|
+
: 'clipboard-request-failed'
|
|
5268
|
+
});
|
|
5269
|
+
}
|
|
5270
|
+
});
|
|
5271
|
+
|
|
5272
|
+
app.get('/api/remote/filesystem/roots', requireHubFeatureAccess, async (req, res) => {
|
|
5245
5273
|
noStore(res);
|
|
5246
5274
|
try {
|
|
5247
5275
|
res.json(await hubFilesystem.getRoots({ refresh: /^(1|true|yes|on)$/i.test(String(req.query?.refresh || '')) }));
|
|
@@ -5793,7 +5821,7 @@ frameWss.on('connection', (ws, req) => {
|
|
|
5793
5821
|
} catch {
|
|
5794
5822
|
updateFrameSubscription(ws, {});
|
|
5795
5823
|
}
|
|
5796
|
-
ws.on('message', data => {
|
|
5824
|
+
ws.on('message', data => {
|
|
5797
5825
|
try {
|
|
5798
5826
|
const payload = JSON.parse(Buffer.isBuffer(data) ? data.toString('utf8') : String(data || ''));
|
|
5799
5827
|
if (payload?.type === 'subscribe') {
|
|
@@ -5899,11 +5927,29 @@ inputWss.on('connection', ws => {
|
|
|
5899
5927
|
try {
|
|
5900
5928
|
payload = JSON.parse(Buffer.isBuffer(data) ? data.toString('utf8') : String(data || ''));
|
|
5901
5929
|
} catch {
|
|
5902
|
-
sendJson(ws, { type: 'RemoteInputError', error: 'invalid-json' });
|
|
5903
|
-
return;
|
|
5904
|
-
}
|
|
5905
|
-
const deviceId = String(payload?.deviceId || '').trim();
|
|
5906
|
-
|
|
5930
|
+
sendJson(ws, { type: 'RemoteInputError', error: 'invalid-json' });
|
|
5931
|
+
return;
|
|
5932
|
+
}
|
|
5933
|
+
const deviceId = String(payload?.deviceId || '').trim();
|
|
5934
|
+
if (payload?.type === 'watch') {
|
|
5935
|
+
if (!deviceId) {
|
|
5936
|
+
sendJson(ws, { type: 'RemoteInputError', error: 'device-id-required' });
|
|
5937
|
+
return;
|
|
5938
|
+
}
|
|
5939
|
+
for (const previousDeviceId of ws.liveDeskInputDeviceIds) {
|
|
5940
|
+
if (previousDeviceId !== deviceId) {
|
|
5941
|
+
remoteHub.releaseInputOwner(
|
|
5942
|
+
previousDeviceId,
|
|
5943
|
+
ws.liveDeskInputClientId,
|
|
5944
|
+
'browser-input-target-changed');
|
|
5945
|
+
}
|
|
5946
|
+
}
|
|
5947
|
+
ws.liveDeskInputDeviceIds.clear();
|
|
5948
|
+
ws.liveDeskInputDeviceIds.add(deviceId);
|
|
5949
|
+
sendRemoteInputRouteState(ws, deviceId, 'browser-input-watch');
|
|
5950
|
+
return;
|
|
5951
|
+
}
|
|
5952
|
+
const inputEventId = String(
|
|
5907
5953
|
payload?.input?.inputEventId
|
|
5908
5954
|
|| payload?.inputEventId
|
|
5909
5955
|
|| payload?.requestId
|
|
@@ -5955,14 +6001,14 @@ await remoteHub.start();
|
|
|
5955
6001
|
connectedDeviceCount = Number(remoteHub.getStatus({ includeSecrets: false }).connectedDeviceCount || 0);
|
|
5956
6002
|
hubSharedFolders.startAutoSync(
|
|
5957
6003
|
() => remoteHub.listDevices({ includeDataUrl: false }).filter(device => device.connected).map(device => device.deviceId),
|
|
5958
|
-
() => process.env.LIVEDESK_REMOTE_DIRECTORY || 'Desktop/
|
|
6004
|
+
() => process.env.LIVEDESK_REMOTE_DIRECTORY || 'Desktop/VuvoDeskFiles'
|
|
5959
6005
|
);
|
|
5960
6006
|
httpServer.listen(httpPort, httpHost, () => {
|
|
5961
6007
|
const status = remoteHub.getStatus({ includeSecrets: true });
|
|
5962
6008
|
const managerVersion = String(process.env.LIVEDESK_MANAGER_VERSION || packageInfo.version || 'dev');
|
|
5963
|
-
console.log(`[
|
|
5964
|
-
console.log(`[
|
|
5965
|
-
console.log(`[
|
|
6009
|
+
console.log(`[VuvoDesk Hub] Version ${managerVersion}`);
|
|
6010
|
+
console.log(`[VuvoDesk Hub] HTTP API http://${httpHost}:${httpPort}`);
|
|
6011
|
+
console.log(`[VuvoDesk Hub] Client endpoint ${status.agentEndpoint} pair=${status.pairTokenPreview}`);
|
|
5966
6012
|
hubConsoleRelay?.start();
|
|
5967
6013
|
});
|
|
5968
6014
|
|
|
@@ -5988,9 +6034,9 @@ async function boundedShutdownStep(label, action, timeoutMs) {
|
|
|
5988
6034
|
timer.unref?.();
|
|
5989
6035
|
})
|
|
5990
6036
|
]);
|
|
5991
|
-
console.log(`[
|
|
6037
|
+
console.log(`[VuvoDesk Hub] Shutdown stage completed: ${label}.`);
|
|
5992
6038
|
} catch (error) {
|
|
5993
|
-
console.warn(`[
|
|
6039
|
+
console.warn(`[VuvoDesk Hub] Shutdown stage warning: ${error instanceof Error ? error.message : String(error)}`);
|
|
5994
6040
|
} finally {
|
|
5995
6041
|
if (timer) clearTimeout(timer);
|
|
5996
6042
|
}
|
|
@@ -6000,7 +6046,7 @@ function runSynchronousShutdownStep(label, action) {
|
|
|
6000
6046
|
try {
|
|
6001
6047
|
action();
|
|
6002
6048
|
} catch (error) {
|
|
6003
|
-
console.warn(`[
|
|
6049
|
+
console.warn(`[VuvoDesk Hub] Shutdown stage warning: ${label} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
6004
6050
|
}
|
|
6005
6051
|
}
|
|
6006
6052
|
|
|
@@ -6010,13 +6056,13 @@ function closeBrowserWebSockets() {
|
|
|
6010
6056
|
for (const ws of wss.clients) {
|
|
6011
6057
|
closeRequested += 1;
|
|
6012
6058
|
try {
|
|
6013
|
-
ws.close(1012, '
|
|
6059
|
+
ws.close(1012, 'VuvoDesk Hub restarting');
|
|
6014
6060
|
} catch {
|
|
6015
6061
|
try { ws.terminate(); } catch { /* socket already closed */ }
|
|
6016
6062
|
}
|
|
6017
6063
|
}
|
|
6018
6064
|
}
|
|
6019
|
-
console.log(`[
|
|
6065
|
+
console.log(`[VuvoDesk Hub] Shutdown stage: requested ${closeRequested} browser WebSocket connection(s) to close.`);
|
|
6020
6066
|
}
|
|
6021
6067
|
|
|
6022
6068
|
function terminateOpenConnections() {
|
|
@@ -6035,7 +6081,7 @@ function terminateOpenConnections() {
|
|
|
6035
6081
|
try { socket.destroy(); } catch { /* socket already closed */ }
|
|
6036
6082
|
}
|
|
6037
6083
|
try { httpServer.closeAllConnections?.(); } catch { /* older Node runtime */ }
|
|
6038
|
-
console.log(`[
|
|
6084
|
+
console.log(`[VuvoDesk Hub] Shutdown stage: force-closed ${terminatedWebSockets} WebSocket and ${destroyedHttpConnections} HTTP connection(s).`);
|
|
6039
6085
|
}
|
|
6040
6086
|
|
|
6041
6087
|
let hubShutdownPromise = null;
|
|
@@ -6043,7 +6089,7 @@ function shutdownHub(signal) {
|
|
|
6043
6089
|
if (hubShutdownPromise) return hubShutdownPromise;
|
|
6044
6090
|
hubShutdownPromise = (async () => {
|
|
6045
6091
|
const startedAt = Date.now();
|
|
6046
|
-
console.log(`[
|
|
6092
|
+
console.log(`[VuvoDesk Hub] Shutdown started signal=${signal} grace=${hubShutdownGraceMs}ms timeout=${hubShutdownTimeoutMs}ms.`);
|
|
6047
6093
|
if (roleWatchTimer) clearInterval(roleWatchTimer);
|
|
6048
6094
|
clearInterval(browserWebSocketHeartbeatTimer);
|
|
6049
6095
|
runSynchronousShutdownStep('update manager close', () => liveDeskUpdateManager?.close());
|
|
@@ -6055,15 +6101,15 @@ function shutdownHub(signal) {
|
|
|
6055
6101
|
try {
|
|
6056
6102
|
httpServer.close(error => {
|
|
6057
6103
|
if (error) {
|
|
6058
|
-
console.warn(`[
|
|
6104
|
+
console.warn(`[VuvoDesk Hub] HTTP listener close warning: ${error.message}`);
|
|
6059
6105
|
} else {
|
|
6060
|
-
console.log('[
|
|
6106
|
+
console.log('[VuvoDesk Hub] Shutdown stage completed: HTTP listener closed.');
|
|
6061
6107
|
}
|
|
6062
6108
|
resolveClose();
|
|
6063
6109
|
});
|
|
6064
6110
|
httpServer.closeIdleConnections?.();
|
|
6065
6111
|
} catch (error) {
|
|
6066
|
-
console.warn(`[
|
|
6112
|
+
console.warn(`[VuvoDesk Hub] HTTP listener close warning: ${error instanceof Error ? error.message : String(error)}`);
|
|
6067
6113
|
resolveClose();
|
|
6068
6114
|
}
|
|
6069
6115
|
});
|
|
@@ -6083,7 +6129,7 @@ function shutdownHub(signal) {
|
|
|
6083
6129
|
await Promise.race([httpClosed, waitForShutdownDelay(remainingMs)]);
|
|
6084
6130
|
clearTimeout(forceCloseTimer);
|
|
6085
6131
|
terminateOpenConnections();
|
|
6086
|
-
console.log(`[
|
|
6132
|
+
console.log(`[VuvoDesk Hub] Shutdown complete in ${Date.now() - startedAt}ms.`);
|
|
6087
6133
|
})();
|
|
6088
6134
|
return hubShutdownPromise;
|
|
6089
6135
|
}
|
|
@@ -6093,7 +6139,7 @@ for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
|
6093
6139
|
void shutdownHub(signal)
|
|
6094
6140
|
.then(() => process.exit(0))
|
|
6095
6141
|
.catch(error => {
|
|
6096
|
-
console.error(`[
|
|
6142
|
+
console.error(`[VuvoDesk Hub] Shutdown failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
6097
6143
|
terminateOpenConnections();
|
|
6098
6144
|
process.exit(1);
|
|
6099
6145
|
});
|