@livedesk/hub 0.1.52 → 0.1.54
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-clipboard-contract.mjs +4 -2
- package/src/remote-hub.js +94 -57
- package/src/server.js +4 -3
package/package.json
CHANGED
|
@@ -465,8 +465,10 @@ export function normalizeRemoteClipboardCommandResult(action, operationId, value
|
|
|
465
465
|
response.manifest = normalizeRemoteClipboardManifest(result.manifest, operationId);
|
|
466
466
|
response.kind = response.manifest.contentKind;
|
|
467
467
|
} else {
|
|
468
|
-
const
|
|
469
|
-
|
|
468
|
+
const explicitContentKind = String(result.contentKind || '').trim().toLowerCase();
|
|
469
|
+
const legacyKind = String(result.kind || '').trim().toLowerCase();
|
|
470
|
+
if (explicitContentKind) response.kind = normalizeContentKind(explicitContentKind);
|
|
471
|
+
else if (['text', 'image', 'files'].includes(legacyKind)) response.kind = legacyKind;
|
|
470
472
|
}
|
|
471
473
|
for (const key of ['receivedBytes', 'totalBytes']) {
|
|
472
474
|
if (result[key] !== undefined) {
|
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()) {
|
|
@@ -1319,9 +1334,10 @@ function normalizeRemoteInputEvent(value = {}) {
|
|
|
1319
1334
|
// input must prove the exact monitor owned by its capture generation.
|
|
1320
1335
|
monitorIndex,
|
|
1321
1336
|
normalizedX: Number.isFinite(normalizedX) ? Math.max(0, Math.min(1, normalizedX)) : undefined,
|
|
1322
|
-
normalizedY: Number.isFinite(normalizedY) ? Math.max(0, Math.min(1, normalizedY)) : undefined,
|
|
1323
|
-
button: safeString(input.button || input.Button, 24),
|
|
1324
|
-
|
|
1337
|
+
normalizedY: Number.isFinite(normalizedY) ? Math.max(0, Math.min(1, normalizedY)) : undefined,
|
|
1338
|
+
button: safeString(input.button || input.Button, 24),
|
|
1339
|
+
inputSource: safeString(input.inputSource || input.InputSource, 48).toLowerCase(),
|
|
1340
|
+
deltaX: Number.isFinite(deltaX) ? Math.max(-4096, Math.min(4096, deltaX)) : 0,
|
|
1325
1341
|
deltaY: Number.isFinite(deltaY) ? Math.max(-4096, Math.min(4096, deltaY)) : 0,
|
|
1326
1342
|
key,
|
|
1327
1343
|
code,
|
|
@@ -4982,22 +4998,38 @@ export function createRemoteHub(options = {}) {
|
|
|
4982
4998
|
&& device.inputSocket === socket
|
|
4983
4999
|
&& device.inputSocketConnectionId === inputSocketConnectionId
|
|
4984
5000
|
&& device.inputWriteOwner === writeOwner,
|
|
4985
|
-
onFailClosed: reason => {
|
|
4986
|
-
if (device.inputWriteOwner !== writeOwner
|
|
4987
|
-
|| device.inputSocket !== socket
|
|
4988
|
-
|| device.inputSocketConnectionId !== inputSocketConnectionId) {
|
|
4989
|
-
return;
|
|
4990
|
-
}
|
|
4991
|
-
device
|
|
4992
|
-
device.
|
|
4993
|
-
device.
|
|
5001
|
+
onFailClosed: reason => {
|
|
5002
|
+
if (device.inputWriteOwner !== writeOwner
|
|
5003
|
+
|| device.inputSocket !== socket
|
|
5004
|
+
|| device.inputSocketConnectionId !== inputSocketConnectionId) {
|
|
5005
|
+
return;
|
|
5006
|
+
}
|
|
5007
|
+
clearPendingDedicatedInputAcks(device);
|
|
5008
|
+
device.inputWriteOwner = null;
|
|
5009
|
+
device.inputSocket = null;
|
|
5010
|
+
device.inputSocketConnectionId = '';
|
|
4994
5011
|
device.inputOwnerConnectionId = '';
|
|
4995
5012
|
device.inputOwnerBindingKey = '';
|
|
4996
5013
|
inputSockets.delete(socket);
|
|
4997
|
-
device.inputLastSeenAt = new Date().toISOString();
|
|
4998
|
-
emitRemoteEvent('RemoteInputSocketDisconnected', device, { reason });
|
|
4999
|
-
}
|
|
5000
|
-
|
|
5014
|
+
device.inputLastSeenAt = new Date().toISOString();
|
|
5015
|
+
emitRemoteEvent('RemoteInputSocketDisconnected', device, { reason });
|
|
5016
|
+
},
|
|
5017
|
+
onAcknowledgedWrite: (message, bindingKey) => {
|
|
5018
|
+
if (device.inputWriteOwner !== writeOwner
|
|
5019
|
+
|| device.inputSocket !== socket
|
|
5020
|
+
|| device.inputSocketConnectionId !== inputSocketConnectionId) {
|
|
5021
|
+
return;
|
|
5022
|
+
}
|
|
5023
|
+
const pending = message?.payload && typeof message.payload === 'object'
|
|
5024
|
+
? message.payload
|
|
5025
|
+
: message;
|
|
5026
|
+
schedulePendingDedicatedInputAck(device, {
|
|
5027
|
+
...pending,
|
|
5028
|
+
inputSocketConnectionId,
|
|
5029
|
+
bindingKey
|
|
5030
|
+
});
|
|
5031
|
+
}
|
|
5032
|
+
});
|
|
5001
5033
|
device.inputWriteOwner = writeOwner;
|
|
5002
5034
|
|
|
5003
5035
|
emitRemoteEvent('RemoteInputSocketConnected', device);
|
|
@@ -5757,11 +5789,17 @@ export function createRemoteHub(options = {}) {
|
|
|
5757
5789
|
});
|
|
5758
5790
|
}
|
|
5759
5791
|
|
|
5760
|
-
function emitRemoteInputError(
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5792
|
+
function emitRemoteInputError(
|
|
5793
|
+
device,
|
|
5794
|
+
message = {},
|
|
5795
|
+
pending = null,
|
|
5796
|
+
fallbackError = 'remote-input-failed',
|
|
5797
|
+
errorKind = 'route') {
|
|
5798
|
+
const outcome = buildRemoteInputOutcome(device, message, pending);
|
|
5799
|
+
emitEvent('RemoteInputError', {
|
|
5800
|
+
...outcome,
|
|
5801
|
+
errorKind: errorKind === 'native-apply' ? 'native-apply' : 'route',
|
|
5802
|
+
error: safeString(
|
|
5765
5803
|
message.error
|
|
5766
5804
|
|| message.Error
|
|
5767
5805
|
|| message.result?.error
|
|
@@ -5791,9 +5829,9 @@ export function createRemoteHub(options = {}) {
|
|
|
5791
5829
|
// session cannot acknowledge input from the current control session.
|
|
5792
5830
|
if (!pending && source === 'main') {
|
|
5793
5831
|
return false;
|
|
5794
|
-
}
|
|
5795
|
-
if (message.type === 'input.error') {
|
|
5796
|
-
emitRemoteInputError(device, message, pending);
|
|
5832
|
+
}
|
|
5833
|
+
if (message.type === 'input.error') {
|
|
5834
|
+
emitRemoteInputError(device, message, pending, 'remote-input-failed', 'native-apply');
|
|
5797
5835
|
} else {
|
|
5798
5836
|
emitRemoteInputApplied(device, message, pending);
|
|
5799
5837
|
}
|
|
@@ -5813,8 +5851,13 @@ export function createRemoteHub(options = {}) {
|
|
|
5813
5851
|
|| !!error
|
|
5814
5852
|
|| result.ok === false
|
|
5815
5853
|
|| safeString(result.status, 40).toLowerCase() === 'failed';
|
|
5816
|
-
if (failed) {
|
|
5817
|
-
emitRemoteInputError(
|
|
5854
|
+
if (failed) {
|
|
5855
|
+
emitRemoteInputError(
|
|
5856
|
+
device,
|
|
5857
|
+
{ ...message, error: error || 'remote-input-failed' },
|
|
5858
|
+
completed,
|
|
5859
|
+
'remote-input-failed',
|
|
5860
|
+
'native-apply');
|
|
5818
5861
|
} else {
|
|
5819
5862
|
emitRemoteInputApplied(device, message, completed);
|
|
5820
5863
|
}
|
|
@@ -9459,13 +9502,7 @@ export function createRemoteHub(options = {}) {
|
|
|
9459
9502
|
device.inputOwnerBindingKey = activeInputBindingKey;
|
|
9460
9503
|
device.counters.commandsSent += 1;
|
|
9461
9504
|
device.inputLastSeenAt = new Date().toISOString();
|
|
9462
|
-
|
|
9463
|
-
...normalized,
|
|
9464
|
-
inputSocketConnectionId: device.inputSocketConnectionId,
|
|
9465
|
-
bindingKey: activeInputBindingKey,
|
|
9466
|
-
hubForwardedAtEpochMs
|
|
9467
|
-
});
|
|
9468
|
-
if (clipboardOperationKey) {
|
|
9505
|
+
if (clipboardOperationKey) {
|
|
9469
9506
|
const operation = clipboardOperations.get(clipboardOperationKey);
|
|
9470
9507
|
if (operation) {
|
|
9471
9508
|
operation.status = 'pasting';
|
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,
|