refactor(desktop): DRY the cloud helpers

Tighten the salvaged Hermes Cloud code with no behavior change:
- main: one `trimCloudOrg` projection reused by the success-echo and the 409
  org list (drop the duplicated map), and a `cloudLoginError()` factory for the
  three needsCloudLogin throw sites.
- renderer: a `cloudLoginLapsed()` predicate for the duplicated
  needsCloudLogin→signed-out check.
This commit is contained in:
Brooklyn Nicholson 2026-07-10 01:47:46 -05:00
parent 2d315d30f8
commit 0ff097439e
2 changed files with 29 additions and 24 deletions

View file

@ -5552,6 +5552,20 @@ function openPortalLoginWindow() {
})
}
// A "portal session is gone, re-login" error the renderer flips to signed-out
// on (via the `needsCloudLogin` tag). `cause` chains the underlying 401 when
// the session lapsed mid-flight.
function cloudLoginError(message, cause?) {
const err = new Error(message) as any
err.needsCloudLogin = true
if (cause) {
err.cause = cause
}
return err
}
// Discover the hosted (Hermes Cloud) agents the signed-in user can see. Calls
// the NAS trimmed-summary endpoint over the partition-bound net, so the portal
// session cookie is attached automatically (no bearer needed — NAS accepts the
@ -5564,11 +5578,9 @@ async function discoverCloudAgents(org?: string) {
const portalBaseUrl = resolvePortalBaseUrl()
if (!(await hasLivePortalSession())) {
const err = new Error(
throw cloudLoginError(
'You are not signed in to Hermes Cloud. Open Settings → Gateway, choose Hermes Cloud, and sign in.'
) as any
err.needsCloudLogin = true
throw err
)
}
const orgQuery = org ? `?org=${encodeURIComponent(org)}` : ''
@ -5583,10 +5595,7 @@ async function discoverCloudAgents(org?: string) {
// A 401 means the portal session lapsed between the liveness check and the
// call — surface it as a re-login, not a generic failure.
if (error && error.statusCode === 401) {
const err = new Error('Your Hermes Cloud session has expired. Open Settings → Gateway and sign in again.') as any
err.needsCloudLogin = true
err.cause = error
throw err
throw cloudLoginError('Your Hermes Cloud session has expired. Open Settings → Gateway and sign in again.', error)
}
// A 409 means we're a multi-org user who hasn't picked an org. The body
@ -5607,8 +5616,10 @@ async function discoverCloudAgents(org?: string) {
return { agents: trimCloudAgents(body), org: trimCloudOrg(body?.org) }
}
// Project a NAS response org ({ id, slug, name, isPersonal }) to the trimmed
// shape the renderer persists, or null when absent/malformed.
// Project a NAS org row ({ id, slug, name, isPersonal, role }) to the trimmed
// shape the renderer consumes/persists, or null when absent/malformed. Shared
// by the success-body echo (trimCloudOrg) and the 409 org list, so the two can
// never drift.
function trimCloudOrg(org) {
if (!org || typeof org !== 'object' || typeof org.id !== 'string') {
return null
@ -5646,15 +5657,7 @@ function parseOrgSelectionError(error) {
return null
}
return parsed.orgs
.filter(o => o && typeof o === 'object' && typeof o.id === 'string')
.map(o => ({
id: o.id,
slug: typeof o.slug === 'string' ? o.slug : null,
name: typeof o.name === 'string' ? o.name : o.id,
isPersonal: Boolean(o.isPersonal),
role: typeof o.role === 'string' ? o.role : 'MEMBER'
}))
return parsed.orgs.map(trimCloudOrg).filter(Boolean)
}
// Project NAS's agent rows to the trimmed DTO the renderer consumes.
@ -5688,9 +5691,7 @@ async function cloudAgentSilentSignIn(dashboardUrl) {
// interactive prompt rather than a silent cascade. Discovery already gates on
// this, but a selection can arrive after the session lapsed.
if (!(await hasLivePortalSession())) {
const err = new Error('Your Hermes Cloud session has expired. Sign in to Hermes Cloud again.') as any
err.needsCloudLogin = true
throw err
throw cloudLoginError('Your Hermes Cloud session has expired. Sign in to Hermes Cloud again.')
}
await openOauthLoginWindow(baseUrl, { silent: true })

View file

@ -428,6 +428,10 @@ export function GatewaySettings() {
// --- Hermes Cloud handlers ---
// A rejected cloud IPC call tagged `needsCloudLogin` means the portal session
// lapsed — treat it as signed-out so the panel falls back to the sign-in view.
const cloudLoginLapsed = (err: unknown) => Boolean(err && typeof err === 'object' && 'needsCloudLogin' in err)
// Pull the discovered agent list over the shared portal session. Tolerant of
// a lapsed session: a needsCloudLogin error flips us back to signed-out.
// `org` scopes discovery for multi-org users; when discovery comes back with
@ -475,7 +479,7 @@ export function GatewaySettings() {
setCloudDiscover('error')
// A lapsed/absent portal session means we're effectively signed out.
if (err && typeof err === 'object' && 'needsCloudLogin' in err) {
if (cloudLoginLapsed(err)) {
setCloudSignedIn(false)
}
@ -644,7 +648,7 @@ export function GatewaySettings() {
setState(next)
notify({ kind: 'success', title: g.cloudConnectedTitle, message: g.cloudConnectedTo(agent.name) })
} catch (err) {
if (err && typeof err === 'object' && 'needsCloudLogin' in err) {
if (cloudLoginLapsed(err)) {
setCloudSignedIn(false)
}