import 'server-only'; import { createHmac } from 'node:crypto'; import type { NormalizedRecord } from '@rareindex/shared'; import { NormalizedRecordSchema } from '@rareindex/shared'; /** * URL lookup is delegated to the API service (apps/api `/internal/lookup`): connector modules are * loaded dynamically from disk, which the Next.js bundler cannot do. Shared secret = SESSION_SECRET. */ export function apiBaseUrl(): string { return process.env.RI_API_URL ?? `http://127.0.0.1:${process.env.API_PORT ?? 8211}`; } export function internalToken(): string { return createHmac('sha256', process.env.SESSION_SECRET ?? 'dev-only').update('internal').digest('hex'); } export interface LookupResult { connectorId: string; sourceId: string; records: NormalizedRecord[]; durationMs: number; } export async function lookupViaApi(url: string, timeoutMs = 90_000): Promise { const ctrl = new AbortController(); const t = setTimeout(() => ctrl.abort(), timeoutMs); try { const res = await fetch(`${apiBaseUrl()}/internal/lookup`, { method: 'POST', headers: { 'content-type': 'application/json', 'x-internal-token': internalToken() }, body: JSON.stringify({ url }), signal: ctrl.signal, cache: 'no-store', }); if (!res.ok) return 'unavailable'; const json = (await res.json()) as { data: { connectorId: string; sourceId: string; records: unknown[] } | null; meta: { supported: boolean; durationMs?: number } }; if (!json.data) return null; const records = json.data.records.map((r) => NormalizedRecordSchema.parse(r)); return { connectorId: json.data.connectorId, sourceId: json.data.sourceId, records, durationMs: json.meta.durationMs ?? 0 }; } catch { return 'unavailable'; } finally { clearTimeout(t); } }