@livedesk/hub 0.1.51 → 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 +1 -1
- package/src/remote-hub.js +183 -24
package/package.json
CHANGED
package/src/remote-hub.js
CHANGED
|
@@ -4288,13 +4288,14 @@ 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;
|
|
@@ -4317,7 +4318,7 @@ export function createRemoteHub(options = {}) {
|
|
|
4317
4318
|
}
|
|
4318
4319
|
}
|
|
4319
4320
|
|
|
4320
|
-
function detachInputSocket(socket, reason = 'input-socket-closed') {
|
|
4321
|
+
function detachInputSocket(socket, reason = 'input-socket-closed') {
|
|
4321
4322
|
const deviceId = inputSockets.get(socket);
|
|
4322
4323
|
inputSockets.delete(socket);
|
|
4323
4324
|
if (!deviceId) {
|
|
@@ -4329,7 +4330,8 @@ export function createRemoteHub(options = {}) {
|
|
|
4329
4330
|
return;
|
|
4330
4331
|
}
|
|
4331
4332
|
|
|
4332
|
-
const writeOwner = device.inputWriteOwner;
|
|
4333
|
+
const writeOwner = device.inputWriteOwner;
|
|
4334
|
+
clearPendingDedicatedInputAcks(device);
|
|
4333
4335
|
clearClipboardOperationsForDevice(device, reason);
|
|
4334
4336
|
device.inputSocket = null;
|
|
4335
4337
|
device.inputWriteOwner = null;
|
|
@@ -4948,7 +4950,8 @@ export function createRemoteHub(options = {}) {
|
|
|
4948
4950
|
return null;
|
|
4949
4951
|
}
|
|
4950
4952
|
|
|
4951
|
-
closeInputSocket(device, 'replaced-by-new-input-socket');
|
|
4953
|
+
closeInputSocket(device, 'replaced-by-new-input-socket');
|
|
4954
|
+
clearPendingDedicatedInputAcks(device);
|
|
4952
4955
|
|
|
4953
4956
|
const now = new Date().toISOString();
|
|
4954
4957
|
const inputSocketConnectionId = crypto.randomUUID();
|
|
@@ -5392,7 +5395,7 @@ export function createRemoteHub(options = {}) {
|
|
|
5392
5395
|
return failed;
|
|
5393
5396
|
}
|
|
5394
5397
|
|
|
5395
|
-
function ensurePendingInputFallbacks(device) {
|
|
5398
|
+
function ensurePendingInputFallbacks(device) {
|
|
5396
5399
|
if (!(device?.pendingInputFallbacks instanceof Map)) {
|
|
5397
5400
|
Object.defineProperty(device, 'pendingInputFallbacks', {
|
|
5398
5401
|
value: new Map(),
|
|
@@ -5409,8 +5412,151 @@ export function createRemoteHub(options = {}) {
|
|
|
5409
5412
|
writable: true
|
|
5410
5413
|
});
|
|
5411
5414
|
}
|
|
5412
|
-
return device.pendingInputFallbacks;
|
|
5413
|
-
}
|
|
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
|
+
}
|
|
5414
5560
|
|
|
5415
5561
|
function pruneCompletedInputFallbacks(device, now = Date.now()) {
|
|
5416
5562
|
ensurePendingInputFallbacks(device);
|
|
@@ -7866,10 +8012,14 @@ export function createRemoteHub(options = {}) {
|
|
|
7866
8012
|
return;
|
|
7867
8013
|
}
|
|
7868
8014
|
|
|
7869
|
-
if (state.inputOnly) {
|
|
7870
|
-
device.
|
|
7871
|
-
|
|
7872
|
-
|
|
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, {
|
|
7873
8023
|
...message,
|
|
7874
8024
|
agentApplyMs: Number(message.agentApplyMs || 0) || 0
|
|
7875
8025
|
}, 'input');
|
|
@@ -9265,9 +9415,10 @@ export function createRemoteHub(options = {}) {
|
|
|
9265
9415
|
&& normalized.hubConnectionId !== previousOwnerConnectionId;
|
|
9266
9416
|
const bindingChanged = previousInputBindingKey
|
|
9267
9417
|
&& previousInputBindingKey !== activeInputBindingKey;
|
|
9268
|
-
const writeBindingChanged = inputWriteOwner.getBindingKey() !== activeInputBindingKey;
|
|
9269
|
-
if (ownerChanged || bindingChanged || writeBindingChanged) {
|
|
9270
|
-
|
|
9418
|
+
const writeBindingChanged = inputWriteOwner.getBindingKey() !== activeInputBindingKey;
|
|
9419
|
+
if (ownerChanged || bindingChanged || writeBindingChanged) {
|
|
9420
|
+
clearPendingDedicatedInputAcks(device);
|
|
9421
|
+
if (ownerChanged) {
|
|
9271
9422
|
clearClipboardOperationsForDevice(
|
|
9272
9423
|
device,
|
|
9273
9424
|
'browser-input-owner-changed',
|
|
@@ -9292,11 +9443,12 @@ export function createRemoteHub(options = {}) {
|
|
|
9292
9443
|
return { ok: false, error: 'INPUT_CHANNEL_RECONNECTING' };
|
|
9293
9444
|
}
|
|
9294
9445
|
}
|
|
9295
|
-
const
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9299
|
-
|
|
9446
|
+
const hubForwardedAtEpochMs = Date.now();
|
|
9447
|
+
const sent = inputWriteOwner.enqueue({
|
|
9448
|
+
type: 'input.control',
|
|
9449
|
+
payload: {
|
|
9450
|
+
...normalized,
|
|
9451
|
+
hubForwardedAtEpochMs,
|
|
9300
9452
|
issuedAt: normalized.issuedAt || new Date().toISOString()
|
|
9301
9453
|
}
|
|
9302
9454
|
}, activeInputBindingKey);
|
|
@@ -9304,9 +9456,15 @@ export function createRemoteHub(options = {}) {
|
|
|
9304
9456
|
if (normalized.hubConnectionId) {
|
|
9305
9457
|
device.inputOwnerConnectionId = normalized.hubConnectionId;
|
|
9306
9458
|
}
|
|
9307
|
-
device.inputOwnerBindingKey = activeInputBindingKey;
|
|
9308
|
-
device.counters.commandsSent += 1;
|
|
9309
|
-
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
|
+
});
|
|
9310
9468
|
if (clipboardOperationKey) {
|
|
9311
9469
|
const operation = clipboardOperations.get(clipboardOperationKey);
|
|
9312
9470
|
if (operation) {
|
|
@@ -9397,7 +9555,8 @@ export function createRemoteHub(options = {}) {
|
|
|
9397
9555
|
return { ok: false, error: 'input-owner-not-current' };
|
|
9398
9556
|
}
|
|
9399
9557
|
|
|
9400
|
-
clearClipboardOperationsForDevice(device, reason, owner);
|
|
9558
|
+
clearClipboardOperationsForDevice(device, reason, owner);
|
|
9559
|
+
clearPendingDedicatedInputAcks(device);
|
|
9401
9560
|
device.inputOwnerConnectionId = '';
|
|
9402
9561
|
device.inputOwnerBindingKey = '';
|
|
9403
9562
|
const inputSocket = device.inputSocket;
|