@shotapi/sdk 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 +265 -0
- package/dist/index.d.mts +262 -0
- package/dist/index.d.ts +262 -0
- package/dist/index.js +245 -0
- package/dist/index.mjs +209 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# ShotAPI SDK
|
|
2
|
+
|
|
3
|
+
Official Node.js SDK for [ShotAPI](https://shotapi.dev) - Screenshot API for Developers.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @shotapi/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import ShotAPI from '@shotapi/sdk';
|
|
15
|
+
|
|
16
|
+
const client = new ShotAPI({ apiKey: 'your-api-key' });
|
|
17
|
+
|
|
18
|
+
// Capture a screenshot
|
|
19
|
+
const result = await client.screenshot({
|
|
20
|
+
url: 'https://example.com'
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
console.log(result.url); // URL to the screenshot
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- Full TypeScript support
|
|
29
|
+
- Automatic retries with exponential backoff
|
|
30
|
+
- Device presets (iPhone, iPad, Android, etc.)
|
|
31
|
+
- Batch screenshot capture
|
|
32
|
+
- Save directly to file
|
|
33
|
+
- Get screenshot as Buffer
|
|
34
|
+
|
|
35
|
+
## Usage Examples
|
|
36
|
+
|
|
37
|
+
### Basic Screenshot
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
const result = await client.screenshot({
|
|
41
|
+
url: 'https://example.com',
|
|
42
|
+
width: 1280,
|
|
43
|
+
height: 720
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Full Page Screenshot
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
const result = await client.screenshot({
|
|
51
|
+
url: 'https://example.com',
|
|
52
|
+
fullPage: true
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Device Preset
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// Use iPhone 14 Pro dimensions
|
|
60
|
+
const result = await client.screenshot({
|
|
61
|
+
url: 'https://example.com',
|
|
62
|
+
device: 'iphone-14-pro'
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Available presets:
|
|
66
|
+
// desktop, laptop, tablet, mobile,
|
|
67
|
+
// iphone-14, iphone-14-pro, iphone-14-pro-max,
|
|
68
|
+
// ipad, ipad-pro, galaxy-s23, pixel-7
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Different Formats
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// JPEG with quality
|
|
75
|
+
const jpeg = await client.screenshot({
|
|
76
|
+
url: 'https://example.com',
|
|
77
|
+
format: 'jpeg',
|
|
78
|
+
quality: 90
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// WebP
|
|
82
|
+
const webp = await client.screenshot({
|
|
83
|
+
url: 'https://example.com',
|
|
84
|
+
format: 'webp',
|
|
85
|
+
quality: 85
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// PDF
|
|
89
|
+
const pdf = await client.screenshot({
|
|
90
|
+
url: 'https://example.com',
|
|
91
|
+
format: 'pdf',
|
|
92
|
+
fullPage: true
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Wait for Content
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// Wait for a specific element
|
|
100
|
+
const result = await client.screenshot({
|
|
101
|
+
url: 'https://example.com',
|
|
102
|
+
waitForSelector: '.main-content'
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Add delay before capture
|
|
106
|
+
const result2 = await client.screenshot({
|
|
107
|
+
url: 'https://example.com',
|
|
108
|
+
delay: 2000 // 2 seconds
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Premium Features
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
// Element screenshot
|
|
116
|
+
const element = await client.screenshot({
|
|
117
|
+
url: 'https://example.com',
|
|
118
|
+
selector: '#hero-section'
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Inject custom CSS
|
|
122
|
+
const styled = await client.screenshot({
|
|
123
|
+
url: 'https://example.com',
|
|
124
|
+
css: 'body { background: #f0f0f0; }'
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Execute JavaScript before capture
|
|
128
|
+
const dynamic = await client.screenshot({
|
|
129
|
+
url: 'https://example.com',
|
|
130
|
+
js: 'document.querySelector(".popup").remove();'
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Block ads
|
|
134
|
+
const clean = await client.screenshot({
|
|
135
|
+
url: 'https://example.com',
|
|
136
|
+
blockAds: true,
|
|
137
|
+
hideCookieBanners: true
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Generate thumbnail
|
|
141
|
+
const thumb = await client.screenshot({
|
|
142
|
+
url: 'https://example.com',
|
|
143
|
+
thumbnailWidth: 300
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Save to File
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
await client.screenshotToFile(
|
|
151
|
+
{ url: 'https://example.com' },
|
|
152
|
+
'./screenshot.png'
|
|
153
|
+
);
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Get as Buffer
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
const buffer = await client.screenshotBuffer({
|
|
160
|
+
url: 'https://example.com'
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Use buffer (e.g., upload to S3, send in response, etc.)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Batch Screenshots
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
const urls = [
|
|
170
|
+
'https://example.com',
|
|
171
|
+
'https://google.com',
|
|
172
|
+
'https://github.com'
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
const results = await client.batch(urls, {
|
|
176
|
+
width: 1280,
|
|
177
|
+
height: 720
|
|
178
|
+
});
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Configuration
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
const client = new ShotAPI({
|
|
185
|
+
apiKey: 'your-api-key', // Required
|
|
186
|
+
baseUrl: 'https://shotapi.dev', // Optional (default)
|
|
187
|
+
timeout: 30000, // Request timeout in ms (default: 30000)
|
|
188
|
+
retries: 2 // Retry attempts (default: 2)
|
|
189
|
+
});
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Error Handling
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import ShotAPI, { ShotAPIException } from '@shotapi/sdk';
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
const result = await client.screenshot({
|
|
199
|
+
url: 'https://example.com'
|
|
200
|
+
});
|
|
201
|
+
} catch (error) {
|
|
202
|
+
if (error instanceof ShotAPIException) {
|
|
203
|
+
console.error('API Error:', error.message);
|
|
204
|
+
console.error('Error Code:', error.code);
|
|
205
|
+
console.error('Status Code:', error.statusCode);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## TypeScript
|
|
211
|
+
|
|
212
|
+
The SDK is written in TypeScript and includes full type definitions:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
import ShotAPI, {
|
|
216
|
+
ScreenshotOptions,
|
|
217
|
+
ScreenshotResponse,
|
|
218
|
+
DevicePreset,
|
|
219
|
+
ImageFormat,
|
|
220
|
+
ShotAPIConfig
|
|
221
|
+
} from '@shotapi/sdk';
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Response Format
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
interface ScreenshotResponse {
|
|
228
|
+
url: string; // URL to the screenshot
|
|
229
|
+
thumbnailUrl?: string; // Thumbnail URL (if requested)
|
|
230
|
+
metadata: {
|
|
231
|
+
width: number;
|
|
232
|
+
height: number;
|
|
233
|
+
format: string;
|
|
234
|
+
size: number; // File size in bytes
|
|
235
|
+
};
|
|
236
|
+
creditsUsed: number;
|
|
237
|
+
creditsRemaining: number;
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Device Presets
|
|
242
|
+
|
|
243
|
+
| Preset | Width | Height | Mobile | Scale |
|
|
244
|
+
|--------|-------|--------|--------|-------|
|
|
245
|
+
| desktop | 1920 | 1080 | No | 1 |
|
|
246
|
+
| laptop | 1366 | 768 | No | 1 |
|
|
247
|
+
| tablet | 768 | 1024 | Yes | 2 |
|
|
248
|
+
| mobile | 375 | 812 | Yes | 3 |
|
|
249
|
+
| iphone-14 | 390 | 844 | Yes | 3 |
|
|
250
|
+
| iphone-14-pro | 393 | 852 | Yes | 3 |
|
|
251
|
+
| iphone-14-pro-max | 430 | 932 | Yes | 3 |
|
|
252
|
+
| ipad | 810 | 1080 | Yes | 2 |
|
|
253
|
+
| ipad-pro | 1024 | 1366 | Yes | 2 |
|
|
254
|
+
| galaxy-s23 | 360 | 780 | Yes | 3 |
|
|
255
|
+
| pixel-7 | 412 | 915 | Yes | 2.625 |
|
|
256
|
+
|
|
257
|
+
## License
|
|
258
|
+
|
|
259
|
+
MIT
|
|
260
|
+
|
|
261
|
+
## Links
|
|
262
|
+
|
|
263
|
+
- [Website](https://shotapi.dev)
|
|
264
|
+
- [Documentation](https://shotapi.dev/docs)
|
|
265
|
+
- [API Reference](https://shotapi.dev/docs/api)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device presets for responsive screenshots
|
|
3
|
+
*/
|
|
4
|
+
type DevicePreset = 'desktop' | 'laptop' | 'tablet' | 'mobile' | 'iphone-14' | 'iphone-14-pro' | 'iphone-14-pro-max' | 'ipad' | 'ipad-pro' | 'galaxy-s23' | 'pixel-7';
|
|
5
|
+
/**
|
|
6
|
+
* Output format for screenshots
|
|
7
|
+
*/
|
|
8
|
+
type ImageFormat = 'png' | 'jpeg' | 'webp' | 'pdf';
|
|
9
|
+
/**
|
|
10
|
+
* Screenshot options
|
|
11
|
+
*/
|
|
12
|
+
interface ScreenshotOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Target URL to capture
|
|
15
|
+
*/
|
|
16
|
+
url: string;
|
|
17
|
+
/**
|
|
18
|
+
* Viewport width in pixels (default: 1280)
|
|
19
|
+
*/
|
|
20
|
+
width?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Viewport height in pixels (default: 720)
|
|
23
|
+
*/
|
|
24
|
+
height?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Use a device preset instead of custom width/height
|
|
27
|
+
*/
|
|
28
|
+
device?: DevicePreset;
|
|
29
|
+
/**
|
|
30
|
+
* Whether to capture full page (default: false)
|
|
31
|
+
*/
|
|
32
|
+
fullPage?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Output format (default: 'png')
|
|
35
|
+
*/
|
|
36
|
+
format?: ImageFormat;
|
|
37
|
+
/**
|
|
38
|
+
* Image quality 1-100 (only for jpeg/webp, default: 80)
|
|
39
|
+
*/
|
|
40
|
+
quality?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Device scale factor (default: 1)
|
|
43
|
+
*/
|
|
44
|
+
scale?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Delay in milliseconds before capture (default: 0)
|
|
47
|
+
*/
|
|
48
|
+
delay?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Wait for a specific CSS selector to appear
|
|
51
|
+
*/
|
|
52
|
+
waitForSelector?: string;
|
|
53
|
+
/**
|
|
54
|
+
* CSS selector to capture (element screenshot)
|
|
55
|
+
* Premium feature
|
|
56
|
+
*/
|
|
57
|
+
selector?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Custom CSS to inject before capture
|
|
60
|
+
* Premium feature
|
|
61
|
+
*/
|
|
62
|
+
css?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Custom JavaScript to execute before capture
|
|
65
|
+
* Premium feature
|
|
66
|
+
*/
|
|
67
|
+
js?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Block ads and trackers (default: false)
|
|
70
|
+
* Premium feature
|
|
71
|
+
*/
|
|
72
|
+
blockAds?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Hide cookie banners (default: false)
|
|
75
|
+
* Premium feature
|
|
76
|
+
*/
|
|
77
|
+
hideCookieBanners?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Dark mode (default: false)
|
|
80
|
+
*/
|
|
81
|
+
darkMode?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Emulate mobile device (default: false)
|
|
84
|
+
*/
|
|
85
|
+
mobile?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Generate thumbnail with specified width
|
|
88
|
+
* Premium feature
|
|
89
|
+
*/
|
|
90
|
+
thumbnailWidth?: number;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Screenshot response
|
|
94
|
+
*/
|
|
95
|
+
interface ScreenshotResponse {
|
|
96
|
+
/**
|
|
97
|
+
* URL to the captured screenshot
|
|
98
|
+
*/
|
|
99
|
+
url: string;
|
|
100
|
+
/**
|
|
101
|
+
* Thumbnail URL (if thumbnailWidth was specified)
|
|
102
|
+
*/
|
|
103
|
+
thumbnailUrl?: string;
|
|
104
|
+
/**
|
|
105
|
+
* Screenshot metadata
|
|
106
|
+
*/
|
|
107
|
+
metadata: {
|
|
108
|
+
width: number;
|
|
109
|
+
height: number;
|
|
110
|
+
format: ImageFormat;
|
|
111
|
+
size: number;
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Credits used for this request
|
|
115
|
+
*/
|
|
116
|
+
creditsUsed: number;
|
|
117
|
+
/**
|
|
118
|
+
* Remaining credits
|
|
119
|
+
*/
|
|
120
|
+
creditsRemaining: number;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* API Error response
|
|
124
|
+
*/
|
|
125
|
+
interface ShotAPIError {
|
|
126
|
+
error: string;
|
|
127
|
+
code?: string;
|
|
128
|
+
details?: string;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* SDK Configuration
|
|
132
|
+
*/
|
|
133
|
+
interface ShotAPIConfig {
|
|
134
|
+
/**
|
|
135
|
+
* Your ShotAPI API key
|
|
136
|
+
*/
|
|
137
|
+
apiKey: string;
|
|
138
|
+
/**
|
|
139
|
+
* Base URL for the API (default: https://shotapi.dev)
|
|
140
|
+
*/
|
|
141
|
+
baseUrl?: string;
|
|
142
|
+
/**
|
|
143
|
+
* Request timeout in milliseconds (default: 30000)
|
|
144
|
+
*/
|
|
145
|
+
timeout?: number;
|
|
146
|
+
/**
|
|
147
|
+
* Number of retry attempts on failure (default: 2)
|
|
148
|
+
*/
|
|
149
|
+
retries?: number;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* ShotAPI Error class
|
|
154
|
+
*/
|
|
155
|
+
declare class ShotAPIException extends Error {
|
|
156
|
+
readonly code?: string;
|
|
157
|
+
readonly details?: string;
|
|
158
|
+
readonly statusCode?: number;
|
|
159
|
+
constructor(message: string, code?: string, details?: string, statusCode?: number);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* ShotAPI - Screenshot API SDK
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```typescript
|
|
166
|
+
* import ShotAPI from 'shotapi';
|
|
167
|
+
*
|
|
168
|
+
* const client = new ShotAPI({ apiKey: 'your-api-key' });
|
|
169
|
+
*
|
|
170
|
+
* // Basic screenshot
|
|
171
|
+
* const result = await client.screenshot({ url: 'https://example.com' });
|
|
172
|
+
* console.log(result.url);
|
|
173
|
+
*
|
|
174
|
+
* // Full page screenshot with custom options
|
|
175
|
+
* const result2 = await client.screenshot({
|
|
176
|
+
* url: 'https://example.com',
|
|
177
|
+
* fullPage: true,
|
|
178
|
+
* format: 'jpeg',
|
|
179
|
+
* quality: 90,
|
|
180
|
+
* });
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
declare class ShotAPI {
|
|
184
|
+
private readonly apiKey;
|
|
185
|
+
private readonly baseUrl;
|
|
186
|
+
private readonly timeout;
|
|
187
|
+
private readonly retries;
|
|
188
|
+
constructor(config: ShotAPIConfig);
|
|
189
|
+
/**
|
|
190
|
+
* Capture a screenshot of a URL
|
|
191
|
+
*
|
|
192
|
+
* @param options - Screenshot options
|
|
193
|
+
* @returns Screenshot response with URL and metadata
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```typescript
|
|
197
|
+
* const result = await client.screenshot({
|
|
198
|
+
* url: 'https://example.com',
|
|
199
|
+
* width: 1280,
|
|
200
|
+
* height: 720,
|
|
201
|
+
* fullPage: true,
|
|
202
|
+
* });
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
205
|
+
screenshot(options: ScreenshotOptions): Promise<ScreenshotResponse>;
|
|
206
|
+
/**
|
|
207
|
+
* Capture a screenshot and return as a Buffer
|
|
208
|
+
*
|
|
209
|
+
* @param options - Screenshot options
|
|
210
|
+
* @returns Screenshot as a Buffer
|
|
211
|
+
*/
|
|
212
|
+
screenshotBuffer(options: ScreenshotOptions): Promise<Buffer>;
|
|
213
|
+
/**
|
|
214
|
+
* Capture a screenshot and save to a file
|
|
215
|
+
*
|
|
216
|
+
* @param options - Screenshot options
|
|
217
|
+
* @param filePath - Path to save the screenshot
|
|
218
|
+
*/
|
|
219
|
+
screenshotToFile(options: ScreenshotOptions, filePath: string): Promise<ScreenshotResponse>;
|
|
220
|
+
/**
|
|
221
|
+
* Capture multiple screenshots in batch
|
|
222
|
+
*
|
|
223
|
+
* @param urls - Array of URLs to capture
|
|
224
|
+
* @param options - Common options for all screenshots
|
|
225
|
+
* @returns Array of screenshot responses
|
|
226
|
+
*/
|
|
227
|
+
batch(urls: string[], options?: Omit<ScreenshotOptions, 'url'>): Promise<ScreenshotResponse[]>;
|
|
228
|
+
/**
|
|
229
|
+
* Get device preset dimensions
|
|
230
|
+
*
|
|
231
|
+
* @param device - Device preset name
|
|
232
|
+
* @returns Device dimensions
|
|
233
|
+
*/
|
|
234
|
+
static getDevicePreset(device: DevicePreset): {
|
|
235
|
+
width: number;
|
|
236
|
+
height: number;
|
|
237
|
+
mobile: boolean;
|
|
238
|
+
scale: number;
|
|
239
|
+
};
|
|
240
|
+
/**
|
|
241
|
+
* List all available device presets
|
|
242
|
+
*/
|
|
243
|
+
static get devicePresets(): DevicePreset[];
|
|
244
|
+
/**
|
|
245
|
+
* Build URL parameters from options
|
|
246
|
+
*/
|
|
247
|
+
private buildParams;
|
|
248
|
+
/**
|
|
249
|
+
* Make an API request with retries
|
|
250
|
+
*/
|
|
251
|
+
private request;
|
|
252
|
+
/**
|
|
253
|
+
* Fetch a URL as Buffer
|
|
254
|
+
*/
|
|
255
|
+
private fetchBuffer;
|
|
256
|
+
/**
|
|
257
|
+
* Sleep for specified milliseconds
|
|
258
|
+
*/
|
|
259
|
+
private sleep;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export { type DevicePreset, type ImageFormat, type ScreenshotOptions, type ScreenshotResponse, ShotAPI, type ShotAPIConfig, type ShotAPIError, ShotAPIException, ShotAPI as default };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device presets for responsive screenshots
|
|
3
|
+
*/
|
|
4
|
+
type DevicePreset = 'desktop' | 'laptop' | 'tablet' | 'mobile' | 'iphone-14' | 'iphone-14-pro' | 'iphone-14-pro-max' | 'ipad' | 'ipad-pro' | 'galaxy-s23' | 'pixel-7';
|
|
5
|
+
/**
|
|
6
|
+
* Output format for screenshots
|
|
7
|
+
*/
|
|
8
|
+
type ImageFormat = 'png' | 'jpeg' | 'webp' | 'pdf';
|
|
9
|
+
/**
|
|
10
|
+
* Screenshot options
|
|
11
|
+
*/
|
|
12
|
+
interface ScreenshotOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Target URL to capture
|
|
15
|
+
*/
|
|
16
|
+
url: string;
|
|
17
|
+
/**
|
|
18
|
+
* Viewport width in pixels (default: 1280)
|
|
19
|
+
*/
|
|
20
|
+
width?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Viewport height in pixels (default: 720)
|
|
23
|
+
*/
|
|
24
|
+
height?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Use a device preset instead of custom width/height
|
|
27
|
+
*/
|
|
28
|
+
device?: DevicePreset;
|
|
29
|
+
/**
|
|
30
|
+
* Whether to capture full page (default: false)
|
|
31
|
+
*/
|
|
32
|
+
fullPage?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Output format (default: 'png')
|
|
35
|
+
*/
|
|
36
|
+
format?: ImageFormat;
|
|
37
|
+
/**
|
|
38
|
+
* Image quality 1-100 (only for jpeg/webp, default: 80)
|
|
39
|
+
*/
|
|
40
|
+
quality?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Device scale factor (default: 1)
|
|
43
|
+
*/
|
|
44
|
+
scale?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Delay in milliseconds before capture (default: 0)
|
|
47
|
+
*/
|
|
48
|
+
delay?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Wait for a specific CSS selector to appear
|
|
51
|
+
*/
|
|
52
|
+
waitForSelector?: string;
|
|
53
|
+
/**
|
|
54
|
+
* CSS selector to capture (element screenshot)
|
|
55
|
+
* Premium feature
|
|
56
|
+
*/
|
|
57
|
+
selector?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Custom CSS to inject before capture
|
|
60
|
+
* Premium feature
|
|
61
|
+
*/
|
|
62
|
+
css?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Custom JavaScript to execute before capture
|
|
65
|
+
* Premium feature
|
|
66
|
+
*/
|
|
67
|
+
js?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Block ads and trackers (default: false)
|
|
70
|
+
* Premium feature
|
|
71
|
+
*/
|
|
72
|
+
blockAds?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Hide cookie banners (default: false)
|
|
75
|
+
* Premium feature
|
|
76
|
+
*/
|
|
77
|
+
hideCookieBanners?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Dark mode (default: false)
|
|
80
|
+
*/
|
|
81
|
+
darkMode?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Emulate mobile device (default: false)
|
|
84
|
+
*/
|
|
85
|
+
mobile?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Generate thumbnail with specified width
|
|
88
|
+
* Premium feature
|
|
89
|
+
*/
|
|
90
|
+
thumbnailWidth?: number;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Screenshot response
|
|
94
|
+
*/
|
|
95
|
+
interface ScreenshotResponse {
|
|
96
|
+
/**
|
|
97
|
+
* URL to the captured screenshot
|
|
98
|
+
*/
|
|
99
|
+
url: string;
|
|
100
|
+
/**
|
|
101
|
+
* Thumbnail URL (if thumbnailWidth was specified)
|
|
102
|
+
*/
|
|
103
|
+
thumbnailUrl?: string;
|
|
104
|
+
/**
|
|
105
|
+
* Screenshot metadata
|
|
106
|
+
*/
|
|
107
|
+
metadata: {
|
|
108
|
+
width: number;
|
|
109
|
+
height: number;
|
|
110
|
+
format: ImageFormat;
|
|
111
|
+
size: number;
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Credits used for this request
|
|
115
|
+
*/
|
|
116
|
+
creditsUsed: number;
|
|
117
|
+
/**
|
|
118
|
+
* Remaining credits
|
|
119
|
+
*/
|
|
120
|
+
creditsRemaining: number;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* API Error response
|
|
124
|
+
*/
|
|
125
|
+
interface ShotAPIError {
|
|
126
|
+
error: string;
|
|
127
|
+
code?: string;
|
|
128
|
+
details?: string;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* SDK Configuration
|
|
132
|
+
*/
|
|
133
|
+
interface ShotAPIConfig {
|
|
134
|
+
/**
|
|
135
|
+
* Your ShotAPI API key
|
|
136
|
+
*/
|
|
137
|
+
apiKey: string;
|
|
138
|
+
/**
|
|
139
|
+
* Base URL for the API (default: https://shotapi.dev)
|
|
140
|
+
*/
|
|
141
|
+
baseUrl?: string;
|
|
142
|
+
/**
|
|
143
|
+
* Request timeout in milliseconds (default: 30000)
|
|
144
|
+
*/
|
|
145
|
+
timeout?: number;
|
|
146
|
+
/**
|
|
147
|
+
* Number of retry attempts on failure (default: 2)
|
|
148
|
+
*/
|
|
149
|
+
retries?: number;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* ShotAPI Error class
|
|
154
|
+
*/
|
|
155
|
+
declare class ShotAPIException extends Error {
|
|
156
|
+
readonly code?: string;
|
|
157
|
+
readonly details?: string;
|
|
158
|
+
readonly statusCode?: number;
|
|
159
|
+
constructor(message: string, code?: string, details?: string, statusCode?: number);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* ShotAPI - Screenshot API SDK
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```typescript
|
|
166
|
+
* import ShotAPI from 'shotapi';
|
|
167
|
+
*
|
|
168
|
+
* const client = new ShotAPI({ apiKey: 'your-api-key' });
|
|
169
|
+
*
|
|
170
|
+
* // Basic screenshot
|
|
171
|
+
* const result = await client.screenshot({ url: 'https://example.com' });
|
|
172
|
+
* console.log(result.url);
|
|
173
|
+
*
|
|
174
|
+
* // Full page screenshot with custom options
|
|
175
|
+
* const result2 = await client.screenshot({
|
|
176
|
+
* url: 'https://example.com',
|
|
177
|
+
* fullPage: true,
|
|
178
|
+
* format: 'jpeg',
|
|
179
|
+
* quality: 90,
|
|
180
|
+
* });
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
declare class ShotAPI {
|
|
184
|
+
private readonly apiKey;
|
|
185
|
+
private readonly baseUrl;
|
|
186
|
+
private readonly timeout;
|
|
187
|
+
private readonly retries;
|
|
188
|
+
constructor(config: ShotAPIConfig);
|
|
189
|
+
/**
|
|
190
|
+
* Capture a screenshot of a URL
|
|
191
|
+
*
|
|
192
|
+
* @param options - Screenshot options
|
|
193
|
+
* @returns Screenshot response with URL and metadata
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```typescript
|
|
197
|
+
* const result = await client.screenshot({
|
|
198
|
+
* url: 'https://example.com',
|
|
199
|
+
* width: 1280,
|
|
200
|
+
* height: 720,
|
|
201
|
+
* fullPage: true,
|
|
202
|
+
* });
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
205
|
+
screenshot(options: ScreenshotOptions): Promise<ScreenshotResponse>;
|
|
206
|
+
/**
|
|
207
|
+
* Capture a screenshot and return as a Buffer
|
|
208
|
+
*
|
|
209
|
+
* @param options - Screenshot options
|
|
210
|
+
* @returns Screenshot as a Buffer
|
|
211
|
+
*/
|
|
212
|
+
screenshotBuffer(options: ScreenshotOptions): Promise<Buffer>;
|
|
213
|
+
/**
|
|
214
|
+
* Capture a screenshot and save to a file
|
|
215
|
+
*
|
|
216
|
+
* @param options - Screenshot options
|
|
217
|
+
* @param filePath - Path to save the screenshot
|
|
218
|
+
*/
|
|
219
|
+
screenshotToFile(options: ScreenshotOptions, filePath: string): Promise<ScreenshotResponse>;
|
|
220
|
+
/**
|
|
221
|
+
* Capture multiple screenshots in batch
|
|
222
|
+
*
|
|
223
|
+
* @param urls - Array of URLs to capture
|
|
224
|
+
* @param options - Common options for all screenshots
|
|
225
|
+
* @returns Array of screenshot responses
|
|
226
|
+
*/
|
|
227
|
+
batch(urls: string[], options?: Omit<ScreenshotOptions, 'url'>): Promise<ScreenshotResponse[]>;
|
|
228
|
+
/**
|
|
229
|
+
* Get device preset dimensions
|
|
230
|
+
*
|
|
231
|
+
* @param device - Device preset name
|
|
232
|
+
* @returns Device dimensions
|
|
233
|
+
*/
|
|
234
|
+
static getDevicePreset(device: DevicePreset): {
|
|
235
|
+
width: number;
|
|
236
|
+
height: number;
|
|
237
|
+
mobile: boolean;
|
|
238
|
+
scale: number;
|
|
239
|
+
};
|
|
240
|
+
/**
|
|
241
|
+
* List all available device presets
|
|
242
|
+
*/
|
|
243
|
+
static get devicePresets(): DevicePreset[];
|
|
244
|
+
/**
|
|
245
|
+
* Build URL parameters from options
|
|
246
|
+
*/
|
|
247
|
+
private buildParams;
|
|
248
|
+
/**
|
|
249
|
+
* Make an API request with retries
|
|
250
|
+
*/
|
|
251
|
+
private request;
|
|
252
|
+
/**
|
|
253
|
+
* Fetch a URL as Buffer
|
|
254
|
+
*/
|
|
255
|
+
private fetchBuffer;
|
|
256
|
+
/**
|
|
257
|
+
* Sleep for specified milliseconds
|
|
258
|
+
*/
|
|
259
|
+
private sleep;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export { type DevicePreset, type ImageFormat, type ScreenshotOptions, type ScreenshotResponse, ShotAPI, type ShotAPIConfig, type ShotAPIError, ShotAPIException, ShotAPI as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
ShotAPI: () => ShotAPI,
|
|
34
|
+
ShotAPIException: () => ShotAPIException,
|
|
35
|
+
default: () => index_default
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(index_exports);
|
|
38
|
+
var ShotAPIException = class extends Error {
|
|
39
|
+
constructor(message, code, details, statusCode) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = "ShotAPIException";
|
|
42
|
+
this.code = code;
|
|
43
|
+
this.details = details;
|
|
44
|
+
this.statusCode = statusCode;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var DEVICE_PRESETS = {
|
|
48
|
+
desktop: { width: 1920, height: 1080, mobile: false, scale: 1 },
|
|
49
|
+
laptop: { width: 1366, height: 768, mobile: false, scale: 1 },
|
|
50
|
+
tablet: { width: 768, height: 1024, mobile: true, scale: 2 },
|
|
51
|
+
mobile: { width: 375, height: 812, mobile: true, scale: 3 },
|
|
52
|
+
"iphone-14": { width: 390, height: 844, mobile: true, scale: 3 },
|
|
53
|
+
"iphone-14-pro": { width: 393, height: 852, mobile: true, scale: 3 },
|
|
54
|
+
"iphone-14-pro-max": { width: 430, height: 932, mobile: true, scale: 3 },
|
|
55
|
+
"ipad": { width: 810, height: 1080, mobile: true, scale: 2 },
|
|
56
|
+
"ipad-pro": { width: 1024, height: 1366, mobile: true, scale: 2 },
|
|
57
|
+
"galaxy-s23": { width: 360, height: 780, mobile: true, scale: 3 },
|
|
58
|
+
"pixel-7": { width: 412, height: 915, mobile: true, scale: 2.625 }
|
|
59
|
+
};
|
|
60
|
+
var ShotAPI = class {
|
|
61
|
+
constructor(config) {
|
|
62
|
+
if (!config.apiKey) {
|
|
63
|
+
throw new ShotAPIException("API key is required");
|
|
64
|
+
}
|
|
65
|
+
this.apiKey = config.apiKey;
|
|
66
|
+
this.baseUrl = config.baseUrl || "https://shotapi.dev";
|
|
67
|
+
this.timeout = config.timeout || 3e4;
|
|
68
|
+
this.retries = config.retries ?? 2;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Capture a screenshot of a URL
|
|
72
|
+
*
|
|
73
|
+
* @param options - Screenshot options
|
|
74
|
+
* @returns Screenshot response with URL and metadata
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* const result = await client.screenshot({
|
|
79
|
+
* url: 'https://example.com',
|
|
80
|
+
* width: 1280,
|
|
81
|
+
* height: 720,
|
|
82
|
+
* fullPage: true,
|
|
83
|
+
* });
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
async screenshot(options) {
|
|
87
|
+
if (!options.url) {
|
|
88
|
+
throw new ShotAPIException("URL is required");
|
|
89
|
+
}
|
|
90
|
+
const params = this.buildParams(options);
|
|
91
|
+
return this.request("/api/v1/screenshot", params);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Capture a screenshot and return as a Buffer
|
|
95
|
+
*
|
|
96
|
+
* @param options - Screenshot options
|
|
97
|
+
* @returns Screenshot as a Buffer
|
|
98
|
+
*/
|
|
99
|
+
async screenshotBuffer(options) {
|
|
100
|
+
const response = await this.screenshot(options);
|
|
101
|
+
const imageResponse = await fetch(response.url);
|
|
102
|
+
if (!imageResponse.ok) {
|
|
103
|
+
throw new ShotAPIException("Failed to fetch screenshot image");
|
|
104
|
+
}
|
|
105
|
+
const arrayBuffer = await imageResponse.arrayBuffer();
|
|
106
|
+
return Buffer.from(arrayBuffer);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Capture a screenshot and save to a file
|
|
110
|
+
*
|
|
111
|
+
* @param options - Screenshot options
|
|
112
|
+
* @param filePath - Path to save the screenshot
|
|
113
|
+
*/
|
|
114
|
+
async screenshotToFile(options, filePath) {
|
|
115
|
+
const response = await this.screenshot(options);
|
|
116
|
+
const buffer = await this.fetchBuffer(response.url);
|
|
117
|
+
const fs = await import("fs/promises");
|
|
118
|
+
await fs.writeFile(filePath, buffer);
|
|
119
|
+
return response;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Capture multiple screenshots in batch
|
|
123
|
+
*
|
|
124
|
+
* @param urls - Array of URLs to capture
|
|
125
|
+
* @param options - Common options for all screenshots
|
|
126
|
+
* @returns Array of screenshot responses
|
|
127
|
+
*/
|
|
128
|
+
async batch(urls, options) {
|
|
129
|
+
const promises = urls.map(
|
|
130
|
+
(url) => this.screenshot({ ...options, url })
|
|
131
|
+
);
|
|
132
|
+
return Promise.all(promises);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Get device preset dimensions
|
|
136
|
+
*
|
|
137
|
+
* @param device - Device preset name
|
|
138
|
+
* @returns Device dimensions
|
|
139
|
+
*/
|
|
140
|
+
static getDevicePreset(device) {
|
|
141
|
+
return DEVICE_PRESETS[device];
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* List all available device presets
|
|
145
|
+
*/
|
|
146
|
+
static get devicePresets() {
|
|
147
|
+
return Object.keys(DEVICE_PRESETS);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Build URL parameters from options
|
|
151
|
+
*/
|
|
152
|
+
buildParams(options) {
|
|
153
|
+
const params = new URLSearchParams();
|
|
154
|
+
params.set("url", options.url);
|
|
155
|
+
params.set("api_key", this.apiKey);
|
|
156
|
+
if (options.device) {
|
|
157
|
+
const preset = DEVICE_PRESETS[options.device];
|
|
158
|
+
if (preset) {
|
|
159
|
+
params.set("width", preset.width.toString());
|
|
160
|
+
params.set("height", preset.height.toString());
|
|
161
|
+
if (preset.mobile) params.set("mobile", "true");
|
|
162
|
+
if (preset.scale !== 1) params.set("scale", preset.scale.toString());
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
if (options.width) params.set("width", options.width.toString());
|
|
166
|
+
if (options.height) params.set("height", options.height.toString());
|
|
167
|
+
}
|
|
168
|
+
if (options.fullPage) params.set("full_page", "true");
|
|
169
|
+
if (options.format) params.set("format", options.format);
|
|
170
|
+
if (options.quality) params.set("quality", options.quality.toString());
|
|
171
|
+
if (options.scale) params.set("scale", options.scale.toString());
|
|
172
|
+
if (options.delay) params.set("delay", options.delay.toString());
|
|
173
|
+
if (options.waitForSelector) params.set("wait_for", options.waitForSelector);
|
|
174
|
+
if (options.darkMode) params.set("dark_mode", "true");
|
|
175
|
+
if (options.mobile) params.set("mobile", "true");
|
|
176
|
+
if (options.selector) params.set("selector", options.selector);
|
|
177
|
+
if (options.css) params.set("css", options.css);
|
|
178
|
+
if (options.js) params.set("js", options.js);
|
|
179
|
+
if (options.blockAds) params.set("block_ads", "true");
|
|
180
|
+
if (options.hideCookieBanners) params.set("hide_cookie_banners", "true");
|
|
181
|
+
if (options.thumbnailWidth) params.set("thumbnail_width", options.thumbnailWidth.toString());
|
|
182
|
+
return params;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Make an API request with retries
|
|
186
|
+
*/
|
|
187
|
+
async request(endpoint, params) {
|
|
188
|
+
const url = `${this.baseUrl}${endpoint}?${params.toString()}`;
|
|
189
|
+
let lastError = null;
|
|
190
|
+
for (let attempt = 0; attempt <= this.retries; attempt++) {
|
|
191
|
+
try {
|
|
192
|
+
const controller = new AbortController();
|
|
193
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
194
|
+
const response = await fetch(url, {
|
|
195
|
+
method: "GET",
|
|
196
|
+
signal: controller.signal
|
|
197
|
+
});
|
|
198
|
+
clearTimeout(timeoutId);
|
|
199
|
+
const data = await response.json();
|
|
200
|
+
if (!response.ok) {
|
|
201
|
+
const error = data;
|
|
202
|
+
throw new ShotAPIException(
|
|
203
|
+
error.error || "Unknown error",
|
|
204
|
+
error.code,
|
|
205
|
+
error.details,
|
|
206
|
+
response.status
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
return data;
|
|
210
|
+
} catch (error) {
|
|
211
|
+
lastError = error;
|
|
212
|
+
if (error instanceof ShotAPIException && error.statusCode && error.statusCode < 500) {
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
if (attempt < this.retries) {
|
|
216
|
+
await this.sleep(Math.pow(2, attempt) * 1e3);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
throw lastError || new ShotAPIException("Request failed after retries");
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Fetch a URL as Buffer
|
|
224
|
+
*/
|
|
225
|
+
async fetchBuffer(url) {
|
|
226
|
+
const response = await fetch(url);
|
|
227
|
+
if (!response.ok) {
|
|
228
|
+
throw new ShotAPIException("Failed to fetch image");
|
|
229
|
+
}
|
|
230
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
231
|
+
return Buffer.from(arrayBuffer);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Sleep for specified milliseconds
|
|
235
|
+
*/
|
|
236
|
+
sleep(ms) {
|
|
237
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
var index_default = ShotAPI;
|
|
241
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
242
|
+
0 && (module.exports = {
|
|
243
|
+
ShotAPI,
|
|
244
|
+
ShotAPIException
|
|
245
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var ShotAPIException = class extends Error {
|
|
3
|
+
constructor(message, code, details, statusCode) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "ShotAPIException";
|
|
6
|
+
this.code = code;
|
|
7
|
+
this.details = details;
|
|
8
|
+
this.statusCode = statusCode;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var DEVICE_PRESETS = {
|
|
12
|
+
desktop: { width: 1920, height: 1080, mobile: false, scale: 1 },
|
|
13
|
+
laptop: { width: 1366, height: 768, mobile: false, scale: 1 },
|
|
14
|
+
tablet: { width: 768, height: 1024, mobile: true, scale: 2 },
|
|
15
|
+
mobile: { width: 375, height: 812, mobile: true, scale: 3 },
|
|
16
|
+
"iphone-14": { width: 390, height: 844, mobile: true, scale: 3 },
|
|
17
|
+
"iphone-14-pro": { width: 393, height: 852, mobile: true, scale: 3 },
|
|
18
|
+
"iphone-14-pro-max": { width: 430, height: 932, mobile: true, scale: 3 },
|
|
19
|
+
"ipad": { width: 810, height: 1080, mobile: true, scale: 2 },
|
|
20
|
+
"ipad-pro": { width: 1024, height: 1366, mobile: true, scale: 2 },
|
|
21
|
+
"galaxy-s23": { width: 360, height: 780, mobile: true, scale: 3 },
|
|
22
|
+
"pixel-7": { width: 412, height: 915, mobile: true, scale: 2.625 }
|
|
23
|
+
};
|
|
24
|
+
var ShotAPI = class {
|
|
25
|
+
constructor(config) {
|
|
26
|
+
if (!config.apiKey) {
|
|
27
|
+
throw new ShotAPIException("API key is required");
|
|
28
|
+
}
|
|
29
|
+
this.apiKey = config.apiKey;
|
|
30
|
+
this.baseUrl = config.baseUrl || "https://shotapi.dev";
|
|
31
|
+
this.timeout = config.timeout || 3e4;
|
|
32
|
+
this.retries = config.retries ?? 2;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Capture a screenshot of a URL
|
|
36
|
+
*
|
|
37
|
+
* @param options - Screenshot options
|
|
38
|
+
* @returns Screenshot response with URL and metadata
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* const result = await client.screenshot({
|
|
43
|
+
* url: 'https://example.com',
|
|
44
|
+
* width: 1280,
|
|
45
|
+
* height: 720,
|
|
46
|
+
* fullPage: true,
|
|
47
|
+
* });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
async screenshot(options) {
|
|
51
|
+
if (!options.url) {
|
|
52
|
+
throw new ShotAPIException("URL is required");
|
|
53
|
+
}
|
|
54
|
+
const params = this.buildParams(options);
|
|
55
|
+
return this.request("/api/v1/screenshot", params);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Capture a screenshot and return as a Buffer
|
|
59
|
+
*
|
|
60
|
+
* @param options - Screenshot options
|
|
61
|
+
* @returns Screenshot as a Buffer
|
|
62
|
+
*/
|
|
63
|
+
async screenshotBuffer(options) {
|
|
64
|
+
const response = await this.screenshot(options);
|
|
65
|
+
const imageResponse = await fetch(response.url);
|
|
66
|
+
if (!imageResponse.ok) {
|
|
67
|
+
throw new ShotAPIException("Failed to fetch screenshot image");
|
|
68
|
+
}
|
|
69
|
+
const arrayBuffer = await imageResponse.arrayBuffer();
|
|
70
|
+
return Buffer.from(arrayBuffer);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Capture a screenshot and save to a file
|
|
74
|
+
*
|
|
75
|
+
* @param options - Screenshot options
|
|
76
|
+
* @param filePath - Path to save the screenshot
|
|
77
|
+
*/
|
|
78
|
+
async screenshotToFile(options, filePath) {
|
|
79
|
+
const response = await this.screenshot(options);
|
|
80
|
+
const buffer = await this.fetchBuffer(response.url);
|
|
81
|
+
const fs = await import("fs/promises");
|
|
82
|
+
await fs.writeFile(filePath, buffer);
|
|
83
|
+
return response;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Capture multiple screenshots in batch
|
|
87
|
+
*
|
|
88
|
+
* @param urls - Array of URLs to capture
|
|
89
|
+
* @param options - Common options for all screenshots
|
|
90
|
+
* @returns Array of screenshot responses
|
|
91
|
+
*/
|
|
92
|
+
async batch(urls, options) {
|
|
93
|
+
const promises = urls.map(
|
|
94
|
+
(url) => this.screenshot({ ...options, url })
|
|
95
|
+
);
|
|
96
|
+
return Promise.all(promises);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get device preset dimensions
|
|
100
|
+
*
|
|
101
|
+
* @param device - Device preset name
|
|
102
|
+
* @returns Device dimensions
|
|
103
|
+
*/
|
|
104
|
+
static getDevicePreset(device) {
|
|
105
|
+
return DEVICE_PRESETS[device];
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* List all available device presets
|
|
109
|
+
*/
|
|
110
|
+
static get devicePresets() {
|
|
111
|
+
return Object.keys(DEVICE_PRESETS);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Build URL parameters from options
|
|
115
|
+
*/
|
|
116
|
+
buildParams(options) {
|
|
117
|
+
const params = new URLSearchParams();
|
|
118
|
+
params.set("url", options.url);
|
|
119
|
+
params.set("api_key", this.apiKey);
|
|
120
|
+
if (options.device) {
|
|
121
|
+
const preset = DEVICE_PRESETS[options.device];
|
|
122
|
+
if (preset) {
|
|
123
|
+
params.set("width", preset.width.toString());
|
|
124
|
+
params.set("height", preset.height.toString());
|
|
125
|
+
if (preset.mobile) params.set("mobile", "true");
|
|
126
|
+
if (preset.scale !== 1) params.set("scale", preset.scale.toString());
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
if (options.width) params.set("width", options.width.toString());
|
|
130
|
+
if (options.height) params.set("height", options.height.toString());
|
|
131
|
+
}
|
|
132
|
+
if (options.fullPage) params.set("full_page", "true");
|
|
133
|
+
if (options.format) params.set("format", options.format);
|
|
134
|
+
if (options.quality) params.set("quality", options.quality.toString());
|
|
135
|
+
if (options.scale) params.set("scale", options.scale.toString());
|
|
136
|
+
if (options.delay) params.set("delay", options.delay.toString());
|
|
137
|
+
if (options.waitForSelector) params.set("wait_for", options.waitForSelector);
|
|
138
|
+
if (options.darkMode) params.set("dark_mode", "true");
|
|
139
|
+
if (options.mobile) params.set("mobile", "true");
|
|
140
|
+
if (options.selector) params.set("selector", options.selector);
|
|
141
|
+
if (options.css) params.set("css", options.css);
|
|
142
|
+
if (options.js) params.set("js", options.js);
|
|
143
|
+
if (options.blockAds) params.set("block_ads", "true");
|
|
144
|
+
if (options.hideCookieBanners) params.set("hide_cookie_banners", "true");
|
|
145
|
+
if (options.thumbnailWidth) params.set("thumbnail_width", options.thumbnailWidth.toString());
|
|
146
|
+
return params;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Make an API request with retries
|
|
150
|
+
*/
|
|
151
|
+
async request(endpoint, params) {
|
|
152
|
+
const url = `${this.baseUrl}${endpoint}?${params.toString()}`;
|
|
153
|
+
let lastError = null;
|
|
154
|
+
for (let attempt = 0; attempt <= this.retries; attempt++) {
|
|
155
|
+
try {
|
|
156
|
+
const controller = new AbortController();
|
|
157
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
158
|
+
const response = await fetch(url, {
|
|
159
|
+
method: "GET",
|
|
160
|
+
signal: controller.signal
|
|
161
|
+
});
|
|
162
|
+
clearTimeout(timeoutId);
|
|
163
|
+
const data = await response.json();
|
|
164
|
+
if (!response.ok) {
|
|
165
|
+
const error = data;
|
|
166
|
+
throw new ShotAPIException(
|
|
167
|
+
error.error || "Unknown error",
|
|
168
|
+
error.code,
|
|
169
|
+
error.details,
|
|
170
|
+
response.status
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
return data;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
lastError = error;
|
|
176
|
+
if (error instanceof ShotAPIException && error.statusCode && error.statusCode < 500) {
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
if (attempt < this.retries) {
|
|
180
|
+
await this.sleep(Math.pow(2, attempt) * 1e3);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
throw lastError || new ShotAPIException("Request failed after retries");
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Fetch a URL as Buffer
|
|
188
|
+
*/
|
|
189
|
+
async fetchBuffer(url) {
|
|
190
|
+
const response = await fetch(url);
|
|
191
|
+
if (!response.ok) {
|
|
192
|
+
throw new ShotAPIException("Failed to fetch image");
|
|
193
|
+
}
|
|
194
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
195
|
+
return Buffer.from(arrayBuffer);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Sleep for specified milliseconds
|
|
199
|
+
*/
|
|
200
|
+
sleep(ms) {
|
|
201
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
var index_default = ShotAPI;
|
|
205
|
+
export {
|
|
206
|
+
ShotAPI,
|
|
207
|
+
ShotAPIException,
|
|
208
|
+
index_default as default
|
|
209
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shotapi/sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Official Node.js SDK for ShotAPI - Screenshot API for Developers",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
20
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
21
|
+
"test": "vitest",
|
|
22
|
+
"prepublishOnly": "npm run build"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"screenshot",
|
|
26
|
+
"api",
|
|
27
|
+
"web",
|
|
28
|
+
"capture",
|
|
29
|
+
"puppeteer",
|
|
30
|
+
"headless",
|
|
31
|
+
"browser",
|
|
32
|
+
"shotapi"
|
|
33
|
+
],
|
|
34
|
+
"author": "ShotAPI",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/gaurav-aryal/shotapi-sdk"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://shotapi.dev",
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/gaurav-aryal/shotapi-sdk/issues"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^20.10.0",
|
|
46
|
+
"tsup": "^8.0.0",
|
|
47
|
+
"typescript": "^5.3.0",
|
|
48
|
+
"vitest": "^1.0.0"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=18.0.0"
|
|
52
|
+
}
|
|
53
|
+
}
|