livedesk 0.1.604 → 0.1.606
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/client/package.json +5 -5
- package/diagnostics/livedesk-mode3-capture-smoke.mjs +177 -110
- package/hub/package.json +1 -1
- package/hub/src/remote-hub.js +90 -54
- package/hub/src/server.js +4 -3
- package/package.json +6 -6
- package/web/dist/app.webmanifest +1 -1
- package/web/dist/assets/LiveDeskApp-BoS9GSoS.js +179 -0
- package/web/dist/assets/{icons-tFaWqIIe.js → icons-JxbtiHLQ.js} +1 -1
- package/web/dist/assets/{index-DbM49IXr.js → index-BGB-rcGS.js} +4 -4
- package/web/dist/assets/{react-BQos8uGS.js → react-hDor0Pi9.js} +1 -1
- package/web/dist/assets/supabase-C0Z0FS3K.js +29 -0
- package/web/dist/index.html +3 -3
- package/web/dist/livedesk-build-evidence.json +24 -24
- package/web/dist/sw.js +1 -1
- package/web/dist/assets/LiveDeskApp-FTUehpNH.js +0 -178
- package/web/dist/assets/supabase-C7qjtLN3.js +0 -29
package/client/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livedesk/client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.261",
|
|
4
4
|
"description": "VuvoDesk local remote client",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
"ws": "^8.18.3"
|
|
43
43
|
},
|
|
44
44
|
"optionalDependencies": {
|
|
45
|
-
"@livedesk/fast-linux-x64": "0.1.
|
|
46
|
-
"@livedesk/fast-osx-arm64": "0.1.
|
|
47
|
-
"@livedesk/fast-osx-x64": "0.1.
|
|
48
|
-
"@livedesk/fast-win-x64": "0.1.
|
|
45
|
+
"@livedesk/fast-linux-x64": "0.1.458",
|
|
46
|
+
"@livedesk/fast-osx-arm64": "0.1.458",
|
|
47
|
+
"@livedesk/fast-osx-x64": "0.1.458",
|
|
48
|
+
"@livedesk/fast-win-x64": "0.1.458"
|
|
49
49
|
},
|
|
50
50
|
"publishConfig": {
|
|
51
51
|
"access": "public"
|
|
@@ -130,7 +130,7 @@ if (!ffmpegStaticPath || !existsSync(ffmpegStaticPath)) {
|
|
|
130
130
|
throw new Error('The packaged ffmpeg-static decoder is unavailable.');
|
|
131
131
|
}
|
|
132
132
|
let ffmpegPath = ffmpegStaticPath;
|
|
133
|
-
try {
|
|
133
|
+
try {
|
|
134
134
|
ffmpegPath = String(require('@ffmpeg-installer/ffmpeg')?.path || ffmpegStaticPath);
|
|
135
135
|
} catch {
|
|
136
136
|
// ffmpeg-static remains the portable decoder and compatibility fallback.
|
|
@@ -554,7 +554,7 @@ async function runWindowsInputAckProbe(binding, { expectHookLateStartFailure = f
|
|
|
554
554
|
const socket = new WebSocket(`ws://127.0.0.1:${httpPort}/api/remote/input/ws`, {
|
|
555
555
|
perMessageDeflate: false
|
|
556
556
|
});
|
|
557
|
-
const sendInput = async (input, timeoutMs = 5_000) => {
|
|
557
|
+
const sendInput = async (input, timeoutMs = 5_000) => {
|
|
558
558
|
const outcome = waitForWebSocketJson(
|
|
559
559
|
socket,
|
|
560
560
|
message => (
|
|
@@ -577,17 +577,101 @@ async function runWindowsInputAckProbe(binding, { expectHookLateStartFailure = f
|
|
|
577
577
|
captureGeneration: binding.captureGeneration
|
|
578
578
|
}
|
|
579
579
|
}));
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
580
|
+
try {
|
|
581
|
+
return await outcome;
|
|
582
|
+
} catch (error) {
|
|
583
|
+
throw new Error(
|
|
584
|
+
`Windows input ${input.type} seq=${input.inputSeq} did not return an outcome: `
|
|
585
|
+
+ `${error instanceof Error ? error.message : String(error)}`
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
const dispatchReset = (reason) => {
|
|
591
|
+
if (socket.readyState !== WebSocket.OPEN) {
|
|
592
|
+
return false;
|
|
593
|
+
}
|
|
594
|
+
socket.send(JSON.stringify({
|
|
595
|
+
type: 'input',
|
|
596
|
+
deviceId: binding.deviceId,
|
|
597
|
+
fireAndForget: true,
|
|
598
|
+
input: {
|
|
599
|
+
type: 'keyboard.reset',
|
|
600
|
+
inputSeq: 0,
|
|
601
|
+
requestAck: false,
|
|
602
|
+
pressedCodes: [],
|
|
603
|
+
shiftKey: false,
|
|
604
|
+
ctrlKey: false,
|
|
605
|
+
altKey: false,
|
|
606
|
+
metaKey: false,
|
|
607
|
+
repeat: false,
|
|
608
|
+
reason,
|
|
609
|
+
monitorIndex: binding.monitorIndex,
|
|
610
|
+
controlSessionId: binding.sessionId,
|
|
611
|
+
controlCommandId: binding.commandId,
|
|
612
|
+
captureGeneration: binding.captureGeneration
|
|
613
|
+
}
|
|
614
|
+
}));
|
|
615
|
+
return true;
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
let resetDispatched = false;
|
|
619
|
+
try {
|
|
620
|
+
const ready = waitForWebSocketJson(socket, message => message.type === 'RemoteInputSocketReady');
|
|
621
|
+
await Promise.all([waitForWebSocketOpen(socket), ready]);
|
|
622
|
+
const pointer = await sendInput({
|
|
623
|
+
type: 'pointermove',
|
|
624
|
+
inputSeq: 1,
|
|
625
|
+
inputEventId: `windows-physical-pointer-${Date.now()}`,
|
|
626
|
+
normalizedX: 0.5,
|
|
627
|
+
normalizedY: 0.5
|
|
628
|
+
});
|
|
629
|
+
assert.equal(pointer.type, 'RemoteInputApplied', String(pointer.error || 'Windows pointer input failed.'));
|
|
630
|
+
assert.equal(pointer.duplicate, false, 'Windows pointer input was acknowledged as a duplicate.');
|
|
631
|
+
assert.equal(pointer.nativeInputBackend, 'win32-setcursorpos');
|
|
632
|
+
assert.equal(Number(pointer.nativeEventsRequested), 1);
|
|
633
|
+
assert.equal(Number(pointer.nativeEventsAccepted), 1);
|
|
634
|
+
|
|
635
|
+
const shiftDown = await sendInput({
|
|
636
|
+
type: 'keydown',
|
|
637
|
+
inputSeq: 2,
|
|
638
|
+
inputEventId: `windows-physical-shift-down-${Date.now()}`,
|
|
639
|
+
key: 'Shift',
|
|
640
|
+
code: 'ShiftLeft',
|
|
641
|
+
keyCode: 16,
|
|
642
|
+
location: 1,
|
|
643
|
+
pressedCodes: ['ShiftLeft'],
|
|
644
|
+
shiftKey: true,
|
|
645
|
+
ctrlKey: false,
|
|
646
|
+
altKey: false,
|
|
647
|
+
metaKey: false,
|
|
648
|
+
repeat: false
|
|
649
|
+
});
|
|
650
|
+
assert.equal(shiftDown.type, 'RemoteInputApplied', String(shiftDown.error || 'Windows Shift keydown failed.'));
|
|
651
|
+
assert.equal(shiftDown.duplicate, false, 'Windows Shift keydown was acknowledged as a duplicate.');
|
|
652
|
+
|
|
653
|
+
const shiftUp = await sendInput({
|
|
654
|
+
type: 'keyup',
|
|
655
|
+
inputSeq: 3,
|
|
656
|
+
inputEventId: `windows-physical-shift-up-${Date.now()}`,
|
|
657
|
+
key: 'Shift',
|
|
658
|
+
code: 'ShiftLeft',
|
|
659
|
+
keyCode: 16,
|
|
660
|
+
location: 1,
|
|
661
|
+
pressedCodes: [],
|
|
662
|
+
shiftKey: false,
|
|
663
|
+
ctrlKey: false,
|
|
664
|
+
altKey: false,
|
|
665
|
+
metaKey: false,
|
|
666
|
+
repeat: false
|
|
667
|
+
});
|
|
668
|
+
assert.equal(shiftUp.type, 'RemoteInputApplied', String(shiftUp.error || 'Windows Shift keyup failed.'));
|
|
669
|
+
assert.equal(shiftUp.duplicate, false, 'Windows Shift keyup was acknowledged as a duplicate.');
|
|
670
|
+
|
|
671
|
+
const probe = await sendInput({
|
|
672
|
+
type: 'keyboard.probe',
|
|
673
|
+
inputSeq: 4,
|
|
674
|
+
inputEventId: `windows-physical-probe-${Date.now()}`,
|
|
591
675
|
pressedCodes: [],
|
|
592
676
|
shiftKey: false,
|
|
593
677
|
ctrlKey: false,
|
|
@@ -606,26 +690,31 @@ async function runWindowsInputAckProbe(binding, { expectHookLateStartFailure = f
|
|
|
606
690
|
/diagnostic keyboard isolation hook did not become ready/i,
|
|
607
691
|
'The forced late hook start did not fail at the bounded ready deadline.'
|
|
608
692
|
);
|
|
609
|
-
const
|
|
610
|
-
type: '
|
|
611
|
-
inputSeq:
|
|
612
|
-
inputEventId: `windows-late-start-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
assert.equal(
|
|
622
|
-
assert.equal(
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
693
|
+
const recovery = await sendInput({
|
|
694
|
+
type: 'pointermove',
|
|
695
|
+
inputSeq: 5,
|
|
696
|
+
inputEventId: `windows-late-start-recovery-${Date.now()}`,
|
|
697
|
+
normalizedX: 0.5,
|
|
698
|
+
normalizedY: 0.5
|
|
699
|
+
});
|
|
700
|
+
assert.equal(
|
|
701
|
+
recovery.type,
|
|
702
|
+
'RemoteInputApplied',
|
|
703
|
+
String(recovery.error || 'Windows input owner did not recover after the bounded hook failure.')
|
|
704
|
+
);
|
|
705
|
+
assert.equal(recovery.nativeInputBackend, 'win32-setcursorpos');
|
|
706
|
+
assert.equal(Number(recovery.nativeEventsRequested), 1);
|
|
707
|
+
assert.equal(Number(recovery.nativeEventsAccepted), 1);
|
|
708
|
+
resetDispatched = dispatchReset('windows-hook-late-start-cleanup');
|
|
709
|
+
assert.equal(resetDispatched, true, 'Windows late-start cleanup reset was not dispatched.');
|
|
710
|
+
return {
|
|
711
|
+
lateStartFailure: true,
|
|
712
|
+
pointerBackend: String(pointer.nativeInputBackend || ''),
|
|
713
|
+
normalKeyAcks: 2,
|
|
714
|
+
error: String(probe.error || ''),
|
|
715
|
+
recoveryApplied: true,
|
|
716
|
+
resetDispatched
|
|
717
|
+
};
|
|
629
718
|
}
|
|
630
719
|
assert.equal(probe.type, 'RemoteInputApplied', String(probe.error || 'Windows native input probe failed.'));
|
|
631
720
|
assert.equal(probe.duplicate, false, 'Windows native input probe was acknowledged as a duplicate.');
|
|
@@ -637,57 +726,28 @@ async function runWindowsInputAckProbe(binding, { expectHookLateStartFailure = f
|
|
|
637
726
|
assert.equal(probe.focusChanged, false, 'Windows native input probe changed the foreground window.');
|
|
638
727
|
assert.equal(probe.inputDiagnostic, true);
|
|
639
728
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
metaKey: false,
|
|
649
|
-
repeat: false,
|
|
650
|
-
reason: 'windows-native-input-diagnostic-cleanup'
|
|
651
|
-
});
|
|
652
|
-
assert.equal(reset.type, 'RemoteInputApplied', String(reset.error || 'Windows native input reset failed.'));
|
|
653
|
-
assert.equal(reset.duplicate, false, 'Windows native input reset was acknowledged as a duplicate.');
|
|
654
|
-
resetApplied = true;
|
|
655
|
-
return {
|
|
656
|
-
backend: String(probe.nativeInputBackend || ''),
|
|
729
|
+
resetDispatched = dispatchReset('windows-native-input-diagnostic-cleanup');
|
|
730
|
+
assert.equal(resetDispatched, true, 'Windows native input cleanup reset was not dispatched.');
|
|
731
|
+
return {
|
|
732
|
+
pointerBackend: String(pointer.nativeInputBackend || ''),
|
|
733
|
+
pointerRequested: Number(pointer.nativeEventsRequested || 0),
|
|
734
|
+
pointerAccepted: Number(pointer.nativeEventsAccepted || 0),
|
|
735
|
+
normalKeyAcks: 2,
|
|
736
|
+
backend: String(probe.nativeInputBackend || ''),
|
|
657
737
|
requested: Number(probe.nativeEventsRequested || 0),
|
|
658
738
|
accepted: Number(probe.nativeEventsAccepted || 0),
|
|
659
739
|
hookObserved: Number(probe.hookEventsObserved || 0),
|
|
660
740
|
hookBlocked: Number(probe.hookEventsBlocked || 0),
|
|
661
741
|
duplicate: probe.duplicate === true,
|
|
662
742
|
focusChanged: probe.focusChanged === true,
|
|
663
|
-
|
|
664
|
-
};
|
|
665
|
-
} finally {
|
|
666
|
-
if (!
|
|
667
|
-
try {
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
fireAndForget: true,
|
|
672
|
-
input: {
|
|
673
|
-
type: 'keyboard.reset',
|
|
674
|
-
inputSeq: 0,
|
|
675
|
-
requestAck: false,
|
|
676
|
-
pressedCodes: [],
|
|
677
|
-
shiftKey: false,
|
|
678
|
-
ctrlKey: false,
|
|
679
|
-
altKey: false,
|
|
680
|
-
metaKey: false,
|
|
681
|
-
repeat: false,
|
|
682
|
-
reason: 'windows-native-input-diagnostic-finally',
|
|
683
|
-
monitorIndex: binding.monitorIndex,
|
|
684
|
-
controlSessionId: binding.sessionId,
|
|
685
|
-
controlCommandId: binding.commandId,
|
|
686
|
-
captureGeneration: binding.captureGeneration
|
|
687
|
-
}
|
|
688
|
-
}));
|
|
689
|
-
} catch {
|
|
690
|
-
// Closing the exact input owner also triggers RemoteFast ReleaseAll.
|
|
743
|
+
resetDispatched
|
|
744
|
+
};
|
|
745
|
+
} finally {
|
|
746
|
+
if (!resetDispatched) {
|
|
747
|
+
try {
|
|
748
|
+
dispatchReset('windows-native-input-diagnostic-finally');
|
|
749
|
+
} catch {
|
|
750
|
+
// Closing the exact input owner also triggers RemoteFast ReleaseAll.
|
|
691
751
|
}
|
|
692
752
|
}
|
|
693
753
|
try { socket.close(); } catch {}
|
|
@@ -1704,9 +1764,9 @@ function removeMacDiagnosticSignalHandlers() {
|
|
|
1704
1764
|
macSignalHandlers.clear();
|
|
1705
1765
|
}
|
|
1706
1766
|
|
|
1707
|
-
installMacDiagnosticSignalHandlers();
|
|
1708
|
-
|
|
1709
|
-
try {
|
|
1767
|
+
installMacDiagnosticSignalHandlers();
|
|
1768
|
+
|
|
1769
|
+
inputDiagnostic: try {
|
|
1710
1770
|
macIsolation = await prepareMacDiagnosticIsolation();
|
|
1711
1771
|
if (isMacPhysicalGate) {
|
|
1712
1772
|
macDiagnosticAbort.throwIfRequested();
|
|
@@ -1924,7 +1984,8 @@ try {
|
|
|
1924
1984
|
if (metadata.deviceId !== connectedDeviceId || metadata.frameMode !== 'mode3-h264-hw') return;
|
|
1925
1985
|
const payload = Buffer.from(data.subarray(4 + metadataLength));
|
|
1926
1986
|
frames.push({ metadata, receivedAt: performance.now(), bytes: payload.length, payload });
|
|
1927
|
-
|
|
1987
|
+
const requiredFrameCount = windowsInputAck ? 1 : (isMacPhysicalGate ? 75 : 90);
|
|
1988
|
+
if (frames.length >= requiredFrameCount) {
|
|
1928
1989
|
finish(resolve, frames);
|
|
1929
1990
|
}
|
|
1930
1991
|
};
|
|
@@ -1953,9 +2014,40 @@ try {
|
|
|
1953
2014
|
monitorSelections: { [connectedDeviceId]: requestedMonitorIndex }
|
|
1954
2015
|
}));
|
|
1955
2016
|
|
|
1956
|
-
const frames = await withMacDiagnosticAbort(framePromise, cancelFrameWait);
|
|
1957
|
-
let activeInputBindingFrame = frames.at(-1);
|
|
1958
|
-
|
|
2017
|
+
const frames = await withMacDiagnosticAbort(framePromise, cancelFrameWait);
|
|
2018
|
+
let activeInputBindingFrame = frames.at(-1);
|
|
2019
|
+
if (windowsInputAck) {
|
|
2020
|
+
const inputProbe = await runWindowsInputAckProbe({
|
|
2021
|
+
deviceId: connectedDeviceId,
|
|
2022
|
+
sessionId: String(activeInputBindingFrame.metadata.sessionId || ''),
|
|
2023
|
+
commandId: String(activeInputBindingFrame.metadata.commandId || ''),
|
|
2024
|
+
captureGeneration: Number(activeInputBindingFrame.metadata.captureGeneration || 0),
|
|
2025
|
+
monitorIndex: Number(activeInputBindingFrame.metadata.monitorIndex || 0)
|
|
2026
|
+
}, {
|
|
2027
|
+
expectHookLateStartFailure: windowsInputHookLateStart
|
|
2028
|
+
});
|
|
2029
|
+
if (windowsInputHookLateStart) {
|
|
2030
|
+
console.log(
|
|
2031
|
+
`Windows diagnostic hook late-start cleanup OK: expected timeout, `
|
|
2032
|
+
+ `Agent responsive=${inputProbe.recoveryApplied}, reset dispatched=${inputProbe.resetDispatched}.`
|
|
2033
|
+
);
|
|
2034
|
+
} else {
|
|
2035
|
+
console.log(
|
|
2036
|
+
`Windows pointer input ACK: ${inputProbe.pointerAccepted}/${inputProbe.pointerRequested} `
|
|
2037
|
+
+ `via ${inputProbe.pointerBackend}; normal key ACK: ${inputProbe.normalKeyAcks}/2; `
|
|
2038
|
+
+ `isolated modifier ACK: `
|
|
2039
|
+
+ `${inputProbe.accepted}/${inputProbe.requested} via ${inputProbe.backend}, `
|
|
2040
|
+
+ `hook=${inputProbe.hookObserved}/${inputProbe.hookBlocked}, `
|
|
2041
|
+
+ `duplicate=${inputProbe.duplicate}, `
|
|
2042
|
+
+ `focusChanged=${inputProbe.focusChanged}, reset dispatched=${inputProbe.resetDispatched}.`
|
|
2043
|
+
);
|
|
2044
|
+
}
|
|
2045
|
+
// The Windows input gate shares only the minimum current Control binding
|
|
2046
|
+
// setup with the video smoke. Encoder profile and soak assertions belong
|
|
2047
|
+
// to their own commands and must not obscure native input evidence.
|
|
2048
|
+
break inputDiagnostic;
|
|
2049
|
+
}
|
|
2050
|
+
const initialCaptureIdentity = switchMonitor && isWindows
|
|
1959
2051
|
? await waitFor(
|
|
1960
2052
|
() => findWindowsCaptureIdentity(agentLogs, requestedMonitorIndex),
|
|
1961
2053
|
3_000,
|
|
@@ -2426,31 +2518,6 @@ try {
|
|
|
2426
2518
|
console.log(`Mode 3 monitor transition OK: ${requestedMonitorIndex} -> ${nextMonitorIndex}, first frame ${switchedFirstFrameMs.toFixed(0)}ms, generation ${previousCaptureGeneration} -> ${firstSwitchedFrame.metadata.captureGeneration}.`);
|
|
2427
2519
|
}
|
|
2428
2520
|
|
|
2429
|
-
if (windowsInputAck) {
|
|
2430
|
-
const inputProbe = await runWindowsInputAckProbe({
|
|
2431
|
-
deviceId: connectedDeviceId,
|
|
2432
|
-
sessionId: String(activeInputBindingFrame.metadata.sessionId || ''),
|
|
2433
|
-
commandId: String(activeInputBindingFrame.metadata.commandId || ''),
|
|
2434
|
-
captureGeneration: Number(activeInputBindingFrame.metadata.captureGeneration || 0),
|
|
2435
|
-
monitorIndex: Number(activeInputBindingFrame.metadata.monitorIndex || 0)
|
|
2436
|
-
}, {
|
|
2437
|
-
expectHookLateStartFailure: windowsInputHookLateStart
|
|
2438
|
-
});
|
|
2439
|
-
if (windowsInputHookLateStart) {
|
|
2440
|
-
console.log(
|
|
2441
|
-
`Windows diagnostic hook late-start cleanup OK: expected timeout, `
|
|
2442
|
-
+ `Agent responsive, reset=${inputProbe.resetApplied}.`
|
|
2443
|
-
);
|
|
2444
|
-
} else {
|
|
2445
|
-
console.log(
|
|
2446
|
-
`Windows native modifier input ACK: ${inputProbe.accepted}/${inputProbe.requested} `
|
|
2447
|
-
+ `via ${inputProbe.backend}, hook=${inputProbe.hookObserved}/${inputProbe.hookBlocked}, `
|
|
2448
|
-
+ `duplicate=${inputProbe.duplicate}, `
|
|
2449
|
-
+ `focusChanged=${inputProbe.focusChanged}, reset=${inputProbe.resetApplied}.`
|
|
2450
|
-
);
|
|
2451
|
-
}
|
|
2452
|
-
}
|
|
2453
|
-
|
|
2454
2521
|
if (transitionToMode5) {
|
|
2455
2522
|
const previousFrameSocket = frameSocket;
|
|
2456
2523
|
frameSocket = new WebSocket(`ws://127.0.0.1:${httpPort}/api/remote/frames/ws?devices=${encodeURIComponent(connectedDeviceId)}`);
|
package/hub/package.json
CHANGED
package/hub/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()) {
|
|
@@ -4982,22 +4997,38 @@ export function createRemoteHub(options = {}) {
|
|
|
4982
4997
|
&& device.inputSocket === socket
|
|
4983
4998
|
&& device.inputSocketConnectionId === inputSocketConnectionId
|
|
4984
4999
|
&& 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.
|
|
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 = '';
|
|
4994
5010
|
device.inputOwnerConnectionId = '';
|
|
4995
5011
|
device.inputOwnerBindingKey = '';
|
|
4996
5012
|
inputSockets.delete(socket);
|
|
4997
|
-
device.inputLastSeenAt = new Date().toISOString();
|
|
4998
|
-
emitRemoteEvent('RemoteInputSocketDisconnected', device, { reason });
|
|
4999
|
-
}
|
|
5000
|
-
|
|
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
|
+
});
|
|
5001
5032
|
device.inputWriteOwner = writeOwner;
|
|
5002
5033
|
|
|
5003
5034
|
emitRemoteEvent('RemoteInputSocketConnected', device);
|
|
@@ -5757,11 +5788,17 @@ export function createRemoteHub(options = {}) {
|
|
|
5757
5788
|
});
|
|
5758
5789
|
}
|
|
5759
5790
|
|
|
5760
|
-
function emitRemoteInputError(
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
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(
|
|
5765
5802
|
message.error
|
|
5766
5803
|
|| message.Error
|
|
5767
5804
|
|| message.result?.error
|
|
@@ -5791,9 +5828,9 @@ export function createRemoteHub(options = {}) {
|
|
|
5791
5828
|
// session cannot acknowledge input from the current control session.
|
|
5792
5829
|
if (!pending && source === 'main') {
|
|
5793
5830
|
return false;
|
|
5794
|
-
}
|
|
5795
|
-
if (message.type === 'input.error') {
|
|
5796
|
-
emitRemoteInputError(device, message, pending);
|
|
5831
|
+
}
|
|
5832
|
+
if (message.type === 'input.error') {
|
|
5833
|
+
emitRemoteInputError(device, message, pending, 'remote-input-failed', 'native-apply');
|
|
5797
5834
|
} else {
|
|
5798
5835
|
emitRemoteInputApplied(device, message, pending);
|
|
5799
5836
|
}
|
|
@@ -5813,8 +5850,13 @@ export function createRemoteHub(options = {}) {
|
|
|
5813
5850
|
|| !!error
|
|
5814
5851
|
|| result.ok === false
|
|
5815
5852
|
|| safeString(result.status, 40).toLowerCase() === 'failed';
|
|
5816
|
-
if (failed) {
|
|
5817
|
-
emitRemoteInputError(
|
|
5853
|
+
if (failed) {
|
|
5854
|
+
emitRemoteInputError(
|
|
5855
|
+
device,
|
|
5856
|
+
{ ...message, error: error || 'remote-input-failed' },
|
|
5857
|
+
completed,
|
|
5858
|
+
'remote-input-failed',
|
|
5859
|
+
'native-apply');
|
|
5818
5860
|
} else {
|
|
5819
5861
|
emitRemoteInputApplied(device, message, completed);
|
|
5820
5862
|
}
|
|
@@ -9459,13 +9501,7 @@ export function createRemoteHub(options = {}) {
|
|
|
9459
9501
|
device.inputOwnerBindingKey = activeInputBindingKey;
|
|
9460
9502
|
device.counters.commandsSent += 1;
|
|
9461
9503
|
device.inputLastSeenAt = new Date().toISOString();
|
|
9462
|
-
|
|
9463
|
-
...normalized,
|
|
9464
|
-
inputSocketConnectionId: device.inputSocketConnectionId,
|
|
9465
|
-
bindingKey: activeInputBindingKey,
|
|
9466
|
-
hubForwardedAtEpochMs
|
|
9467
|
-
});
|
|
9468
|
-
if (clipboardOperationKey) {
|
|
9504
|
+
if (clipboardOperationKey) {
|
|
9469
9505
|
const operation = clipboardOperations.get(clipboardOperationKey);
|
|
9470
9506
|
if (operation) {
|
|
9471
9507
|
operation.status = 'pasting';
|
package/hub/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,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "livedesk",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"livedeskClientVersion": "0.1.
|
|
3
|
+
"version": "0.1.606",
|
|
4
|
+
"livedeskClientVersion": "0.1.261",
|
|
5
5
|
"buildFlavor": "production",
|
|
6
6
|
"description": "VuvoDesk Hub and client launcher",
|
|
7
7
|
"type": "module",
|
|
@@ -51,10 +51,10 @@
|
|
|
51
51
|
"ws": "^8.18.3"
|
|
52
52
|
},
|
|
53
53
|
"optionalDependencies": {
|
|
54
|
-
"@livedesk/fast-linux-x64": "0.1.
|
|
55
|
-
"@livedesk/fast-osx-arm64": "0.1.
|
|
56
|
-
"@livedesk/fast-osx-x64": "0.1.
|
|
57
|
-
"@livedesk/fast-win-x64": "0.1.
|
|
54
|
+
"@livedesk/fast-linux-x64": "0.1.458",
|
|
55
|
+
"@livedesk/fast-osx-arm64": "0.1.458",
|
|
56
|
+
"@livedesk/fast-osx-x64": "0.1.458",
|
|
57
|
+
"@livedesk/fast-win-x64": "0.1.458"
|
|
58
58
|
},
|
|
59
59
|
"publishConfig": {
|
|
60
60
|
"access": "public"
|
package/web/dist/app.webmanifest
CHANGED