livedesk 0.1.459 → 0.1.461
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/README.md +28 -5
- package/bin/livedesk.js +223 -24
- package/bootstrap/runtime-lock.js +6 -0
- package/client/package.json +9 -8
- package/client/src/runtime/windows-owned-process-manifest.js +181 -14
- package/diagnostics/livedesk-mac-physical-gate-core.mjs +214 -26
- package/diagnostics/livedesk-mode3-capture-smoke.mjs +406 -4
- package/diagnostics/livedesk-transport-physical-core.mjs +200 -1
- package/diagnostics/livedesk-transport-physical.mjs +60 -1
- package/hub/package.json +1 -1
- package/hub/src/remote-hub.js +364 -49
- package/hub/src/server.js +4 -0
- package/hub/src/transport/udp-hub-transport.js +29 -6
- package/package.json +10 -9
- package/scripts/sync-web-dist.js +127 -0
- package/web/dist/assets/index-l4YzRK9d.js +25 -0
- package/web/dist/index.html +1 -1
- package/web/dist/assets/index-JqqQ5DKN.js +0 -25
package/hub/src/remote-hub.js
CHANGED
|
@@ -4689,6 +4689,10 @@ export function createRemoteHub(options = {}) {
|
|
|
4689
4689
|
? message.nativeEncoderHardwareAccelerated
|
|
4690
4690
|
: null,
|
|
4691
4691
|
nativeEncoderHardwareQueryStatus: safeString(message.nativeEncoderHardwareQueryStatus, 80),
|
|
4692
|
+
nativeCaptureDisplayId: Number.isSafeInteger(Number(message.nativeCaptureDisplayId))
|
|
4693
|
+
&& Number(message.nativeCaptureDisplayId) > 0
|
|
4694
|
+
? Number(message.nativeCaptureDisplayId)
|
|
4695
|
+
: 0,
|
|
4692
4696
|
nativeCaptureFps: Number.isFinite(Number(message.nativeCaptureFps)) ? Number(message.nativeCaptureFps) : 0,
|
|
4693
4697
|
nativeCompressionFps: Number.isFinite(Number(message.nativeCompressionFps)) ? Number(message.nativeCompressionFps) : 0,
|
|
4694
4698
|
nativeEncodedFps: Number.isFinite(Number(message.nativeEncodedFps)) ? Number(message.nativeEncodedFps) : 0,
|
|
@@ -4746,7 +4750,7 @@ export function createRemoteHub(options = {}) {
|
|
|
4746
4750
|
return true;
|
|
4747
4751
|
}
|
|
4748
4752
|
|
|
4749
|
-
function
|
|
4753
|
+
function matchPendingLiveStreamOwner(device, streamState, message, transport) {
|
|
4750
4754
|
const pending = getPendingLiveStreamDescriptor(streamState);
|
|
4751
4755
|
const commandId = safeString(message.commandId, 128);
|
|
4752
4756
|
if (!pending || !commandId || commandId !== safeString(pending.commandId, 128)) {
|
|
@@ -4754,9 +4758,11 @@ export function createRemoteHub(options = {}) {
|
|
|
4754
4758
|
}
|
|
4755
4759
|
const messageCaptureGeneration = Number(message.captureGeneration || 0);
|
|
4756
4760
|
const pendingCaptureGeneration = Number(pending.captureGeneration || 0);
|
|
4757
|
-
if (messageCaptureGeneration
|
|
4758
|
-
|
|
4759
|
-
|
|
4761
|
+
if (!Number.isSafeInteger(messageCaptureGeneration)
|
|
4762
|
+
|| messageCaptureGeneration <= 0
|
|
4763
|
+
|| !Number.isSafeInteger(pendingCaptureGeneration)
|
|
4764
|
+
|| pendingCaptureGeneration <= 0
|
|
4765
|
+
|| messageCaptureGeneration !== pendingCaptureGeneration) {
|
|
4760
4766
|
emitRemoteEvent('RemoteLiveStreamOpenIgnored', device, {
|
|
4761
4767
|
reason: 'stale-pending-capture-generation',
|
|
4762
4768
|
streamId: streamState.streamId,
|
|
@@ -4795,6 +4801,27 @@ export function createRemoteHub(options = {}) {
|
|
|
4795
4801
|
});
|
|
4796
4802
|
return null;
|
|
4797
4803
|
}
|
|
4804
|
+
return {
|
|
4805
|
+
pending,
|
|
4806
|
+
commandId,
|
|
4807
|
+
messageCaptureGeneration,
|
|
4808
|
+
pendingCaptureGeneration,
|
|
4809
|
+
pendingMonitorIndex
|
|
4810
|
+
};
|
|
4811
|
+
}
|
|
4812
|
+
|
|
4813
|
+
function promotePendingLiveStream(device, streamState, message, transport) {
|
|
4814
|
+
const owner = matchPendingLiveStreamOwner(device, streamState, message, transport);
|
|
4815
|
+
if (!owner) {
|
|
4816
|
+
return null;
|
|
4817
|
+
}
|
|
4818
|
+
const {
|
|
4819
|
+
pending,
|
|
4820
|
+
commandId,
|
|
4821
|
+
messageCaptureGeneration,
|
|
4822
|
+
pendingCaptureGeneration,
|
|
4823
|
+
pendingMonitorIndex
|
|
4824
|
+
} = owner;
|
|
4798
4825
|
|
|
4799
4826
|
const previousCommandId = safeString(streamState.commandId, 128);
|
|
4800
4827
|
const transfer = buildRemoteFrameTransferDescriptorFromMessage(
|
|
@@ -4903,16 +4930,22 @@ export function createRemoteHub(options = {}) {
|
|
|
4903
4930
|
|
|
4904
4931
|
const activatingPendingGeneration = isPendingGeneration && commandId !== activeCommandId;
|
|
4905
4932
|
if (activatingPendingGeneration) {
|
|
4906
|
-
const
|
|
4907
|
-
if (!
|
|
4933
|
+
const owner = matchPendingLiveStreamOwner(device, streamState, message, transport);
|
|
4934
|
+
if (!owner) {
|
|
4908
4935
|
return false;
|
|
4909
4936
|
}
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4937
|
+
// stream.open travels on the manager connection while the binary
|
|
4938
|
+
// first frame travels on a separate transport. Their arrival order
|
|
4939
|
+
// is not defined. Keep the current generation and its last frame
|
|
4940
|
+
// until the matching first real frame validates and commits the
|
|
4941
|
+
// pending owner atomically.
|
|
4942
|
+
emitRemoteEvent('RemoteLiveStreamOpenDeferred', device, {
|
|
4943
|
+
streamId,
|
|
4944
|
+
commandId,
|
|
4945
|
+
captureGeneration: owner.messageCaptureGeneration,
|
|
4946
|
+
monitorIndex: owner.pendingMonitorIndex,
|
|
4947
|
+
transport
|
|
4948
|
+
});
|
|
4916
4949
|
return true;
|
|
4917
4950
|
}
|
|
4918
4951
|
|
|
@@ -4989,8 +5022,8 @@ export function createRemoteHub(options = {}) {
|
|
|
4989
5022
|
const commandId = safeString(message.commandId, 128);
|
|
4990
5023
|
let streamState = getDeviceLiveStream(device, streamId);
|
|
4991
5024
|
if (!streamState?.active) {
|
|
4992
|
-
device.counters.liveFramesDropped += 1;
|
|
4993
|
-
emitRemoteEvent('RemoteFrameDropped', device, {
|
|
5025
|
+
device.counters.liveFramesDropped += 1;
|
|
5026
|
+
emitRemoteEvent('RemoteFrameDropped', device, {
|
|
4994
5027
|
reason: 'stale-live-stream-frame',
|
|
4995
5028
|
streamId,
|
|
4996
5029
|
frameSeq: Number.isFinite(frameSeq) ? frameSeq : null,
|
|
@@ -4998,9 +5031,91 @@ export function createRemoteHub(options = {}) {
|
|
|
4998
5031
|
});
|
|
4999
5032
|
return false;
|
|
5000
5033
|
}
|
|
5034
|
+
const byteLength = normalizeFrameByteLength(framePayload, frameData);
|
|
5035
|
+
const streamFrameSeq = Number.isFinite(Number(message.streamFrameSeq))
|
|
5036
|
+
? Number(message.streamFrameSeq)
|
|
5037
|
+
: frameSeq;
|
|
5038
|
+
if (byteLength <= 0
|
|
5039
|
+
|| byteLength > MAX_STREAM_BINARY_BYTES
|
|
5040
|
+
|| !Number.isSafeInteger(frameSeq)
|
|
5041
|
+
|| frameSeq <= 0
|
|
5042
|
+
|| !Number.isSafeInteger(streamFrameSeq)
|
|
5043
|
+
|| streamFrameSeq <= 0) {
|
|
5044
|
+
device.counters.liveFramesDropped += 1;
|
|
5045
|
+
emitRemoteEvent('RemoteFrameDropped', device, {
|
|
5046
|
+
reason: 'invalid-live-frame',
|
|
5047
|
+
streamId,
|
|
5048
|
+
commandId,
|
|
5049
|
+
captureGeneration: Number(message.captureGeneration || 0),
|
|
5050
|
+
monitorIndex: parseExactLiveStreamMonitorIndex(message.monitorIndex),
|
|
5051
|
+
frameSeq: Number.isFinite(frameSeq) ? frameSeq : null,
|
|
5052
|
+
transport
|
|
5053
|
+
});
|
|
5054
|
+
return false;
|
|
5055
|
+
}
|
|
5056
|
+
const payload = buildFramePayloadBuffer(framePayload, frameData);
|
|
5057
|
+
if (payload.length <= 0 || payload.length > MAX_STREAM_BINARY_BYTES) {
|
|
5058
|
+
device.counters.liveFramesDropped += 1;
|
|
5059
|
+
emitRemoteEvent('RemoteFrameDropped', device, {
|
|
5060
|
+
reason: 'invalid-live-frame-payload',
|
|
5061
|
+
streamId,
|
|
5062
|
+
commandId,
|
|
5063
|
+
captureGeneration: Number(message.captureGeneration || 0),
|
|
5064
|
+
monitorIndex: parseExactLiveStreamMonitorIndex(message.monitorIndex),
|
|
5065
|
+
frameSeq,
|
|
5066
|
+
transport
|
|
5067
|
+
});
|
|
5068
|
+
return false;
|
|
5069
|
+
}
|
|
5001
5070
|
let activeCommandId = safeString(streamState.commandId, 128);
|
|
5002
|
-
|
|
5071
|
+
const pendingDescriptor = getPendingLiveStreamDescriptor(streamState);
|
|
5072
|
+
let pendingCommandId = safeString(pendingDescriptor?.commandId, 128);
|
|
5003
5073
|
if (pendingCommandId && commandId === pendingCommandId && commandId !== activeCommandId) {
|
|
5074
|
+
if (!matchPendingLiveStreamOwner(device, streamState, message, `${transport}-implicit`)) {
|
|
5075
|
+
return false;
|
|
5076
|
+
}
|
|
5077
|
+
const pendingFrameMode = safeString(
|
|
5078
|
+
message.frameMode
|
|
5079
|
+
|| message.mode
|
|
5080
|
+
|| pendingDescriptor?.frameMode
|
|
5081
|
+
|| pendingDescriptor?.mode,
|
|
5082
|
+
40).toLowerCase();
|
|
5083
|
+
const pendingMimeType = safeString(
|
|
5084
|
+
message.mimeType
|
|
5085
|
+
|| message.format
|
|
5086
|
+
|| pendingDescriptor?.compression,
|
|
5087
|
+
80).toLowerCase();
|
|
5088
|
+
const pendingCodec = safeString(
|
|
5089
|
+
message.codec
|
|
5090
|
+
|| pendingDescriptor?.codec
|
|
5091
|
+
|| pendingDescriptor?.encoding,
|
|
5092
|
+
40).toLowerCase();
|
|
5093
|
+
const requiresH264KeyFrame = pendingFrameMode === 'mode3-h264-hw'
|
|
5094
|
+
|| pendingMimeType.includes('h264')
|
|
5095
|
+
|| pendingMimeType.includes('avc')
|
|
5096
|
+
|| pendingCodec.includes('h264')
|
|
5097
|
+
|| pendingCodec.includes('avc');
|
|
5098
|
+
const isH264KeyFrame = message.isKeyFrame === true
|
|
5099
|
+
|| safeString(message.chunkType, 20).toLowerCase() === 'key';
|
|
5100
|
+
const h264 = requiresH264KeyFrame ? inspectAnnexBH264(payload) : null;
|
|
5101
|
+
const independentlyDecodableH264KeyFrame = isH264KeyFrame
|
|
5102
|
+
&& h264?.annexB === true
|
|
5103
|
+
&& h264.hasSps === true
|
|
5104
|
+
&& h264.hasPps === true
|
|
5105
|
+
&& h264.hasIdr === true
|
|
5106
|
+
&& h264.nalOrderValid === true;
|
|
5107
|
+
if (requiresH264KeyFrame && !independentlyDecodableH264KeyFrame) {
|
|
5108
|
+
device.counters.liveFramesDropped += 1;
|
|
5109
|
+
emitRemoteEvent('RemoteFrameDropped', device, {
|
|
5110
|
+
reason: 'pending-live-stream-decodable-keyframe-required',
|
|
5111
|
+
streamId,
|
|
5112
|
+
commandId,
|
|
5113
|
+
frameSeq,
|
|
5114
|
+
streamFrameSeq,
|
|
5115
|
+
transport
|
|
5116
|
+
});
|
|
5117
|
+
return false;
|
|
5118
|
+
}
|
|
5004
5119
|
const promoted = promotePendingLiveStream(device, streamState, message, `${transport}-implicit`);
|
|
5005
5120
|
if (promoted) {
|
|
5006
5121
|
streamState = promoted.streamState;
|
|
@@ -5078,24 +5193,38 @@ export function createRemoteHub(options = {}) {
|
|
|
5078
5193
|
return false;
|
|
5079
5194
|
}
|
|
5080
5195
|
|
|
5081
|
-
const
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5196
|
+
const previousFrame = streamState.latestFrame;
|
|
5197
|
+
const currentOwnerCommandId = commandId || activeCommandId;
|
|
5198
|
+
const currentOwnerCaptureGeneration = activeCaptureGeneration || frameCaptureGeneration;
|
|
5199
|
+
const previousStreamFrameSeq = Number(previousFrame?.streamFrameSeq ?? previousFrame?.frameSeq);
|
|
5200
|
+
const sameFrameOwner = previousFrame
|
|
5201
|
+
&& safeString(previousFrame.streamId, 128) === streamId
|
|
5202
|
+
&& safeString(previousFrame.commandId, 128) === currentOwnerCommandId
|
|
5203
|
+
&& Number(previousFrame.captureGeneration || 0) === currentOwnerCaptureGeneration
|
|
5204
|
+
&& parseExactLiveStreamMonitorIndex(previousFrame.monitorIndex) === frameMonitorIndex;
|
|
5205
|
+
if (sameFrameOwner
|
|
5206
|
+
&& Number.isSafeInteger(previousStreamFrameSeq)
|
|
5207
|
+
&& streamFrameSeq <= previousStreamFrameSeq) {
|
|
5208
|
+
device.counters.liveFramesDropped += 1;
|
|
5209
|
+
emitRemoteEvent('RemoteFrameDropped', device, {
|
|
5210
|
+
reason: streamFrameSeq === previousStreamFrameSeq
|
|
5211
|
+
? 'duplicate-live-stream-frame'
|
|
5212
|
+
: 'out-of-order-live-stream-frame',
|
|
5213
|
+
streamId,
|
|
5214
|
+
commandId: currentOwnerCommandId,
|
|
5215
|
+
captureGeneration: currentOwnerCaptureGeneration,
|
|
5216
|
+
monitorIndex: frameMonitorIndex,
|
|
5217
|
+
frameSeq,
|
|
5218
|
+
streamFrameSeq,
|
|
5219
|
+
previousStreamFrameSeq,
|
|
5220
|
+
transport
|
|
5221
|
+
});
|
|
5222
|
+
return false;
|
|
5223
|
+
}
|
|
5224
|
+
|
|
5225
|
+
const mimeType = safeString(message.mimeType || message.format || 'image/jpeg', 80) || 'image/jpeg';
|
|
5226
|
+
const capturedAt = safeString(message.capturedAt, 80) || device.lastSeenAt;
|
|
5227
|
+
const contentHash = buildFrameContentHash(message, payload);
|
|
5099
5228
|
const sameContentStreak = computeSameContentStreak(streamState.latestFrame, contentHash);
|
|
5100
5229
|
const transfer = buildRemoteFrameTransferDescriptorFromMessage(message, streamState.mode || DEFAULT_REMOTE_FRAME_MODE);
|
|
5101
5230
|
if (streamState.open !== true) {
|
|
@@ -5129,7 +5258,7 @@ export function createRemoteHub(options = {}) {
|
|
|
5129
5258
|
device.latestLiveFrame = {
|
|
5130
5259
|
streamId,
|
|
5131
5260
|
frameSeq,
|
|
5132
|
-
streamFrameSeq
|
|
5261
|
+
streamFrameSeq,
|
|
5133
5262
|
commandId: commandId || activeCommandId,
|
|
5134
5263
|
sessionId: device.sessionId,
|
|
5135
5264
|
captureGeneration: activeCaptureGeneration || frameCaptureGeneration,
|
|
@@ -5175,6 +5304,10 @@ export function createRemoteHub(options = {}) {
|
|
|
5175
5304
|
? message.nativeEncoderHardwareAccelerated
|
|
5176
5305
|
: null,
|
|
5177
5306
|
nativeEncoderHardwareQueryStatus: safeString(message.nativeEncoderHardwareQueryStatus, 80),
|
|
5307
|
+
nativeCaptureDisplayId: Number.isSafeInteger(Number(message.nativeCaptureDisplayId))
|
|
5308
|
+
&& Number(message.nativeCaptureDisplayId) > 0
|
|
5309
|
+
? Number(message.nativeCaptureDisplayId)
|
|
5310
|
+
: 0,
|
|
5178
5311
|
nativeCaptureFps: Number.isFinite(Number(message.nativeCaptureFps)) ? Number(message.nativeCaptureFps) : 0,
|
|
5179
5312
|
nativeCompressionFps: Number.isFinite(Number(message.nativeCompressionFps)) ? Number(message.nativeCompressionFps) : 0,
|
|
5180
5313
|
nativeEncodedFps: Number.isFinite(Number(message.nativeEncodedFps)) ? Number(message.nativeEncodedFps) : 0,
|
|
@@ -5369,7 +5502,7 @@ export function createRemoteHub(options = {}) {
|
|
|
5369
5502
|
return true;
|
|
5370
5503
|
}
|
|
5371
5504
|
|
|
5372
|
-
function applyAudioStatus(device, message, framePayload, transport = 'binary') {
|
|
5505
|
+
function applyAudioStatus(device, message, framePayload, transport = 'binary') {
|
|
5373
5506
|
if (!Buffer.isBuffer(framePayload) || framePayload.length < 1) {
|
|
5374
5507
|
device.counters.audioFramesDropped += 1;
|
|
5375
5508
|
return false;
|
|
@@ -5410,9 +5543,184 @@ export function createRemoteHub(options = {}) {
|
|
|
5410
5543
|
payload: framePayload,
|
|
5411
5544
|
byteLength: framePayload.length
|
|
5412
5545
|
});
|
|
5413
|
-
return true;
|
|
5414
|
-
}
|
|
5415
|
-
|
|
5546
|
+
return true;
|
|
5547
|
+
}
|
|
5548
|
+
|
|
5549
|
+
function readFrameTransportProof(message = {}) {
|
|
5550
|
+
if (message.frameTransportProofId === undefined
|
|
5551
|
+
&& message.frameTransportProofVersion === undefined) {
|
|
5552
|
+
return null;
|
|
5553
|
+
}
|
|
5554
|
+
const rawProofId = typeof message.frameTransportProofId === 'string'
|
|
5555
|
+
? message.frameTransportProofId.trim()
|
|
5556
|
+
: '';
|
|
5557
|
+
const captureGeneration = Number(message.captureGeneration);
|
|
5558
|
+
const monitorIndex = parseExactLiveStreamMonitorIndex(message.monitorIndex);
|
|
5559
|
+
const proof = {
|
|
5560
|
+
proofId: rawProofId,
|
|
5561
|
+
streamId: safeString(message.streamId, 128),
|
|
5562
|
+
commandId: safeString(message.commandId, 128),
|
|
5563
|
+
captureGeneration,
|
|
5564
|
+
monitorIndex
|
|
5565
|
+
};
|
|
5566
|
+
const valid = Number(message.frameTransportProofVersion) === 1
|
|
5567
|
+
&& /^[a-f0-9]{32}$/.test(rawProofId)
|
|
5568
|
+
&& !!proof.streamId
|
|
5569
|
+
&& !!proof.commandId
|
|
5570
|
+
&& Number.isSafeInteger(captureGeneration)
|
|
5571
|
+
&& captureGeneration > 0
|
|
5572
|
+
&& monitorIndex !== null;
|
|
5573
|
+
return {
|
|
5574
|
+
...proof,
|
|
5575
|
+
valid,
|
|
5576
|
+
error: valid ? '' : 'frame-proof-owner-invalid'
|
|
5577
|
+
};
|
|
5578
|
+
}
|
|
5579
|
+
|
|
5580
|
+
function applyLiveFrameWithTransportProof(
|
|
5581
|
+
device,
|
|
5582
|
+
message,
|
|
5583
|
+
framePayload,
|
|
5584
|
+
transport) {
|
|
5585
|
+
const proof = readFrameTransportProof(message);
|
|
5586
|
+
if (!proof) {
|
|
5587
|
+
return {
|
|
5588
|
+
accepted: applyLiveFrame(device, message, framePayload, transport),
|
|
5589
|
+
proofAccepted: false,
|
|
5590
|
+
proof: null,
|
|
5591
|
+
error: ''
|
|
5592
|
+
};
|
|
5593
|
+
}
|
|
5594
|
+
const frameMode = safeString(message.frameMode || message.mode, 40).toLowerCase();
|
|
5595
|
+
const mimeType = safeString(message.mimeType || message.format, 80).toLowerCase();
|
|
5596
|
+
const chunkType = safeString(message.chunkType, 20).toLowerCase();
|
|
5597
|
+
const proofFrameSeq = Number(message.frameSeq);
|
|
5598
|
+
const proofStreamFrameSeq = message.streamFrameSeq === undefined
|
|
5599
|
+
|| message.streamFrameSeq === null
|
|
5600
|
+
|| message.streamFrameSeq === ''
|
|
5601
|
+
? proofFrameSeq
|
|
5602
|
+
: Number(message.streamFrameSeq);
|
|
5603
|
+
const proofOnly = message.frameTransportProofOnly === true;
|
|
5604
|
+
const h264 = inspectAnnexBH264(framePayload);
|
|
5605
|
+
const proofPayloadValid = proof.valid
|
|
5606
|
+
&& (transport === 'udp-p2p' || transport === 'relay-binary')
|
|
5607
|
+
&& Buffer.isBuffer(framePayload)
|
|
5608
|
+
&& framePayload.length > 0
|
|
5609
|
+
&& framePayload.length <= MAX_STREAM_BINARY_BYTES
|
|
5610
|
+
&& Number.isSafeInteger(proofFrameSeq)
|
|
5611
|
+
&& proofFrameSeq > 0
|
|
5612
|
+
&& Number.isSafeInteger(proofStreamFrameSeq)
|
|
5613
|
+
&& proofStreamFrameSeq > 0
|
|
5614
|
+
&& frameMode === 'mode3-h264-hw'
|
|
5615
|
+
&& mimeType === 'video/h264'
|
|
5616
|
+
&& (message.isKeyFrame === true || chunkType === 'key')
|
|
5617
|
+
&& safeString(message.frameTransportActive, 20).toLowerCase() === 'none'
|
|
5618
|
+
&& safeString(message.frameTransportState, 20).toLowerCase() === 'probing'
|
|
5619
|
+
&& h264.annexB
|
|
5620
|
+
&& h264.hasSps
|
|
5621
|
+
&& h264.hasPps
|
|
5622
|
+
&& h264.hasIdr
|
|
5623
|
+
&& h264.nalOrderValid;
|
|
5624
|
+
if (!proofPayloadValid) {
|
|
5625
|
+
device.counters.liveFramesDropped += 1;
|
|
5626
|
+
emitRemoteEvent('RemoteFrameDropped', device, {
|
|
5627
|
+
reason: proof.error || 'frame-proof-h264-invalid',
|
|
5628
|
+
streamId: proof.streamId,
|
|
5629
|
+
commandId: proof.commandId,
|
|
5630
|
+
captureGeneration: proof.captureGeneration,
|
|
5631
|
+
monitorIndex: proof.monitorIndex,
|
|
5632
|
+
transport
|
|
5633
|
+
});
|
|
5634
|
+
return {
|
|
5635
|
+
accepted: false,
|
|
5636
|
+
proofAccepted: false,
|
|
5637
|
+
proof,
|
|
5638
|
+
error: proof.error || 'frame-proof-h264-invalid'
|
|
5639
|
+
};
|
|
5640
|
+
}
|
|
5641
|
+
if (proofOnly) {
|
|
5642
|
+
const streamState = getDeviceLiveStream(device, proof.streamId);
|
|
5643
|
+
// A proof-only frame is an out-of-band path challenge. It must
|
|
5644
|
+
// prove the exact current stream owner and carry a complete
|
|
5645
|
+
// independently decodable H.264 key frame, but it must not race
|
|
5646
|
+
// the presentation lane's latest delta frame. WAN fragmentation
|
|
5647
|
+
// can legitimately deliver this challenge several relay frames
|
|
5648
|
+
// after the same source key was presented.
|
|
5649
|
+
const exactActiveStreamOwner = transport === 'udp-p2p'
|
|
5650
|
+
&& streamState?.active === true
|
|
5651
|
+
&& safeString(streamState.commandId, 128) === proof.commandId
|
|
5652
|
+
&& Number(streamState.captureGeneration || 0) === proof.captureGeneration
|
|
5653
|
+
&& parseExactLiveStreamMonitorIndex(streamState.monitorIndex) === proof.monitorIndex;
|
|
5654
|
+
if (exactActiveStreamOwner) {
|
|
5655
|
+
emitRemoteEvent('RemoteFrameTransportProofAccepted', device, {
|
|
5656
|
+
proofOnly: true,
|
|
5657
|
+
proofId: proof.proofId,
|
|
5658
|
+
streamId: proof.streamId,
|
|
5659
|
+
commandId: proof.commandId,
|
|
5660
|
+
captureGeneration: proof.captureGeneration,
|
|
5661
|
+
monitorIndex: proof.monitorIndex,
|
|
5662
|
+
frameSeq: proofFrameSeq,
|
|
5663
|
+
streamFrameSeq: proofStreamFrameSeq,
|
|
5664
|
+
transport
|
|
5665
|
+
});
|
|
5666
|
+
}
|
|
5667
|
+
return {
|
|
5668
|
+
accepted: exactActiveStreamOwner,
|
|
5669
|
+
proofAccepted: exactActiveStreamOwner,
|
|
5670
|
+
proofOnly: true,
|
|
5671
|
+
proof,
|
|
5672
|
+
error: exactActiveStreamOwner
|
|
5673
|
+
? ''
|
|
5674
|
+
: 'frame-proof-active-owner-mismatch'
|
|
5675
|
+
};
|
|
5676
|
+
}
|
|
5677
|
+
const accepted = applyLiveFrame(device, message, framePayload, transport);
|
|
5678
|
+
const frame = device.latestLiveFrame;
|
|
5679
|
+
const exactAcceptedFrame = accepted
|
|
5680
|
+
&& frame?.currentGenerationVerified === true
|
|
5681
|
+
&& safeString(frame.streamId, 128) === proof.streamId
|
|
5682
|
+
&& safeString(frame.commandId, 128) === proof.commandId
|
|
5683
|
+
&& Number(frame.captureGeneration) === proof.captureGeneration
|
|
5684
|
+
&& parseExactLiveStreamMonitorIndex(frame.monitorIndex) === proof.monitorIndex;
|
|
5685
|
+
if (exactAcceptedFrame) {
|
|
5686
|
+
emitRemoteEvent('RemoteFrameTransportProofAccepted', device, {
|
|
5687
|
+
proofOnly: false,
|
|
5688
|
+
proofId: proof.proofId,
|
|
5689
|
+
streamId: proof.streamId,
|
|
5690
|
+
commandId: proof.commandId,
|
|
5691
|
+
captureGeneration: proof.captureGeneration,
|
|
5692
|
+
monitorIndex: proof.monitorIndex,
|
|
5693
|
+
frameSeq: proofFrameSeq,
|
|
5694
|
+
streamFrameSeq: proofStreamFrameSeq,
|
|
5695
|
+
transport
|
|
5696
|
+
});
|
|
5697
|
+
}
|
|
5698
|
+
return {
|
|
5699
|
+
accepted: exactAcceptedFrame,
|
|
5700
|
+
proofAccepted: exactAcceptedFrame,
|
|
5701
|
+
proof,
|
|
5702
|
+
error: exactAcceptedFrame ? '' : 'frame-proof-current-generation-rejected'
|
|
5703
|
+
};
|
|
5704
|
+
}
|
|
5705
|
+
|
|
5706
|
+
function writeFrameTransportProofResponse(socket, result) {
|
|
5707
|
+
const proof = result?.proof;
|
|
5708
|
+
if (!proof?.proofId) return;
|
|
5709
|
+
const accepted = result.proofAccepted === true;
|
|
5710
|
+
writeJsonLine(socket, {
|
|
5711
|
+
type: accepted
|
|
5712
|
+
? 'frame.transport.proof-accepted'
|
|
5713
|
+
: 'frame.transport.proof-rejected',
|
|
5714
|
+
protocol: 'livedesk.frame-transport-proof.v1',
|
|
5715
|
+
proofId: proof.proofId,
|
|
5716
|
+
streamId: proof.streamId,
|
|
5717
|
+
commandId: proof.commandId,
|
|
5718
|
+
captureGeneration: proof.captureGeneration,
|
|
5719
|
+
monitorIndex: proof.monitorIndex,
|
|
5720
|
+
...(accepted ? {} : { error: safeString(result.error || 'frame-proof-rejected', 160) })
|
|
5721
|
+
});
|
|
5722
|
+
}
|
|
5723
|
+
|
|
5416
5724
|
function handleAgentBinaryFrame(socket, state, header, framePayload) {
|
|
5417
5725
|
if (!state.authenticated || !state.device) {
|
|
5418
5726
|
writeJsonLine(socket, { type: 'error', error: 'hello-required' });
|
|
@@ -5448,11 +5756,12 @@ export function createRemoteHub(options = {}) {
|
|
|
5448
5756
|
}
|
|
5449
5757
|
|
|
5450
5758
|
if (frameKind === 'stream' || frameKind === 'live' || header.type === 'stream.binary') {
|
|
5451
|
-
|
|
5759
|
+
const result = applyLiveFrameWithTransportProof(device, {
|
|
5452
5760
|
...header,
|
|
5453
5761
|
hubIngressDropped: Number(state.binaryQueueDrops || 0),
|
|
5454
5762
|
hubIngressCounterEpoch: state.binaryQueueEpoch
|
|
5455
5763
|
}, framePayload, binaryTransport);
|
|
5764
|
+
writeFrameTransportProofResponse(socket, result);
|
|
5456
5765
|
return;
|
|
5457
5766
|
}
|
|
5458
5767
|
|
|
@@ -5472,7 +5781,7 @@ export function createRemoteHub(options = {}) {
|
|
|
5472
5781
|
});
|
|
5473
5782
|
}
|
|
5474
5783
|
|
|
5475
|
-
function handleUdpFrame({ deviceId, sessionId, header, payload } = {}) {
|
|
5784
|
+
function handleUdpFrame({ deviceId, sessionId, header, payload } = {}) {
|
|
5476
5785
|
const device = devices.get(safeString(deviceId, 160));
|
|
5477
5786
|
if (!device || !device.connected) {
|
|
5478
5787
|
return false;
|
|
@@ -5485,19 +5794,25 @@ export function createRemoteHub(options = {}) {
|
|
|
5485
5794
|
emitRemoteEvent('RemoteFrameDropped', device, { reason: 'udp-frame-mode-not-supported', transport: 'udp-p2p', frameMode });
|
|
5486
5795
|
return false;
|
|
5487
5796
|
}
|
|
5488
|
-
|
|
5489
|
-
...device.udp,
|
|
5490
|
-
state: 'udp-active',
|
|
5491
|
-
ready: true,
|
|
5492
|
-
lastFrameAt: new Date().toISOString(),
|
|
5493
|
-
framesReceived: Number(device.udp?.framesReceived || 0) + 1
|
|
5494
|
-
};
|
|
5495
|
-
return applyLiveFrame(device, {
|
|
5797
|
+
const result = applyLiveFrameWithTransportProof(device, {
|
|
5496
5798
|
...header,
|
|
5497
5799
|
transport: 'udp-p2p',
|
|
5498
5800
|
hubIngressCounterEpoch: sessionId
|
|
5499
5801
|
}, payload, 'udp-p2p');
|
|
5500
|
-
|
|
5802
|
+
if (result.accepted) {
|
|
5803
|
+
device.udp = {
|
|
5804
|
+
...device.udp,
|
|
5805
|
+
state: result.proof ? 'udp-proof-accepted' : 'udp-active',
|
|
5806
|
+
ready: true,
|
|
5807
|
+
lastFrameAt: new Date().toISOString(),
|
|
5808
|
+
framesReceived: Number(device.udp?.framesReceived || 0)
|
|
5809
|
+
+ (result.proofOnly === true ? 0 : 1)
|
|
5810
|
+
};
|
|
5811
|
+
}
|
|
5812
|
+
return result.proof
|
|
5813
|
+
? result
|
|
5814
|
+
: result.accepted;
|
|
5815
|
+
}
|
|
5501
5816
|
|
|
5502
5817
|
function dropQueuedAgentBinaryFrame(state) {
|
|
5503
5818
|
if (!Array.isArray(state.binaryQueue) || state.binaryQueue.length === 0) {
|
package/hub/src/server.js
CHANGED
|
@@ -2137,6 +2137,10 @@ function buildRemoteFrameBinaryPacket(frameEvent, diagnostics = {}) {
|
|
|
2137
2137
|
? frame.nativeEncoderHardwareAccelerated
|
|
2138
2138
|
: null,
|
|
2139
2139
|
nativeEncoderHardwareQueryStatus: frame.nativeEncoderHardwareQueryStatus || '',
|
|
2140
|
+
nativeCaptureDisplayId: Number.isSafeInteger(Number(frame.nativeCaptureDisplayId))
|
|
2141
|
+
&& Number(frame.nativeCaptureDisplayId) > 0
|
|
2142
|
+
? Number(frame.nativeCaptureDisplayId)
|
|
2143
|
+
: 0,
|
|
2140
2144
|
nativeCaptureFps: Number(frame.nativeCaptureFps || 0) || 0,
|
|
2141
2145
|
nativeCompressionFps: Number(frame.nativeCompressionFps || 0) || 0,
|
|
2142
2146
|
nativeEncodedFps: Number(frame.nativeEncodedFps || 0) || 0,
|
|
@@ -263,7 +263,7 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
|
|
|
263
263
|
if (!complete) return;
|
|
264
264
|
session.framesReceived += 1;
|
|
265
265
|
const reassembly = session.reassembler.getStats();
|
|
266
|
-
frameHandler({
|
|
266
|
+
const handled = frameHandler({
|
|
267
267
|
deviceId: session.deviceId,
|
|
268
268
|
sessionId: session.sessionId,
|
|
269
269
|
header: {
|
|
@@ -276,11 +276,34 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
|
|
|
276
276
|
},
|
|
277
277
|
payload: complete.payload,
|
|
278
278
|
transport: 'udp-p2p'
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
279
|
+
});
|
|
280
|
+
const accepted = handled === true || handled?.accepted === true;
|
|
281
|
+
const proofId = safeText(complete.header?.frameTransportProofId, 96);
|
|
282
|
+
if (proofId) {
|
|
283
|
+
if (handled?.suppressProofResponse === true) return;
|
|
284
|
+
const proofAccepted = accepted && handled?.proofAccepted === true;
|
|
285
|
+
sendPacket(session, 'pong', {
|
|
286
|
+
state: proofAccepted ? 'frame-proof-ack' : 'frame-proof-reject',
|
|
287
|
+
proofId,
|
|
288
|
+
protocol: 'livedesk.frame-transport-proof.v1',
|
|
289
|
+
streamId: safeText(complete.header?.streamId, 128),
|
|
290
|
+
commandId: safeText(complete.header?.commandId, 128),
|
|
291
|
+
captureGeneration: Number(complete.header?.captureGeneration || 0) || 0,
|
|
292
|
+
monitorIndex: Number(complete.header?.monitorIndex ?? -1),
|
|
293
|
+
error: proofAccepted ? '' : safeText(handled?.error || 'frame-proof-rejected', 160)
|
|
294
|
+
});
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (accepted) {
|
|
298
|
+
sendPacket(session, 'pong', {
|
|
299
|
+
state: 'frame-ack',
|
|
300
|
+
frameSeq: Number(complete.header?.frameSeq || 0) || 0,
|
|
301
|
+
streamId: safeText(complete.header?.streamId, 128),
|
|
302
|
+
commandId: safeText(complete.header?.commandId, 128),
|
|
303
|
+
captureGeneration: Number(complete.header?.captureGeneration || 0) || 0,
|
|
304
|
+
monitorIndex: Number(complete.header?.monitorIndex ?? -1)
|
|
305
|
+
});
|
|
306
|
+
}
|
|
284
307
|
});
|
|
285
308
|
socket.on('error', error => logWarn('udp', `UDP socket error: ${error?.message || error}`));
|
|
286
309
|
|
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.461",
|
|
4
|
+
"livedeskClientVersion": "0.1.214",
|
|
5
5
|
"buildFlavor": "production",
|
|
6
6
|
"description": "LiveDesk Hub and client launcher",
|
|
7
7
|
"type": "module",
|
|
@@ -15,9 +15,10 @@
|
|
|
15
15
|
"diagnostics/",
|
|
16
16
|
"hub/",
|
|
17
17
|
"runtime-core/",
|
|
18
|
-
"client/",
|
|
19
|
-
"electron/",
|
|
20
|
-
"
|
|
18
|
+
"client/",
|
|
19
|
+
"electron/",
|
|
20
|
+
"scripts/",
|
|
21
|
+
"web/",
|
|
21
22
|
"README.md"
|
|
22
23
|
],
|
|
23
24
|
"scripts": {
|
|
@@ -50,10 +51,10 @@
|
|
|
50
51
|
"ws": "^8.18.3"
|
|
51
52
|
},
|
|
52
53
|
"optionalDependencies": {
|
|
53
|
-
"@livedesk/fast-linux-x64": "0.1.
|
|
54
|
-
"@livedesk/fast-osx-arm64": "0.1.
|
|
55
|
-
"@livedesk/fast-osx-x64": "0.1.
|
|
56
|
-
"@livedesk/fast-win-x64": "0.1.
|
|
54
|
+
"@livedesk/fast-linux-x64": "0.1.419",
|
|
55
|
+
"@livedesk/fast-osx-arm64": "0.1.419",
|
|
56
|
+
"@livedesk/fast-osx-x64": "0.1.419",
|
|
57
|
+
"@livedesk/fast-win-x64": "0.1.419"
|
|
57
58
|
},
|
|
58
59
|
"publishConfig": {
|
|
59
60
|
"access": "public"
|