Skip to content
← Public packages

@kody/browser-run

Call Cloudflare Browser Run Quick Actions and reuse shared sessions with createBrowserContext.

src/quick-action.ts

111 lines · 2.9 KB · TypeScript
import { any, object, optional, parse, string } from 'remix/data-schema'
import {
	QUICK_ACTIONS,
	accountPath,
	browserRunFetch,
	isQuickAction,
	pickAuth,
	requireAccountId,
	type AuthOptions,
	type QuickActionName,
} from './client.ts'

const quickActionInput = object(
	{
		accountId: string(),
		action: string(),
		apiTokenSecret: optional(string()),
		/** Request body fields passed through to Cloudflare (url/html/options/…). */
		body: optional(any()),
	},
	{ unknownKeys: 'error' },
)

export type QuickActionInput = AuthOptions & {
	action: QuickActionName | string
	body?: Record<string, unknown> | unknown
}

/**
 * POST a Cloudflare Browser Run Quick Action under
 * `/accounts/{accountId}/browser-rendering/{action}`.
 * Use for one-shot screenshot, pdf, content, markdown, scrape, links, json,
 * snapshot, or accessibilityTree. Binary responses (screenshot/pdf) return
 * base64 + contentType.
 *
 * @param raw.accountId - Cloudflare account id
 * @param raw.action - Quick Action name
 * @param raw.body - Fields from CF docs (`url` or `html`, viewport, …)
 * @param raw.apiTokenSecret - Optional alternate secret **name**
 * @returns Structured JSON result or `{ kind: 'binary', base64, contentType }`
 *
 * @example
 * import quickAction from 'kody:@kody/browser-run/quick-action'
 * const shot = await quickAction({
 *   accountId: 'YOUR_ACCOUNT_ID',
 *   action: 'screenshot',
 *   body: { url: 'https://example.com' },
 * })
 */
export default async function quickAction(raw: unknown = {}) {
	const input = parse(quickActionInput, raw ?? {})
	const action = String(input.action).trim()
	if (!isQuickAction(action)) {
		throw new Error(
			`Unknown Quick Action "${action}". Valid: ${QUICK_ACTIONS.join(', ')}.`,
		)
	}
	const auth = pickAuth(input)
	const accountId = requireAccountId(auth)
	const body =
		input.body && typeof input.body === 'object' && !Array.isArray(input.body)
			? (input.body as Record<string, unknown>)
			: {}

	const acceptBinary = action === 'screenshot' || action === 'pdf'
	const response = await browserRunFetch(
		accountPath(accountId, `/browser-rendering/${action}`),
		{
			...auth,
			method: 'POST',
			body,
			acceptBinary,
		},
	)

	if (response.kind === 'binary') {
		return {
			ok: true,
			action,
			accountId,
			kind: 'binary' as const,
			contentType: response.contentType,
			base64: response.base64,
			byteLength: response.byteLength,
			browserMsUsed: response.browserMsUsed,
		}
	}
	if (response.kind === 'json') {
		return {
			ok: true,
			action,
			accountId,
			kind: 'json' as const,
			result: response.result,
			errors: response.errors,
			messages: response.messages,
			browserMsUsed: response.browserMsUsed,
			status: response.status,
		}
	}
	return {
		ok: true,
		action,
		accountId,
		kind: 'text' as const,
		text: response.text,
		contentType: response.contentType,
		browserMsUsed: response.browserMsUsed,
		status: response.status,
	}
}