fix(desktop): look up v1 SSH terminals with the bootstrap pool key
resolveDesktopRemoteRoute tags v1 SSH with a registry connectionId when the host matches. The terminal then looked up sshConnections with backendScopeKey(connectionId, profile), but bootstrap stored the tunnel under sshScopeKey(profile or null). The pane stayed pending. Ignore the identity tag for v1 pool lookup.
This commit is contained in:
@@ -3,7 +3,8 @@ import assert from 'node:assert/strict'
|
||||
import { test } from 'vitest'
|
||||
|
||||
import { normalizeRegistry, REGISTRY_VERSION } from './connection-registry'
|
||||
import { resolveDesktopRemoteRoute } from './desktop-remote-route'
|
||||
import { backendScopeKey } from './connection-registry'
|
||||
import { resolveDesktopRemoteRoute, v1SshTerminalPoolKey } from './desktop-remote-route'
|
||||
|
||||
const tokenA = { encoding: 'plain', value: 'token-a' }
|
||||
const tokenB = { encoding: 'plain', value: 'token-b' }
|
||||
@@ -153,6 +154,46 @@ test('global SSH treats an omitted port as 22 and checks the primary route', ()
|
||||
assert.equal(route?.connectionId, 'ssh-primary')
|
||||
})
|
||||
|
||||
test('v1 settings SSH pool key ignores registry identity tags', () => {
|
||||
const route = resolveDesktopRemoteRoute({
|
||||
config: { mode: 'ssh', remote: { mode: 'ssh', host: 'box.test', user: 'hermes' } },
|
||||
profile: 'worker',
|
||||
registry: registry('ssh-primary', [
|
||||
{ id: 'ssh-primary', kind: 'ssh', label: 'SSH primary', host: 'box.test', user: 'hermes', port: 22 }
|
||||
])
|
||||
})
|
||||
|
||||
assert.ok(route)
|
||||
assert.equal(route.kind, 'ssh')
|
||||
assert.equal(route.connectionId, 'ssh-primary')
|
||||
assert.equal(v1SshTerminalPoolKey(route, 'worker'), '')
|
||||
assert.notEqual(v1SshTerminalPoolKey(route, 'worker'), backendScopeKey(route.connectionId, 'worker'))
|
||||
})
|
||||
|
||||
test('v1 profile SSH pool key is the profile, not conn:id::profile', () => {
|
||||
const ssh = {
|
||||
mode: 'ssh',
|
||||
host: 'box.test',
|
||||
user: 'hermes',
|
||||
port: 2222,
|
||||
keyPath: '/keys/a',
|
||||
remoteHermesPath: '/srv/hermes',
|
||||
remoteProfile: 'worker'
|
||||
}
|
||||
|
||||
const route = resolveDesktopRemoteRoute({
|
||||
config: { mode: 'local', profiles: { worker: ssh } },
|
||||
profile: 'worker',
|
||||
registry: registry('local', [{ id: 'worker-ssh', kind: 'ssh', label: 'Worker SSH', ...ssh }])
|
||||
})
|
||||
|
||||
assert.ok(route)
|
||||
assert.equal(route.kind, 'ssh')
|
||||
assert.equal(route.connectionId, 'worker-ssh')
|
||||
assert.equal(v1SshTerminalPoolKey(route, 'worker'), 'worker')
|
||||
assert.notEqual(v1SshTerminalPoolKey(route, 'worker'), backendScopeKey(route.connectionId, 'worker'))
|
||||
})
|
||||
|
||||
test('profile route omits identity when two registry entries match exactly', () => {
|
||||
const block = { mode: 'remote', url: 'https://worker.test', authMode: 'token', token: tokenA }
|
||||
|
||||
|
||||
@@ -51,6 +51,22 @@ function withConnectionId<T extends object>(route: T, connectionId?: string): T
|
||||
return connectionId ? { ...route, connectionId } : route
|
||||
}
|
||||
|
||||
/**
|
||||
* Pool key used by v1 settings/profile SSH (`sshConnections`).
|
||||
*
|
||||
* Bootstrap stores the tunnel under sshScopeKey(profile or null).
|
||||
* resolveDesktopRemoteRoute may also stamp a registry connectionId when
|
||||
* the host matches a v2 row. That id is identity, not the pool key.
|
||||
* Looking up backendScopeKey(connectionId, profile) misses the live tunnel.
|
||||
*/
|
||||
export function v1SshTerminalPoolKey(route: { source: string }, profile?: null | string): string {
|
||||
if (route.source === 'profile') {
|
||||
return connectionScopeKey(profile) || ''
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* Select one remote route with the existing precedence and freeze any exact
|
||||
* registry identity before I/O. A null result means the profile resolves
|
||||
|
||||
@@ -158,7 +158,7 @@ import { describeCrashReason, installCrashForensics } from './crash-forensics'
|
||||
import { adoptServedDashboardToken } from './dashboard-token'
|
||||
import { loadOrCreateInstallationId, sshOwnershipId } from './desktop-installation'
|
||||
import { formatDesktopLogLine } from './desktop-log-line'
|
||||
import { resolveDesktopRemoteRoute } from './desktop-remote-route'
|
||||
import { resolveDesktopRemoteRoute, v1SshTerminalPoolKey } from './desktop-remote-route'
|
||||
import {
|
||||
buildPosixCleanupScript,
|
||||
buildWindowsCleanupScript,
|
||||
@@ -10335,9 +10335,7 @@ function activeSshTerminalTarget(webContentsId?: number) {
|
||||
return null
|
||||
}
|
||||
|
||||
const scope = route.connectionId
|
||||
? backendScopeKey(route.connectionId, profile)
|
||||
: sshScopeKey(route.source === 'profile' ? profile : null)
|
||||
const scope = v1SshTerminalPoolKey(route, profile)
|
||||
|
||||
const state = sshConnections.get(scope)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user