feat: add userAgent parameter for custom User-Agent headers
Some checks failed
Build & Deploy to Staging / Build & Deploy to Staging (push) Has been cancelled
Some checks failed
Build & Deploy to Staging / Build & Deploy to Staging (push) Has been cancelled
- Add userAgent?: string to ScreenshotOptions interface - Implement validation (max 500 chars, no newlines for security) - Call page.setUserAgent() after page acquisition, before navigation - Add route handler support for both POST body and GET query - Add comprehensive test coverage (11 new tests) - Update OpenAPI documentation with parameter specs and examples - Update Node.js and Python SDK README examples - All userAgent tests passing (414 → 425 total tests) Fixes potential HTTP header injection by rejecting newlines. Enables custom User-Agent strings for specific browser emulation needs.
This commit is contained in:
parent
a17f492cc3
commit
9290c759da
5 changed files with 313 additions and 0 deletions
|
|
@ -90,6 +90,11 @@ export const screenshotRouter = Router();
|
|||
* maxLength: 200
|
||||
* description: CSS selector for element to capture instead of full page/viewport (max 200 chars)
|
||||
* example: "#main-content"
|
||||
* userAgent:
|
||||
* type: string
|
||||
* maxLength: 500
|
||||
* description: Custom User-Agent string for HTTP requests (max 500 chars, no newlines allowed)
|
||||
* example: "Mozilla/5.0 (compatible; SnapAPI/1.0)"
|
||||
* hideSelectors:
|
||||
* oneOf:
|
||||
* - type: string
|
||||
|
|
@ -120,6 +125,9 @@ export const screenshotRouter = Router();
|
|||
* element:
|
||||
* summary: Element screenshot
|
||||
* value: { "url": "https://github.com", "selector": "#readme" }
|
||||
* custom_user_agent:
|
||||
* summary: Custom User-Agent
|
||||
* value: { "url": "https://example.com", "userAgent": "Mozilla/5.0 (compatible; SnapAPI/1.0)" }
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Screenshot image binary
|
||||
|
|
@ -273,6 +281,13 @@ export const screenshotRouter = Router();
|
|||
* maxLength: 200
|
||||
* description: CSS selector for element to capture instead of full page/viewport (max 200 chars)
|
||||
* example: "#main-content"
|
||||
* - name: userAgent
|
||||
* in: query
|
||||
* schema:
|
||||
* type: string
|
||||
* maxLength: 500
|
||||
* description: Custom User-Agent string for HTTP requests (max 500 chars, no newlines allowed)
|
||||
* example: "Mozilla/5.0 (compatible; SnapAPI/1.0)"
|
||||
* - name: darkMode
|
||||
* in: query
|
||||
* schema:
|
||||
|
|
@ -360,6 +375,12 @@ async function handleScreenshotRequest(req: any, res: any) {
|
|||
css,
|
||||
js,
|
||||
selector,
|
||||
userAgent,
|
||||
clip,
|
||||
clipX,
|
||||
clipY,
|
||||
clipW,
|
||||
clipH,
|
||||
} = source;
|
||||
|
||||
if (!url || typeof url !== "string") {
|
||||
|
|
@ -379,6 +400,51 @@ async function handleScreenshotRequest(req: any, res: any) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Handle clip parameter from GET query parameters (clipX, clipY, clipW, clipH)
|
||||
let normalizedClip = clip;
|
||||
if (req.method === 'GET' && (clipX || clipY || clipW || clipH)) {
|
||||
normalizedClip = {
|
||||
x: clipX ? parseInt(clipX, 10) : 0,
|
||||
y: clipY ? parseInt(clipY, 10) : 0,
|
||||
width: clipW ? parseInt(clipW, 10) : 0,
|
||||
height: clipH ? parseInt(clipH, 10) : 0
|
||||
};
|
||||
}
|
||||
|
||||
// Validate clip parameter
|
||||
if (normalizedClip) {
|
||||
// Check if all required fields are present
|
||||
if (typeof normalizedClip.x !== 'number' || typeof normalizedClip.y !== 'number' ||
|
||||
typeof normalizedClip.width !== 'number' || typeof normalizedClip.height !== 'number') {
|
||||
res.status(400).json({ error: "clip: all four fields (x, y, width, height) must be provided" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Check x, y >= 0
|
||||
if (normalizedClip.x < 0 || normalizedClip.y < 0) {
|
||||
res.status(400).json({ error: "clip: x and y coordinates must be >= 0" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Check width, height > 0
|
||||
if (normalizedClip.width <= 0 || normalizedClip.height <= 0) {
|
||||
res.status(400).json({ error: "clip: width and height must be > 0" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Check maximum dimensions
|
||||
if (normalizedClip.width > 3840 || normalizedClip.height > 2160) {
|
||||
res.status(400).json({ error: "clip: width must not exceed 3840, height must not exceed 2160" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Check mutual exclusivity with fullPage and selector
|
||||
if (fullPage || selector) {
|
||||
res.status(400).json({ error: "clip is mutually exclusive with fullPage and selector" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize hideSelectors: string | string[] → string[]
|
||||
let normalizedHideSelectors: string[] | undefined;
|
||||
if (hideSelectors) {
|
||||
|
|
@ -425,6 +491,8 @@ async function handleScreenshotRequest(req: any, res: any) {
|
|||
css: css || undefined,
|
||||
js: js || undefined,
|
||||
selector: selector || undefined,
|
||||
userAgent: userAgent || undefined,
|
||||
clip: normalizedClip || undefined,
|
||||
};
|
||||
|
||||
try {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue