@livedesk/hub 0.1.50 → 0.1.52
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 +223 -36
- 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.52",
|
|
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
|
@@ -4288,22 +4288,25 @@ export function createRemoteHub(options = {}) {
|
|
|
4288
4288
|
}
|
|
4289
4289
|
}
|
|
4290
4290
|
|
|
4291
|
-
function closeInputSocket(device, reason = 'input-socket-closed') {
|
|
4291
|
+
function closeInputSocket(device, reason = 'input-socket-closed') {
|
|
4292
4292
|
const socket = device?.inputSocket;
|
|
4293
4293
|
if (!socket) {
|
|
4294
4294
|
return;
|
|
4295
4295
|
}
|
|
4296
4296
|
|
|
4297
|
-
const writeOwner = device.inputWriteOwner;
|
|
4297
|
+
const writeOwner = device.inputWriteOwner;
|
|
4298
|
+
clearPendingDedicatedInputAcks(device);
|
|
4298
4299
|
clearClipboardOperationsForDevice(device, reason);
|
|
4299
4300
|
device.inputSocket = null;
|
|
4300
4301
|
device.inputWriteOwner = null;
|
|
4301
4302
|
device.inputSocketConnectionId = '';
|
|
4302
4303
|
device.inputOwnerConnectionId = '';
|
|
4303
|
-
device.inputOwnerBindingKey = '';
|
|
4304
|
-
inputSockets.delete(socket);
|
|
4305
|
-
writeOwner?.close?.(reason);
|
|
4306
|
-
|
|
4304
|
+
device.inputOwnerBindingKey = '';
|
|
4305
|
+
inputSockets.delete(socket);
|
|
4306
|
+
writeOwner?.close?.(reason);
|
|
4307
|
+
device.inputLastSeenAt = new Date().toISOString();
|
|
4308
|
+
emitRemoteEvent('RemoteInputSocketDisconnected', device, { reason });
|
|
4309
|
+
try {
|
|
4307
4310
|
writeJsonLine(socket, { type: 'disconnect', reason });
|
|
4308
4311
|
} catch {
|
|
4309
4312
|
// Best-effort notice before closing the side channel.
|
|
@@ -4315,7 +4318,7 @@ export function createRemoteHub(options = {}) {
|
|
|
4315
4318
|
}
|
|
4316
4319
|
}
|
|
4317
4320
|
|
|
4318
|
-
function detachInputSocket(socket, reason = 'input-socket-closed') {
|
|
4321
|
+
function detachInputSocket(socket, reason = 'input-socket-closed') {
|
|
4319
4322
|
const deviceId = inputSockets.get(socket);
|
|
4320
4323
|
inputSockets.delete(socket);
|
|
4321
4324
|
if (!deviceId) {
|
|
@@ -4327,7 +4330,8 @@ export function createRemoteHub(options = {}) {
|
|
|
4327
4330
|
return;
|
|
4328
4331
|
}
|
|
4329
4332
|
|
|
4330
|
-
const writeOwner = device.inputWriteOwner;
|
|
4333
|
+
const writeOwner = device.inputWriteOwner;
|
|
4334
|
+
clearPendingDedicatedInputAcks(device);
|
|
4331
4335
|
clearClipboardOperationsForDevice(device, reason);
|
|
4332
4336
|
device.inputSocket = null;
|
|
4333
4337
|
device.inputWriteOwner = null;
|
|
@@ -4946,7 +4950,8 @@ export function createRemoteHub(options = {}) {
|
|
|
4946
4950
|
return null;
|
|
4947
4951
|
}
|
|
4948
4952
|
|
|
4949
|
-
closeInputSocket(device, 'replaced-by-new-input-socket');
|
|
4953
|
+
closeInputSocket(device, 'replaced-by-new-input-socket');
|
|
4954
|
+
clearPendingDedicatedInputAcks(device);
|
|
4950
4955
|
|
|
4951
4956
|
const now = new Date().toISOString();
|
|
4952
4957
|
const inputSocketConnectionId = crypto.randomUUID();
|
|
@@ -5390,7 +5395,7 @@ export function createRemoteHub(options = {}) {
|
|
|
5390
5395
|
return failed;
|
|
5391
5396
|
}
|
|
5392
5397
|
|
|
5393
|
-
function ensurePendingInputFallbacks(device) {
|
|
5398
|
+
function ensurePendingInputFallbacks(device) {
|
|
5394
5399
|
if (!(device?.pendingInputFallbacks instanceof Map)) {
|
|
5395
5400
|
Object.defineProperty(device, 'pendingInputFallbacks', {
|
|
5396
5401
|
value: new Map(),
|
|
@@ -5407,8 +5412,151 @@ export function createRemoteHub(options = {}) {
|
|
|
5407
5412
|
writable: true
|
|
5408
5413
|
});
|
|
5409
5414
|
}
|
|
5410
|
-
return device.pendingInputFallbacks;
|
|
5411
|
-
}
|
|
5415
|
+
return device.pendingInputFallbacks;
|
|
5416
|
+
}
|
|
5417
|
+
|
|
5418
|
+
function ensurePendingDedicatedInputAckState(device) {
|
|
5419
|
+
if (!device?.pendingDedicatedInputAckState) {
|
|
5420
|
+
Object.defineProperty(device, 'pendingDedicatedInputAckState', {
|
|
5421
|
+
value: { current: null, next: null, timeoutTimer: null },
|
|
5422
|
+
enumerable: false,
|
|
5423
|
+
configurable: true,
|
|
5424
|
+
writable: true
|
|
5425
|
+
});
|
|
5426
|
+
}
|
|
5427
|
+
return device.pendingDedicatedInputAckState;
|
|
5428
|
+
}
|
|
5429
|
+
|
|
5430
|
+
function clearPendingDedicatedInputAcks(device) {
|
|
5431
|
+
if (!device?.pendingDedicatedInputAckState) {
|
|
5432
|
+
return;
|
|
5433
|
+
}
|
|
5434
|
+
const state = device.pendingDedicatedInputAckState;
|
|
5435
|
+
if (state.timeoutTimer) {
|
|
5436
|
+
clearTimeout(state.timeoutTimer);
|
|
5437
|
+
}
|
|
5438
|
+
state.timeoutTimer = null;
|
|
5439
|
+
state.current = null;
|
|
5440
|
+
state.next = null;
|
|
5441
|
+
}
|
|
5442
|
+
|
|
5443
|
+
function dedicatedInputAckMatches(pending, message = {}) {
|
|
5444
|
+
if (!pending) {
|
|
5445
|
+
return false;
|
|
5446
|
+
}
|
|
5447
|
+
const result = message?.result && typeof message.result === 'object'
|
|
5448
|
+
? message.result
|
|
5449
|
+
: {};
|
|
5450
|
+
const inputEventId = safeString(
|
|
5451
|
+
message.inputEventId
|
|
5452
|
+
|| message.InputEventId
|
|
5453
|
+
|| result.inputEventId
|
|
5454
|
+
|| result.InputEventId,
|
|
5455
|
+
128);
|
|
5456
|
+
const hubConnectionId = safeString(
|
|
5457
|
+
message.hubConnectionId
|
|
5458
|
+
|| message.HubConnectionId
|
|
5459
|
+
|| result.hubConnectionId
|
|
5460
|
+
|| result.HubConnectionId,
|
|
5461
|
+
128);
|
|
5462
|
+
const inputSeq = Number(
|
|
5463
|
+
message.inputSeq
|
|
5464
|
+
?? message.InputSeq
|
|
5465
|
+
?? result.inputSeq
|
|
5466
|
+
?? result.InputSeq
|
|
5467
|
+
?? 0) || 0;
|
|
5468
|
+
if (pending.inputEventId && inputEventId) {
|
|
5469
|
+
return pending.inputEventId === inputEventId
|
|
5470
|
+
&& pending.hubConnectionId === hubConnectionId;
|
|
5471
|
+
}
|
|
5472
|
+
return pending.inputSeq > 0
|
|
5473
|
+
&& pending.inputSeq === inputSeq
|
|
5474
|
+
&& pending.hubConnectionId === hubConnectionId;
|
|
5475
|
+
}
|
|
5476
|
+
|
|
5477
|
+
function armPendingDedicatedInputAck(device) {
|
|
5478
|
+
const state = ensurePendingDedicatedInputAckState(device);
|
|
5479
|
+
if (state.timeoutTimer) {
|
|
5480
|
+
clearTimeout(state.timeoutTimer);
|
|
5481
|
+
state.timeoutTimer = null;
|
|
5482
|
+
}
|
|
5483
|
+
const pending = state.current;
|
|
5484
|
+
if (!pending) {
|
|
5485
|
+
return;
|
|
5486
|
+
}
|
|
5487
|
+
const remainingMs = Math.max(0, pending.deadlineAtEpochMs - Date.now());
|
|
5488
|
+
state.timeoutTimer = setTimeout(() => {
|
|
5489
|
+
state.timeoutTimer = null;
|
|
5490
|
+
if (state.current !== pending) {
|
|
5491
|
+
return;
|
|
5492
|
+
}
|
|
5493
|
+
state.current = null;
|
|
5494
|
+
state.next = null;
|
|
5495
|
+
if (device.inputSocketConnectionId !== pending.inputSocketConnectionId) {
|
|
5496
|
+
return;
|
|
5497
|
+
}
|
|
5498
|
+
emitRemoteInputError(
|
|
5499
|
+
device,
|
|
5500
|
+
{ error: 'remote-input-timeout' },
|
|
5501
|
+
pending,
|
|
5502
|
+
'remote-input-timeout');
|
|
5503
|
+
closeInputSocket(device, 'input-ack-timeout');
|
|
5504
|
+
}, remainingMs);
|
|
5505
|
+
state.timeoutTimer.unref?.();
|
|
5506
|
+
}
|
|
5507
|
+
|
|
5508
|
+
function schedulePendingDedicatedInputAck(device, pending) {
|
|
5509
|
+
if (pending?.requestAck !== true
|
|
5510
|
+
|| !Number.isSafeInteger(pending.inputSeq)
|
|
5511
|
+
|| pending.inputSeq <= 0) {
|
|
5512
|
+
return;
|
|
5513
|
+
}
|
|
5514
|
+
const state = ensurePendingDedicatedInputAckState(device);
|
|
5515
|
+
const record = {
|
|
5516
|
+
...pending,
|
|
5517
|
+
deadlineAtEpochMs: Date.now() + inputAckTimeoutMs
|
|
5518
|
+
};
|
|
5519
|
+
if (!state.current) {
|
|
5520
|
+
state.current = record;
|
|
5521
|
+
state.next = null;
|
|
5522
|
+
armPendingDedicatedInputAck(device);
|
|
5523
|
+
return;
|
|
5524
|
+
}
|
|
5525
|
+
if (state.current.inputSocketConnectionId !== record.inputSocketConnectionId
|
|
5526
|
+
|| state.current.bindingKey !== record.bindingKey) {
|
|
5527
|
+
clearPendingDedicatedInputAcks(device);
|
|
5528
|
+
state.current = record;
|
|
5529
|
+
armPendingDedicatedInputAck(device);
|
|
5530
|
+
return;
|
|
5531
|
+
}
|
|
5532
|
+
// Keep only one later checkpoint. If the current acknowledgement
|
|
5533
|
+
// arrives, this latest sampled input becomes the next exact deadline.
|
|
5534
|
+
state.next = record;
|
|
5535
|
+
}
|
|
5536
|
+
|
|
5537
|
+
function acknowledgePendingDedicatedInput(device, message = {}) {
|
|
5538
|
+
const state = device?.pendingDedicatedInputAckState;
|
|
5539
|
+
if (!state?.current) {
|
|
5540
|
+
return false;
|
|
5541
|
+
}
|
|
5542
|
+
if (dedicatedInputAckMatches(state.current, message)) {
|
|
5543
|
+
if (state.timeoutTimer) {
|
|
5544
|
+
clearTimeout(state.timeoutTimer);
|
|
5545
|
+
state.timeoutTimer = null;
|
|
5546
|
+
}
|
|
5547
|
+
state.current = state.next;
|
|
5548
|
+
state.next = null;
|
|
5549
|
+
armPendingDedicatedInputAck(device);
|
|
5550
|
+
return true;
|
|
5551
|
+
}
|
|
5552
|
+
if (dedicatedInputAckMatches(state.next, message)) {
|
|
5553
|
+
// A later ordered acknowledgement proves that the input reader is
|
|
5554
|
+
// still draining, even if an earlier diagnostic response vanished.
|
|
5555
|
+
clearPendingDedicatedInputAcks(device);
|
|
5556
|
+
return true;
|
|
5557
|
+
}
|
|
5558
|
+
return false;
|
|
5559
|
+
}
|
|
5412
5560
|
|
|
5413
5561
|
function pruneCompletedInputFallbacks(device, now = Date.now()) {
|
|
5414
5562
|
ensurePendingInputFallbacks(device);
|
|
@@ -7864,10 +8012,14 @@ export function createRemoteHub(options = {}) {
|
|
|
7864
8012
|
return;
|
|
7865
8013
|
}
|
|
7866
8014
|
|
|
7867
|
-
if (state.inputOnly) {
|
|
7868
|
-
device.
|
|
7869
|
-
|
|
7870
|
-
|
|
8015
|
+
if (state.inputOnly) {
|
|
8016
|
+
if (device.inputSocket !== socket) {
|
|
8017
|
+
return;
|
|
8018
|
+
}
|
|
8019
|
+
device.inputLastSeenAt = new Date().toISOString();
|
|
8020
|
+
if (message.type === 'input.applied' || message.type === 'input.error') {
|
|
8021
|
+
acknowledgePendingDedicatedInput(device, message);
|
|
8022
|
+
handleRemoteInputOutcome(device, {
|
|
7871
8023
|
...message,
|
|
7872
8024
|
agentApplyMs: Number(message.agentApplyMs || 0) || 0
|
|
7873
8025
|
}, 'input');
|
|
@@ -9026,15 +9178,40 @@ export function createRemoteHub(options = {}) {
|
|
|
9026
9178
|
};
|
|
9027
9179
|
}
|
|
9028
9180
|
|
|
9029
|
-
function getCurrentInputWriteOwner(device) {
|
|
9181
|
+
function getCurrentInputWriteOwner(device) {
|
|
9030
9182
|
if (!device?.inputSocket
|
|
9031
9183
|
|| device.inputSocket.destroyed
|
|
9032
9184
|
|| !device.inputWriteOwner
|
|
9033
9185
|
|| !safeString(device.inputSocketConnectionId, 128)) {
|
|
9034
9186
|
return null;
|
|
9035
9187
|
}
|
|
9036
|
-
return device.inputWriteOwner;
|
|
9037
|
-
}
|
|
9188
|
+
return device.inputWriteOwner;
|
|
9189
|
+
}
|
|
9190
|
+
|
|
9191
|
+
function getInputRouteState(deviceId) {
|
|
9192
|
+
const device = devices.get(String(deviceId || ''));
|
|
9193
|
+
if (!device || !device.connected || !device.socket || device.socket.destroyed) {
|
|
9194
|
+
return {
|
|
9195
|
+
deviceId: safeString(deviceId, 160),
|
|
9196
|
+
ready: false,
|
|
9197
|
+
dedicatedInputChannel: false,
|
|
9198
|
+
reason: 'device-not-connected'
|
|
9199
|
+
};
|
|
9200
|
+
}
|
|
9201
|
+
|
|
9202
|
+
const dedicatedInputChannel = readCapabilityFlag(
|
|
9203
|
+
device.capabilities,
|
|
9204
|
+
'dedicatedInputChannel');
|
|
9205
|
+
const ready = dedicatedInputChannel
|
|
9206
|
+
? !!getCurrentInputWriteOwner(device)
|
|
9207
|
+
: true;
|
|
9208
|
+
return {
|
|
9209
|
+
deviceId: device.deviceId,
|
|
9210
|
+
ready,
|
|
9211
|
+
dedicatedInputChannel,
|
|
9212
|
+
reason: ready ? 'ready' : 'input-channel-not-connected'
|
|
9213
|
+
};
|
|
9214
|
+
}
|
|
9038
9215
|
|
|
9039
9216
|
function sendInputControl(deviceId, input = {}) {
|
|
9040
9217
|
const device = devices.get(String(deviceId || ''));
|
|
@@ -9057,9 +9234,9 @@ export function createRemoteHub(options = {}) {
|
|
|
9057
9234
|
return { ok: false, error: 'missing-input-type' };
|
|
9058
9235
|
}
|
|
9059
9236
|
|
|
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.
|
|
9237
|
+
// A reset releases native mouse-button and keyboard state. The
|
|
9238
|
+
// current browser input owner must be able to send it even when its
|
|
9239
|
+
// former Control binding has just become stale during a monitor/capture-generation switch.
|
|
9063
9240
|
const currentInputOwner = safeString(device.inputOwnerConnectionId, 128);
|
|
9064
9241
|
const isCurrentOwnerKeyboardReset = normalized.type.toLowerCase() === 'keyboard.reset'
|
|
9065
9242
|
&& normalized.hubConnectionId
|
|
@@ -9238,9 +9415,10 @@ export function createRemoteHub(options = {}) {
|
|
|
9238
9415
|
&& normalized.hubConnectionId !== previousOwnerConnectionId;
|
|
9239
9416
|
const bindingChanged = previousInputBindingKey
|
|
9240
9417
|
&& previousInputBindingKey !== activeInputBindingKey;
|
|
9241
|
-
const writeBindingChanged = inputWriteOwner.getBindingKey() !== activeInputBindingKey;
|
|
9242
|
-
if (ownerChanged || bindingChanged || writeBindingChanged) {
|
|
9243
|
-
|
|
9418
|
+
const writeBindingChanged = inputWriteOwner.getBindingKey() !== activeInputBindingKey;
|
|
9419
|
+
if (ownerChanged || bindingChanged || writeBindingChanged) {
|
|
9420
|
+
clearPendingDedicatedInputAcks(device);
|
|
9421
|
+
if (ownerChanged) {
|
|
9244
9422
|
clearClipboardOperationsForDevice(
|
|
9245
9423
|
device,
|
|
9246
9424
|
'browser-input-owner-changed',
|
|
@@ -9265,11 +9443,12 @@ export function createRemoteHub(options = {}) {
|
|
|
9265
9443
|
return { ok: false, error: 'INPUT_CHANNEL_RECONNECTING' };
|
|
9266
9444
|
}
|
|
9267
9445
|
}
|
|
9268
|
-
const
|
|
9269
|
-
|
|
9270
|
-
|
|
9271
|
-
|
|
9272
|
-
|
|
9446
|
+
const hubForwardedAtEpochMs = Date.now();
|
|
9447
|
+
const sent = inputWriteOwner.enqueue({
|
|
9448
|
+
type: 'input.control',
|
|
9449
|
+
payload: {
|
|
9450
|
+
...normalized,
|
|
9451
|
+
hubForwardedAtEpochMs,
|
|
9273
9452
|
issuedAt: normalized.issuedAt || new Date().toISOString()
|
|
9274
9453
|
}
|
|
9275
9454
|
}, activeInputBindingKey);
|
|
@@ -9277,9 +9456,15 @@ export function createRemoteHub(options = {}) {
|
|
|
9277
9456
|
if (normalized.hubConnectionId) {
|
|
9278
9457
|
device.inputOwnerConnectionId = normalized.hubConnectionId;
|
|
9279
9458
|
}
|
|
9280
|
-
device.inputOwnerBindingKey = activeInputBindingKey;
|
|
9281
|
-
device.counters.commandsSent += 1;
|
|
9282
|
-
device.inputLastSeenAt = new Date().toISOString();
|
|
9459
|
+
device.inputOwnerBindingKey = activeInputBindingKey;
|
|
9460
|
+
device.counters.commandsSent += 1;
|
|
9461
|
+
device.inputLastSeenAt = new Date().toISOString();
|
|
9462
|
+
schedulePendingDedicatedInputAck(device, {
|
|
9463
|
+
...normalized,
|
|
9464
|
+
inputSocketConnectionId: device.inputSocketConnectionId,
|
|
9465
|
+
bindingKey: activeInputBindingKey,
|
|
9466
|
+
hubForwardedAtEpochMs
|
|
9467
|
+
});
|
|
9283
9468
|
if (clipboardOperationKey) {
|
|
9284
9469
|
const operation = clipboardOperations.get(clipboardOperationKey);
|
|
9285
9470
|
if (operation) {
|
|
@@ -9370,7 +9555,8 @@ export function createRemoteHub(options = {}) {
|
|
|
9370
9555
|
return { ok: false, error: 'input-owner-not-current' };
|
|
9371
9556
|
}
|
|
9372
9557
|
|
|
9373
|
-
clearClipboardOperationsForDevice(device, reason, owner);
|
|
9558
|
+
clearClipboardOperationsForDevice(device, reason, owner);
|
|
9559
|
+
clearPendingDedicatedInputAcks(device);
|
|
9374
9560
|
device.inputOwnerConnectionId = '';
|
|
9375
9561
|
device.inputOwnerBindingKey = '';
|
|
9376
9562
|
const inputSocket = device.inputSocket;
|
|
@@ -11835,8 +12021,9 @@ export function createRemoteHub(options = {}) {
|
|
|
11835
12021
|
sendClipboardCommand,
|
|
11836
12022
|
refreshDevicePolicies,
|
|
11837
12023
|
sendLegacyClientUpdate,
|
|
11838
|
-
sendInputControl,
|
|
11839
|
-
|
|
12024
|
+
sendInputControl,
|
|
12025
|
+
getInputRouteState,
|
|
12026
|
+
releaseInputOwner,
|
|
11840
12027
|
notifyAgentProgress,
|
|
11841
12028
|
requestAgentTask,
|
|
11842
12029
|
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
|