mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-26 11:12:03 +00:00
87 lines
2.7 KiB
JavaScript
87 lines
2.7 KiB
JavaScript
/**
|
|
* Helpers for local dashboard session-token discovery.
|
|
*
|
|
* The desktop main process can pass HERMES_DASHBOARD_SESSION_TOKEN when it
|
|
* spawns the local dashboard, but the dashboard is the source of truth for the
|
|
* token it actually serves to the renderer. If those drift, HTTP readiness
|
|
* probes still pass while /api/ws rejects the renderer's token.
|
|
*/
|
|
|
|
const http = require('node:http')
|
|
const https = require('node:https')
|
|
|
|
const DEFAULT_TOKEN_FETCH_TIMEOUT_MS = 3_000
|
|
|
|
function fetchPublicText(url, options = {}) {
|
|
return new Promise((resolve, reject) => {
|
|
let parsed
|
|
try {
|
|
parsed = new URL(url)
|
|
} catch (error) {
|
|
reject(new Error(`Invalid URL: ${error.message}`))
|
|
return
|
|
}
|
|
|
|
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
reject(new Error(`Unsupported Hermes backend URL protocol: ${parsed.protocol}`))
|
|
return
|
|
}
|
|
|
|
const client = parsed.protocol === 'https:' ? https : http
|
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TOKEN_FETCH_TIMEOUT_MS
|
|
const req = client.request(parsed, { method: options.method || 'GET' }, res => {
|
|
const chunks = []
|
|
res.on('data', chunk => chunks.push(chunk))
|
|
res.on('end', () => {
|
|
const text = Buffer.concat(chunks).toString('utf8')
|
|
if ((res.statusCode || 500) >= 400) {
|
|
reject(new Error(`${res.statusCode}: ${text || res.statusMessage}`))
|
|
return
|
|
}
|
|
resolve(text)
|
|
})
|
|
})
|
|
|
|
req.on('error', reject)
|
|
req.setTimeout(timeoutMs, () => {
|
|
req.destroy(new Error(`Timed out connecting to Hermes backend after ${timeoutMs}ms`))
|
|
})
|
|
req.end()
|
|
})
|
|
}
|
|
|
|
function extractInjectedDashboardToken(html) {
|
|
const match = /window\.__HERMES_SESSION_TOKEN__\s*=\s*("(?:\\.|[^"\\])*")/.exec(String(html || ''))
|
|
if (!match) return null
|
|
try {
|
|
return JSON.parse(match[1])
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
function dashboardIndexUrl(baseUrl) {
|
|
return `${String(baseUrl || '').replace(/\/+$/, '')}/`
|
|
}
|
|
|
|
async function resolveServedDashboardToken(baseUrl, fallbackToken, options = {}) {
|
|
const fetchText = options.fetchText || fetchPublicText
|
|
const html = await fetchText(dashboardIndexUrl(baseUrl), {
|
|
timeoutMs: options.timeoutMs ?? DEFAULT_TOKEN_FETCH_TIMEOUT_MS
|
|
})
|
|
const servedToken = extractInjectedDashboardToken(html)
|
|
|
|
if (servedToken && servedToken !== fallbackToken && typeof options.rememberLog === 'function') {
|
|
options.rememberLog('[boot] dashboard served a different session token; using served token for WebSocket auth')
|
|
}
|
|
|
|
return servedToken || fallbackToken
|
|
}
|
|
|
|
module.exports = {
|
|
DEFAULT_TOKEN_FETCH_TIMEOUT_MS,
|
|
dashboardIndexUrl,
|
|
extractInjectedDashboardToken,
|
|
fetchPublicText,
|
|
resolveServedDashboardToken
|
|
}
|