TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1import 'server-only';2import { createHmac } from 'node:crypto';3import type { NormalizedRecord } from '@rareindex/shared';4import { NormalizedRecordSchema } from '@rareindex/shared';56/**7 * URL lookup is delegated to the API service (apps/api `/internal/lookup`): connector modules are8 * loaded dynamically from disk, which the Next.js bundler cannot do. Shared secret = SESSION_SECRET.9 */10export function apiBaseUrl(): string {11 return process.env.RI_API_URL ?? `http://127.0.0.1:${process.env.API_PORT ?? 8211}`;12}1314export function internalToken(): string {15 return createHmac('sha256', process.env.SESSION_SECRET ?? 'dev-only').update('internal').digest('hex');16}1718export interface LookupResult {19 connectorId: string;20 sourceId: string;21 records: NormalizedRecord[];22 durationMs: number;23}2425export async function lookupViaApi(url: string, timeoutMs = 90_000): Promise<LookupResult | null | 'unavailable'> {26 const ctrl = new AbortController();27 const t = setTimeout(() => ctrl.abort(), timeoutMs);28 try {29 const res = await fetch(`${apiBaseUrl()}/internal/lookup`, {30 method: 'POST',31 headers: { 'content-type': 'application/json', 'x-internal-token': internalToken() },32 body: JSON.stringify({ url }),33 signal: ctrl.signal,34 cache: 'no-store',35 });36 if (!res.ok) return 'unavailable';37 const json = (await res.json()) as { data: { connectorId: string; sourceId: string; records: unknown[] } | null; meta: { supported: boolean; durationMs?: number } };38 if (!json.data) return null;39 const records = json.data.records.map((r) => NormalizedRecordSchema.parse(r));40 return { connectorId: json.data.connectorId, sourceId: json.data.sourceId, records, durationMs: json.meta.durationMs ?? 0 };41 } catch {42 return 'unavailable';43 } finally {44 clearTimeout(t);45 }46}47