fix(desktop): isolate remote runtime auth and embeds

Fix remote Desktop runtime bootstrapping across context-panel session chats, additional windows, and host switches.\n\n- Bootstrap embedded session-chat frames through a same-origin parent handshake that supplies the active endpoint, bearer token, runtime headers, local origin, and a credential-free relay descriptor.\n- Keep relay pairing grants out of iframe state and explicitly rebind the SDK after embedded bootstrap or relay restoration.\n- Preserve each additional and Mini Chat window's own init script instead of overwriting it when the main window's host configuration changes.\n- Replace direct iframe global calls with same-origin postMessage synchronization for theme, chat settings, and visibility.\n\nHarden Desktop host authentication and probing.\n\n- Bind password, passkey, session-status, and token-persistence completions to the runtime identity that started them, so a late result cannot alter a newly selected host.\n- Cancel active passkey operations and reset transient auth UI state on endpoint changes.\n- Verify stored client authentication via /auth/session for direct and relay host probes, distinguishing reachable hosts from hosts that require re-authentication.\n- Bound every relay probe request with an aborting timeout so a stalled auth request cannot hang refresh or host switching.\n\nAdd regression coverage for the embedded bootstrap handshake, credential-free relay descriptor exposure, runtime configuration, stale password completion after an A-to-B switch, and SDK errors that carry a zero response status.\n\nAlso preserve SDK response status on session-message loader errors so callers can distinguish transport and server failures.
This commit is contained in:
Bohdan Triapitsyn
2026-07-30 17:43:39 +03:00
parent 4ae3debf54
commit 3b00c91893
19 changed files with 819 additions and 159 deletions
@@ -140,6 +140,7 @@ describe("SessionMessageLoader", () => {
await loader.ensure(target, { force: true })
expect(loader.getSnapshot(target).status).toBe("error")
expect((loader.getSnapshot(target).error as Error & { status?: number }).status).toBe(400)
expect(store.getState().message[target.sessionID]?.[0]?.id).toBe("cached")
fail = false
@@ -149,6 +150,20 @@ describe("SessionMessageLoader", () => {
childStores.disposeAll()
})
test("propagates a zero response status on SDK errors", async () => {
const { childStores, loader } = createLoader(async () => ({
error: { message: "network rejected" },
response: { status: 0 },
}))
const target = { directory: "/repo", sessionID: "session-a" }
await loader.ensure(target, { force: true })
expect((loader.getSnapshot(target).error as Error & { status?: number }).status).toBe(0)
loader.dispose()
childStores.disposeAll()
})
test("prevents an evicted in-flight request from repopulating the store", async () => {
const pending = deferred<ReturnType<typeof response>>()
const { childStores, loader } = createLoader(async () => pending.promise)
@@ -98,7 +98,10 @@ const assertSdkSuccess = (result: {
}, operation: string): void => {
if (!result.error) return
const status = result.response?.status
throw new Error(`${operation} failed${status ? ` (${status})` : ""}: ${formatSdkError(result.error)}`)
const message = `${operation} failed${status ? ` (${status})` : ""}: ${formatSdkError(result.error)}`
const error = new Error(message) as Error & { status?: number }
if (status !== undefined) error.status = status
throw error
}
const sortParts = (parts: Part[]): Part[] => parts