diff --git a/mobile/src/components/use-new-workspace-execution-target.ts b/mobile/src/components/use-new-workspace-execution-target.ts index e506d0ab9be..c87c75e5b18 100644 --- a/mobile/src/components/use-new-workspace-execution-target.ts +++ b/mobile/src/components/use-new-workspace-execution-target.ts @@ -9,6 +9,7 @@ import { } from '../tasks/mobile-workspace-source-operations' import { deriveWorkspaceSshGate, + workspaceSshTargetKey, type WorkspaceSshGate, type WorkspaceSshRecord } from '../tasks/workspace-ssh-gate' @@ -43,8 +44,7 @@ export function useNewWorkspaceExecutionTarget(args: { connect: () => Promise } { const { client, connectionId, server, visible } = args - // Why the host too: the desktop and a server can each hold a target with the same id. - const target = JSON.stringify([server ?? null, connectionId]) + const target = workspaceSshTargetKey(server, connectionId) const [sshState, setSshEntry] = useState(null) const setSshState = (record: WorkspaceSshRecord) => setSshEntry({ target, record }) // A connect reply lands only while its target is still the one shown, so it never overwrites another's. diff --git a/mobile/src/tasks/workspace-ssh-gate.test.ts b/mobile/src/tasks/workspace-ssh-gate.test.ts index c0bfce8c539..e97688ab381 100644 --- a/mobile/src/tasks/workspace-ssh-gate.test.ts +++ b/mobile/src/tasks/workspace-ssh-gate.test.ts @@ -1,7 +1,23 @@ import { describe, expect, it } from 'vitest' -import { deriveWorkspaceSshGate, workspaceSshStatusLabel } from './workspace-ssh-gate' +import { + deriveWorkspaceSshGate, + workspaceSshStatusLabel, + workspaceSshTargetKey +} from './workspace-ssh-gate' describe('workspace SSH gate', () => { + it('keys a target by the host holding it, so same-named targets on two hosts differ', () => { + expect(workspaceSshTargetKey(undefined, 'devbox')).not.toBe( + workspaceSshTargetKey('runtime:env-1', 'devbox') + ) + expect(workspaceSshTargetKey('runtime:env-1', 'devbox')).not.toBe( + workspaceSshTargetKey('runtime:env-2', 'devbox') + ) + expect(workspaceSshTargetKey('runtime:env-1', 'devbox')).toBe( + workspaceSshTargetKey('runtime:env-1', 'devbox') + ) + }) + it('does not gate local repositories', () => { expect( deriveWorkspaceSshGate({ diff --git a/mobile/src/tasks/workspace-ssh-gate.ts b/mobile/src/tasks/workspace-ssh-gate.ts index f031ed5fd5b..53b314aa7bf 100644 --- a/mobile/src/tasks/workspace-ssh-gate.ts +++ b/mobile/src/tasks/workspace-ssh-gate.ts @@ -1,3 +1,4 @@ +import type { ExecutionHostId } from '../../../src/shared/execution-host' import type { SshConnectionState, SshConnectionStatus } from '../../../src/shared/ssh-types' /** @@ -14,6 +15,17 @@ export type WorkspaceSshRecord = Omit