livedesk 0.1.247 → 0.1.249
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 +7 -0
- package/bin/livedesk.js +48 -11
- package/hub/src/server.js +25 -11
- package/package.json +1 -1
- package/web/dist/assets/{index-B2tTM5BZ.js → index-Dsgv1pBs.js} +11 -11
- package/web/dist/index.html +1 -1
package/README.md
CHANGED
|
@@ -13,6 +13,13 @@ This starts the LiveDesk Hub, opens the local screen wall, and accepts clients.
|
|
|
13
13
|
The Hub UI/API listens on `127.0.0.1` by default while the client endpoint
|
|
14
14
|
continues to listen on the LAN.
|
|
15
15
|
|
|
16
|
+
On startup, the launcher checks the Hub ports (`5179` and `5197`). It stops
|
|
17
|
+
only a stale process whose command line identifies it as a LiveDesk Hub, waits
|
|
18
|
+
for both ports to be released, and then starts the new Hub. An unrelated
|
|
19
|
+
process is preserved and reported instead of being terminated; use `--port`,
|
|
20
|
+
`--remote-port`, or stop that process manually. Pass `--no-clean` to disable
|
|
21
|
+
the startup cleanup explicitly.
|
|
22
|
+
|
|
16
23
|
## Client
|
|
17
24
|
|
|
18
25
|
```powershell
|
package/bin/livedesk.js
CHANGED
|
@@ -222,21 +222,58 @@ async function stopProcessesOnPorts(ports) {
|
|
|
222
222
|
const script = [
|
|
223
223
|
'$ErrorActionPreference = "SilentlyContinue";',
|
|
224
224
|
`$ports = @(${uniquePorts.join(',')});`,
|
|
225
|
-
|
|
226
|
-
'
|
|
227
|
-
'}',
|
|
228
|
-
'$
|
|
229
|
-
'
|
|
230
|
-
'
|
|
231
|
-
'
|
|
232
|
-
'
|
|
233
|
-
'
|
|
225
|
+
`$currentNodePid = ${process.pid};`,
|
|
226
|
+
'$records = foreach ($port in $ports) {',
|
|
227
|
+
' $connections = @(Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue | Where-Object { $_.OwningProcess -and $_.OwningProcess -ne $currentNodePid });',
|
|
228
|
+
' foreach ($connection in $connections) {',
|
|
229
|
+
' $owner = [int]$connection.OwningProcess;',
|
|
230
|
+
' $proc = Get-CimInstance Win32_Process -Filter "ProcessId = $owner" -ErrorAction SilentlyContinue;',
|
|
231
|
+
' $processName = if ($proc) { [string]$proc.Name } else { "" };',
|
|
232
|
+
' $commandLine = if ($proc) { [string]$proc.CommandLine } else { "" };',
|
|
233
|
+
' $knownLiveDesk = $commandLine -match "(?i)(?:livedesk|@livedesk).*hub[\\\\/].*server\\.js";',
|
|
234
|
+
' [pscustomobject]@{ Port = [int]$port; Pid = $owner; ProcessName = $processName; CommandLine = $commandLine; KnownLiveDesk = [bool]$knownLiveDesk; Action = "preserved" }',
|
|
234
235
|
' }',
|
|
235
|
-
'}'
|
|
236
|
+
'}',
|
|
237
|
+
'$records = @($records | Sort-Object Port,Pid -Unique);',
|
|
238
|
+
'foreach ($record in @($records | Where-Object { $_.KnownLiveDesk })) {',
|
|
239
|
+
' Stop-Process -Id $record.Pid -Force -ErrorAction SilentlyContinue;',
|
|
240
|
+
' $record.Action = "stopped";',
|
|
241
|
+
'}',
|
|
242
|
+
'$deadline = (Get-Date).ToUniversalTime().AddMilliseconds(3000);',
|
|
243
|
+
'do {',
|
|
244
|
+
' $busyPorts = @($ports | Where-Object { Get-NetTCPConnection -LocalPort $_ -State Listen -ErrorAction SilentlyContinue });',
|
|
245
|
+
' if ($busyPorts.Count -eq 0 -or (Get-Date).ToUniversalTime() -ge $deadline) { break }',
|
|
246
|
+
' Start-Sleep -Milliseconds 100;',
|
|
247
|
+
'} while ($true);',
|
|
248
|
+
'$busyPortNumbers = @($busyPorts | Select-Object -ExpandProperty LocalPort -Unique);',
|
|
249
|
+
'foreach ($record in $records) {',
|
|
250
|
+
' $record.PortAvailable = $busyPortNumbers -notcontains $record.Port;',
|
|
251
|
+
'}',
|
|
252
|
+
'$records | ConvertTo-Json -Compress'
|
|
236
253
|
].join(' ');
|
|
237
254
|
const result = await runQuiet('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', script]);
|
|
255
|
+
if (!result.ok && !result.stdout) {
|
|
256
|
+
throw new Error(`Could not inspect existing LiveDesk Hub ports: ${result.stderr || result.error?.message || 'PowerShell failed'}`);
|
|
257
|
+
}
|
|
258
|
+
let records = [];
|
|
238
259
|
if (result.stdout) {
|
|
239
|
-
|
|
260
|
+
try {
|
|
261
|
+
const parsed = JSON.parse(result.stdout);
|
|
262
|
+
records = Array.isArray(parsed) ? parsed : [parsed];
|
|
263
|
+
} catch {
|
|
264
|
+
throw new Error(`Could not parse existing LiveDesk Hub port state: ${result.stdout}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
const stopped = records.filter(record => record?.Action === 'stopped');
|
|
268
|
+
const preserved = records.filter(record => record?.Action !== 'stopped');
|
|
269
|
+
if (stopped.length > 0) {
|
|
270
|
+
const summary = stopped.map(record => `${record.Pid}:${record.ProcessName || 'node'}@${record.Port}`).join(', ');
|
|
271
|
+
console.log(`Restarting LiveDesk Hub. Stopped stale LiveDesk owner(s): ${summary}`);
|
|
272
|
+
}
|
|
273
|
+
const blocked = records.filter(record => record?.PortAvailable !== true);
|
|
274
|
+
if (blocked.length > 0) {
|
|
275
|
+
const summary = blocked.map(record => `${record.Pid}:${record.ProcessName || 'unknown'}@${record.Port}`).join(', ');
|
|
276
|
+
throw new Error(`LiveDesk Hub port(s) already in use by an unrelated process: ${summary}. Use --port/--remote-port or stop that process manually.`);
|
|
240
277
|
}
|
|
241
278
|
return;
|
|
242
279
|
}
|
package/hub/src/server.js
CHANGED
|
@@ -38,8 +38,9 @@ const webIndexPath = resolve(webDistPath, 'index.html');
|
|
|
38
38
|
const packageInfo = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf8'));
|
|
39
39
|
const httpHost = process.env.LIVEDESK_HUB_HTTP_HOST || '127.0.0.1';
|
|
40
40
|
const httpPort = Number(process.env.LIVEDESK_HUB_HTTP_PORT || process.env.PORT || 5179);
|
|
41
|
-
const frameBackpressureBytes = readPositiveIntegerEnv('LIVEDESK_FRAME_WS_BACKPRESSURE_BYTES', 64 * 1024 * 1024);
|
|
42
|
-
const frameClientQueuePackets = readPositiveIntegerEnv('LIVEDESK_FRAME_WS_QUEUE_PACKETS', 256);
|
|
41
|
+
const frameBackpressureBytes = readPositiveIntegerEnv('LIVEDESK_FRAME_WS_BACKPRESSURE_BYTES', 64 * 1024 * 1024);
|
|
42
|
+
const frameClientQueuePackets = readPositiveIntegerEnv('LIVEDESK_FRAME_WS_QUEUE_PACKETS', 256);
|
|
43
|
+
const controlFrameClientQueuePackets = readPositiveIntegerEnv('LIVEDESK_CONTROL_FRAME_WS_QUEUE_PACKETS', 4);
|
|
43
44
|
const frameClientDrainBudgetPackets = readPositiveIntegerEnv('LIVEDESK_FRAME_WS_DRAIN_BUDGET_PACKETS', 64);
|
|
44
45
|
const frameStreamStopGraceMs = readPositiveIntegerEnv('LIVEDESK_FRAME_STREAM_STOP_GRACE_MS', 1500);
|
|
45
46
|
const mode5FrameBackpressureBytes = readPositiveIntegerEnv('LIVEDESK_MODE5_WS_BACKPRESSURE_BYTES', 2 * 1024 * 1024);
|
|
@@ -1128,7 +1129,7 @@ function registerFrameClient(ws) {
|
|
|
1128
1129
|
}
|
|
1129
1130
|
}
|
|
1130
1131
|
|
|
1131
|
-
function ensureFrameClientSendLane(ws) {
|
|
1132
|
+
function ensureFrameClientSendLane(ws) {
|
|
1132
1133
|
if (!ws.liveDeskFrameSendLane) {
|
|
1133
1134
|
ws.liveDeskFrameSendLane = {
|
|
1134
1135
|
queue: [],
|
|
@@ -1137,8 +1138,14 @@ function ensureFrameClientSendLane(ws) {
|
|
|
1137
1138
|
awaitingKeyFrames: new Set()
|
|
1138
1139
|
};
|
|
1139
1140
|
}
|
|
1140
|
-
return ws.liveDeskFrameSendLane;
|
|
1141
|
-
}
|
|
1141
|
+
return ws.liveDeskFrameSendLane;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
function frameClientQueueLimit(ws) {
|
|
1145
|
+
return ws?.liveDeskLiveOptions?.streamPurpose === 'control'
|
|
1146
|
+
? controlFrameClientQueuePackets
|
|
1147
|
+
: frameClientQueuePackets;
|
|
1148
|
+
}
|
|
1142
1149
|
|
|
1143
1150
|
function dropQueuedFrameForLane(lane) {
|
|
1144
1151
|
const deltaIndex = lane.queue.findIndex(item => item?.isKeyFrame !== true);
|
|
@@ -1243,7 +1250,7 @@ function enqueueFramePacketForClient(ws, packet, meta) {
|
|
|
1243
1250
|
ws.liveDeskFrameBackpressureDrops = Number(ws.liveDeskFrameBackpressureDrops || 0) + 1;
|
|
1244
1251
|
}
|
|
1245
1252
|
}
|
|
1246
|
-
while (lane.queue.length >=
|
|
1253
|
+
while (lane.queue.length >= frameClientQueueLimit(ws)) {
|
|
1247
1254
|
dropQueuedFrameForLane(lane);
|
|
1248
1255
|
ws.liveDeskFrameBackpressureDrops = Number(ws.liveDeskFrameBackpressureDrops || 0) + 1;
|
|
1249
1256
|
}
|
|
@@ -2866,9 +2873,14 @@ audioWss.on('connection', (ws, req) => {
|
|
|
2866
2873
|
});
|
|
2867
2874
|
});
|
|
2868
2875
|
|
|
2869
|
-
inputWss.on('connection', ws => {
|
|
2870
|
-
inputClients.add(ws);
|
|
2871
|
-
ws.liveDeskInputClientId = `riws-${++inputClientSeq}`;
|
|
2876
|
+
inputWss.on('connection', ws => {
|
|
2877
|
+
inputClients.add(ws);
|
|
2878
|
+
ws.liveDeskInputClientId = `riws-${++inputClientSeq}`;
|
|
2879
|
+
try {
|
|
2880
|
+
ws._socket?.setNoDelay?.(true);
|
|
2881
|
+
} catch {
|
|
2882
|
+
// Best-effort latency hint for browser input sockets.
|
|
2883
|
+
}
|
|
2872
2884
|
ws.on('message', data => {
|
|
2873
2885
|
let payload;
|
|
2874
2886
|
try {
|
|
@@ -2882,8 +2894,10 @@ inputWss.on('connection', ws => {
|
|
|
2882
2894
|
? { ...payload.input, hubReceivedAtEpochMs: Date.now() }
|
|
2883
2895
|
: { ...payload, hubReceivedAtEpochMs: Date.now() };
|
|
2884
2896
|
const result = remoteHub.sendInputControl(deviceId, input);
|
|
2885
|
-
const
|
|
2886
|
-
|
|
2897
|
+
const inputType = String(input?.type || '').toLowerCase();
|
|
2898
|
+
const fireAndForget = payload?.fireAndForget === true
|
|
2899
|
+
|| inputType === 'pointermove'
|
|
2900
|
+
|| inputType === 'wheel';
|
|
2887
2901
|
if (fireAndForget && result?.ok) {
|
|
2888
2902
|
return;
|
|
2889
2903
|
}
|