quality / changes (push) Successful in 7s
quality / commitlint (push) Skipped
quality / openapi (push) Successful in 30s
quality / web (push) Successful in 1m3s
quality / api (push) Successful in 1m15s
quality / linux-agent-smoke (push) Failing after 27s
quality / docker-check (push) Failing after 2m28s
CD / quality (push) Failing after 5m52s
CD / publish (push) Skipped
Implement reliable agent apply, telemetry v2, analytics retention, guarded rollouts, enrollment, labels and webhooks. Improve ReUI frontend data safety and bounded list loading, and add Docker smoke, recovery and resource checks. Document local verification and pending native acceptance.
42 lines
2.3 KiB
TypeScript
42 lines
2.3 KiB
TypeScript
import { safeFetchJson, SOURCE_LIMITS } from './safe-fetch.js'
|
|
|
|
/** Fully collect bounded cursor pages. Never accept an incomplete snapshot. */
|
|
export async function fetchEvobgpPages(base: string, token: string, path: string, limit: number): Promise<Record<string, unknown>[]> {
|
|
const pages: Record<string, unknown>[] = []
|
|
const seen = new Set<string>()
|
|
let cursor: string | undefined
|
|
let elements = 0
|
|
let bytes = 0
|
|
const deadline = Date.now() + SOURCE_LIMITS.timeoutMs
|
|
for (let page = 0; page < SOURCE_LIMITS.pages; page++) {
|
|
if (Date.now() >= deadline) throw new Error('EvoBGP snapshot deadline exceeded')
|
|
if (bytes >= SOURCE_LIMITS.totalBytes) throw new Error('EvoBGP snapshot byte limit exceeded')
|
|
const url = new URL(`${base.replace(/\/$/, '')}${path}`)
|
|
url.searchParams.set('limit', String(limit))
|
|
if (cursor) url.searchParams.set('cursor', cursor)
|
|
const response = await safeFetchJson(url, {
|
|
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
|
|
timeoutMs: deadline - Date.now(),
|
|
maxBytes: Math.min(SOURCE_LIMITS.bytes, SOURCE_LIMITS.totalBytes - bytes),
|
|
})
|
|
const data = response.data
|
|
bytes += response.bytes
|
|
if (bytes > SOURCE_LIMITS.totalBytes) throw new Error('EvoBGP snapshot byte limit exceeded')
|
|
if (!data || typeof data !== 'object' || Array.isArray(data)) throw new Error('Invalid EvoBGP page')
|
|
const body = data as Record<string, unknown>
|
|
const items = body.prefixes ?? body.items
|
|
if (!Array.isArray(items)) throw new Error('Invalid EvoBGP page items')
|
|
elements += items.length
|
|
if (elements > SOURCE_LIMITS.elements) throw new Error('EvoBGP snapshot element limit exceeded')
|
|
if (body.has_more !== undefined && typeof body.has_more !== 'boolean') throw new Error('Invalid EvoBGP has_more')
|
|
if (body.next_cursor !== undefined && body.next_cursor !== null && (typeof body.next_cursor !== 'string' || body.next_cursor.length > 2048)) throw new Error('Invalid EvoBGP next_cursor')
|
|
pages.push(body)
|
|
if (body.has_more !== true && !body.next_cursor) return pages
|
|
const next = body.next_cursor
|
|
if (typeof next !== 'string' || !next || seen.has(next)) throw new Error('Incomplete or repeated EvoBGP cursor')
|
|
seen.add(next)
|
|
cursor = next
|
|
}
|
|
throw new Error('EvoBGP page limit exceeded')
|
|
}
|