webpeel 0.12.0 → 0.12.2

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.
Files changed (148) hide show
  1. package/README.md +82 -9
  2. package/dist/cli.js +97 -6
  3. package/dist/cli.js.map +1 -1
  4. package/dist/core/actions.d.ts +28 -0
  5. package/dist/core/actions.d.ts.map +1 -1
  6. package/dist/core/actions.js +60 -0
  7. package/dist/core/actions.js.map +1 -1
  8. package/dist/core/bm25-filter.d.ts +10 -0
  9. package/dist/core/bm25-filter.d.ts.map +1 -1
  10. package/dist/core/bm25-filter.js +40 -0
  11. package/dist/core/bm25-filter.js.map +1 -1
  12. package/dist/core/content-pruner.d.ts +12 -5
  13. package/dist/core/content-pruner.d.ts.map +1 -1
  14. package/dist/core/content-pruner.js +247 -190
  15. package/dist/core/content-pruner.js.map +1 -1
  16. package/dist/core/research.d.ts +67 -0
  17. package/dist/core/research.d.ts.map +1 -0
  18. package/dist/core/research.js +254 -0
  19. package/dist/core/research.js.map +1 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +37 -3
  22. package/dist/index.js.map +1 -1
  23. package/dist/mcp/server.js +107 -2
  24. package/dist/mcp/server.js.map +1 -1
  25. package/dist/server/app.d.ts +14 -0
  26. package/dist/server/app.d.ts.map +1 -0
  27. package/dist/server/app.js +189 -0
  28. package/dist/server/app.js.map +1 -0
  29. package/dist/server/auth-store.d.ts +28 -0
  30. package/dist/server/auth-store.d.ts.map +1 -0
  31. package/dist/server/auth-store.js +89 -0
  32. package/dist/server/auth-store.js.map +1 -0
  33. package/dist/server/job-queue.d.ts +93 -0
  34. package/dist/server/job-queue.d.ts.map +1 -0
  35. package/dist/server/job-queue.js +144 -0
  36. package/dist/server/job-queue.js.map +1 -0
  37. package/dist/server/middleware/auth.d.ts +28 -0
  38. package/dist/server/middleware/auth.d.ts.map +1 -0
  39. package/dist/server/middleware/auth.js +183 -0
  40. package/dist/server/middleware/auth.js.map +1 -0
  41. package/dist/server/middleware/rate-limit.d.ts +23 -0
  42. package/dist/server/middleware/rate-limit.d.ts.map +1 -0
  43. package/dist/server/middleware/rate-limit.js +126 -0
  44. package/dist/server/middleware/rate-limit.js.map +1 -0
  45. package/dist/server/middleware/url-validator.d.ts +16 -0
  46. package/dist/server/middleware/url-validator.d.ts.map +1 -0
  47. package/dist/server/middleware/url-validator.js +187 -0
  48. package/dist/server/middleware/url-validator.js.map +1 -0
  49. package/dist/server/pg-auth-store.d.ts +129 -0
  50. package/dist/server/pg-auth-store.d.ts.map +1 -0
  51. package/dist/server/pg-auth-store.js +457 -0
  52. package/dist/server/pg-auth-store.js.map +1 -0
  53. package/dist/server/pg-job-queue.d.ts +60 -0
  54. package/dist/server/pg-job-queue.d.ts.map +1 -0
  55. package/dist/server/pg-job-queue.js +365 -0
  56. package/dist/server/pg-job-queue.js.map +1 -0
  57. package/dist/server/premium/domain-intel.d.ts +17 -0
  58. package/dist/server/premium/domain-intel.d.ts.map +1 -0
  59. package/dist/server/premium/domain-intel.js +134 -0
  60. package/dist/server/premium/domain-intel.js.map +1 -0
  61. package/dist/server/premium/index.d.ts +18 -0
  62. package/dist/server/premium/index.d.ts.map +1 -0
  63. package/dist/server/premium/index.js +36 -0
  64. package/dist/server/premium/index.js.map +1 -0
  65. package/dist/server/premium/swr-cache.d.ts +15 -0
  66. package/dist/server/premium/swr-cache.d.ts.map +1 -0
  67. package/dist/server/premium/swr-cache.js +35 -0
  68. package/dist/server/premium/swr-cache.js.map +1 -0
  69. package/dist/server/routes/activity.d.ts +7 -0
  70. package/dist/server/routes/activity.d.ts.map +1 -0
  71. package/dist/server/routes/activity.js +66 -0
  72. package/dist/server/routes/activity.js.map +1 -0
  73. package/dist/server/routes/agent.d.ts +12 -0
  74. package/dist/server/routes/agent.d.ts.map +1 -0
  75. package/dist/server/routes/agent.js +356 -0
  76. package/dist/server/routes/agent.js.map +1 -0
  77. package/dist/server/routes/answer.d.ts +6 -0
  78. package/dist/server/routes/answer.d.ts.map +1 -0
  79. package/dist/server/routes/answer.js +124 -0
  80. package/dist/server/routes/answer.js.map +1 -0
  81. package/dist/server/routes/batch.d.ts +7 -0
  82. package/dist/server/routes/batch.d.ts.map +1 -0
  83. package/dist/server/routes/batch.js +287 -0
  84. package/dist/server/routes/batch.js.map +1 -0
  85. package/dist/server/routes/cli-usage.d.ts +7 -0
  86. package/dist/server/routes/cli-usage.d.ts.map +1 -0
  87. package/dist/server/routes/cli-usage.js +121 -0
  88. package/dist/server/routes/cli-usage.js.map +1 -0
  89. package/dist/server/routes/compat.d.ts +24 -0
  90. package/dist/server/routes/compat.d.ts.map +1 -0
  91. package/dist/server/routes/compat.js +651 -0
  92. package/dist/server/routes/compat.js.map +1 -0
  93. package/dist/server/routes/extract.d.ts +9 -0
  94. package/dist/server/routes/extract.d.ts.map +1 -0
  95. package/dist/server/routes/extract.js +121 -0
  96. package/dist/server/routes/extract.js.map +1 -0
  97. package/dist/server/routes/fetch.d.ts +7 -0
  98. package/dist/server/routes/fetch.d.ts.map +1 -0
  99. package/dist/server/routes/fetch.js +537 -0
  100. package/dist/server/routes/fetch.js.map +1 -0
  101. package/dist/server/routes/health.d.ts +8 -0
  102. package/dist/server/routes/health.d.ts.map +1 -0
  103. package/dist/server/routes/health.js +36 -0
  104. package/dist/server/routes/health.js.map +1 -0
  105. package/dist/server/routes/jobs.d.ts +8 -0
  106. package/dist/server/routes/jobs.d.ts.map +1 -0
  107. package/dist/server/routes/jobs.js +374 -0
  108. package/dist/server/routes/jobs.js.map +1 -0
  109. package/dist/server/routes/mcp.d.ts +16 -0
  110. package/dist/server/routes/mcp.d.ts.map +1 -0
  111. package/dist/server/routes/mcp.js +475 -0
  112. package/dist/server/routes/mcp.js.map +1 -0
  113. package/dist/server/routes/oauth.d.ts +10 -0
  114. package/dist/server/routes/oauth.d.ts.map +1 -0
  115. package/dist/server/routes/oauth.js +296 -0
  116. package/dist/server/routes/oauth.js.map +1 -0
  117. package/dist/server/routes/screenshot.d.ts +10 -0
  118. package/dist/server/routes/screenshot.d.ts.map +1 -0
  119. package/dist/server/routes/screenshot.js +217 -0
  120. package/dist/server/routes/screenshot.js.map +1 -0
  121. package/dist/server/routes/search.d.ts +7 -0
  122. package/dist/server/routes/search.d.ts.map +1 -0
  123. package/dist/server/routes/search.js +287 -0
  124. package/dist/server/routes/search.js.map +1 -0
  125. package/dist/server/routes/stats.d.ts +7 -0
  126. package/dist/server/routes/stats.d.ts.map +1 -0
  127. package/dist/server/routes/stats.js +65 -0
  128. package/dist/server/routes/stats.js.map +1 -0
  129. package/dist/server/routes/stripe.d.ts +9 -0
  130. package/dist/server/routes/stripe.d.ts.map +1 -0
  131. package/dist/server/routes/stripe.js +233 -0
  132. package/dist/server/routes/stripe.js.map +1 -0
  133. package/dist/server/routes/users.d.ts +9 -0
  134. package/dist/server/routes/users.d.ts.map +1 -0
  135. package/dist/server/routes/users.js +954 -0
  136. package/dist/server/routes/users.js.map +1 -0
  137. package/dist/server/routes/webhooks.d.ts +15 -0
  138. package/dist/server/routes/webhooks.d.ts.map +1 -0
  139. package/dist/server/routes/webhooks.js +73 -0
  140. package/dist/server/routes/webhooks.js.map +1 -0
  141. package/dist/server/sentry.d.ts +14 -0
  142. package/dist/server/sentry.d.ts.map +1 -0
  143. package/dist/server/sentry.js +39 -0
  144. package/dist/server/sentry.js.map +1 -0
  145. package/dist/types.d.ts +13 -0
  146. package/dist/types.d.ts.map +1 -1
  147. package/dist/types.js.map +1 -1
  148. package/package.json +3 -2
@@ -0,0 +1,296 @@
1
+ /**
2
+ * OAuth authentication routes
3
+ * Handles OAuth login from Auth.js (GitHub, Google)
4
+ */
5
+ import { Router } from 'express';
6
+ import crypto from 'crypto';
7
+ import jwt from 'jsonwebtoken';
8
+ import pg from 'pg';
9
+ import { PostgresAuthStore } from '../pg-auth-store.js';
10
+ const { Pool } = pg;
11
+ /**
12
+ * Validate email format
13
+ */
14
+ function isValidEmail(email) {
15
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
16
+ return emailRegex.test(email);
17
+ }
18
+ /**
19
+ * Simple in-memory rate limiter for OAuth endpoint
20
+ */
21
+ class OAuthRateLimiter {
22
+ attempts = new Map();
23
+ maxAttempts = 10;
24
+ windowMs = 60000; // 1 minute
25
+ check(identifier) {
26
+ const now = Date.now();
27
+ const attempts = this.attempts.get(identifier) || [];
28
+ // Remove old attempts outside the window
29
+ const recentAttempts = attempts.filter(time => now - time < this.windowMs);
30
+ if (recentAttempts.length >= this.maxAttempts) {
31
+ return false;
32
+ }
33
+ recentAttempts.push(now);
34
+ this.attempts.set(identifier, recentAttempts);
35
+ return true;
36
+ }
37
+ cleanup() {
38
+ const now = Date.now();
39
+ for (const [key, attempts] of this.attempts.entries()) {
40
+ const recentAttempts = attempts.filter(time => now - time < this.windowMs);
41
+ if (recentAttempts.length === 0) {
42
+ this.attempts.delete(key);
43
+ }
44
+ else {
45
+ this.attempts.set(key, recentAttempts);
46
+ }
47
+ }
48
+ }
49
+ }
50
+ const rateLimiter = new OAuthRateLimiter();
51
+ // Clean up rate limiter every 2 minutes
52
+ setInterval(() => {
53
+ rateLimiter.cleanup();
54
+ }, 2 * 60 * 1000);
55
+ /**
56
+ * Create OAuth routes
57
+ */
58
+ export function createOAuthRouter() {
59
+ const router = Router();
60
+ const dbUrl = process.env.DATABASE_URL;
61
+ if (!dbUrl) {
62
+ throw new Error('DATABASE_URL environment variable is required');
63
+ }
64
+ const pool = new Pool({
65
+ connectionString: dbUrl,
66
+ // TLS: enabled when DATABASE_URL contains sslmode=require.
67
+ // Secure by default (rejectUnauthorized: true); set PG_REJECT_UNAUTHORIZED=false
68
+ // only for managed DBs (Render/Neon/Supabase) that use self-signed certs.
69
+ ssl: process.env.DATABASE_URL?.includes('sslmode=require')
70
+ ? { rejectUnauthorized: process.env.PG_REJECT_UNAUTHORIZED !== 'false' }
71
+ : undefined,
72
+ });
73
+ /**
74
+ * Helper: generate a refresh token and store its jti in the database
75
+ */
76
+ async function createRefreshToken(userId, jwtSecret) {
77
+ const jti = crypto.randomUUID();
78
+ const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days
79
+ await pool.query(`INSERT INTO refresh_tokens (id, user_id, expires_at) VALUES ($1, $2, $3)`, [jti, userId, expiresAt]);
80
+ return jwt.sign({ userId, jti }, jwtSecret, { expiresIn: '30d' });
81
+ }
82
+ /**
83
+ * POST /v1/auth/oauth
84
+ * OAuth callback handler - called by Auth.js after successful OAuth flow
85
+ * Auto-creates users if they don't exist
86
+ */
87
+ router.post('/v1/auth/oauth', async (req, res) => {
88
+ try {
89
+ const { provider, accessToken, name, avatar } = req.body;
90
+ // Rate limiting — scoped per-IP per-provider (not global) to prevent DoS.
91
+ // IP extracted from cf-connecting-ip (Cloudflare) > x-forwarded-for (reverse proxy) > req.ip.
92
+ // Limit: 10 attempts per minute per IP+provider combination.
93
+ const clientIp = req.headers['cf-connecting-ip'] || req.headers['x-forwarded-for']?.split(',')[0]?.trim() || req.ip || 'unknown';
94
+ if (!rateLimiter.check(`${clientIp}:${provider || 'unknown'}`)) {
95
+ res.status(429).json({
96
+ error: 'rate_limit_exceeded',
97
+ message: 'Too many OAuth attempts. Please try again in a minute.',
98
+ });
99
+ return;
100
+ }
101
+ // Input validation
102
+ if (!provider || !accessToken) {
103
+ res.status(400).json({
104
+ error: 'missing_fields',
105
+ message: 'provider and accessToken are required',
106
+ });
107
+ return;
108
+ }
109
+ // Validate provider
110
+ if (provider !== 'github' && provider !== 'google') {
111
+ res.status(400).json({
112
+ error: 'invalid_provider',
113
+ message: 'provider must be "github" or "google"',
114
+ });
115
+ return;
116
+ }
117
+ // SECURITY: Verify the OAuth token server-side and extract trusted identity
118
+ let providerId;
119
+ let email;
120
+ if (provider === 'github') {
121
+ // Verify GitHub access token
122
+ const ghRes = await fetch('https://api.github.com/user', {
123
+ headers: {
124
+ Authorization: `Bearer ${accessToken}`,
125
+ Accept: 'application/vnd.github+json',
126
+ },
127
+ });
128
+ if (!ghRes.ok) {
129
+ res.status(401).json({
130
+ error: 'invalid_token',
131
+ message: 'Invalid GitHub access token',
132
+ });
133
+ return;
134
+ }
135
+ const ghUser = await ghRes.json();
136
+ providerId = String(ghUser.id);
137
+ // GitHub may not return email on /user; fetch from /user/emails
138
+ if (ghUser.email) {
139
+ email = ghUser.email;
140
+ }
141
+ else {
142
+ const emailRes = await fetch('https://api.github.com/user/emails', {
143
+ headers: {
144
+ Authorization: `Bearer ${accessToken}`,
145
+ Accept: 'application/vnd.github+json',
146
+ },
147
+ });
148
+ if (emailRes.ok) {
149
+ const emails = await emailRes.json();
150
+ const primary = emails.find(e => e.primary && e.verified);
151
+ email = primary?.email || emails[0]?.email || '';
152
+ }
153
+ else {
154
+ email = '';
155
+ }
156
+ }
157
+ }
158
+ else {
159
+ // Verify Google ID token
160
+ const gRes = await fetch(`https://oauth2.googleapis.com/tokeninfo?id_token=${encodeURIComponent(accessToken)}`);
161
+ if (!gRes.ok) {
162
+ res.status(401).json({
163
+ error: 'invalid_token',
164
+ message: 'Invalid Google token',
165
+ });
166
+ return;
167
+ }
168
+ const gUser = await gRes.json();
169
+ providerId = gUser.sub;
170
+ email = gUser.email || '';
171
+ }
172
+ // Validate email from verified token
173
+ if (!email || !isValidEmail(email)) {
174
+ res.status(400).json({
175
+ error: 'invalid_email',
176
+ message: 'Could not retrieve a valid email from OAuth provider',
177
+ });
178
+ return;
179
+ }
180
+ const client = await pool.connect();
181
+ try {
182
+ await client.query('BEGIN');
183
+ // Check if OAuth account already exists
184
+ const oauthResult = await client.query(`SELECT user_id FROM oauth_accounts
185
+ WHERE provider = $1 AND provider_id = $2`, [provider, providerId]);
186
+ let userId;
187
+ let isNew = false;
188
+ let apiKey;
189
+ if (oauthResult.rows.length > 0) {
190
+ // Existing OAuth account - get user
191
+ userId = oauthResult.rows[0].user_id;
192
+ // Update OAuth account info
193
+ await client.query(`UPDATE oauth_accounts
194
+ SET email = $1, name = $2, avatar_url = $3, updated_at = now()
195
+ WHERE provider = $4 AND provider_id = $5`, [email, name || null, avatar || null, provider, providerId]);
196
+ }
197
+ else {
198
+ // New OAuth account - check if user with this email exists
199
+ const userResult = await client.query('SELECT id FROM users WHERE email = $1', [email]);
200
+ if (userResult.rows.length > 0) {
201
+ // User exists - link OAuth account to existing user
202
+ userId = userResult.rows[0].id;
203
+ // Update user info
204
+ await client.query(`UPDATE users
205
+ SET name = COALESCE($1, name),
206
+ avatar_url = COALESCE($2, avatar_url),
207
+ updated_at = now()
208
+ WHERE id = $3`, [name || null, avatar || null, userId]);
209
+ // Create OAuth account link
210
+ await client.query(`INSERT INTO oauth_accounts
211
+ (user_id, provider, provider_id, email, name, avatar_url)
212
+ VALUES ($1, $2, $3, $4, $5, $6)`, [userId, provider, providerId, email, name || null, avatar || null]);
213
+ }
214
+ else {
215
+ // New user - create account
216
+ const newUserResult = await client.query(`INSERT INTO users
217
+ (email, password_hash, tier, weekly_limit, burst_limit, rate_limit, name, avatar_url)
218
+ VALUES ($1, NULL, 'free', 125, 25, 10, $2, $3)
219
+ RETURNING id`, [email, name || null, avatar || null]);
220
+ userId = newUserResult.rows[0].id;
221
+ isNew = true;
222
+ // Create OAuth account link
223
+ await client.query(`INSERT INTO oauth_accounts
224
+ (user_id, provider, provider_id, email, name, avatar_url)
225
+ VALUES ($1, $2, $3, $4, $5, $6)`, [userId, provider, providerId, email, name || null, avatar || null]);
226
+ // Generate first API key for new user
227
+ apiKey = PostgresAuthStore.generateApiKey();
228
+ const keyHash = crypto.createHash('sha256').update(apiKey).digest('hex');
229
+ const keyPrefix = PostgresAuthStore.getKeyPrefix(apiKey);
230
+ await client.query(`INSERT INTO api_keys (user_id, key_hash, key_prefix, name)
231
+ VALUES ($1, $2, $3, 'Default')`, [userId, keyHash, keyPrefix]);
232
+ }
233
+ }
234
+ // Get user info for response
235
+ const userInfoResult = await client.query('SELECT id, email, tier, name, avatar_url FROM users WHERE id = $1', [userId]);
236
+ const user = userInfoResult.rows[0];
237
+ // Generate JWT
238
+ const jwtSecret = process.env.JWT_SECRET;
239
+ if (!jwtSecret) {
240
+ throw new Error('JWT_SECRET not configured');
241
+ }
242
+ const token = jwt.sign({
243
+ userId: user.id,
244
+ email: user.email,
245
+ tier: user.tier,
246
+ }, jwtSecret, { expiresIn: '1h' });
247
+ await client.query('COMMIT');
248
+ // Generate refresh token (after commit, uses pool not client)
249
+ const refreshToken = await createRefreshToken(user.id, jwtSecret);
250
+ // Response
251
+ const response = {
252
+ user: {
253
+ id: user.id,
254
+ email: user.email,
255
+ tier: user.tier,
256
+ name: user.name,
257
+ avatar: user.avatar_url,
258
+ },
259
+ token,
260
+ refreshToken,
261
+ expiresIn: 3600,
262
+ isNew,
263
+ };
264
+ // Include API key only for new users
265
+ if (isNew && apiKey) {
266
+ response.apiKey = apiKey;
267
+ }
268
+ res.json(response);
269
+ }
270
+ catch (error) {
271
+ await client.query('ROLLBACK');
272
+ throw error;
273
+ }
274
+ finally {
275
+ client.release();
276
+ }
277
+ }
278
+ catch (error) {
279
+ console.error('OAuth error:', error);
280
+ // Handle specific errors
281
+ if (error.code === '23505') { // Unique violation
282
+ res.status(409).json({
283
+ error: 'oauth_conflict',
284
+ message: 'OAuth account already exists',
285
+ });
286
+ return;
287
+ }
288
+ res.status(500).json({
289
+ error: 'oauth_failed',
290
+ message: 'Failed to process OAuth login',
291
+ });
292
+ }
293
+ });
294
+ return router;
295
+ }
296
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../../src/server/routes/oauth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Screenshot endpoint — POST /v1/screenshot
3
+ *
4
+ * Takes a screenshot of a URL and returns base64-encoded image data.
5
+ * Uses the same rate limiting / credit system as the fetch endpoint (1 credit).
6
+ */
7
+ import { Router } from 'express';
8
+ import type { AuthStore } from '../auth-store.js';
9
+ export declare function createScreenshotRouter(authStore: AuthStore): Router;
10
+ //# sourceMappingURL=screenshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../../../src/server/routes/screenshot.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AAEpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAIlD,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,SAAS,GAAG,MAAM,CAwPnE"}
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Screenshot endpoint — POST /v1/screenshot
3
+ *
4
+ * Takes a screenshot of a URL and returns base64-encoded image data.
5
+ * Uses the same rate limiting / credit system as the fetch endpoint (1 credit).
6
+ */
7
+ import { Router } from 'express';
8
+ import { takeScreenshot } from '../../core/screenshot.js';
9
+ import { validateUrlForSSRF, SSRFError } from '../middleware/url-validator.js';
10
+ import { normalizeActions } from '../../core/actions.js';
11
+ export function createScreenshotRouter(authStore) {
12
+ const router = Router();
13
+ router.post('/v1/screenshot', async (req, res) => {
14
+ try {
15
+ const { url, fullPage = false, width, height, format = 'png', quality, waitFor, timeout, actions, headers, cookies, stealth, } = req.body;
16
+ // --- Validate URL --------------------------------------------------
17
+ if (!url || typeof url !== 'string') {
18
+ res.status(400).json({
19
+ error: 'invalid_request',
20
+ message: 'Missing or invalid "url" parameter',
21
+ });
22
+ return;
23
+ }
24
+ if (url.length > 2048) {
25
+ res.status(400).json({
26
+ error: 'invalid_url',
27
+ message: 'URL too long (max 2048 characters)',
28
+ });
29
+ return;
30
+ }
31
+ try {
32
+ const parsed = new URL(url);
33
+ if (!['http:', 'https:'].includes(parsed.protocol)) {
34
+ res.status(400).json({
35
+ error: 'invalid_url',
36
+ message: 'Only HTTP and HTTPS protocols are allowed',
37
+ });
38
+ return;
39
+ }
40
+ }
41
+ catch {
42
+ res.status(400).json({
43
+ error: 'invalid_url',
44
+ message: 'Invalid URL format',
45
+ });
46
+ return;
47
+ }
48
+ try {
49
+ validateUrlForSSRF(url);
50
+ }
51
+ catch (error) {
52
+ if (error instanceof SSRFError) {
53
+ res.status(400).json({
54
+ error: 'ssrf_blocked',
55
+ message: 'Cannot fetch localhost, private networks, or non-HTTP URLs',
56
+ });
57
+ return;
58
+ }
59
+ throw error;
60
+ }
61
+ // --- Validate options -----------------------------------------------
62
+ if (format !== undefined && !['png', 'jpeg', 'jpg'].includes(format)) {
63
+ res.status(400).json({
64
+ error: 'invalid_request',
65
+ message: 'Invalid format: must be "png", "jpeg", or "jpg"',
66
+ });
67
+ return;
68
+ }
69
+ if (width !== undefined && (typeof width !== 'number' || width < 100 || width > 5000)) {
70
+ res.status(400).json({
71
+ error: 'invalid_request',
72
+ message: 'Invalid width: must be between 100 and 5000',
73
+ });
74
+ return;
75
+ }
76
+ if (height !== undefined && (typeof height !== 'number' || height < 100 || height > 5000)) {
77
+ res.status(400).json({
78
+ error: 'invalid_request',
79
+ message: 'Invalid height: must be between 100 and 5000',
80
+ });
81
+ return;
82
+ }
83
+ if (quality !== undefined && (typeof quality !== 'number' || quality < 1 || quality > 100)) {
84
+ res.status(400).json({
85
+ error: 'invalid_request',
86
+ message: 'Invalid quality: must be between 1 and 100',
87
+ });
88
+ return;
89
+ }
90
+ if (waitFor !== undefined && (typeof waitFor !== 'number' || waitFor < 0 || waitFor > 60000)) {
91
+ res.status(400).json({
92
+ error: 'invalid_request',
93
+ message: 'Invalid waitFor: must be between 0 and 60000ms',
94
+ });
95
+ return;
96
+ }
97
+ // Normalize user-provided actions (accepts Firecrawl-style too)
98
+ let normalizedActions;
99
+ if (actions !== undefined) {
100
+ try {
101
+ normalizedActions = normalizeActions(actions);
102
+ }
103
+ catch (e) {
104
+ res.status(400).json({
105
+ error: 'invalid_request',
106
+ message: `Invalid actions: ${e.message}`,
107
+ });
108
+ return;
109
+ }
110
+ }
111
+ // --- Take the screenshot -------------------------------------------
112
+ const startTime = Date.now();
113
+ const result = await takeScreenshot(url, {
114
+ fullPage: fullPage === true,
115
+ width,
116
+ height,
117
+ format,
118
+ quality,
119
+ waitFor,
120
+ timeout: timeout || 30000,
121
+ actions: normalizedActions,
122
+ headers,
123
+ cookies,
124
+ stealth: stealth === true,
125
+ });
126
+ const elapsed = Date.now() - startTime;
127
+ // --- Track usage ---------------------------------------------------
128
+ const isSoftLimited = req.auth?.softLimited === true;
129
+ const hasExtraUsage = req.auth?.extraUsageAvailable === true;
130
+ const pgStore = authStore;
131
+ if (req.auth?.keyInfo?.key && typeof pgStore.trackBurstUsage === 'function') {
132
+ await pgStore.trackBurstUsage(req.auth.keyInfo.key);
133
+ if (isSoftLimited && hasExtraUsage) {
134
+ const extraResult = await pgStore.trackExtraUsage(req.auth.keyInfo.key, 'stealth', url, elapsed, 200);
135
+ if (extraResult.success) {
136
+ res.setHeader('X-Extra-Usage-Charged', `$${extraResult.cost.toFixed(4)}`);
137
+ res.setHeader('X-Extra-Usage-New-Balance', extraResult.newBalance.toFixed(2));
138
+ }
139
+ }
140
+ else if (!isSoftLimited) {
141
+ await pgStore.trackUsage(req.auth.keyInfo.key, 'stealth');
142
+ }
143
+ }
144
+ // Log to usage_logs (fire and forget)
145
+ if (req.auth?.keyInfo?.accountId && typeof pgStore.pool !== 'undefined') {
146
+ pgStore.pool.query(`INSERT INTO usage_logs
147
+ (user_id, endpoint, url, method, processing_time_ms, status_code, ip_address, user_agent)
148
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, [
149
+ req.auth.keyInfo.accountId,
150
+ 'screenshot',
151
+ url,
152
+ 'stealth',
153
+ elapsed,
154
+ 200,
155
+ req.ip || req.socket.remoteAddress,
156
+ req.get('user-agent'),
157
+ ]).catch((err) => {
158
+ console.error('Failed to log screenshot request:', err);
159
+ });
160
+ }
161
+ // --- Respond -------------------------------------------------------
162
+ res.setHeader('X-Credits-Used', '1');
163
+ res.setHeader('X-Processing-Time', elapsed.toString());
164
+ res.setHeader('X-Fetch-Type', 'screenshot');
165
+ res.json({
166
+ success: true,
167
+ data: {
168
+ url: result.url,
169
+ screenshot: `data:${result.contentType};base64,${result.screenshot}`,
170
+ metadata: {
171
+ sourceURL: result.url,
172
+ format: result.format,
173
+ width: width || 1280,
174
+ height: height || 720,
175
+ fullPage: fullPage === true,
176
+ },
177
+ },
178
+ });
179
+ }
180
+ catch (error) {
181
+ console.error('Screenshot error:', error);
182
+ // Log error (fire and forget)
183
+ const pgStore = authStore;
184
+ if (req.auth?.keyInfo?.accountId && typeof pgStore.pool !== 'undefined') {
185
+ pgStore.pool.query(`INSERT INTO usage_logs
186
+ (user_id, endpoint, url, method, status_code, error, ip_address, user_agent)
187
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, [
188
+ req.auth.keyInfo.accountId,
189
+ 'screenshot',
190
+ req.body?.url,
191
+ 'stealth',
192
+ 500,
193
+ error.message || 'Unknown error',
194
+ req.ip || req.socket.remoteAddress,
195
+ req.get('user-agent'),
196
+ ]).catch((logErr) => {
197
+ console.error('Failed to log screenshot error:', logErr);
198
+ });
199
+ }
200
+ if (error.code) {
201
+ const safeMessage = error.message.replace(/[<>"']/g, '');
202
+ res.status(500).json({
203
+ error: 'screenshot_error',
204
+ message: safeMessage,
205
+ });
206
+ }
207
+ else {
208
+ res.status(500).json({
209
+ error: 'internal_error',
210
+ message: 'An unexpected error occurred while taking the screenshot',
211
+ });
212
+ }
213
+ }
214
+ });
215
+ return router;
216
+ }
217
+ //# sourceMappingURL=screenshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../../../src/server/routes/screenshot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Search endpoint with caching — supports DuckDuckGo (default) and Brave (BYOK)
3
+ */
4
+ import { Router } from 'express';
5
+ import { AuthStore } from '../auth-store.js';
6
+ export declare function createSearchRouter(authStore: AuthStore): Router;
7
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../src/server/routes/search.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AAIpD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAuC7C,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,SAAS,GAAG,MAAM,CAgU/D"}