Files
orca/src/shared/runtime-environment-store.ts
T
Jinwoo HongandJinwoo-H 5e3a2d25f7 Filter remote workspaces by creating device (#13718)
* Filter remote workspaces by creating device

* Fix workspace origin filter reconnect behavior

* Shorten workspace origin filter label

* Refine remote workspace filter UX

---------

Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-11 01:05:12 -07:00

275 lines
8.8 KiB
TypeScript

import { randomUUID } from 'node:crypto'
import { existsSync } from 'node:fs'
import { join } from 'node:path'
import { JsonStringifyByteLimitError } from './node-bounded-json-stringify'
import { readNodeFileSyncWithinLimit } from './node-bounded-file-reader'
import { parsePairingCode, type PairingOffer } from './pairing'
import { classifyRemotePairingHostname } from './remote-pairing-address'
import { writeSecureJsonFileWithinLimit } from './bounded-secure-json-file'
import { hardenExistingSecureFile } from './secure-file'
import {
createEnvironmentFromPairingOffer,
getPreferredPairingOffer,
KnownRuntimeEnvironmentSchema,
RuntimeEnvironmentStoreSchema,
type KnownRuntimeEnvironment,
type RuntimeEnvironmentSource,
type RuntimeEnvironmentStore
} from './runtime-environments'
const ENVIRONMENTS_FILE = 'orca-environments.json'
export const MAX_RUNTIME_ENVIRONMENT_STORE_FILE_BYTES = 1024 * 1024
export type RuntimeEnvironmentStoreErrorCode = 'invalid_argument' | 'runtime_error'
export class RuntimeEnvironmentStoreError extends Error {
readonly code: RuntimeEnvironmentStoreErrorCode
constructor(code: RuntimeEnvironmentStoreErrorCode, message: string) {
super(message)
this.name = 'RuntimeEnvironmentStoreError'
this.code = code
}
}
export function getEnvironmentStorePath(userDataPath: string): string {
return join(userDataPath, ENVIRONMENTS_FILE)
}
export function listEnvironments(userDataPath: string): KnownRuntimeEnvironment[] {
return readEnvironmentStore(userDataPath).environments
}
export function addEnvironmentFromPairingCode(
userDataPath: string,
args: {
name: string
pairingCode: string
now?: number
source?: RuntimeEnvironmentSource
connectionDependency?: 'ssh-tunnel'
}
): KnownRuntimeEnvironment {
const offer = parsePairingCode(args.pairingCode)
if (!offer) {
throw new RuntimeEnvironmentStoreError(
'invalid_argument',
'Invalid pairing code. Expected an orca://pair?... URL or bare pairing payload.'
)
}
const store = readEnvironmentStore(userDataPath)
const now = args.now ?? Date.now()
const existing = store.environments.find((entry) => entry.name === args.name)
if (existing) {
throw new RuntimeEnvironmentStoreError(
'invalid_argument',
`A server named "${args.name}" already exists.`
)
}
const environment = createEnvironmentFromPairingOffer({
id: randomUUID(),
name: args.name,
now,
offer,
runtimeId: null,
...(args.source ? { source: args.source } : {}),
...getPairingConnectionDependency(args.connectionDependency, offer)
})
const next = {
version: 1 as const,
environments: [
...store.environments.filter((entry) => entry.id !== environment.id),
environment
].sort((a, b) => a.name.localeCompare(b.name))
}
writeEnvironmentStore(userDataPath, next)
return environment
}
export function removeEnvironment(userDataPath: string, selector: string): KnownRuntimeEnvironment {
const store = readEnvironmentStore(userDataPath)
const environment = resolveEnvironmentFromStore(store, selector)
writeEnvironmentStore(userDataPath, {
version: 1,
environments: store.environments.filter((entry) => entry.id !== environment.id)
})
return environment
}
export function updateEnvironmentFromPairingCode(
userDataPath: string,
selector: string,
args: { pairingCode: string; now?: number }
): KnownRuntimeEnvironment {
const offer = parsePairingCode(args.pairingCode)
if (!offer) {
throw new RuntimeEnvironmentStoreError(
'invalid_argument',
'Invalid pairing code. Expected an orca://pair?... URL or bare pairing payload.'
)
}
const store = readEnvironmentStore(userDataPath)
const existing = resolveEnvironmentFromStore(store, selector)
const now = args.now ?? Date.now()
const previousPairingRevision = existing.pairingRevision ?? existing.createdAt
const environment = createEnvironmentFromPairingOffer({
id: existing.id,
name: existing.name,
now: existing.createdAt,
offer,
runtimeId: existing.runtimeId,
...(existing.source ? { source: existing.source } : {}),
...getPairingConnectionDependency(existing.connectionDependency, offer)
})
const next = {
...environment,
createdAt: existing.createdAt,
updatedAt: now,
pairingRevision: Math.max(now, previousPairingRevision + 1),
lastUsedAt: existing.lastUsedAt
}
writeEnvironmentStore(userDataPath, {
version: 1,
environments: store.environments
.map((entry) => (entry.id === existing.id ? next : entry))
.sort((a, b) => a.name.localeCompare(b.name))
})
return next
}
function getPairingConnectionDependency(
dependency: 'ssh-tunnel' | undefined,
offer: PairingOffer
): { connectionDependency?: 'ssh-tunnel' } {
if (!dependency) {
return {}
}
try {
const endpoint = new URL(offer.endpoint)
return classifyRemotePairingHostname(endpoint.hostname) === 'loopback'
? { connectionDependency: dependency }
: {}
} catch {
return {}
}
}
export function resolveEnvironment(
userDataPath: string,
selector: string
): KnownRuntimeEnvironment {
return resolveEnvironmentFromStore(readEnvironmentStore(userDataPath), selector)
}
export function resolveEnvironmentPairingOffer(
userDataPath: string,
selector: string
): PairingOffer {
return getPreferredPairingOffer(resolveEnvironment(userDataPath, selector))
}
// Why: markEnvironmentUsed runs on every runtime round-trip; persisting lastUsedAt each
// time forces a secure-file rewrite (ACL hardening), which blocks the main thread on
// Windows. lastUsedAt only needs coarse freshness, so skip writes within this window.
const LAST_USED_PERSIST_INTERVAL_MS = 60_000
export function markEnvironmentUsed(
userDataPath: string,
selector: string,
args: { runtimeId?: string | null; pairedDeviceId?: string; now?: number } = {}
): void {
const store = readEnvironmentStore(userDataPath)
const environment = resolveEnvironmentFromStore(store, selector)
const now = args.now ?? Date.now()
const runtimeIdChanged = args.runtimeId != null && args.runtimeId !== environment.runtimeId
const pairedDeviceIdChanged =
args.pairedDeviceId != null && args.pairedDeviceId !== environment.pairedDeviceId
const lastUsedIsFresh =
environment.lastUsedAt != null &&
now >= environment.lastUsedAt &&
now - environment.lastUsedAt < LAST_USED_PERSIST_INTERVAL_MS
if (!runtimeIdChanged && !pairedDeviceIdChanged && lastUsedIsFresh) {
return
}
const next = store.environments.map((entry) =>
entry.id === environment.id
? {
...entry,
runtimeId: args.runtimeId ?? entry.runtimeId,
...(args.pairedDeviceId ? { pairedDeviceId: args.pairedDeviceId } : {}),
lastUsedAt: now,
updatedAt: now
}
: entry
)
writeEnvironmentStore(userDataPath, { version: 1, environments: next })
}
function resolveEnvironmentFromStore(
store: RuntimeEnvironmentStore,
selector: string
): KnownRuntimeEnvironment {
const byId = store.environments.find((entry) => entry.id === selector)
if (byId) {
return byId
}
const matches = store.environments.filter((entry) => entry.name === selector)
if (matches.length === 1) {
return matches[0]!
}
if (matches.length > 1) {
throw new RuntimeEnvironmentStoreError(
'invalid_argument',
`Environment name "${selector}" is ambiguous; use the environment id.`
)
}
throw new RuntimeEnvironmentStoreError('invalid_argument', `Unknown environment: ${selector}`)
}
function readEnvironmentStore(userDataPath: string): RuntimeEnvironmentStore {
const path = getEnvironmentStorePath(userDataPath)
if (!existsSync(path)) {
return { version: 1, environments: [] }
}
try {
hardenExistingSecureFile(path)
const parsed = RuntimeEnvironmentStoreSchema.parse(
JSON.parse(
readNodeFileSyncWithinLimit(path, MAX_RUNTIME_ENVIRONMENT_STORE_FILE_BYTES).buffer.toString(
'utf8'
)
)
)
return {
version: 1,
environments: parsed.environments
.map((entry) => KnownRuntimeEnvironmentSchema.parse(entry))
.sort((a, b) => a.name.localeCompare(b.name))
}
} catch {
throw new RuntimeEnvironmentStoreError(
'runtime_error',
`Could not read Orca environments at ${path}; the file is invalid.`
)
}
}
function writeEnvironmentStore(userDataPath: string, store: RuntimeEnvironmentStore): void {
const path = getEnvironmentStorePath(userDataPath)
try {
writeSecureJsonFileWithinLimit(
path,
RuntimeEnvironmentStoreSchema.parse(store),
MAX_RUNTIME_ENVIRONMENT_STORE_FILE_BYTES
)
} catch (error) {
if (error instanceof JsonStringifyByteLimitError) {
throw new RuntimeEnvironmentStoreError(
'runtime_error',
`Could not write Orca environments at ${path}; the store exceeds its durable capacity.`
)
}
throw error
}
}