Skip to content
← Public packages

@kody/browser-run

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

src/smoke-test.ts

113 lines · 3.4 KB · TypeScript
import { boolean, object, optional, parse, string } from 'remix/data-schema'
import {
	ACCOUNT_ID_HELP,
	DEFAULT_API_TOKEN_SECRET,
	DOCS_HOME,
	TOKEN_SETUP_URL,
	pickAuth,
	secretSetupUrl,
} from './client.ts'
import listSessions from './sessions/list.ts'
import overview from './index.ts'

const smokeInput = object(
	{
		accountId: optional(string()),
		apiTokenSecret: optional(string()),
		/** When true (default if accountId missing), skip live CF calls. */
		dryRun: optional(boolean()),
	},
	{ unknownKeys: 'error' },
)

/**
 * Read-only smoke test: returns setup guidance when secrets/accountId are
 * missing; otherwise lists sessions (or dry-runs the list). Prefer this after
 * publish to verify wiring without launching browsers or Quick Actions.
 *
 * @param raw.accountId - Cloudflare account id (optional for dry guidance)
 * @param raw.dryRun - Force dry guidance / list dryRun
 * @param raw.apiTokenSecret - Optional alternate secret name
 * @returns `{ ok, mode, … }` with clear next steps when not fully configured
 *
 * @example
 * import smokeTest from 'kody:@kody/browser-run/smoke-test'
 * const result = await smokeTest()
 * // => { ok: true, mode: 'setup-guidance', … } or live session list
 */
export default async function smokeTest(raw: unknown = {}) {
	const input = parse(smokeInput, raw ?? {})
	const auth = pickAuth(input)
	const secretName = auth.apiTokenSecret || DEFAULT_API_TOKEN_SECRET
	const accountId = (input.accountId || '').trim()
	const forceDry = input.dryRun === true || !accountId

	const meta = await overview()

	if (forceDry && !accountId) {
		return {
			ok: true,
			mode: 'setup-guidance' as const,
			package: meta.name,
			docs: DOCS_HOME,
			secret: {
				name: secretName,
				setupUrl: secretSetupUrl(secretName),
				defaultSetupUrl: TOKEN_SETUP_URL,
				permission: 'Browser Rendering - Edit',
				host: 'api.cloudflare.com',
			},
			accountIdHelp: ACCOUNT_ID_HELP,
			nextSteps: [
				`Save user secret \`${secretName}\` with allowed host api.cloudflare.com: ${secretSetupUrl(secretName)}`,
				`Pass accountId on live calls. ${ACCOUNT_ID_HELP}`,
				'Then: import smokeTest from "kody:@kody/browser-run/smoke-test"; await smokeTest({ accountId })',
				'Default automation path: kody:@kody/browser-run/open-isolated',
			],
			exports: meta.exports,
			pitOfSuccess: meta.pitOfSuccess,
		}
	}

	if (input.dryRun === true) {
		const preview = await listSessions({
			accountId,
			apiTokenSecret: auth.apiTokenSecret,
			dryRun: true,
		})
		return {
			ok: true,
			mode: 'list-dry-run' as const,
			preview,
			pitOfSuccess: meta.pitOfSuccess,
		}
	}

	try {
		const listed = await listSessions({
			accountId,
			apiTokenSecret: auth.apiTokenSecret,
		})
		return {
			ok: true,
			mode: 'list-sessions' as const,
			accountId,
			count: listed.count,
			sessions: listed.sessions,
			pitOfSuccess: meta.pitOfSuccess,
			note: 'Live list succeeded. Prefer open-isolated for shared automation; Quick Actions for one-shots.',
		}
	} catch (error) {
		const message = error instanceof Error ? error.message : String(error)
		return {
			ok: false,
			mode: 'list-failed' as const,
			accountId,
			error: message,
			setupUrl: secretSetupUrl(secretName),
			accountIdHelp: ACCOUNT_ID_HELP,
			hint:
				'Ensure cloudflareApiToken (or apiTokenSecret) exists, host api.cloudflare.com is allowed, token has Browser Rendering - Edit, and accountId is correct.',
		}
	}
}