snapapi-client 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -0
- package/index.js +159 -0
- package/package.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# snapapi
|
|
2
|
+
|
|
3
|
+
Official JavaScript SDK for the [SnapAPI](https://snapapi.tech) web intelligence API.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install snapapi
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Zero dependencies. Node.js 18+ required.
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
const SnapAPI = require('snapapi');
|
|
15
|
+
const client = new SnapAPI(); // reads SNAPAPI_KEY env var
|
|
16
|
+
|
|
17
|
+
// Screenshot
|
|
18
|
+
const png = await client.screenshot('https://github.com');
|
|
19
|
+
require('fs').writeFileSync('screenshot.png', png);
|
|
20
|
+
|
|
21
|
+
// Metadata
|
|
22
|
+
const meta = await client.metadata('https://github.com');
|
|
23
|
+
console.log(meta.og_title, meta.og_image);
|
|
24
|
+
|
|
25
|
+
// Page analysis
|
|
26
|
+
const page = await client.analyze('https://stripe.com');
|
|
27
|
+
console.log(page.page_type, page.primary_cta, page.technologies);
|
|
28
|
+
|
|
29
|
+
// URL → PDF
|
|
30
|
+
const pdf = await client.pdf('https://github.com', { format: 'A4' });
|
|
31
|
+
require('fs').writeFileSync('page.pdf', pdf);
|
|
32
|
+
|
|
33
|
+
// HTML → image (OG cards, email previews)
|
|
34
|
+
const img = await client.render('<h1 style="padding:60px;font-size:48px">Hello</h1>', { width: 1200, height: 630 });
|
|
35
|
+
require('fs').writeFileSync('card.png', img);
|
|
36
|
+
|
|
37
|
+
// Batch — multiple URLs in parallel
|
|
38
|
+
const results = await client.batch(['https://stripe.com', 'https://vercel.com'], 'metadata');
|
|
39
|
+
results.forEach(r => console.log(r.url, r.og_title));
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Get a Free API Key
|
|
43
|
+
|
|
44
|
+
100 calls/month · No credit card · Active in 30 seconds
|
|
45
|
+
|
|
46
|
+
→ **[snapapi.tech/start](https://snapapi.tech/start)**
|
|
47
|
+
|
|
48
|
+
## API
|
|
49
|
+
|
|
50
|
+
### `new SnapAPI(apiKey?, options?)`
|
|
51
|
+
|
|
52
|
+
| Param | Type | Default |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `apiKey` | `string` | `process.env.SNAPAPI_KEY` |
|
|
55
|
+
| `options.baseUrl` | `string` | `https://api.snapapi.tech` |
|
|
56
|
+
| `options.timeout` | `number` | `45000` |
|
|
57
|
+
|
|
58
|
+
### Methods
|
|
59
|
+
|
|
60
|
+
| Method | Returns | Description |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `screenshot(url, opts?)` | `Buffer` | PNG/JPEG/WebP image |
|
|
63
|
+
| `metadata(url)` | `object` | OG tags, title, favicon, canonical |
|
|
64
|
+
| `analyze(url, opts?)` | `object` | CTAs, nav, tech stack, word count |
|
|
65
|
+
| `pdf(url, opts?)` | `Buffer` | PDF binary |
|
|
66
|
+
| `render(html, opts?)` | `Buffer` | HTML → image |
|
|
67
|
+
| `batch(urls, endpoint?, params?)` | `Array` | Parallel multi-URL processing |
|
|
68
|
+
|
|
69
|
+
Full parameter reference: [snapapi.tech/docs](https://snapapi.tech/docs)
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT
|
package/index.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* snapapi — Official JavaScript SDK for the SnapAPI web intelligence API
|
|
4
|
+
*
|
|
5
|
+
* npm install snapapi
|
|
6
|
+
* Docs: https://snapapi.tech/docs
|
|
7
|
+
* Free key: https://snapapi.tech/start
|
|
8
|
+
*
|
|
9
|
+
* Zero runtime dependencies. Node.js 18+ required (uses built-in fetch).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const BASE_URL = 'https://api.snapapi.tech';
|
|
13
|
+
|
|
14
|
+
class SnapAPIError extends Error {
|
|
15
|
+
constructor(status, message) {
|
|
16
|
+
super(`SnapAPI ${status}: ${message}`);
|
|
17
|
+
this.name = 'SnapAPIError';
|
|
18
|
+
this.status = status;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
class SnapAPI {
|
|
23
|
+
/**
|
|
24
|
+
* @param {string} [apiKey] - API key. Falls back to SNAPAPI_KEY env var.
|
|
25
|
+
* @param {object} [options]
|
|
26
|
+
* @param {string} [options.baseUrl] - Override API base URL.
|
|
27
|
+
* @param {number} [options.timeout] - Request timeout in ms (default 45000).
|
|
28
|
+
*/
|
|
29
|
+
constructor(apiKey, options = {}) {
|
|
30
|
+
this._key = apiKey || (typeof process !== 'undefined' && process.env.SNAPAPI_KEY) || '';
|
|
31
|
+
this._base = (options.baseUrl || BASE_URL).replace(/\/$/, '');
|
|
32
|
+
this._timeout = options.timeout || 45_000;
|
|
33
|
+
if (!this._key) {
|
|
34
|
+
throw new SnapAPIError(0, 'No API key. Set SNAPAPI_KEY env var or pass apiKey to constructor. Free key at https://snapapi.tech/start');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async _get(path, params = {}) {
|
|
39
|
+
const qs = new URLSearchParams(
|
|
40
|
+
Object.fromEntries(Object.entries(params).filter(([, v]) => v != null).map(([k, v]) => [k, String(v)]))
|
|
41
|
+
).toString();
|
|
42
|
+
const url = `${this._base}${path}${qs ? '?' + qs : ''}`;
|
|
43
|
+
const signal = AbortSignal.timeout(this._timeout);
|
|
44
|
+
const res = await fetch(url, { headers: { 'x-api-key': this._key }, signal });
|
|
45
|
+
if (!res.ok) {
|
|
46
|
+
let msg = `HTTP ${res.status}`;
|
|
47
|
+
try { const j = await res.json(); msg = j.error || msg; } catch {}
|
|
48
|
+
throw new SnapAPIError(res.status, msg);
|
|
49
|
+
}
|
|
50
|
+
return res;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async _post(path, body) {
|
|
54
|
+
const signal = AbortSignal.timeout(this._timeout);
|
|
55
|
+
const res = await fetch(`${this._base}${path}`, {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: { 'x-api-key': this._key, 'Content-Type': 'application/json' },
|
|
58
|
+
body: JSON.stringify(body),
|
|
59
|
+
signal,
|
|
60
|
+
});
|
|
61
|
+
if (!res.ok) {
|
|
62
|
+
let msg = `HTTP ${res.status}`;
|
|
63
|
+
try { const j = await res.json(); msg = j.error || msg; } catch {}
|
|
64
|
+
throw new SnapAPIError(res.status, msg);
|
|
65
|
+
}
|
|
66
|
+
return res;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Capture a screenshot of any URL.
|
|
71
|
+
* @param {string} url
|
|
72
|
+
* @param {object} [opts]
|
|
73
|
+
* @param {'png'|'jpeg'|'webp'} [opts.format='png']
|
|
74
|
+
* @param {number} [opts.width=1280]
|
|
75
|
+
* @param {number} [opts.height=800]
|
|
76
|
+
* @param {boolean} [opts.full_page=false]
|
|
77
|
+
* @param {boolean} [opts.dark_mode=false]
|
|
78
|
+
* @param {string} [opts.device] - e.g. 'iphone14', 'pixel7', 'ipad'
|
|
79
|
+
* @param {string} [opts.selector] - CSS selector to capture only that element
|
|
80
|
+
* @param {number} [opts.delay] - Extra ms to wait before capture
|
|
81
|
+
* @returns {Promise<Buffer>} Raw image bytes
|
|
82
|
+
*/
|
|
83
|
+
async screenshot(url, opts = {}) {
|
|
84
|
+
const res = await this._get('/v1/screenshot', { url, ...opts });
|
|
85
|
+
return Buffer.from(await res.arrayBuffer());
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Extract metadata from a URL (OG tags, title, favicon, canonical, etc.)
|
|
90
|
+
* @param {string} url
|
|
91
|
+
* @returns {Promise<object>}
|
|
92
|
+
*/
|
|
93
|
+
async metadata(url) {
|
|
94
|
+
const res = await this._get('/v1/metadata', { url });
|
|
95
|
+
return res.json();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Full page analysis — CTAs, nav, tech stack, word count, load time.
|
|
100
|
+
* @param {string} url
|
|
101
|
+
* @param {object} [opts]
|
|
102
|
+
* @param {boolean} [opts.screenshot=false] - Include base64 screenshot in response
|
|
103
|
+
* @returns {Promise<object>}
|
|
104
|
+
*/
|
|
105
|
+
async analyze(url, opts = {}) {
|
|
106
|
+
const res = await this._get('/v1/analyze', { url, ...opts });
|
|
107
|
+
return res.json();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Convert any URL to a PDF.
|
|
112
|
+
* @param {string} url
|
|
113
|
+
* @param {object} [opts]
|
|
114
|
+
* @param {'A4'|'A3'|'A5'|'Letter'|'Legal'|'Tabloid'} [opts.format='A4']
|
|
115
|
+
* @param {boolean} [opts.landscape=false]
|
|
116
|
+
* @param {number} [opts.margin_top=20]
|
|
117
|
+
* @param {number} [opts.margin_bottom=20]
|
|
118
|
+
* @param {number} [opts.margin_left=20]
|
|
119
|
+
* @param {number} [opts.margin_right=20]
|
|
120
|
+
* @param {boolean} [opts.print_background=true]
|
|
121
|
+
* @param {number} [opts.scale=1]
|
|
122
|
+
* @returns {Promise<Buffer>} Raw PDF bytes
|
|
123
|
+
*/
|
|
124
|
+
async pdf(url, opts = {}) {
|
|
125
|
+
const res = await this._get('/v1/pdf', { url, ...opts });
|
|
126
|
+
return Buffer.from(await res.arrayBuffer());
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Render raw HTML to a pixel-perfect image.
|
|
131
|
+
* @param {string} html - Full HTML string
|
|
132
|
+
* @param {object} [opts]
|
|
133
|
+
* @param {number} [opts.width=1200]
|
|
134
|
+
* @param {number} [opts.height=630]
|
|
135
|
+
* @param {'png'|'jpeg'|'webp'} [opts.format='png']
|
|
136
|
+
* @returns {Promise<Buffer>} Raw image bytes
|
|
137
|
+
*/
|
|
138
|
+
async render(html, opts = {}) {
|
|
139
|
+
const res = await this._post('/v1/render', { html, width: 1200, height: 630, format: 'png', ...opts });
|
|
140
|
+
return Buffer.from(await res.arrayBuffer());
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Process multiple URLs in parallel.
|
|
145
|
+
* @param {string[]} urls
|
|
146
|
+
* @param {'screenshot'|'metadata'|'analyze'} [endpoint='screenshot']
|
|
147
|
+
* @param {object} [params] - Extra params forwarded to each URL call
|
|
148
|
+
* @returns {Promise<Array>} Array of per-URL results
|
|
149
|
+
*/
|
|
150
|
+
async batch(urls, endpoint = 'screenshot', params = {}) {
|
|
151
|
+
const res = await this._post('/v1/batch', { urls, endpoint, params });
|
|
152
|
+
const data = await res.json();
|
|
153
|
+
return data.results ?? [];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
module.exports = SnapAPI;
|
|
158
|
+
module.exports.SnapAPI = SnapAPI;
|
|
159
|
+
module.exports.SnapAPIError = SnapAPIError;
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "snapapi-client",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Official JavaScript SDK for the SnapAPI web intelligence API — screenshots, metadata, PDFs, page analysis, HTML rendering, and batch processing",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node test.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"screenshot", "screenshot-api", "web-scraping", "puppeteer-alternative",
|
|
14
|
+
"playwright-alternative", "headless-browser", "metadata", "pdf",
|
|
15
|
+
"page-analysis", "html-to-image", "og-image", "snapapi"
|
|
16
|
+
],
|
|
17
|
+
"author": "SnapAPI <hello@snapapi.tech>",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/Boehner/snapapi-js"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://snapapi.tech",
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18.0.0"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"index.js",
|
|
29
|
+
"README.md"
|
|
30
|
+
]
|
|
31
|
+
}
|