File size: 4,287 Bytes
bcf46c3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/**
 * 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<T = any>(request: ScrapeRequest): Promise<T> {
    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<string, string> = {
      '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}`);
    }
  }
}