fix(desktop): align SSH terminal routing precedence
This commit is contained in:
@@ -1,32 +0,0 @@
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
import { test } from 'vitest'
|
||||
|
||||
import { normalizeRegistry, REGISTRY_VERSION } from './connection-registry'
|
||||
import { activeRegistrySshScope } from './active-ssh-route'
|
||||
|
||||
function registry(lastUsed: string) {
|
||||
return normalizeRegistry({
|
||||
version: REGISTRY_VERSION,
|
||||
primary: 'local',
|
||||
lastUsed,
|
||||
connections: [
|
||||
{ id: 'local', kind: 'local', label: 'This device' },
|
||||
{ id: 'remote', kind: 'remote', label: 'Remote', url: 'https://gateway.test' },
|
||||
{ id: 'ssh-box', kind: 'ssh', label: 'SSH box', host: 'box.test', user: 'hermes' }
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
test('returns the v2 SSH pool scope for the last-used registry source', () => {
|
||||
assert.equal(activeRegistrySshScope(registry('ssh-box'), 'default'), 'conn:ssh-box::default')
|
||||
})
|
||||
|
||||
test('keeps the active profile in the v2 SSH pool scope', () => {
|
||||
assert.equal(activeRegistrySshScope(registry('ssh-box'), 'worker'), 'conn:ssh-box::worker')
|
||||
})
|
||||
|
||||
test('returns null for local and URL-backed registry sources', () => {
|
||||
assert.equal(activeRegistrySshScope(registry('local'), 'default'), null)
|
||||
assert.equal(activeRegistrySshScope(registry('remote'), 'default'), null)
|
||||
})
|
||||
@@ -1,19 +0,0 @@
|
||||
import { backendScopeKey, type ConnectionRegistry } from './connection-registry'
|
||||
|
||||
/**
|
||||
* Return the live SSH pool scope for the registry connection currently used by
|
||||
* the Sessions workspace. Non-SSH sources deliberately return null so callers
|
||||
* can preserve the legacy v1 routing fallback.
|
||||
*/
|
||||
export function activeRegistrySshScope(
|
||||
registry: ConnectionRegistry,
|
||||
profile: null | string | undefined
|
||||
): null | string {
|
||||
const connection = registry.connections.find(candidate => candidate.id === registry.lastUsed)
|
||||
|
||||
if (!connection || connection.kind !== 'ssh') {
|
||||
return null
|
||||
}
|
||||
|
||||
return backendScopeKey(connection.id, profile)
|
||||
}
|
||||
@@ -232,6 +232,77 @@ test('URL route fails closed for different token, headers, kind, or Cloud org',
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
test('profile remote wins over a registry-backed global SSH route', () => {
|
||||
const route = resolveDesktopRemoteRoute({
|
||||
config: {
|
||||
mode: 'ssh',
|
||||
remote: { mode: 'ssh', host: 'global-box.test', user: 'hermes' },
|
||||
profiles: {
|
||||
worker: { mode: 'remote', url: 'https://worker.test', authMode: 'token', token: tokenA }
|
||||
}
|
||||
},
|
||||
profile: 'worker',
|
||||
registry: registry('global-ssh', [
|
||||
{ id: 'global-ssh', kind: 'ssh', label: 'Global SSH', host: 'global-box.test', user: 'hermes' },
|
||||
{ id: 'worker-remote', kind: 'remote', label: 'Worker', url: 'https://worker.test', token: tokenA }
|
||||
])
|
||||
})
|
||||
|
||||
assert.equal(route?.kind, 'remote')
|
||||
assert.equal(route?.source, 'profile')
|
||||
assert.equal(route?.connectionId, 'worker-remote')
|
||||
})
|
||||
|
||||
test('profile SSH wins over a different registry primary SSH route', () => {
|
||||
const route = resolveDesktopRemoteRoute({
|
||||
config: {
|
||||
mode: 'ssh',
|
||||
remote: { mode: 'ssh', host: 'global-box.test', user: 'hermes' },
|
||||
profiles: {
|
||||
worker: { mode: 'ssh', host: 'worker-box.test', user: 'hermes' }
|
||||
}
|
||||
},
|
||||
profile: 'worker',
|
||||
registry: registry('global-ssh', [
|
||||
{ id: 'global-ssh', kind: 'ssh', label: 'Global SSH', host: 'global-box.test', user: 'hermes' },
|
||||
{ id: 'worker-ssh', kind: 'ssh', label: 'Worker SSH', host: 'worker-box.test', user: 'hermes' }
|
||||
])
|
||||
})
|
||||
|
||||
assert.equal(route?.kind, 'ssh')
|
||||
assert.equal(route?.source, 'profile')
|
||||
assert.equal(route?.connectionId, 'worker-ssh')
|
||||
})
|
||||
|
||||
test('environment remote wins over a registry-backed global SSH route', () => {
|
||||
const route = resolveDesktopRemoteRoute({
|
||||
config: {
|
||||
mode: 'ssh',
|
||||
remote: { mode: 'ssh', host: 'global-box.test', user: 'hermes' }
|
||||
},
|
||||
env: { url: 'https://env.test', token: 'env-token' },
|
||||
registry: registry('global-ssh', [
|
||||
{ id: 'global-ssh', kind: 'ssh', label: 'Global SSH', host: 'global-box.test', user: 'hermes' }
|
||||
])
|
||||
})
|
||||
|
||||
assert.equal(route?.kind, 'remote')
|
||||
assert.equal(route?.source, 'env')
|
||||
assert.equal(route?.connectionId, undefined)
|
||||
})
|
||||
|
||||
test('local route does not inherit an unrelated registry SSH connection', () => {
|
||||
const route = resolveDesktopRemoteRoute({
|
||||
config: { mode: 'local' },
|
||||
registry: registry('local', [
|
||||
{ id: 'unused-ssh', kind: 'ssh', label: 'Unused SSH', host: 'box.test', user: 'hermes' }
|
||||
])
|
||||
})
|
||||
|
||||
assert.equal(route, null)
|
||||
})
|
||||
|
||||
test('local config without overrides returns null', () => {
|
||||
assert.equal(resolveDesktopRemoteRoute({ config: { mode: 'local' }, registry: registry('local', []) }), null)
|
||||
})
|
||||
|
||||
@@ -144,7 +144,6 @@ import {
|
||||
updateEligibility,
|
||||
upsertConnection
|
||||
} from './connection-registry'
|
||||
import { activeRegistrySshScope } from './active-ssh-route'
|
||||
import { describeCrashReason, installCrashForensics } from './crash-forensics'
|
||||
import { adoptServedDashboardToken } from './dashboard-token'
|
||||
import { loadOrCreateInstallationId, sshOwnershipId } from './desktop-installation'
|
||||
@@ -9521,35 +9520,26 @@ async function teardownSshConnection(profile) {
|
||||
function activeSshTerminalTarget() {
|
||||
const profile = primaryProfileKey()
|
||||
const config = readDesktopConnectionConfig()
|
||||
const registryScope = activeRegistrySshScope(readDesktopConnectionsRegistry(), profile)
|
||||
const route = resolveDesktopRemoteRoute({
|
||||
config,
|
||||
env: {
|
||||
token: process.env.HERMES_DESKTOP_REMOTE_TOKEN,
|
||||
url: process.env.HERMES_DESKTOP_REMOTE_URL
|
||||
},
|
||||
profile,
|
||||
registry: readDesktopConnectionsRegistry()
|
||||
})
|
||||
|
||||
if (registryScope) {
|
||||
const state = sshConnections.get(registryScope)
|
||||
|
||||
return state && state.ssh ? { ssh: state.ssh, scope: registryScope } : 'pending'
|
||||
}
|
||||
|
||||
if (profileSshOverride(config, profile)) {
|
||||
const scope = sshScopeKey(profile)
|
||||
const state = sshConnections.get(scope)
|
||||
|
||||
return state && state.ssh ? { ssh: state.ssh, scope } : 'pending'
|
||||
}
|
||||
|
||||
if (profileRemoteOverride(config, profile)) {
|
||||
if (!route || route.kind !== 'ssh') {
|
||||
return null
|
||||
}
|
||||
|
||||
if (process.env.HERMES_DESKTOP_REMOTE_URL) {
|
||||
return null
|
||||
}
|
||||
const scope = route.connectionId
|
||||
? backendScopeKey(route.connectionId, profile)
|
||||
: sshScopeKey(route.source === 'profile' ? profile : null)
|
||||
const state = sshConnections.get(scope)
|
||||
|
||||
if (config.mode === 'ssh') {
|
||||
const state = sshConnections.get('')
|
||||
return state && state.ssh ? { ssh: state.ssh, scope: '' } : 'pending'
|
||||
}
|
||||
|
||||
return null
|
||||
return state && state.ssh ? { ssh: state.ssh, scope } : 'pending'
|
||||
}
|
||||
|
||||
// Loopback reach for the browser pane. Scoped to the SSH connection that
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
import { test } from 'vitest'
|
||||
|
||||
import { resolveTerminalConnection } from './connection-apply'
|
||||
|
||||
const ssh = {
|
||||
host: 'registry-box.test',
|
||||
user: 'hermes'
|
||||
}
|
||||
|
||||
test('terminal start preserves the selected SSH target and scope', async () => {
|
||||
const target = {
|
||||
ssh,
|
||||
scope: 'connection:registry-ssh:profile:worker'
|
||||
}
|
||||
|
||||
const resolved = await resolveTerminalConnection(
|
||||
() => target,
|
||||
async () => {
|
||||
throw new Error('backend fallback must not run for an active SSH target')
|
||||
}
|
||||
)
|
||||
|
||||
assert.equal(resolved, target)
|
||||
assert.equal(resolved?.ssh, ssh)
|
||||
assert.equal(resolved?.scope, 'connection:registry-ssh:profile:worker')
|
||||
})
|
||||
|
||||
test('terminal start does not invent SSH when canonical routing selects local or remote HTTP', async () => {
|
||||
let backendChecks = 0
|
||||
|
||||
const resolved = await resolveTerminalConnection(
|
||||
() => null,
|
||||
async () => {
|
||||
backendChecks += 1
|
||||
}
|
||||
)
|
||||
|
||||
assert.equal(resolved, null)
|
||||
assert.equal(backendChecks, 0)
|
||||
})
|
||||
|
||||
test('terminal start re-reads the SSH target after backend startup', async () => {
|
||||
const target = {
|
||||
ssh,
|
||||
scope: 'connection:registry-ssh'
|
||||
}
|
||||
let ready = false
|
||||
|
||||
const resolved = await resolveTerminalConnection(
|
||||
() => (ready ? target : 'pending'),
|
||||
async () => {
|
||||
ready = true
|
||||
}
|
||||
)
|
||||
|
||||
assert.equal(resolved, target)
|
||||
assert.equal(resolved?.scope, 'connection:registry-ssh')
|
||||
})
|
||||
Reference in New Issue
Block a user