@livedesk/hub 0.1.66 → 0.1.68
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 +3 -3
- package/src/captures/capture-store.js +2 -2
- package/src/console-direct.js +60 -5
- package/src/console-direct.test.mjs +95 -1
- package/src/remote-hub.js +320 -97
- package/src/server.js +272 -30
- package/src/shared-wall-profile-contract.mjs +69 -0
- package/src/shared-wall-profile-contract.test.mjs +99 -0
- package/src/{live-desk-update.js → vuvodesk-update.js} +1 -1
- package/src/wall-source-restart-runtime.test.mjs +10 -0
package/src/remote-hub.js
CHANGED
|
@@ -17,14 +17,15 @@ import {
|
|
|
17
17
|
BoundedSegmentedBuffer,
|
|
18
18
|
createBoundedAgentBinaryIngressLane
|
|
19
19
|
} from './transport/agent-binary-ingress.js';
|
|
20
|
-
import {
|
|
21
|
-
REMOTE_CLIPBOARD_FILE_ACTIONS,
|
|
20
|
+
import {
|
|
21
|
+
REMOTE_CLIPBOARD_FILE_ACTIONS,
|
|
22
22
|
REMOTE_CLIPBOARD_LIMITS,
|
|
23
23
|
REMOTE_CLIPBOARD_PROTOCOL,
|
|
24
24
|
normalizeRemoteClipboardCommandResult,
|
|
25
25
|
normalizeRemoteClipboardManifest,
|
|
26
26
|
remoteClipboardContentNeedsFileTransfer
|
|
27
|
-
} from './remote-clipboard-contract.mjs';
|
|
27
|
+
} from './remote-clipboard-contract.mjs';
|
|
28
|
+
import { liveProfileSatisfiesDemand } from './shared-wall-profile-contract.mjs';
|
|
28
29
|
|
|
29
30
|
const DEFAULT_REMOTE_HUB_PORT = 5197;
|
|
30
31
|
const DEFAULT_REMOTE_HUB_HOST = '0.0.0.0';
|
|
@@ -74,6 +75,8 @@ const LIVE_STREAM_MIN_FRESH_MS = 3000;
|
|
|
74
75
|
const LIVE_STREAM_MAX_FRESH_MS = 12000;
|
|
75
76
|
const TASK_BATCH_OWNER = Symbol('remote-hub-task-batch-owner');
|
|
76
77
|
const DEFAULT_REMOTE_INPUT_ACK_TIMEOUT_MS = 5000;
|
|
78
|
+
const REMOTE_INPUT_FAILURE_LOG_INTERVAL_MS = 5000;
|
|
79
|
+
const REMOTE_INPUT_FAILURE_LOG_KEY_LIMIT = 32;
|
|
77
80
|
// RemoteFast owns a shared 7s native stop deadline. Keep four seconds for a
|
|
78
81
|
// priority command.result to cross either Direct or encrypted Relay transport.
|
|
79
82
|
const DEFAULT_LIVE_CAPTURE_STOP_ACK_TIMEOUT_MS = 11000;
|
|
@@ -1530,7 +1533,7 @@ function readCapabilityFlag(capabilities, key) {
|
|
|
1530
1533
|
return /^(1|true|yes|on)$/i.test(String(value || '').trim());
|
|
1531
1534
|
}
|
|
1532
1535
|
|
|
1533
|
-
function createDeviceCounters(overrides = {}) {
|
|
1536
|
+
function createDeviceCounters(overrides = {}) {
|
|
1534
1537
|
return {
|
|
1535
1538
|
messagesReceived: 0,
|
|
1536
1539
|
statusReceived: 0,
|
|
@@ -1546,8 +1549,10 @@ function createDeviceCounters(overrides = {}) {
|
|
|
1546
1549
|
audioStreamsStopped: 0,
|
|
1547
1550
|
audioFramesReceived: 0,
|
|
1548
1551
|
audioFramesDropped: 0,
|
|
1549
|
-
audioStatusReceived: 0,
|
|
1550
|
-
|
|
1552
|
+
audioStatusReceived: 0,
|
|
1553
|
+
remoteInputsApplied: 0,
|
|
1554
|
+
remoteInputsRejected: 0,
|
|
1555
|
+
tasksQueued: 0,
|
|
1551
1556
|
taskResultsReceived: 0,
|
|
1552
1557
|
taskResultsFailed: 0,
|
|
1553
1558
|
taskResultsTimedOut: 0,
|
|
@@ -1815,6 +1820,38 @@ function serializeDeviceTask(task) {
|
|
|
1815
1820
|
};
|
|
1816
1821
|
}
|
|
1817
1822
|
|
|
1823
|
+
function createRemoteInputDiagnostics() {
|
|
1824
|
+
return {
|
|
1825
|
+
appliedCount: 0,
|
|
1826
|
+
rejectedCount: 0,
|
|
1827
|
+
lastOutcome: null,
|
|
1828
|
+
lastApplied: null,
|
|
1829
|
+
lastFailure: null
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
function serializeRemoteInputDiagnostics(diagnostics) {
|
|
1834
|
+
const source = diagnostics && typeof diagnostics === 'object'
|
|
1835
|
+
? diagnostics
|
|
1836
|
+
: createRemoteInputDiagnostics();
|
|
1837
|
+
return {
|
|
1838
|
+
appliedCount: Math.max(0, Number(source.appliedCount) || 0),
|
|
1839
|
+
rejectedCount: Math.max(0, Number(source.rejectedCount) || 0),
|
|
1840
|
+
lastOutcome: source.lastOutcome ? { ...source.lastOutcome } : null,
|
|
1841
|
+
lastApplied: source.lastApplied ? { ...source.lastApplied } : null,
|
|
1842
|
+
lastFailure: source.lastFailure ? { ...source.lastFailure } : null
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
function firstOptionalBoolean(...values) {
|
|
1847
|
+
for (const value of values) {
|
|
1848
|
+
if (typeof value === 'boolean') {
|
|
1849
|
+
return value;
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
return null;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1818
1855
|
function serializeDevice(device, options = {}) {
|
|
1819
1856
|
if (!device) {
|
|
1820
1857
|
return null;
|
|
@@ -1895,8 +1932,9 @@ function serializeDevice(device, options = {}) {
|
|
|
1895
1932
|
recentTasks: Array.isArray(device.recentTasks)
|
|
1896
1933
|
? device.recentTasks.map(serializeDeviceTask).filter(Boolean)
|
|
1897
1934
|
: [],
|
|
1898
|
-
status: { ...device.status },
|
|
1899
|
-
|
|
1935
|
+
status: { ...device.status },
|
|
1936
|
+
remoteInputDiagnostics: serializeRemoteInputDiagnostics(device.remoteInputDiagnostics),
|
|
1937
|
+
counters: { ...device.counters }
|
|
1900
1938
|
};
|
|
1901
1939
|
}
|
|
1902
1940
|
|
|
@@ -4942,9 +4980,11 @@ export function createRemoteHub(options = {}) {
|
|
|
4942
4980
|
latestAudioStatus: null,
|
|
4943
4981
|
latestTask,
|
|
4944
4982
|
recentTasks: latestTask ? [latestTask] : [],
|
|
4945
|
-
pendingTaskCommands: new Map(),
|
|
4946
|
-
pendingTaskTimers: new Map(),
|
|
4947
|
-
|
|
4983
|
+
pendingTaskCommands: new Map(),
|
|
4984
|
+
pendingTaskTimers: new Map(),
|
|
4985
|
+
remoteInputDiagnostics: createRemoteInputDiagnostics(),
|
|
4986
|
+
remoteInputFailureLogAt: new Map(),
|
|
4987
|
+
counters: createDeviceCounters({
|
|
4948
4988
|
messagesReceived: 1,
|
|
4949
4989
|
statusReceived: 1,
|
|
4950
4990
|
tasksQueued: latestTask ? 1 : 0,
|
|
@@ -5239,9 +5279,11 @@ export function createRemoteHub(options = {}) {
|
|
|
5239
5279
|
},
|
|
5240
5280
|
latestTask: null,
|
|
5241
5281
|
recentTasks: [],
|
|
5242
|
-
pendingTaskCommands: new Map(),
|
|
5243
|
-
pendingTaskTimers: new Map(),
|
|
5244
|
-
|
|
5282
|
+
pendingTaskCommands: new Map(),
|
|
5283
|
+
pendingTaskTimers: new Map(),
|
|
5284
|
+
remoteInputDiagnostics: createRemoteInputDiagnostics(),
|
|
5285
|
+
remoteInputFailureLogAt: new Map(),
|
|
5286
|
+
synthetic: false,
|
|
5245
5287
|
counters: createDeviceCounters({ messagesReceived: 1 })
|
|
5246
5288
|
};
|
|
5247
5289
|
|
|
@@ -5979,9 +6021,10 @@ export function createRemoteHub(options = {}) {
|
|
|
5979
6021
|
function acknowledgePendingDedicatedInput(device, message = {}) {
|
|
5980
6022
|
const state = device?.pendingDedicatedInputAckState;
|
|
5981
6023
|
if (!state?.current) {
|
|
5982
|
-
return
|
|
6024
|
+
return null;
|
|
5983
6025
|
}
|
|
5984
6026
|
if (dedicatedInputAckMatches(state.current, message)) {
|
|
6027
|
+
const acknowledged = state.current;
|
|
5985
6028
|
if (state.timeoutTimer) {
|
|
5986
6029
|
clearTimeout(state.timeoutTimer);
|
|
5987
6030
|
state.timeoutTimer = null;
|
|
@@ -5989,15 +6032,16 @@ export function createRemoteHub(options = {}) {
|
|
|
5989
6032
|
state.current = state.next;
|
|
5990
6033
|
state.next = null;
|
|
5991
6034
|
armPendingDedicatedInputAck(device);
|
|
5992
|
-
return
|
|
6035
|
+
return acknowledged;
|
|
5993
6036
|
}
|
|
5994
6037
|
if (dedicatedInputAckMatches(state.next, message)) {
|
|
5995
6038
|
// A later ordered acknowledgement proves that the input reader is
|
|
5996
6039
|
// still draining, even if an earlier diagnostic response vanished.
|
|
6040
|
+
const acknowledged = state.next;
|
|
5997
6041
|
clearPendingDedicatedInputAcks(device);
|
|
5998
|
-
return
|
|
6042
|
+
return acknowledged;
|
|
5999
6043
|
}
|
|
6000
|
-
return
|
|
6044
|
+
return null;
|
|
6001
6045
|
}
|
|
6002
6046
|
|
|
6003
6047
|
function pruneCompletedInputFallbacks(device, now = Date.now()) {
|
|
@@ -6104,14 +6148,39 @@ export function createRemoteHub(options = {}) {
|
|
|
6104
6148
|
|| pending?.inputEventId,
|
|
6105
6149
|
128),
|
|
6106
6150
|
inputSeq,
|
|
6107
|
-
inputType: safeString(
|
|
6151
|
+
inputType: safeString(
|
|
6108
6152
|
message.inputType
|
|
6109
6153
|
|| message.InputType
|
|
6110
6154
|
|| result.inputType
|
|
6111
6155
|
|| result.InputType
|
|
6112
6156
|
|| pending?.inputType,
|
|
6113
|
-
48),
|
|
6114
|
-
|
|
6157
|
+
48),
|
|
6158
|
+
controlSessionId: safeString(
|
|
6159
|
+
message.controlSessionId
|
|
6160
|
+
|| message.ControlSessionId
|
|
6161
|
+
|| result.controlSessionId
|
|
6162
|
+
|| result.ControlSessionId
|
|
6163
|
+
|| pending?.controlSessionId
|
|
6164
|
+
|| device.sessionId,
|
|
6165
|
+
160),
|
|
6166
|
+
controlCommandId: safeString(
|
|
6167
|
+
message.controlCommandId
|
|
6168
|
+
|| message.ControlCommandId
|
|
6169
|
+
|| result.controlCommandId
|
|
6170
|
+
|| result.ControlCommandId
|
|
6171
|
+
|| pending?.controlCommandId,
|
|
6172
|
+
128),
|
|
6173
|
+
captureGeneration: Math.max(0, Number(
|
|
6174
|
+
message.captureGeneration
|
|
6175
|
+
?? message.CaptureGeneration
|
|
6176
|
+
?? result.captureGeneration
|
|
6177
|
+
?? result.CaptureGeneration
|
|
6178
|
+
?? pending?.captureGeneration
|
|
6179
|
+
?? 0) || 0),
|
|
6180
|
+
inputSocketConnectionId: safeString(
|
|
6181
|
+
pending?.inputSocketConnectionId,
|
|
6182
|
+
128),
|
|
6183
|
+
monitorIndex: Number(
|
|
6115
6184
|
message.monitorIndex
|
|
6116
6185
|
?? message.MonitorIndex
|
|
6117
6186
|
?? result.monitorIndex
|
|
@@ -6177,27 +6246,144 @@ export function createRemoteHub(options = {}) {
|
|
|
6177
6246
|
|| message.FocusChanged === true
|
|
6178
6247
|
|| result.focusChanged === true
|
|
6179
6248
|
|| result.FocusChanged === true,
|
|
6180
|
-
inputDiagnostic: message.inputDiagnostic === true
|
|
6249
|
+
inputDiagnostic: message.inputDiagnostic === true
|
|
6181
6250
|
|| message.InputDiagnostic === true
|
|
6182
|
-
|| result.inputDiagnostic === true
|
|
6183
|
-
|| result.InputDiagnostic === true,
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6251
|
+
|| result.inputDiagnostic === true
|
|
6252
|
+
|| result.InputDiagnostic === true,
|
|
6253
|
+
failureStage: safeString(
|
|
6254
|
+
message.failureStage
|
|
6255
|
+
|| message.FailureStage
|
|
6256
|
+
|| result.failureStage
|
|
6257
|
+
|| result.FailureStage,
|
|
6258
|
+
40),
|
|
6259
|
+
exceptionType: safeString(
|
|
6260
|
+
message.exceptionType
|
|
6261
|
+
|| message.ExceptionType
|
|
6262
|
+
|| result.exceptionType
|
|
6263
|
+
|| result.ExceptionType,
|
|
6264
|
+
80),
|
|
6265
|
+
permissionSupported: firstOptionalBoolean(
|
|
6266
|
+
message.permissionSupported,
|
|
6267
|
+
message.PermissionSupported,
|
|
6268
|
+
result.permissionSupported,
|
|
6269
|
+
result.PermissionSupported),
|
|
6270
|
+
permissionGranted: firstOptionalBoolean(
|
|
6271
|
+
message.permissionGranted,
|
|
6272
|
+
message.PermissionGranted,
|
|
6273
|
+
result.permissionGranted,
|
|
6274
|
+
result.PermissionGranted),
|
|
6275
|
+
macAccessibilityTrusted: firstOptionalBoolean(
|
|
6276
|
+
message.macAccessibilityTrusted,
|
|
6277
|
+
message.MacAccessibilityTrusted,
|
|
6278
|
+
result.macAccessibilityTrusted,
|
|
6279
|
+
result.MacAccessibilityTrusted),
|
|
6280
|
+
macPostEventAccess: firstOptionalBoolean(
|
|
6281
|
+
message.macPostEventAccess,
|
|
6282
|
+
message.MacPostEventAccess,
|
|
6283
|
+
result.macPostEventAccess,
|
|
6284
|
+
result.MacPostEventAccess),
|
|
6285
|
+
fallback: pending?.fallback === true,
|
|
6286
|
+
hubAcknowledgedAtEpochMs: Date.now()
|
|
6287
|
+
};
|
|
6288
|
+
}
|
|
6289
|
+
|
|
6290
|
+
function rememberRemoteInputOutcome(device, outcome, state) {
|
|
6291
|
+
const diagnostics = device.remoteInputDiagnostics
|
|
6292
|
+
|| (device.remoteInputDiagnostics = createRemoteInputDiagnostics());
|
|
6293
|
+
const record = {
|
|
6294
|
+
state,
|
|
6295
|
+
receivedAt: new Date(outcome.hubAcknowledgedAtEpochMs).toISOString(),
|
|
6296
|
+
inputType: safeString(outcome.inputType, 48),
|
|
6297
|
+
inputSeq: Math.max(0, Number(outcome.inputSeq) || 0),
|
|
6298
|
+
monitorIndex: Math.max(0, Number(outcome.monitorIndex) || 0),
|
|
6299
|
+
sessionId: safeString(outcome.controlSessionId || device.sessionId, 160),
|
|
6300
|
+
controlCommandId: safeString(outcome.controlCommandId, 128),
|
|
6301
|
+
captureGeneration: Math.max(0, Number(outcome.captureGeneration) || 0),
|
|
6302
|
+
inputSocketConnectionId: safeString(outcome.inputSocketConnectionId, 128),
|
|
6303
|
+
fallback: outcome.fallback === true
|
|
6304
|
+
};
|
|
6305
|
+
if (state === 'applied') {
|
|
6306
|
+
diagnostics.appliedCount += 1;
|
|
6307
|
+
device.counters.remoteInputsApplied += 1;
|
|
6308
|
+
const applied = {
|
|
6309
|
+
...record,
|
|
6310
|
+
duplicate: outcome.duplicate === true,
|
|
6311
|
+
agentApplyMs: Math.max(0, Number(outcome.agentApplyMs) || 0),
|
|
6312
|
+
nativeInputBackend: safeString(outcome.nativeInputBackend, 80)
|
|
6313
|
+
};
|
|
6314
|
+
diagnostics.lastApplied = applied;
|
|
6315
|
+
diagnostics.lastOutcome = applied;
|
|
6316
|
+
return applied;
|
|
6317
|
+
}
|
|
6318
|
+
|
|
6319
|
+
diagnostics.rejectedCount += 1;
|
|
6320
|
+
device.counters.remoteInputsRejected += 1;
|
|
6321
|
+
const failed = {
|
|
6322
|
+
...record,
|
|
6323
|
+
errorKind: outcome.errorKind === 'native-apply' ? 'native-apply' : 'route',
|
|
6324
|
+
failureStage: safeString(outcome.failureStage, 40) || 'unknown',
|
|
6325
|
+
exceptionType: safeString(outcome.exceptionType, 80),
|
|
6326
|
+
permissionSupported: firstOptionalBoolean(outcome.permissionSupported),
|
|
6327
|
+
permissionGranted: firstOptionalBoolean(outcome.permissionGranted),
|
|
6328
|
+
macAccessibilityTrusted: firstOptionalBoolean(outcome.macAccessibilityTrusted),
|
|
6329
|
+
macPostEventAccess: firstOptionalBoolean(outcome.macPostEventAccess),
|
|
6330
|
+
error: safeDiagnosticText(outcome.error, 600) || 'remote-input-failed',
|
|
6331
|
+
failedAtEpochMs: Math.max(0, Number(outcome.failedAtEpochMs) || 0)
|
|
6332
|
+
};
|
|
6333
|
+
diagnostics.lastFailure = failed;
|
|
6334
|
+
diagnostics.lastOutcome = failed;
|
|
6335
|
+
return failed;
|
|
6336
|
+
}
|
|
6337
|
+
|
|
6338
|
+
function logRemoteInputFailure(device, failure) {
|
|
6339
|
+
const rateLimits = device.remoteInputFailureLogAt
|
|
6340
|
+
|| (device.remoteInputFailureLogAt = new Map());
|
|
6341
|
+
const key = `${failure.errorKind}:${failure.failureStage}:${failure.exceptionType}:${failure.error}`;
|
|
6342
|
+
const now = Date.now();
|
|
6343
|
+
const lastAt = Number(rateLimits.get(key) || 0);
|
|
6344
|
+
if (lastAt > 0 && now - lastAt < REMOTE_INPUT_FAILURE_LOG_INTERVAL_MS) {
|
|
6345
|
+
return;
|
|
6346
|
+
}
|
|
6347
|
+
rateLimits.delete(key);
|
|
6348
|
+
rateLimits.set(key, now);
|
|
6349
|
+
while (rateLimits.size > REMOTE_INPUT_FAILURE_LOG_KEY_LIMIT) {
|
|
6350
|
+
const oldestKey = rateLimits.keys().next().value;
|
|
6351
|
+
if (!oldestKey) break;
|
|
6352
|
+
rateLimits.delete(oldestKey);
|
|
6353
|
+
}
|
|
6354
|
+
const shortId = value => safeString(value, 128).slice(0, 12) || 'unknown';
|
|
6355
|
+
const optionalFlag = value => typeof value === 'boolean' ? String(value) : 'unknown';
|
|
6356
|
+
logWarn(
|
|
6357
|
+
'remote',
|
|
6358
|
+
`input rejected device=${device.deviceName} (${device.deviceId})`
|
|
6359
|
+
+ ` session=${shortId(failure.sessionId)}`
|
|
6360
|
+
+ ` command=${shortId(failure.controlCommandId)}`
|
|
6361
|
+
+ ` generation=${failure.captureGeneration}`
|
|
6362
|
+
+ ` monitor=${failure.monitorIndex}`
|
|
6363
|
+
+ ` type=${failure.inputType || 'unknown'}`
|
|
6364
|
+
+ ` seq=${failure.inputSeq}`
|
|
6365
|
+
+ ` kind=${failure.errorKind}`
|
|
6366
|
+
+ ` stage=${failure.failureStage}`
|
|
6367
|
+
+ ` exception=${failure.exceptionType || 'unknown'}`
|
|
6368
|
+
+ ` permission=${optionalFlag(failure.permissionGranted)}`
|
|
6369
|
+
+ ` accessibility=${optionalFlag(failure.macAccessibilityTrusted)}`
|
|
6370
|
+
+ ` postEvent=${optionalFlag(failure.macPostEventAccess)}`
|
|
6371
|
+
+ ` error=${failure.error}`);
|
|
6372
|
+
}
|
|
6373
|
+
|
|
6374
|
+
function emitRemoteInputApplied(device, message = {}, pending = null) {
|
|
6375
|
+
const outcome = {
|
|
6376
|
+
...buildRemoteInputOutcome(device, message, pending),
|
|
6377
|
+
agentApplyMs: Number(
|
|
6378
|
+
message.agentApplyMs
|
|
6379
|
+
?? message.AgentApplyMs
|
|
6380
|
+
?? message.result?.agentApplyMs
|
|
6381
|
+
?? message.result?.AgentApplyMs
|
|
6382
|
+
?? 0) || 0
|
|
6383
|
+
};
|
|
6384
|
+
rememberRemoteInputOutcome(device, outcome, 'applied');
|
|
6385
|
+
emitEvent('RemoteInputApplied', outcome);
|
|
6386
|
+
}
|
|
6201
6387
|
|
|
6202
6388
|
function emitRemoteInputError(
|
|
6203
6389
|
device,
|
|
@@ -6206,29 +6392,37 @@ export function createRemoteHub(options = {}) {
|
|
|
6206
6392
|
fallbackError = 'remote-input-failed',
|
|
6207
6393
|
errorKind = 'route') {
|
|
6208
6394
|
const outcome = buildRemoteInputOutcome(device, message, pending);
|
|
6209
|
-
|
|
6395
|
+
const failure = {
|
|
6210
6396
|
...outcome,
|
|
6211
6397
|
errorKind: errorKind === 'native-apply' ? 'native-apply' : 'route',
|
|
6212
|
-
error:
|
|
6213
|
-
message.error
|
|
6214
|
-
|| message.Error
|
|
6215
|
-
|| message.result?.error
|
|
6216
|
-
|| message.result?.Error
|
|
6217
|
-
|| fallbackError,
|
|
6398
|
+
error: safeDiagnosticText(
|
|
6399
|
+
message.error
|
|
6400
|
+
|| message.Error
|
|
6401
|
+
|| message.result?.error
|
|
6402
|
+
|| message.result?.Error
|
|
6403
|
+
|| fallbackError,
|
|
6218
6404
|
600) || fallbackError,
|
|
6219
6405
|
failedAtEpochMs: Number(
|
|
6220
6406
|
message.failedAtEpochMs
|
|
6221
6407
|
?? message.FailedAtEpochMs
|
|
6222
6408
|
?? message.result?.failedAtEpochMs
|
|
6223
|
-
?? message.result?.FailedAtEpochMs
|
|
6224
|
-
?? 0) || 0
|
|
6225
|
-
}
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6409
|
+
?? message.result?.FailedAtEpochMs
|
|
6410
|
+
?? 0) || 0
|
|
6411
|
+
};
|
|
6412
|
+
const persistentFailure = rememberRemoteInputOutcome(device, failure, 'rejected');
|
|
6413
|
+
logRemoteInputFailure(device, persistentFailure);
|
|
6414
|
+
emitEvent('RemoteInputError', failure);
|
|
6415
|
+
}
|
|
6416
|
+
|
|
6417
|
+
function handleRemoteInputOutcome(
|
|
6418
|
+
device,
|
|
6419
|
+
message = {},
|
|
6420
|
+
source = 'input',
|
|
6421
|
+
acknowledgedPending = null) {
|
|
6229
6422
|
const commandId = safeString(message.commandId || message.CommandId, 128);
|
|
6230
6423
|
const inputSeq = Number(message.inputSeq ?? message.InputSeq ?? 0) || 0;
|
|
6231
|
-
const pending =
|
|
6424
|
+
const pending = acknowledgedPending
|
|
6425
|
+
|| takePendingInputFallback(device, commandId, inputSeq);
|
|
6232
6426
|
if (!pending
|
|
6233
6427
|
&& source === 'main'
|
|
6234
6428
|
&& wasInputFallbackCompleted(device, commandId, inputSeq)) {
|
|
@@ -8571,12 +8765,12 @@ export function createRemoteHub(options = {}) {
|
|
|
8571
8765
|
if (state.inputOnly) {
|
|
8572
8766
|
device.inputLastSeenAt = new Date().toISOString();
|
|
8573
8767
|
if (message.type === 'input.applied' || message.type === 'input.error') {
|
|
8574
|
-
acknowledgePendingDedicatedInput(device, message);
|
|
8768
|
+
const acknowledgedPending = acknowledgePendingDedicatedInput(device, message);
|
|
8575
8769
|
handleRemoteInputOutcome(device, {
|
|
8576
|
-
...message,
|
|
8577
|
-
agentApplyMs: Number(message.agentApplyMs || 0) || 0
|
|
8578
|
-
}, 'input');
|
|
8579
|
-
}
|
|
8770
|
+
...message,
|
|
8771
|
+
agentApplyMs: Number(message.agentApplyMs || 0) || 0
|
|
8772
|
+
}, 'input', acknowledgedPending);
|
|
8773
|
+
}
|
|
8580
8774
|
return;
|
|
8581
8775
|
}
|
|
8582
8776
|
if (state.frameOnly) {
|
|
@@ -10923,23 +11117,29 @@ export function createRemoteHub(options = {}) {
|
|
|
10923
11117
|
ensureLiveStreamReplacementTimers(device).set(stream.streamId, timer);
|
|
10924
11118
|
}
|
|
10925
11119
|
|
|
10926
|
-
function failPendingLiveStream(device, commandId, error = 'stream-start-failed') {
|
|
11120
|
+
function failPendingLiveStream(device, commandId, error = 'stream-start-failed') {
|
|
10927
11121
|
const key = safeString(commandId, 128);
|
|
10928
11122
|
if (!device || !key) {
|
|
10929
11123
|
return false;
|
|
10930
11124
|
}
|
|
10931
11125
|
const streams = ensureDeviceLiveStreams(device);
|
|
10932
11126
|
for (const stream of streams.values()) {
|
|
10933
|
-
const pending = getPendingLiveStreamDescriptor(stream);
|
|
10934
|
-
if (safeString(pending?.commandId, 128) !== key) {
|
|
10935
|
-
continue;
|
|
10936
|
-
}
|
|
10937
|
-
|
|
10938
|
-
|
|
10939
|
-
|
|
10940
|
-
|
|
10941
|
-
|
|
10942
|
-
|
|
11127
|
+
const pending = getPendingLiveStreamDescriptor(stream);
|
|
11128
|
+
if (safeString(pending?.commandId, 128) !== key) {
|
|
11129
|
+
continue;
|
|
11130
|
+
}
|
|
11131
|
+
const failedOwner = { ...pending };
|
|
11132
|
+
clearPendingLiveStreamDescriptor(device, stream);
|
|
11133
|
+
emitRemoteEvent('RemoteLiveStreamRestartFailed', device, {
|
|
11134
|
+
streamId: stream.streamId,
|
|
11135
|
+
commandId: key,
|
|
11136
|
+
streamPurpose: safeString(failedOwner.streamPurpose, 24) || 'wall',
|
|
11137
|
+
captureGeneration: Number(failedOwner.captureGeneration || 0),
|
|
11138
|
+
monitorIndex: Number(failedOwner.monitorIndex || 0),
|
|
11139
|
+
retainedCommandId: safeString(stream.commandId, 128),
|
|
11140
|
+
retainedCaptureGeneration: Number(stream.captureGeneration || 0),
|
|
11141
|
+
error: safeString(error, 500) || 'stream-start-failed'
|
|
11142
|
+
});
|
|
10943
11143
|
return true;
|
|
10944
11144
|
}
|
|
10945
11145
|
for (const stream of streams.values()) {
|
|
@@ -11276,9 +11476,23 @@ export function createRemoteHub(options = {}) {
|
|
|
11276
11476
|
=== normalized.streamPurpose;
|
|
11277
11477
|
}
|
|
11278
11478
|
|
|
11279
|
-
function sharedLiveProfileResult(
|
|
11280
|
-
|
|
11281
|
-
|
|
11479
|
+
function sharedLiveProfileResult(
|
|
11480
|
+
device,
|
|
11481
|
+
activeLiveStream,
|
|
11482
|
+
normalized,
|
|
11483
|
+
extra = {},
|
|
11484
|
+
subscriberRequestedProfile = null
|
|
11485
|
+
) {
|
|
11486
|
+
const requested = subscriberRequestedProfile && typeof subscriberRequestedProfile === 'object'
|
|
11487
|
+
? {
|
|
11488
|
+
fps: clampNumber(subscriberRequestedProfile.fps, 1, normalized.streamPurpose === 'control' ? 60 : 30, normalized.fps),
|
|
11489
|
+
maxWidth: clampNumber(subscriberRequestedProfile.maxWidth, 320, 3840, normalized.maxWidth),
|
|
11490
|
+
maxHeight: clampNumber(subscriberRequestedProfile.maxHeight, 180, 2160, normalized.maxHeight),
|
|
11491
|
+
quality: clampNumber(subscriberRequestedProfile.quality, 20, 95, normalized.quality)
|
|
11492
|
+
}
|
|
11493
|
+
: normalized;
|
|
11494
|
+
return {
|
|
11495
|
+
ok: true,
|
|
11282
11496
|
commandId: activeLiveStream.commandId,
|
|
11283
11497
|
sessionId: device.sessionId,
|
|
11284
11498
|
streamId: activeLiveStream.streamId,
|
|
@@ -11290,12 +11504,12 @@ export function createRemoteHub(options = {}) {
|
|
|
11290
11504
|
captureGeneration: Number(activeLiveStream.captureGeneration || 0),
|
|
11291
11505
|
ready: liveStreamHasCurrentFrame(activeLiveStream),
|
|
11292
11506
|
reused: true,
|
|
11293
|
-
sharedProfileReused: true,
|
|
11294
|
-
requestedProfile: {
|
|
11295
|
-
fps:
|
|
11296
|
-
maxWidth:
|
|
11297
|
-
maxHeight:
|
|
11298
|
-
quality:
|
|
11507
|
+
sharedProfileReused: true,
|
|
11508
|
+
requestedProfile: {
|
|
11509
|
+
fps: requested.fps,
|
|
11510
|
+
maxWidth: requested.maxWidth,
|
|
11511
|
+
maxHeight: requested.maxHeight,
|
|
11512
|
+
quality: requested.quality
|
|
11299
11513
|
},
|
|
11300
11514
|
effectiveProfile: {
|
|
11301
11515
|
fps: Number(activeLiveStream.fps || normalized.fps),
|
|
@@ -11506,10 +11720,11 @@ export function createRemoteHub(options = {}) {
|
|
|
11506
11720
|
const pendingDescriptor = getPendingLiveStreamDescriptor(activeLiveStream);
|
|
11507
11721
|
if (pendingDescriptor?.commandId) {
|
|
11508
11722
|
const pendingMatchesRequest = liveStreamMatchesOptions(pendingDescriptor, normalized);
|
|
11509
|
-
const pendingMatchesSharedOwner = options.forceRestart !== true
|
|
11510
|
-
&& options.reuseExisting === true
|
|
11511
|
-
&& options.reuseSharedExisting === true
|
|
11512
|
-
&& liveStreamMatchesOwnerIdentity(pendingDescriptor, normalized)
|
|
11723
|
+
const pendingMatchesSharedOwner = options.forceRestart !== true
|
|
11724
|
+
&& options.reuseExisting === true
|
|
11725
|
+
&& options.reuseSharedExisting === true
|
|
11726
|
+
&& liveStreamMatchesOwnerIdentity(pendingDescriptor, normalized)
|
|
11727
|
+
&& liveProfileSatisfiesDemand(pendingDescriptor, normalized);
|
|
11513
11728
|
if ((pendingMatchesRequest || pendingMatchesSharedOwner)
|
|
11514
11729
|
&& liveStreamReplacementStillPending(activeLiveStream)) {
|
|
11515
11730
|
emitRemoteEvent('RemoteLiveStreamRestartPending', device, {
|
|
@@ -11519,10 +11734,10 @@ export function createRemoteHub(options = {}) {
|
|
|
11519
11734
|
reason: safeString(options.restartReason || 'duplicate-restart', 128)
|
|
11520
11735
|
});
|
|
11521
11736
|
if (pendingMatchesSharedOwner && !pendingMatchesRequest) {
|
|
11522
|
-
return sharedLiveProfileResult(device, pendingDescriptor, normalized, {
|
|
11523
|
-
ready: false,
|
|
11524
|
-
pending: true
|
|
11525
|
-
});
|
|
11737
|
+
return sharedLiveProfileResult(device, pendingDescriptor, normalized, {
|
|
11738
|
+
ready: false,
|
|
11739
|
+
pending: true
|
|
11740
|
+
}, options.subscriberRequestedProfile);
|
|
11526
11741
|
}
|
|
11527
11742
|
return {
|
|
11528
11743
|
ok: true,
|
|
@@ -11599,10 +11814,11 @@ export function createRemoteHub(options = {}) {
|
|
|
11599
11814
|
}
|
|
11600
11815
|
if (activeLiveStream
|
|
11601
11816
|
&& options.forceRestart !== true
|
|
11602
|
-
&& options.reuseExisting === true
|
|
11603
|
-
&& options.reuseSharedExisting === true
|
|
11604
|
-
&& liveStreamMatchesOwnerIdentity(activeLiveStream, normalized)
|
|
11605
|
-
&& liveStreamIsReusable(activeLiveStream)
|
|
11817
|
+
&& options.reuseExisting === true
|
|
11818
|
+
&& options.reuseSharedExisting === true
|
|
11819
|
+
&& liveStreamMatchesOwnerIdentity(activeLiveStream, normalized)
|
|
11820
|
+
&& liveStreamIsReusable(activeLiveStream)
|
|
11821
|
+
&& liveProfileSatisfiesDemand(activeLiveStream, normalized)) {
|
|
11606
11822
|
emitRemoteEvent('RemoteLiveStreamSharedProfileReused', device, {
|
|
11607
11823
|
streamId,
|
|
11608
11824
|
commandId: activeLiveStream.commandId,
|
|
@@ -11614,7 +11830,13 @@ export function createRemoteHub(options = {}) {
|
|
|
11614
11830
|
effectiveMaxWidth: Number(activeLiveStream.maxWidth || normalized.maxWidth),
|
|
11615
11831
|
effectiveMaxHeight: Number(activeLiveStream.maxHeight || normalized.maxHeight)
|
|
11616
11832
|
});
|
|
11617
|
-
return sharedLiveProfileResult(
|
|
11833
|
+
return sharedLiveProfileResult(
|
|
11834
|
+
device,
|
|
11835
|
+
activeLiveStream,
|
|
11836
|
+
normalized,
|
|
11837
|
+
{},
|
|
11838
|
+
options.subscriberRequestedProfile
|
|
11839
|
+
);
|
|
11618
11840
|
}
|
|
11619
11841
|
if (activeLiveStream
|
|
11620
11842
|
&& options.forceRestart !== true
|
|
@@ -11779,10 +12001,11 @@ export function createRemoteHub(options = {}) {
|
|
|
11779
12001
|
fps,
|
|
11780
12002
|
mode: transfer.mode,
|
|
11781
12003
|
frameMode: transfer.frameMode,
|
|
11782
|
-
monitorIndex,
|
|
11783
|
-
captureGeneration,
|
|
11784
|
-
ready: false
|
|
11785
|
-
|
|
12004
|
+
monitorIndex,
|
|
12005
|
+
captureGeneration,
|
|
12006
|
+
ready: false,
|
|
12007
|
+
pending: replacingActiveStream
|
|
12008
|
+
};
|
|
11786
12009
|
}
|
|
11787
12010
|
|
|
11788
12011
|
function stopLiveStream(deviceId, options = {}) {
|