/** * official TypeScript/JavaScript SDK client for the Opticparse Vision-Scrape API. * Compatible with Node.js environments and modern web browsers. */ export interface ScrapeRequest { targetUrl: string; extractionQuery: string; viewportWidth?: number; viewportHeight?: number; waitUntil?: 'networkidle' | 'load' | 'domcontentloaded'; timeout?: number; } export interface ClientConfig { /** * The API Key for authorization. * Maps to X-API-Key header for direct server connection. */ apiKey?: string; /** * Custom target URL for the Opticparse service. * Defaults to http://localhost:8000. */ apiUrl?: string; } export class OpticparseError extends Error { statusCode?: number; details?: string; constructor(message: string, statusCode?: number, details?: string) { super(message); this.name = 'OpticparseError'; this.statusCode = statusCode; this.details = details; } } export class OpticparseClient { private apiKey: string; private apiUrl: string; constructor(config: ClientConfig = {}) { // Attempt to load from Node.js environment if not provided explicitly const getEnv = (key: string): string => { return (typeof process !== 'undefined' && process.env ? process.env[key] : '') || ''; }; this.apiKey = config.apiKey || getEnv('OPTICPARSE_API_KEY'); this.apiUrl = config.apiUrl || getEnv('OPTICPARSE_API_URL') || 'http://localhost:8000'; } /** * Performs an AI-driven visual scraping operation against the target webpage. * * @param request Target site details and desired JSON format instructions * @returns The parsed JSON object containing target details * @throws {OpticparseError} on HTTP authorization issues, network timeout, or server failure. */ async scrape(request: ScrapeRequest): Promise { if (!this.apiKey) { throw new OpticparseError( 'Client misconfigured: API Key is missing. Configure via the constructor or OPTICPARSE_API_KEY environment variable.' ); } const payload = { target_url: request.targetUrl, extraction_query: request.extractionQuery, viewport_width: request.viewportWidth ?? 1280, viewport_height: request.viewportHeight ?? 800, wait_until: request.waitUntil ?? 'networkidle', timeout: request.timeout ?? 90000, }; const headers: Record = { 'Content-Type': 'application/json', 'X-API-Key': this.apiKey, }; const controller = new AbortController(); const requestTimeout = request.timeout ?? 90000; // default 90s maximum duration const timeoutId = setTimeout(() => controller.abort(), requestTimeout); // Normalize URL pathing const endpoint = `${this.apiUrl.replace(/\/+$/, '')}/api/vision-scrape`; try { const response = await fetch(endpoint, { method: 'POST', headers, body: JSON.stringify(payload), signal: controller.signal, }); clearTimeout(timeoutId); if (!response.ok) { let errorDetails = ''; try { errorDetails = await response.text(); } catch { // ignore parsing error } if (response.status === 401 || response.status === 403) { throw new OpticparseError( 'Unauthorized: Invalid or missing API key credential.', response.status, errorDetails ); } if (response.status === 400) { throw new OpticparseError( 'Bad Request: Scraper endpoint rejected configuration options.', 400, errorDetails ); } throw new OpticparseError( `Request failed with HTTP status ${response.status}`, response.status, errorDetails ); } const data = await response.json(); return data as T; } catch (error: any) { clearTimeout(timeoutId); if (error.name === 'AbortError') { throw new OpticparseError(`Request timed out after limit of ${requestTimeout}ms`); } if (error instanceof OpticparseError) { throw error; } throw new OpticparseError(`Network request failure: ${error.message || error}`); } } }