@livedesk/hub 0.1.51 → 0.1.53
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 +267 -72
- package/src/server.js +4 -3
package/package.json
CHANGED
package/src/remote-hub.js
CHANGED
|
@@ -320,11 +320,14 @@ function serializeRemoteInputWrite(socket, payload) {
|
|
|
320
320
|
* - Ordered transitions: keyboard and pointer-button events are never
|
|
321
321
|
* coalesced. If their bounded queue cannot retain order, the input socket is
|
|
322
322
|
* closed so the Agent releases native pressed state.
|
|
323
|
-
* - High-rate input: only adjacent pointermove/mousemove/wheel samples from the
|
|
324
|
-
* same binding may replace an older queued sample.
|
|
325
|
-
* -
|
|
326
|
-
*
|
|
327
|
-
|
|
323
|
+
* - High-rate input: only adjacent pointermove/mousemove/wheel samples from the
|
|
324
|
+
* same binding may replace an older queued sample.
|
|
325
|
+
* - Acknowledgement health: an acknowledged sample becomes a deadline
|
|
326
|
+
* checkpoint only after that exact message is accepted by the socket write.
|
|
327
|
+
* A sample replaced or dropped in the userland queue owns no timer.
|
|
328
|
+
* - Bounds: one drain listener, one drain deadline, at most 16 userland items,
|
|
329
|
+
* 32 KiB queued bytes, and 64 KiB socket writableLength.
|
|
330
|
+
*/
|
|
328
331
|
export function createBoundedRemoteInputWriter(options = {}) {
|
|
329
332
|
const socket = options.socket;
|
|
330
333
|
const connectionId = safeString(options.connectionId, 128);
|
|
@@ -357,9 +360,12 @@ export function createBoundedRemoteInputWriter(options = {}) {
|
|
|
357
360
|
const isOwnerCurrent = typeof options.isOwnerCurrent === 'function'
|
|
358
361
|
? options.isOwnerCurrent
|
|
359
362
|
: () => true;
|
|
360
|
-
const onFailClosed = typeof options.onFailClosed === 'function'
|
|
361
|
-
? options.onFailClosed
|
|
362
|
-
: () => {};
|
|
363
|
+
const onFailClosed = typeof options.onFailClosed === 'function'
|
|
364
|
+
? options.onFailClosed
|
|
365
|
+
: () => {};
|
|
366
|
+
const onAcknowledgedWrite = typeof options.onAcknowledgedWrite === 'function'
|
|
367
|
+
? options.onAcknowledgedWrite
|
|
368
|
+
: () => {};
|
|
363
369
|
|
|
364
370
|
let closed = false;
|
|
365
371
|
let closeReason = '';
|
|
@@ -501,16 +507,23 @@ export function createBoundedRemoteInputWriter(options = {}) {
|
|
|
501
507
|
if (writableLength() > maxWritableBytes) {
|
|
502
508
|
return failClosed('input-writable-length-exceeded');
|
|
503
509
|
}
|
|
504
|
-
if (writable === false && !armDrainOwner()) {
|
|
505
|
-
return {
|
|
506
|
-
ok: false,
|
|
507
|
-
error: closeReason || 'INPUT_CHANNEL_RECONNECTING',
|
|
508
|
-
failClosed: true
|
|
509
|
-
};
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
510
|
+
if (writable === false && !armDrainOwner()) {
|
|
511
|
+
return {
|
|
512
|
+
ok: false,
|
|
513
|
+
error: closeReason || 'INPUT_CHANNEL_RECONNECTING',
|
|
514
|
+
failClosed: true
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
if (item.ackRequested) {
|
|
518
|
+
try {
|
|
519
|
+
onAcknowledgedWrite(item.payload, item.bindingKey);
|
|
520
|
+
} catch {
|
|
521
|
+
return failClosed('input-ack-write-observer-failed');
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
ok: true,
|
|
526
|
+
queued: false,
|
|
514
527
|
backpressured: writable === false
|
|
515
528
|
};
|
|
516
529
|
}
|
|
@@ -613,12 +626,14 @@ export function createBoundedRemoteInputWriter(options = {}) {
|
|
|
613
626
|
return failClosed('input-transition-message-too-large');
|
|
614
627
|
}
|
|
615
628
|
|
|
616
|
-
const item = {
|
|
617
|
-
payload,
|
|
618
|
-
bindingKey: normalizedBindingKey,
|
|
619
|
-
serialized,
|
|
620
|
-
coalescible
|
|
621
|
-
|
|
629
|
+
const item = {
|
|
630
|
+
payload,
|
|
631
|
+
bindingKey: normalizedBindingKey,
|
|
632
|
+
serialized,
|
|
633
|
+
coalescible,
|
|
634
|
+
ackRequested: payload?.payload?.requestAck === true
|
|
635
|
+
|| payload?.requestAck === true
|
|
636
|
+
};
|
|
622
637
|
if (blocked || queue.length > 0 || writableLength() >= maxWritableBytes) {
|
|
623
638
|
const queued = queueItem(item);
|
|
624
639
|
if (queued.ok && !blocked && !armDrainOwner()) {
|
|
@@ -4288,13 +4303,14 @@ export function createRemoteHub(options = {}) {
|
|
|
4288
4303
|
}
|
|
4289
4304
|
}
|
|
4290
4305
|
|
|
4291
|
-
function closeInputSocket(device, reason = 'input-socket-closed') {
|
|
4306
|
+
function closeInputSocket(device, reason = 'input-socket-closed') {
|
|
4292
4307
|
const socket = device?.inputSocket;
|
|
4293
4308
|
if (!socket) {
|
|
4294
4309
|
return;
|
|
4295
4310
|
}
|
|
4296
4311
|
|
|
4297
|
-
const writeOwner = device.inputWriteOwner;
|
|
4312
|
+
const writeOwner = device.inputWriteOwner;
|
|
4313
|
+
clearPendingDedicatedInputAcks(device);
|
|
4298
4314
|
clearClipboardOperationsForDevice(device, reason);
|
|
4299
4315
|
device.inputSocket = null;
|
|
4300
4316
|
device.inputWriteOwner = null;
|
|
@@ -4317,7 +4333,7 @@ export function createRemoteHub(options = {}) {
|
|
|
4317
4333
|
}
|
|
4318
4334
|
}
|
|
4319
4335
|
|
|
4320
|
-
function detachInputSocket(socket, reason = 'input-socket-closed') {
|
|
4336
|
+
function detachInputSocket(socket, reason = 'input-socket-closed') {
|
|
4321
4337
|
const deviceId = inputSockets.get(socket);
|
|
4322
4338
|
inputSockets.delete(socket);
|
|
4323
4339
|
if (!deviceId) {
|
|
@@ -4329,7 +4345,8 @@ export function createRemoteHub(options = {}) {
|
|
|
4329
4345
|
return;
|
|
4330
4346
|
}
|
|
4331
4347
|
|
|
4332
|
-
const writeOwner = device.inputWriteOwner;
|
|
4348
|
+
const writeOwner = device.inputWriteOwner;
|
|
4349
|
+
clearPendingDedicatedInputAcks(device);
|
|
4333
4350
|
clearClipboardOperationsForDevice(device, reason);
|
|
4334
4351
|
device.inputSocket = null;
|
|
4335
4352
|
device.inputWriteOwner = null;
|
|
@@ -4948,7 +4965,8 @@ export function createRemoteHub(options = {}) {
|
|
|
4948
4965
|
return null;
|
|
4949
4966
|
}
|
|
4950
4967
|
|
|
4951
|
-
closeInputSocket(device, 'replaced-by-new-input-socket');
|
|
4968
|
+
closeInputSocket(device, 'replaced-by-new-input-socket');
|
|
4969
|
+
clearPendingDedicatedInputAcks(device);
|
|
4952
4970
|
|
|
4953
4971
|
const now = new Date().toISOString();
|
|
4954
4972
|
const inputSocketConnectionId = crypto.randomUUID();
|
|
@@ -4979,22 +4997,38 @@ export function createRemoteHub(options = {}) {
|
|
|
4979
4997
|
&& device.inputSocket === socket
|
|
4980
4998
|
&& device.inputSocketConnectionId === inputSocketConnectionId
|
|
4981
4999
|
&& device.inputWriteOwner === writeOwner,
|
|
4982
|
-
onFailClosed: reason => {
|
|
4983
|
-
if (device.inputWriteOwner !== writeOwner
|
|
4984
|
-
|| device.inputSocket !== socket
|
|
4985
|
-
|| device.inputSocketConnectionId !== inputSocketConnectionId) {
|
|
4986
|
-
return;
|
|
4987
|
-
}
|
|
4988
|
-
device
|
|
4989
|
-
device.
|
|
4990
|
-
device.
|
|
5000
|
+
onFailClosed: reason => {
|
|
5001
|
+
if (device.inputWriteOwner !== writeOwner
|
|
5002
|
+
|| device.inputSocket !== socket
|
|
5003
|
+
|| device.inputSocketConnectionId !== inputSocketConnectionId) {
|
|
5004
|
+
return;
|
|
5005
|
+
}
|
|
5006
|
+
clearPendingDedicatedInputAcks(device);
|
|
5007
|
+
device.inputWriteOwner = null;
|
|
5008
|
+
device.inputSocket = null;
|
|
5009
|
+
device.inputSocketConnectionId = '';
|
|
4991
5010
|
device.inputOwnerConnectionId = '';
|
|
4992
5011
|
device.inputOwnerBindingKey = '';
|
|
4993
5012
|
inputSockets.delete(socket);
|
|
4994
|
-
device.inputLastSeenAt = new Date().toISOString();
|
|
4995
|
-
emitRemoteEvent('RemoteInputSocketDisconnected', device, { reason });
|
|
4996
|
-
}
|
|
4997
|
-
|
|
5013
|
+
device.inputLastSeenAt = new Date().toISOString();
|
|
5014
|
+
emitRemoteEvent('RemoteInputSocketDisconnected', device, { reason });
|
|
5015
|
+
},
|
|
5016
|
+
onAcknowledgedWrite: (message, bindingKey) => {
|
|
5017
|
+
if (device.inputWriteOwner !== writeOwner
|
|
5018
|
+
|| device.inputSocket !== socket
|
|
5019
|
+
|| device.inputSocketConnectionId !== inputSocketConnectionId) {
|
|
5020
|
+
return;
|
|
5021
|
+
}
|
|
5022
|
+
const pending = message?.payload && typeof message.payload === 'object'
|
|
5023
|
+
? message.payload
|
|
5024
|
+
: message;
|
|
5025
|
+
schedulePendingDedicatedInputAck(device, {
|
|
5026
|
+
...pending,
|
|
5027
|
+
inputSocketConnectionId,
|
|
5028
|
+
bindingKey
|
|
5029
|
+
});
|
|
5030
|
+
}
|
|
5031
|
+
});
|
|
4998
5032
|
device.inputWriteOwner = writeOwner;
|
|
4999
5033
|
|
|
5000
5034
|
emitRemoteEvent('RemoteInputSocketConnected', device);
|
|
@@ -5392,7 +5426,7 @@ export function createRemoteHub(options = {}) {
|
|
|
5392
5426
|
return failed;
|
|
5393
5427
|
}
|
|
5394
5428
|
|
|
5395
|
-
function ensurePendingInputFallbacks(device) {
|
|
5429
|
+
function ensurePendingInputFallbacks(device) {
|
|
5396
5430
|
if (!(device?.pendingInputFallbacks instanceof Map)) {
|
|
5397
5431
|
Object.defineProperty(device, 'pendingInputFallbacks', {
|
|
5398
5432
|
value: new Map(),
|
|
@@ -5409,8 +5443,151 @@ export function createRemoteHub(options = {}) {
|
|
|
5409
5443
|
writable: true
|
|
5410
5444
|
});
|
|
5411
5445
|
}
|
|
5412
|
-
return device.pendingInputFallbacks;
|
|
5413
|
-
}
|
|
5446
|
+
return device.pendingInputFallbacks;
|
|
5447
|
+
}
|
|
5448
|
+
|
|
5449
|
+
function ensurePendingDedicatedInputAckState(device) {
|
|
5450
|
+
if (!device?.pendingDedicatedInputAckState) {
|
|
5451
|
+
Object.defineProperty(device, 'pendingDedicatedInputAckState', {
|
|
5452
|
+
value: { current: null, next: null, timeoutTimer: null },
|
|
5453
|
+
enumerable: false,
|
|
5454
|
+
configurable: true,
|
|
5455
|
+
writable: true
|
|
5456
|
+
});
|
|
5457
|
+
}
|
|
5458
|
+
return device.pendingDedicatedInputAckState;
|
|
5459
|
+
}
|
|
5460
|
+
|
|
5461
|
+
function clearPendingDedicatedInputAcks(device) {
|
|
5462
|
+
if (!device?.pendingDedicatedInputAckState) {
|
|
5463
|
+
return;
|
|
5464
|
+
}
|
|
5465
|
+
const state = device.pendingDedicatedInputAckState;
|
|
5466
|
+
if (state.timeoutTimer) {
|
|
5467
|
+
clearTimeout(state.timeoutTimer);
|
|
5468
|
+
}
|
|
5469
|
+
state.timeoutTimer = null;
|
|
5470
|
+
state.current = null;
|
|
5471
|
+
state.next = null;
|
|
5472
|
+
}
|
|
5473
|
+
|
|
5474
|
+
function dedicatedInputAckMatches(pending, message = {}) {
|
|
5475
|
+
if (!pending) {
|
|
5476
|
+
return false;
|
|
5477
|
+
}
|
|
5478
|
+
const result = message?.result && typeof message.result === 'object'
|
|
5479
|
+
? message.result
|
|
5480
|
+
: {};
|
|
5481
|
+
const inputEventId = safeString(
|
|
5482
|
+
message.inputEventId
|
|
5483
|
+
|| message.InputEventId
|
|
5484
|
+
|| result.inputEventId
|
|
5485
|
+
|| result.InputEventId,
|
|
5486
|
+
128);
|
|
5487
|
+
const hubConnectionId = safeString(
|
|
5488
|
+
message.hubConnectionId
|
|
5489
|
+
|| message.HubConnectionId
|
|
5490
|
+
|| result.hubConnectionId
|
|
5491
|
+
|| result.HubConnectionId,
|
|
5492
|
+
128);
|
|
5493
|
+
const inputSeq = Number(
|
|
5494
|
+
message.inputSeq
|
|
5495
|
+
?? message.InputSeq
|
|
5496
|
+
?? result.inputSeq
|
|
5497
|
+
?? result.InputSeq
|
|
5498
|
+
?? 0) || 0;
|
|
5499
|
+
if (pending.inputEventId && inputEventId) {
|
|
5500
|
+
return pending.inputEventId === inputEventId
|
|
5501
|
+
&& pending.hubConnectionId === hubConnectionId;
|
|
5502
|
+
}
|
|
5503
|
+
return pending.inputSeq > 0
|
|
5504
|
+
&& pending.inputSeq === inputSeq
|
|
5505
|
+
&& pending.hubConnectionId === hubConnectionId;
|
|
5506
|
+
}
|
|
5507
|
+
|
|
5508
|
+
function armPendingDedicatedInputAck(device) {
|
|
5509
|
+
const state = ensurePendingDedicatedInputAckState(device);
|
|
5510
|
+
if (state.timeoutTimer) {
|
|
5511
|
+
clearTimeout(state.timeoutTimer);
|
|
5512
|
+
state.timeoutTimer = null;
|
|
5513
|
+
}
|
|
5514
|
+
const pending = state.current;
|
|
5515
|
+
if (!pending) {
|
|
5516
|
+
return;
|
|
5517
|
+
}
|
|
5518
|
+
const remainingMs = Math.max(0, pending.deadlineAtEpochMs - Date.now());
|
|
5519
|
+
state.timeoutTimer = setTimeout(() => {
|
|
5520
|
+
state.timeoutTimer = null;
|
|
5521
|
+
if (state.current !== pending) {
|
|
5522
|
+
return;
|
|
5523
|
+
}
|
|
5524
|
+
state.current = null;
|
|
5525
|
+
state.next = null;
|
|
5526
|
+
if (device.inputSocketConnectionId !== pending.inputSocketConnectionId) {
|
|
5527
|
+
return;
|
|
5528
|
+
}
|
|
5529
|
+
emitRemoteInputError(
|
|
5530
|
+
device,
|
|
5531
|
+
{ error: 'remote-input-timeout' },
|
|
5532
|
+
pending,
|
|
5533
|
+
'remote-input-timeout');
|
|
5534
|
+
closeInputSocket(device, 'input-ack-timeout');
|
|
5535
|
+
}, remainingMs);
|
|
5536
|
+
state.timeoutTimer.unref?.();
|
|
5537
|
+
}
|
|
5538
|
+
|
|
5539
|
+
function schedulePendingDedicatedInputAck(device, pending) {
|
|
5540
|
+
if (pending?.requestAck !== true
|
|
5541
|
+
|| !Number.isSafeInteger(pending.inputSeq)
|
|
5542
|
+
|| pending.inputSeq <= 0) {
|
|
5543
|
+
return;
|
|
5544
|
+
}
|
|
5545
|
+
const state = ensurePendingDedicatedInputAckState(device);
|
|
5546
|
+
const record = {
|
|
5547
|
+
...pending,
|
|
5548
|
+
deadlineAtEpochMs: Date.now() + inputAckTimeoutMs
|
|
5549
|
+
};
|
|
5550
|
+
if (!state.current) {
|
|
5551
|
+
state.current = record;
|
|
5552
|
+
state.next = null;
|
|
5553
|
+
armPendingDedicatedInputAck(device);
|
|
5554
|
+
return;
|
|
5555
|
+
}
|
|
5556
|
+
if (state.current.inputSocketConnectionId !== record.inputSocketConnectionId
|
|
5557
|
+
|| state.current.bindingKey !== record.bindingKey) {
|
|
5558
|
+
clearPendingDedicatedInputAcks(device);
|
|
5559
|
+
state.current = record;
|
|
5560
|
+
armPendingDedicatedInputAck(device);
|
|
5561
|
+
return;
|
|
5562
|
+
}
|
|
5563
|
+
// Keep only one later checkpoint. If the current acknowledgement
|
|
5564
|
+
// arrives, this latest sampled input becomes the next exact deadline.
|
|
5565
|
+
state.next = record;
|
|
5566
|
+
}
|
|
5567
|
+
|
|
5568
|
+
function acknowledgePendingDedicatedInput(device, message = {}) {
|
|
5569
|
+
const state = device?.pendingDedicatedInputAckState;
|
|
5570
|
+
if (!state?.current) {
|
|
5571
|
+
return false;
|
|
5572
|
+
}
|
|
5573
|
+
if (dedicatedInputAckMatches(state.current, message)) {
|
|
5574
|
+
if (state.timeoutTimer) {
|
|
5575
|
+
clearTimeout(state.timeoutTimer);
|
|
5576
|
+
state.timeoutTimer = null;
|
|
5577
|
+
}
|
|
5578
|
+
state.current = state.next;
|
|
5579
|
+
state.next = null;
|
|
5580
|
+
armPendingDedicatedInputAck(device);
|
|
5581
|
+
return true;
|
|
5582
|
+
}
|
|
5583
|
+
if (dedicatedInputAckMatches(state.next, message)) {
|
|
5584
|
+
// A later ordered acknowledgement proves that the input reader is
|
|
5585
|
+
// still draining, even if an earlier diagnostic response vanished.
|
|
5586
|
+
clearPendingDedicatedInputAcks(device);
|
|
5587
|
+
return true;
|
|
5588
|
+
}
|
|
5589
|
+
return false;
|
|
5590
|
+
}
|
|
5414
5591
|
|
|
5415
5592
|
function pruneCompletedInputFallbacks(device, now = Date.now()) {
|
|
5416
5593
|
ensurePendingInputFallbacks(device);
|
|
@@ -5611,11 +5788,17 @@ export function createRemoteHub(options = {}) {
|
|
|
5611
5788
|
});
|
|
5612
5789
|
}
|
|
5613
5790
|
|
|
5614
|
-
function emitRemoteInputError(
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5791
|
+
function emitRemoteInputError(
|
|
5792
|
+
device,
|
|
5793
|
+
message = {},
|
|
5794
|
+
pending = null,
|
|
5795
|
+
fallbackError = 'remote-input-failed',
|
|
5796
|
+
errorKind = 'route') {
|
|
5797
|
+
const outcome = buildRemoteInputOutcome(device, message, pending);
|
|
5798
|
+
emitEvent('RemoteInputError', {
|
|
5799
|
+
...outcome,
|
|
5800
|
+
errorKind: errorKind === 'native-apply' ? 'native-apply' : 'route',
|
|
5801
|
+
error: safeString(
|
|
5619
5802
|
message.error
|
|
5620
5803
|
|| message.Error
|
|
5621
5804
|
|| message.result?.error
|
|
@@ -5645,9 +5828,9 @@ export function createRemoteHub(options = {}) {
|
|
|
5645
5828
|
// session cannot acknowledge input from the current control session.
|
|
5646
5829
|
if (!pending && source === 'main') {
|
|
5647
5830
|
return false;
|
|
5648
|
-
}
|
|
5649
|
-
if (message.type === 'input.error') {
|
|
5650
|
-
emitRemoteInputError(device, message, pending);
|
|
5831
|
+
}
|
|
5832
|
+
if (message.type === 'input.error') {
|
|
5833
|
+
emitRemoteInputError(device, message, pending, 'remote-input-failed', 'native-apply');
|
|
5651
5834
|
} else {
|
|
5652
5835
|
emitRemoteInputApplied(device, message, pending);
|
|
5653
5836
|
}
|
|
@@ -5667,8 +5850,13 @@ export function createRemoteHub(options = {}) {
|
|
|
5667
5850
|
|| !!error
|
|
5668
5851
|
|| result.ok === false
|
|
5669
5852
|
|| safeString(result.status, 40).toLowerCase() === 'failed';
|
|
5670
|
-
if (failed) {
|
|
5671
|
-
emitRemoteInputError(
|
|
5853
|
+
if (failed) {
|
|
5854
|
+
emitRemoteInputError(
|
|
5855
|
+
device,
|
|
5856
|
+
{ ...message, error: error || 'remote-input-failed' },
|
|
5857
|
+
completed,
|
|
5858
|
+
'remote-input-failed',
|
|
5859
|
+
'native-apply');
|
|
5672
5860
|
} else {
|
|
5673
5861
|
emitRemoteInputApplied(device, message, completed);
|
|
5674
5862
|
}
|
|
@@ -7866,10 +8054,14 @@ export function createRemoteHub(options = {}) {
|
|
|
7866
8054
|
return;
|
|
7867
8055
|
}
|
|
7868
8056
|
|
|
7869
|
-
if (state.inputOnly) {
|
|
7870
|
-
device.
|
|
7871
|
-
|
|
7872
|
-
|
|
8057
|
+
if (state.inputOnly) {
|
|
8058
|
+
if (device.inputSocket !== socket) {
|
|
8059
|
+
return;
|
|
8060
|
+
}
|
|
8061
|
+
device.inputLastSeenAt = new Date().toISOString();
|
|
8062
|
+
if (message.type === 'input.applied' || message.type === 'input.error') {
|
|
8063
|
+
acknowledgePendingDedicatedInput(device, message);
|
|
8064
|
+
handleRemoteInputOutcome(device, {
|
|
7873
8065
|
...message,
|
|
7874
8066
|
agentApplyMs: Number(message.agentApplyMs || 0) || 0
|
|
7875
8067
|
}, 'input');
|
|
@@ -9265,9 +9457,10 @@ export function createRemoteHub(options = {}) {
|
|
|
9265
9457
|
&& normalized.hubConnectionId !== previousOwnerConnectionId;
|
|
9266
9458
|
const bindingChanged = previousInputBindingKey
|
|
9267
9459
|
&& previousInputBindingKey !== activeInputBindingKey;
|
|
9268
|
-
const writeBindingChanged = inputWriteOwner.getBindingKey() !== activeInputBindingKey;
|
|
9269
|
-
if (ownerChanged || bindingChanged || writeBindingChanged) {
|
|
9270
|
-
|
|
9460
|
+
const writeBindingChanged = inputWriteOwner.getBindingKey() !== activeInputBindingKey;
|
|
9461
|
+
if (ownerChanged || bindingChanged || writeBindingChanged) {
|
|
9462
|
+
clearPendingDedicatedInputAcks(device);
|
|
9463
|
+
if (ownerChanged) {
|
|
9271
9464
|
clearClipboardOperationsForDevice(
|
|
9272
9465
|
device,
|
|
9273
9466
|
'browser-input-owner-changed',
|
|
@@ -9292,11 +9485,12 @@ export function createRemoteHub(options = {}) {
|
|
|
9292
9485
|
return { ok: false, error: 'INPUT_CHANNEL_RECONNECTING' };
|
|
9293
9486
|
}
|
|
9294
9487
|
}
|
|
9295
|
-
const
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9299
|
-
|
|
9488
|
+
const hubForwardedAtEpochMs = Date.now();
|
|
9489
|
+
const sent = inputWriteOwner.enqueue({
|
|
9490
|
+
type: 'input.control',
|
|
9491
|
+
payload: {
|
|
9492
|
+
...normalized,
|
|
9493
|
+
hubForwardedAtEpochMs,
|
|
9300
9494
|
issuedAt: normalized.issuedAt || new Date().toISOString()
|
|
9301
9495
|
}
|
|
9302
9496
|
}, activeInputBindingKey);
|
|
@@ -9304,10 +9498,10 @@ export function createRemoteHub(options = {}) {
|
|
|
9304
9498
|
if (normalized.hubConnectionId) {
|
|
9305
9499
|
device.inputOwnerConnectionId = normalized.hubConnectionId;
|
|
9306
9500
|
}
|
|
9307
|
-
device.inputOwnerBindingKey = activeInputBindingKey;
|
|
9308
|
-
device.counters.commandsSent += 1;
|
|
9309
|
-
device.inputLastSeenAt = new Date().toISOString();
|
|
9310
|
-
if (clipboardOperationKey) {
|
|
9501
|
+
device.inputOwnerBindingKey = activeInputBindingKey;
|
|
9502
|
+
device.counters.commandsSent += 1;
|
|
9503
|
+
device.inputLastSeenAt = new Date().toISOString();
|
|
9504
|
+
if (clipboardOperationKey) {
|
|
9311
9505
|
const operation = clipboardOperations.get(clipboardOperationKey);
|
|
9312
9506
|
if (operation) {
|
|
9313
9507
|
operation.status = 'pasting';
|
|
@@ -9397,7 +9591,8 @@ export function createRemoteHub(options = {}) {
|
|
|
9397
9591
|
return { ok: false, error: 'input-owner-not-current' };
|
|
9398
9592
|
}
|
|
9399
9593
|
|
|
9400
|
-
clearClipboardOperationsForDevice(device, reason, owner);
|
|
9594
|
+
clearClipboardOperationsForDevice(device, reason, owner);
|
|
9595
|
+
clearPendingDedicatedInputAcks(device);
|
|
9401
9596
|
device.inputOwnerConnectionId = '';
|
|
9402
9597
|
device.inputOwnerBindingKey = '';
|
|
9403
9598
|
const inputSocket = device.inputSocket;
|
package/src/server.js
CHANGED
|
@@ -5927,13 +5927,13 @@ inputWss.on('connection', ws => {
|
|
|
5927
5927
|
try {
|
|
5928
5928
|
payload = JSON.parse(Buffer.isBuffer(data) ? data.toString('utf8') : String(data || ''));
|
|
5929
5929
|
} catch {
|
|
5930
|
-
sendJson(ws, { type: 'RemoteInputError', error: 'invalid-json' });
|
|
5930
|
+
sendJson(ws, { type: 'RemoteInputError', errorKind: 'route', error: 'invalid-json' });
|
|
5931
5931
|
return;
|
|
5932
5932
|
}
|
|
5933
5933
|
const deviceId = String(payload?.deviceId || '').trim();
|
|
5934
5934
|
if (payload?.type === 'watch') {
|
|
5935
5935
|
if (!deviceId) {
|
|
5936
|
-
sendJson(ws, { type: 'RemoteInputError', error: 'device-id-required' });
|
|
5936
|
+
sendJson(ws, { type: 'RemoteInputError', errorKind: 'route', error: 'device-id-required' });
|
|
5937
5937
|
return;
|
|
5938
5938
|
}
|
|
5939
5939
|
for (const previousDeviceId of ws.liveDeskInputDeviceIds) {
|
|
@@ -5970,7 +5970,8 @@ inputWss.on('connection', ws => {
|
|
|
5970
5970
|
return;
|
|
5971
5971
|
}
|
|
5972
5972
|
sendJson(ws, {
|
|
5973
|
-
type: result?.ok ? 'RemoteInputQueued' : 'RemoteInputError',
|
|
5973
|
+
type: result?.ok ? 'RemoteInputQueued' : 'RemoteInputError',
|
|
5974
|
+
errorKind: result?.ok ? '' : 'route',
|
|
5974
5975
|
requestId: payload?.requestId || '',
|
|
5975
5976
|
deviceId,
|
|
5976
5977
|
inputSeq: Number(input?.inputSeq || 0) || 0,
|