livedesk 0.1.563 → 0.1.565
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/bin/livedesk.js +40 -29
- package/client/bin/livedesk-client.js +63 -26
- package/client/package.json +1 -1
- package/package.json +2 -2
- package/web/dist/assets/{index-9Nf9bDf1.css → index-Da6y4aBe.css} +1 -1
- package/web/dist/index.html +2 -2
- package/web/dist/livedesk-build-evidence.json +14 -14
- package/web/dist/sw.js +1 -1
- /package/web/dist/assets/{index-DfvqA5oA.js → index-BimpwK6S.js} +0 -0
package/bin/livedesk.js
CHANGED
|
@@ -1616,9 +1616,9 @@ function isPortConflictOutput(output) {
|
|
|
1616
1616
|
return /\bEADDRINUSE\b|address already in use|requires client endpoint TCP \d+/i.test(String(output || ''));
|
|
1617
1617
|
}
|
|
1618
1618
|
|
|
1619
|
-
function formatPortOwner(record) {
|
|
1620
|
-
return `${record?.Pid || 'unknown'}:${record?.ProcessName || 'unknown'} cmd=${String(record?.CommandLine || 'unavailable').replace(/\s+/g, ' ').slice(0, 240)}`;
|
|
1621
|
-
}
|
|
1619
|
+
function formatPortOwner(record) {
|
|
1620
|
+
return `${record?.Pid || 'unknown'}:${record?.ProcessName || 'unknown'} state=${record?.State || 'unknown'} cmd=${String(record?.CommandLine || 'unavailable').replace(/\s+/g, ' ').slice(0, 240)}`;
|
|
1621
|
+
}
|
|
1622
1622
|
|
|
1623
1623
|
function assertPortCleanupReady(finalRecords, portStates) {
|
|
1624
1624
|
const blockedPorts = portStates.filter(port => port?.Ready !== true);
|
|
@@ -1640,35 +1640,37 @@ async function collectWindowsPortRecords(ports) {
|
|
|
1640
1640
|
'$ErrorActionPreference = "SilentlyContinue";',
|
|
1641
1641
|
`$ports = @(${ports.join(',')});`,
|
|
1642
1642
|
`$currentNodePid = ${process.pid};`,
|
|
1643
|
-
'function Get-
|
|
1644
|
-
' $connections = @(Get-NetTCPConnection -LocalPort $port -
|
|
1645
|
-
' if ($connections.Count -gt 0) { return $connections }',
|
|
1646
|
-
' $pattern = "^\\s*TCP\\s+\\S+:" + $port + "\\s+\\S+\\s+
|
|
1647
|
-
' foreach ($line in @(netstat -ano -p tcp)) {',
|
|
1648
|
-
' $match = [regex]::Match([string]$line, $pattern);',
|
|
1649
|
-
' if ($match.Success) {',
|
|
1650
|
-
' [pscustomobject]@{ LocalPort = [int]$port; OwningProcess = [int]$match.Groups[
|
|
1651
|
-
' }',
|
|
1652
|
-
' }',
|
|
1643
|
+
'function Get-PortConnections($port) {',
|
|
1644
|
+
' $connections = @(Get-NetTCPConnection -LocalPort $port -ErrorAction SilentlyContinue);',
|
|
1645
|
+
' if ($connections.Count -gt 0) { return $connections }',
|
|
1646
|
+
' $pattern = "^\\s*TCP\\s+\\S+:" + $port + "\\s+\\S+\\s+(\\S+)\\s+(\\d+)\\s*$";',
|
|
1647
|
+
' foreach ($line in @(netstat -ano -p tcp)) {',
|
|
1648
|
+
' $match = [regex]::Match([string]$line, $pattern);',
|
|
1649
|
+
' if ($match.Success) {',
|
|
1650
|
+
' [pscustomobject]@{ LocalPort = [int]$port; State = [string]$match.Groups[1].Value; OwningProcess = [int]$match.Groups[2].Value }',
|
|
1651
|
+
' }',
|
|
1652
|
+
' }',
|
|
1653
1653
|
'}',
|
|
1654
|
-
'$records = @();',
|
|
1655
|
-
'foreach ($port in $ports) {',
|
|
1656
|
-
' $connections = @(Get-
|
|
1657
|
-
' foreach ($connection in $connections) {',
|
|
1658
|
-
' $owner = [int]$connection.OwningProcess;',
|
|
1654
|
+
'$records = @();',
|
|
1655
|
+
'foreach ($port in $ports) {',
|
|
1656
|
+
' $connections = @(Get-PortConnections $port | Where-Object { $_.OwningProcess -and $_.OwningProcess -ne $currentNodePid -and $_.OwningProcess -ne $PID });',
|
|
1657
|
+
' foreach ($connection in $connections) {',
|
|
1658
|
+
' $owner = [int]$connection.OwningProcess;',
|
|
1659
1659
|
' $proc = Get-CimInstance Win32_Process -Filter "ProcessId = $owner" -Property ProcessId,Name,CommandLine,CreationDate -ErrorAction SilentlyContinue;',
|
|
1660
1660
|
' $processName = if ($proc) { [string]$proc.Name } else { "" };',
|
|
1661
1661
|
' $commandLine = if ($proc) { [string]$proc.CommandLine } else { "" };',
|
|
1662
1662
|
' $creationUtcTicks = if ($proc -and $proc.CreationDate) { [string]$proc.CreationDate.ToUniversalTime().Ticks } else { "" };',
|
|
1663
1663
|
' $isNodeProcess = $processName -match "(?i)^node(?:\\.exe)?$";',
|
|
1664
1664
|
' $hasKnownLiveDeskHubPath = $commandLine -match "(?i)(?:packages[\\\\/](?:livedesk[\\\\/])?hub|node_modules[\\\\/]@?livedesk[\\\\/]hub)[\\\\/]src[\\\\/]server\\.js";',
|
|
1665
|
-
' $hasUnifiedLauncherPath = $commandLine -match "(?i)(?:node_modules[\\\\/]livedesk|packages[\\\\/]livedesk)[\\\\/]bin[\\\\/]livedesk\\.js";',
|
|
1666
|
-
' $hasLegacyClientPath = $commandLine -match "(?i)(?:node_modules[\\\\/]@livedesk[\\\\/]client|packages[\\\\/]client|packages[\\\\/]livedesk[\\\\/]client)[\\\\/]bin[\\\\/]livedesk-client\\.js";',
|
|
1667
|
-
' $knownLiveDesk = [bool]($isNodeProcess -and ($hasKnownLiveDeskHubPath -or $hasUnifiedLauncherPath -or $hasLegacyClientPath));',
|
|
1668
|
-
' $
|
|
1669
|
-
'
|
|
1670
|
-
'}',
|
|
1671
|
-
'
|
|
1665
|
+
' $hasUnifiedLauncherPath = $commandLine -match "(?i)(?:node_modules[\\\\/]livedesk|packages[\\\\/]livedesk)[\\\\/]bin[\\\\/]livedesk\\.js";',
|
|
1666
|
+
' $hasLegacyClientPath = $commandLine -match "(?i)(?:node_modules[\\\\/]@livedesk[\\\\/]client|packages[\\\\/]client|packages[\\\\/]livedesk[\\\\/]client)[\\\\/]bin[\\\\/]livedesk-client\\.js";',
|
|
1667
|
+
' $knownLiveDesk = [bool]($isNodeProcess -and ($hasKnownLiveDeskHubPath -or $hasUnifiedLauncherPath -or $hasLegacyClientPath));',
|
|
1668
|
+
' $state = [string]$connection.State;',
|
|
1669
|
+
' $isListener = $state -match "^(?i:Listen|Listening)$";',
|
|
1670
|
+
' $records += [pscustomobject]@{ Port = [int]$port; Pid = $owner; ProcessName = $processName; CommandLine = $commandLine; CreationUtcTicks = $creationUtcTicks; State = $state; IsListener = [bool]$isListener; KnownLiveDesk = $knownLiveDesk; Action = "preserved"; KillAttempted = $false };',
|
|
1671
|
+
' }',
|
|
1672
|
+
'}',
|
|
1673
|
+
'$records = @($records | Sort-Object Port,Pid,State -Unique);',
|
|
1672
1674
|
'[pscustomobject]@{ Records = $records } | ConvertTo-Json -Compress -Depth 5'
|
|
1673
1675
|
].join(' ');
|
|
1674
1676
|
const result = await runQuiet(
|
|
@@ -1701,8 +1703,9 @@ async function terminateExactWindowsPortOwners(records) {
|
|
|
1701
1703
|
for (const record of Array.isArray(records) ? records : []) {
|
|
1702
1704
|
const pid = Number(record?.Pid || 0);
|
|
1703
1705
|
const startOrder = String(record?.CreationUtcTicks || '').trim();
|
|
1704
|
-
if (record?.KnownLiveDesk !== true
|
|
1705
|
-
||
|
|
1706
|
+
if (record?.KnownLiveDesk !== true
|
|
1707
|
+
|| record?.IsListener !== true
|
|
1708
|
+
|| !Number.isInteger(pid)
|
|
1706
1709
|
|| pid <= 1
|
|
1707
1710
|
|| !/^\d+$/.test(startOrder)
|
|
1708
1711
|
|| seenRoots.has(`${pid}:${startOrder}`)) {
|
|
@@ -1839,7 +1842,7 @@ async function terminateExactWindowsPortOwners(records) {
|
|
|
1839
1842
|
|
|
1840
1843
|
async function stopWindowsProcessesOnPorts(ports) {
|
|
1841
1844
|
const records = await collectWindowsPortRecords(ports);
|
|
1842
|
-
const killable = records.filter(record => record?.KnownLiveDesk === true);
|
|
1845
|
+
const killable = records.filter(record => record?.KnownLiveDesk === true && record?.IsListener === true);
|
|
1843
1846
|
if (killable.length > 0) {
|
|
1844
1847
|
await terminateExactWindowsPortOwners(killable);
|
|
1845
1848
|
for (const record of killable) {
|
|
@@ -3186,7 +3189,15 @@ async function runClient(args, resolvedRole = null, runtimeLock = null) {
|
|
|
3186
3189
|
await new Promise(() => undefined);
|
|
3187
3190
|
});
|
|
3188
3191
|
try {
|
|
3189
|
-
await clientModule.runClientRuntime(clientArgs
|
|
3192
|
+
await clientModule.runClientRuntime(clientArgs, {
|
|
3193
|
+
recoverHubClientPort: async port => {
|
|
3194
|
+
const normalizedPort = normalizePort(port, 0);
|
|
3195
|
+
if (!normalizedPort) {
|
|
3196
|
+
throw new Error(`LiveDesk cannot recover an invalid Hub client port: ${port}.`);
|
|
3197
|
+
}
|
|
3198
|
+
await stopProcessesOnPorts([normalizedPort]);
|
|
3199
|
+
}
|
|
3200
|
+
});
|
|
3190
3201
|
} finally {
|
|
3191
3202
|
runtimeShutdownWatcher?.stop();
|
|
3192
3203
|
}
|
|
@@ -588,7 +588,7 @@ export function resolveHubClientPort(env = process.env) {
|
|
|
588
588
|
return normalizePort(env.REMOTE_HUB_PORT || env.LIVEDESK_HUB_REMOTE_PORT) || DEFAULT_HUB_CLIENT_PORT;
|
|
589
589
|
}
|
|
590
590
|
|
|
591
|
-
export function preflightHubClientPort(port = resolveHubClientPort()) {
|
|
591
|
+
export function preflightHubClientPort(port = resolveHubClientPort()) {
|
|
592
592
|
const normalizedPort = normalizePort(port);
|
|
593
593
|
if (!normalizedPort) {
|
|
594
594
|
return Promise.resolve({ ok: false, port: Number(port) || 0, code: 'invalid-hub-client-port' });
|
|
@@ -618,18 +618,46 @@ export function preflightHubClientPort(port = resolveHubClientPort()) {
|
|
|
618
618
|
finish({ ok: true, code: 'ok' });
|
|
619
619
|
});
|
|
620
620
|
});
|
|
621
|
-
});
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
function
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
export async function recoverHubClientPortConflict(preflight, recoverPortOwner) {
|
|
625
|
+
if (preflight?.ok
|
|
626
|
+
|| preflight?.code !== 'hub-client-port-in-use'
|
|
627
|
+
|| typeof recoverPortOwner !== 'function') {
|
|
628
|
+
return preflight;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
try {
|
|
632
|
+
await recoverPortOwner(Number(preflight.port || resolveHubClientPort()));
|
|
633
|
+
} catch (error) {
|
|
634
|
+
return {
|
|
635
|
+
...preflight,
|
|
636
|
+
recoveryAttempted: true,
|
|
637
|
+
recoveryError: error instanceof Error ? error.message : String(error)
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const verified = await preflightHubClientPort(preflight.port);
|
|
642
|
+
return {
|
|
643
|
+
...verified,
|
|
644
|
+
recoveryAttempted: true,
|
|
645
|
+
recovered: verified.ok === true
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function hubClientPortPreflightError(preflight) {
|
|
650
|
+
const port = Number(preflight?.port || resolveHubClientPort());
|
|
651
|
+
const recoveryError = String(preflight?.recoveryError || '').trim();
|
|
652
|
+
return {
|
|
653
|
+
ok: false,
|
|
654
|
+
code: String(preflight?.code || 'hub-client-port-unavailable'),
|
|
655
|
+
port,
|
|
656
|
+
error: recoveryError
|
|
657
|
+
? `${recoveryError} The current Hub was not changed.`
|
|
658
|
+
: preflight?.code === 'hub-client-port-in-use'
|
|
659
|
+
? `LiveDesk cannot switch this computer to Hub because TCP ${port} is already in use. Change or stop the owning service, then try Switch to Hub again. The current Hub was not changed.`
|
|
660
|
+
: `LiveDesk cannot switch this computer to Hub because TCP ${port} is unavailable. Check the local port configuration, then try again. The current Hub was not changed.`
|
|
633
661
|
};
|
|
634
662
|
}
|
|
635
663
|
|
|
@@ -3082,12 +3110,16 @@ async function startConnectionChoiceServer(supabase, options = {}) {
|
|
|
3082
3110
|
res.end(JSON.stringify({ ok: false, error: 'client-can-only-transition-to-hub' }));
|
|
3083
3111
|
return;
|
|
3084
3112
|
}
|
|
3085
|
-
const portPreflight = await preflightHubClientPort();
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3113
|
+
const portPreflight = await preflightHubClientPort();
|
|
3114
|
+
const readyPortPreflight = await recoverHubClientPortConflict(
|
|
3115
|
+
portPreflight,
|
|
3116
|
+
options.recoverHubClientPort
|
|
3117
|
+
);
|
|
3118
|
+
if (!readyPortPreflight.ok) {
|
|
3119
|
+
res.writeHead(409, { 'Content-Type': 'application/json; charset=utf-8', 'Access-Control-Allow-Origin': '*' });
|
|
3120
|
+
res.end(JSON.stringify(hubClientPortPreflightError(readyPortPreflight)));
|
|
3121
|
+
return;
|
|
3122
|
+
}
|
|
3091
3123
|
const session = await refreshSessionIfNeeded(supabase);
|
|
3092
3124
|
const expectedRoleVersion = Number(dashboardState.roleVersion || 0);
|
|
3093
3125
|
const { data, error } = await supabase.rpc('set_livedesk_device_role', {
|
|
@@ -3403,10 +3435,14 @@ async function chooseClientConnection(supabase, options = {}) {
|
|
|
3403
3435
|
if (!activeSupabase) {
|
|
3404
3436
|
return { ok: false, error: 'supabase-session-required' };
|
|
3405
3437
|
}
|
|
3406
|
-
const portPreflight = await preflightHubClientPort();
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3438
|
+
const portPreflight = await preflightHubClientPort();
|
|
3439
|
+
const readyPortPreflight = await recoverHubClientPortConflict(
|
|
3440
|
+
portPreflight,
|
|
3441
|
+
options.recoverHubClientPort
|
|
3442
|
+
);
|
|
3443
|
+
if (!readyPortPreflight.ok) {
|
|
3444
|
+
return hubClientPortPreflightError(readyPortPreflight);
|
|
3445
|
+
}
|
|
3410
3446
|
const session = await refreshSessionIfNeeded(activeSupabase);
|
|
3411
3447
|
if (!session?.access_token) {
|
|
3412
3448
|
return { ok: false, error: 'supabase-session-required' };
|
|
@@ -5085,9 +5121,10 @@ export async function runClientRuntime(argv = process.argv.slice(2), runtimeOpti
|
|
|
5085
5121
|
savedSession: null,
|
|
5086
5122
|
loadSavedSession: false,
|
|
5087
5123
|
savedPin: null,
|
|
5088
|
-
allowRelayFallback: transportAllowsRelay(parsed.transport),
|
|
5089
|
-
relayEndpoint: parsed.relay,
|
|
5090
|
-
|
|
5124
|
+
allowRelayFallback: transportAllowsRelay(parsed.transport),
|
|
5125
|
+
relayEndpoint: parsed.relay,
|
|
5126
|
+
recoverHubClientPort: runtimeOptions.recoverHubClientPort,
|
|
5127
|
+
openBrowser: parsed.openBrowserOnStart,
|
|
5091
5128
|
onStarted: page => {
|
|
5092
5129
|
connectionPage = page;
|
|
5093
5130
|
resolveStarted(page);
|
package/client/package.json
CHANGED
package/package.json
CHANGED