All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 9m1s
- Node.js SDK: TypeScript, ESM+CJS, zero deps (uses native fetch) - Python SDK: zero deps (uses urllib), Python 3.8+ - Both fully documented with examples and type hints - Ready for npm/PyPI publishing
184 lines
4.9 KiB
TypeScript
184 lines
4.9 KiB
TypeScript
/**
|
|
* SnapAPI Node.js SDK
|
|
* Official client for https://snapapi.eu — EU-hosted screenshot API
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* import { SnapAPI } from 'snapapi';
|
|
*
|
|
* const snap = new SnapAPI('your-api-key');
|
|
* const screenshot = await snap.capture('https://example.com');
|
|
* fs.writeFileSync('screenshot.png', screenshot);
|
|
* ```
|
|
*/
|
|
|
|
export interface ScreenshotOptions {
|
|
/** URL to capture (required) */
|
|
url: string;
|
|
/** Output format: png, jpeg, or webp (default: png) */
|
|
format?: "png" | "jpeg" | "webp";
|
|
/** Viewport width in pixels, 320-3840 (default: 1280) */
|
|
width?: number;
|
|
/** Viewport height in pixels, 200-2160 (default: 800) */
|
|
height?: number;
|
|
/** Capture full scrollable page (default: false) */
|
|
fullPage?: boolean;
|
|
/** JPEG/WebP quality 1-100 (default: 80, ignored for PNG) */
|
|
quality?: number;
|
|
/** CSS selector to wait for before capturing */
|
|
waitForSelector?: string;
|
|
/** Device scale factor 1-3 (default: 1, use 2 for Retina) */
|
|
deviceScale?: number;
|
|
/** Extra delay in ms after page load, 0-5000 (default: 0) */
|
|
delay?: number;
|
|
/** Page load event to wait for (default: domcontentloaded) */
|
|
waitUntil?: "load" | "domcontentloaded" | "networkidle0" | "networkidle2";
|
|
}
|
|
|
|
export interface SnapAPIConfig {
|
|
/** API base URL (default: https://snapapi.eu) */
|
|
baseUrl?: string;
|
|
/** Request timeout in ms (default: 30000) */
|
|
timeout?: number;
|
|
}
|
|
|
|
export class SnapAPIError extends Error {
|
|
/** HTTP status code */
|
|
status: number;
|
|
/** Error message from the API */
|
|
detail: string;
|
|
|
|
constructor(status: number, detail: string) {
|
|
super(`SnapAPI error ${status}: ${detail}`);
|
|
this.name = "SnapAPIError";
|
|
this.status = status;
|
|
this.detail = detail;
|
|
}
|
|
}
|
|
|
|
export class SnapAPI {
|
|
private apiKey: string;
|
|
private baseUrl: string;
|
|
private timeout: number;
|
|
|
|
/**
|
|
* Create a new SnapAPI client.
|
|
*
|
|
* @param apiKey - Your SnapAPI API key
|
|
* @param config - Optional configuration
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const snap = new SnapAPI('sk_live_...');
|
|
* ```
|
|
*/
|
|
constructor(apiKey: string, config?: SnapAPIConfig) {
|
|
if (!apiKey) throw new Error("SnapAPI: apiKey is required");
|
|
this.apiKey = apiKey;
|
|
this.baseUrl = (config?.baseUrl ?? "https://snapapi.eu").replace(/\/$/, "");
|
|
this.timeout = config?.timeout ?? 30000;
|
|
}
|
|
|
|
/**
|
|
* Capture a screenshot of a URL.
|
|
*
|
|
* Returns the screenshot as a Buffer (Node.js) or Uint8Array.
|
|
*
|
|
* @param urlOrOptions - URL string or full ScreenshotOptions object
|
|
* @param options - Additional options when first arg is a URL string
|
|
* @returns Screenshot image as Buffer
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* // Simple usage
|
|
* const png = await snap.capture('https://example.com');
|
|
*
|
|
* // With options
|
|
* const jpg = await snap.capture('https://example.com', {
|
|
* format: 'jpeg',
|
|
* width: 1920,
|
|
* height: 1080,
|
|
* quality: 90
|
|
* });
|
|
*
|
|
* // Full-page capture
|
|
* const full = await snap.capture({
|
|
* url: 'https://example.com',
|
|
* fullPage: true,
|
|
* format: 'png',
|
|
* deviceScale: 2
|
|
* });
|
|
* ```
|
|
*
|
|
* @throws {SnapAPIError} When the API returns an error response
|
|
*/
|
|
async capture(
|
|
urlOrOptions: string | ScreenshotOptions,
|
|
options?: Omit<ScreenshotOptions, "url">
|
|
): Promise<Buffer> {
|
|
const body: ScreenshotOptions =
|
|
typeof urlOrOptions === "string"
|
|
? { url: urlOrOptions, ...options }
|
|
: urlOrOptions;
|
|
|
|
if (!body.url) throw new Error("SnapAPI: url is required");
|
|
|
|
const controller = new AbortController();
|
|
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
|
|
try {
|
|
const res = await fetch(`${this.baseUrl}/v1/screenshot`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${this.apiKey}`,
|
|
},
|
|
body: JSON.stringify(body),
|
|
signal: controller.signal,
|
|
});
|
|
|
|
if (!res.ok) {
|
|
let detail = `HTTP ${res.status}`;
|
|
try {
|
|
const json = (await res.json()) as { error?: string };
|
|
detail = json.error ?? detail;
|
|
} catch {}
|
|
throw new SnapAPIError(res.status, detail);
|
|
}
|
|
|
|
const arrayBuffer = await res.arrayBuffer();
|
|
return Buffer.from(arrayBuffer);
|
|
} finally {
|
|
clearTimeout(timer);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check API health status.
|
|
*
|
|
* @returns Health check response
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const health = await snap.health();
|
|
* console.log(health.status); // "ok"
|
|
* ```
|
|
*/
|
|
async health(): Promise<{
|
|
status: string;
|
|
version: string;
|
|
uptime: number;
|
|
browser: {
|
|
browsers: number;
|
|
totalPages: number;
|
|
availablePages: number;
|
|
queueDepth: number;
|
|
};
|
|
}> {
|
|
const res = await fetch(`${this.baseUrl}/health`);
|
|
if (!res.ok) throw new SnapAPIError(res.status, "Health check failed");
|
|
return res.json() as any;
|
|
}
|
|
}
|
|
|
|
export default SnapAPI;
|