TypeScript 97.5%
SQL 1.4%
Python 0.8%
1import type { ConcreteNetwork, GeoTarget, HttpMethod } from "@fetcha/core";2import type { CookieJar } from "./cookies";3import type { FingerprintProfile } from "./fingerprint";45export type ProviderId = "oxylabs" | "decodo" | "soax" | "direct";67export interface ProxyEndpoint {8 host: string;9 port: number;10 username: string;11 password: string;12}1314export interface ProviderRequest {15 requestId: string;16 attemptId: string;17 url: string;18 method: HttpMethod;19 headers: Record<string, string>;20 body?: string | Buffer;21 timeoutMs: number;22 network: ConcreteNetwork;23 geo: GeoTarget;24 /** Sticky session key (provider-agnostic). When set the provider keeps the same exit IP. */25 sessionKey?: string | null;26 /** Session lifetime hint in minutes. */27 sessionMinutes?: number;28 followRedirects: boolean;29 maxRedirects: number;30 maxResponseBytes: number;31 /** Called for every redirect hop; must throw to abort. */32 onRedirect?: (nextUrl: string) => Promise<void>;33 /** Browser fingerprint profile to emulate (headers, TLS). Picked by the executor. */34 profile?: FingerprintProfile;35 /** Accept-Language source. */36 locale?: string | null;37 /** Referer to send on the first hop (null/undefined = none). */38 referer?: string | null;39 /** Cookie jar shared across hops (and across requests for sticky sessions). */40 jar?: CookieJar;41 /** Prefer HTTP/2 when the origin supports it (default true). */42 http2?: boolean;43}4445export interface ProviderTiming {46 dns_ms: number;47 proxy_connect_ms: number;48 tls_ms: number;49 origin_ms: number;50 processing_ms: number;51 total_ms: number;52}5354export interface ProviderResponse {55 status: number;56 headers: Record<string, string>;57 body: Buffer;58 finalUrl: string;59 redirects: number;60 bytesIn: number;61 bytesOut: number;62 timing: ProviderTiming;63 /** Provider exit info when known (never exposed to customers). */64 exit?: { ip?: string; country?: string };65 /** Fingerprint profile that was used. */66 profileId?: string;67 /** Negotiated protocol of the final hop when known ("h2" | "http/1.1"). */68 protocol?: string;69}7071export type ProviderHealthStatus = "healthy" | "degraded" | "down" | "unconfigured";7273export interface ProviderHealth {74 provider: ProviderId;75 network: ConcreteNetwork;76 status: ProviderHealthStatus;77 latencyMs: number | null;78 detail?: string;79 checkedAt: Date;80}8182export class ProviderError extends Error {83 readonly provider: ProviderId;84 readonly kind: "timeout" | "connect" | "auth" | "proxy" | "too_large" | "redirect" | "tls" | "unknown";85 readonly status?: number;86 constructor(provider: ProviderId, kind: ProviderError["kind"], message: string, opts: { status?: number; cause?: unknown } = {}) {87 super(message, { cause: opts.cause });88 this.name = "ProviderError";89 this.provider = provider;90 this.kind = kind;91 this.status = opts.status;92 }93}9495export interface ProxyProvider {96 readonly id: ProviderId;97 /** Human label for admin screens. */98 readonly label: string;99 /** Network classes this provider can serve. */100 readonly networks: ConcreteNetwork[];101 /** Whether credentials are present. Unconfigured providers are never routed to. */102 isConfigured(): boolean;103 /** Countries the provider supports; null = worldwide/unknown. */104 supportsGeo(geo: GeoTarget): boolean;105 fetch(request: ProviderRequest): Promise<ProviderResponse>;106 health(network?: ConcreteNetwork): Promise<ProviderHealth>;107 /** Estimated upstream cost in USD for a request transferring `bytes`. */108 estimateCost(network: ConcreteNetwork, bytes: number): number;109 /** Unit price in USD per GB for a network class. */110 pricePerGb(network: ConcreteNetwork): number;111 /** Upstream proxy endpoint for a request (null = direct egress). Used by the managed browser. */112 proxyEndpoint(req: Pick<ProviderRequest, "geo" | "sessionKey" | "sessionMinutes">): ProxyEndpoint | null;113}114