@livedesk/hub 0.1.50 → 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/package.json +2 -2
- package/src/remote-hub.js +40 -12
- package/src/server.js +100 -54
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livedesk/hub",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.51",
|
|
4
4
|
"description": "VuvoDesk local Hub API and browser frame bridge",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/server.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
|
19
|
-
"@livedesk/runtime-core": "0.1.6",
|
|
19
|
+
"@livedesk/runtime-core": "0.1.6",
|
|
20
20
|
"@openai/codex-sdk": "0.145.0",
|
|
21
21
|
"cors": "^2.8.5",
|
|
22
22
|
"express": "^4.21.2",
|
package/src/remote-hub.js
CHANGED
|
@@ -4300,10 +4300,12 @@ export function createRemoteHub(options = {}) {
|
|
|
4300
4300
|
device.inputWriteOwner = null;
|
|
4301
4301
|
device.inputSocketConnectionId = '';
|
|
4302
4302
|
device.inputOwnerConnectionId = '';
|
|
4303
|
-
device.inputOwnerBindingKey = '';
|
|
4304
|
-
inputSockets.delete(socket);
|
|
4305
|
-
writeOwner?.close?.(reason);
|
|
4306
|
-
|
|
4303
|
+
device.inputOwnerBindingKey = '';
|
|
4304
|
+
inputSockets.delete(socket);
|
|
4305
|
+
writeOwner?.close?.(reason);
|
|
4306
|
+
device.inputLastSeenAt = new Date().toISOString();
|
|
4307
|
+
emitRemoteEvent('RemoteInputSocketDisconnected', device, { reason });
|
|
4308
|
+
try {
|
|
4307
4309
|
writeJsonLine(socket, { type: 'disconnect', reason });
|
|
4308
4310
|
} catch {
|
|
4309
4311
|
// Best-effort notice before closing the side channel.
|
|
@@ -9026,15 +9028,40 @@ export function createRemoteHub(options = {}) {
|
|
|
9026
9028
|
};
|
|
9027
9029
|
}
|
|
9028
9030
|
|
|
9029
|
-
function getCurrentInputWriteOwner(device) {
|
|
9031
|
+
function getCurrentInputWriteOwner(device) {
|
|
9030
9032
|
if (!device?.inputSocket
|
|
9031
9033
|
|| device.inputSocket.destroyed
|
|
9032
9034
|
|| !device.inputWriteOwner
|
|
9033
9035
|
|| !safeString(device.inputSocketConnectionId, 128)) {
|
|
9034
9036
|
return null;
|
|
9035
9037
|
}
|
|
9036
|
-
return device.inputWriteOwner;
|
|
9037
|
-
}
|
|
9038
|
+
return device.inputWriteOwner;
|
|
9039
|
+
}
|
|
9040
|
+
|
|
9041
|
+
function getInputRouteState(deviceId) {
|
|
9042
|
+
const device = devices.get(String(deviceId || ''));
|
|
9043
|
+
if (!device || !device.connected || !device.socket || device.socket.destroyed) {
|
|
9044
|
+
return {
|
|
9045
|
+
deviceId: safeString(deviceId, 160),
|
|
9046
|
+
ready: false,
|
|
9047
|
+
dedicatedInputChannel: false,
|
|
9048
|
+
reason: 'device-not-connected'
|
|
9049
|
+
};
|
|
9050
|
+
}
|
|
9051
|
+
|
|
9052
|
+
const dedicatedInputChannel = readCapabilityFlag(
|
|
9053
|
+
device.capabilities,
|
|
9054
|
+
'dedicatedInputChannel');
|
|
9055
|
+
const ready = dedicatedInputChannel
|
|
9056
|
+
? !!getCurrentInputWriteOwner(device)
|
|
9057
|
+
: true;
|
|
9058
|
+
return {
|
|
9059
|
+
deviceId: device.deviceId,
|
|
9060
|
+
ready,
|
|
9061
|
+
dedicatedInputChannel,
|
|
9062
|
+
reason: ready ? 'ready' : 'input-channel-not-connected'
|
|
9063
|
+
};
|
|
9064
|
+
}
|
|
9038
9065
|
|
|
9039
9066
|
function sendInputControl(deviceId, input = {}) {
|
|
9040
9067
|
const device = devices.get(String(deviceId || ''));
|
|
@@ -9057,9 +9084,9 @@ export function createRemoteHub(options = {}) {
|
|
|
9057
9084
|
return { ok: false, error: 'missing-input-type' };
|
|
9058
9085
|
}
|
|
9059
9086
|
|
|
9060
|
-
// A reset
|
|
9061
|
-
// owner must be able to send it even when its
|
|
9062
|
-
// has just become stale during a monitor/capture-generation switch.
|
|
9087
|
+
// A reset releases native mouse-button and keyboard state. The
|
|
9088
|
+
// current browser input owner must be able to send it even when its
|
|
9089
|
+
// former Control binding has just become stale during a monitor/capture-generation switch.
|
|
9063
9090
|
const currentInputOwner = safeString(device.inputOwnerConnectionId, 128);
|
|
9064
9091
|
const isCurrentOwnerKeyboardReset = normalized.type.toLowerCase() === 'keyboard.reset'
|
|
9065
9092
|
&& normalized.hubConnectionId
|
|
@@ -11835,8 +11862,9 @@ export function createRemoteHub(options = {}) {
|
|
|
11835
11862
|
sendClipboardCommand,
|
|
11836
11863
|
refreshDevicePolicies,
|
|
11837
11864
|
sendLegacyClientUpdate,
|
|
11838
|
-
sendInputControl,
|
|
11839
|
-
|
|
11865
|
+
sendInputControl,
|
|
11866
|
+
getInputRouteState,
|
|
11867
|
+
releaseInputOwner,
|
|
11840
11868
|
notifyAgentProgress,
|
|
11841
11869
|
requestAgentTask,
|
|
11842
11870
|
requestAgentTaskBatch,
|
package/src/server.js
CHANGED
|
@@ -253,15 +253,35 @@ 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
|
|
@@ -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
|
}
|
|
@@ -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;
|
|
@@ -3860,7 +3889,7 @@ function buildHubHealthPayload({
|
|
|
3860
3889
|
};
|
|
3861
3890
|
return {
|
|
3862
3891
|
ok: true,
|
|
3863
|
-
product: 'LiveDesk',
|
|
3892
|
+
product: 'LiveDesk',
|
|
3864
3893
|
timestamp: new Date().toISOString(),
|
|
3865
3894
|
persistentSessionGc,
|
|
3866
3895
|
memoryBeforeGc,
|
|
@@ -4323,7 +4352,7 @@ app.get('/api/remote/status', (_req, res) => {
|
|
|
4323
4352
|
res.json({
|
|
4324
4353
|
...remoteHub.getStatus({ includeSecrets: false }),
|
|
4325
4354
|
pairingPin: secretStatus.pairingPin,
|
|
4326
|
-
product: 'LiveDesk',
|
|
4355
|
+
product: 'LiveDesk',
|
|
4327
4356
|
runtimeRole,
|
|
4328
4357
|
deviceId: runtimeDeviceId,
|
|
4329
4358
|
deviceName: runtimeDeviceName,
|
|
@@ -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
|
}
|
|
@@ -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
|