@@ -11,6 +11,44 @@ import { BrowserEvaluator } from './utils/browser-evaluator';
1111// Maximum payload size for API requests (10MB server limit)
1212const MAX_PAYLOAD_BYTES = 10 * 1024 * 1024 ;
1313
14+ /**
15+ * Structured error for server-side (gateway) snapshot failures.
16+ *
17+ * Keeps HTTP status/URL/response details available to callers for better logging/debugging.
18+ */
19+ export class SnapshotGatewayError extends Error {
20+ public statusCode ?: number ;
21+ public url ?: string ;
22+ public requestId ?: string ;
23+ public responseText ?: string ;
24+ public cause ?: unknown ;
25+
26+ constructor (
27+ message : string ,
28+ opts ?: {
29+ statusCode ?: number ;
30+ url ?: string ;
31+ requestId ?: string ;
32+ responseText ?: string ;
33+ cause ?: unknown ;
34+ }
35+ ) {
36+ super ( message ) ;
37+ this . name = 'SnapshotGatewayError' ;
38+ this . statusCode = opts ?. statusCode ;
39+ this . url = opts ?. url ;
40+ this . requestId = opts ?. requestId ;
41+ this . responseText = opts ?. responseText ;
42+ this . cause = opts ?. cause ;
43+ }
44+
45+ static snip ( s : string | undefined , n : number = 400 ) : string | undefined {
46+ if ( ! s ) return undefined ;
47+ const t = String ( s ) . replace ( / \r / g, ' ' ) . replace ( / \n / g, ' ' ) . trim ( ) ;
48+ return t . slice ( 0 , n ) ;
49+ }
50+ }
51+
1452export interface SnapshotOptions {
1553 screenshot ?: boolean | { format : 'png' | 'jpeg' ; quality ?: number } ;
1654 limit ?: number ;
@@ -202,6 +240,7 @@ async function snapshotViaApi(
202240 if ( ! page ) {
203241 throw new Error ( 'Browser not started. Call start() first.' ) ;
204242 }
243+ const gatewayUrl = `${ apiUrl } /v1/snapshot` ;
205244
206245 // CRITICAL: Wait for extension injection to complete (CSP-resistant architecture)
207246 // Even for API mode, we need the extension to collect raw data locally
@@ -278,15 +317,38 @@ async function snapshotViaApi(
278317 } ;
279318
280319 try {
281- const response = await fetch ( ` ${ apiUrl } /v1/snapshot` , {
320+ const response = await fetch ( gatewayUrl , {
282321 method : 'POST' ,
283322 headers,
284323 body : payloadJson ,
285324 } ) ;
286325
287326 if ( ! response . ok ) {
288- const errorText = await response . text ( ) ;
289- throw new Error ( `API request failed: ${ response . status } ${ errorText } ` ) ;
327+ let errorText : string | undefined = undefined ;
328+ try {
329+ errorText = await response . text ( ) ;
330+ } catch ( _e ) {
331+ errorText = undefined ;
332+ }
333+ const requestId =
334+ response . headers . get ( 'x-request-id' ) || response . headers . get ( 'x-trace-id' ) || undefined ;
335+ const bodySnip = SnapshotGatewayError . snip ( errorText ) ;
336+
337+ const parts : string [ ] = [ ] ;
338+ parts . push ( `status=${ response . status } ` ) ;
339+ parts . push ( `url=${ gatewayUrl } ` ) ;
340+ if ( requestId ) parts . push ( `request_id=${ requestId } ` ) ;
341+ if ( bodySnip ) parts . push ( `body=${ bodySnip } ` ) ;
342+
343+ throw new SnapshotGatewayError (
344+ `Server-side snapshot API failed: ${ parts . join ( ' ' ) } . Try using use_api: false to use local extension instead.` ,
345+ {
346+ statusCode : response . status ,
347+ url : gatewayUrl ,
348+ requestId,
349+ responseText : bodySnip ,
350+ }
351+ ) ;
290352 }
291353
292354 const apiResult = await response . json ( ) ;
@@ -359,6 +421,14 @@ async function snapshotViaApi(
359421
360422 return snapshotData ;
361423 } catch ( e : any ) {
362- throw new Error ( `API request failed: ${ e . message } ` ) ;
424+ if ( e instanceof SnapshotGatewayError ) {
425+ throw e ;
426+ }
427+ const errType = e instanceof Error ? e . name : typeof e ;
428+ const errMsg = e instanceof Error ? `${ e . name } : ${ e . message } ` : String ( e ) ;
429+ throw new SnapshotGatewayError (
430+ `Server-side snapshot API failed: url=${ gatewayUrl } err_type=${ SnapshotGatewayError . snip ( errType , 80 ) } err=${ SnapshotGatewayError . snip ( errMsg , 220 ) } . Try using use_api: false to use local extension instead.` ,
431+ { url : gatewayUrl , cause : e }
432+ ) ;
363433 }
364434}
0 commit comments