1import * as Effect from "effect/Effect";
2import * as Layer from "effect/Layer";
3import type * as HttpClient from "effect/http/HttpClient";
4import { CloudflareEnvironment } from "../CloudflareEnvironment.ts";
5import type { Credentials } from "../Credentials.ts";
6import { Browser } from "./Browser.ts";
7import type { BrowserBinding } from "./BrowserBinding.ts";
8import {
9 type BrowserAuth,
10 makeHttpBrowserClient,
11} from "./BrowserHttpClient.ts";
12
13/**
14 * Local implementation of the {@link Browser} binding — drives Cloudflare
15 * Browser Rendering over its REST data-plane (`/accounts/{id}/browser-rendering/*`)
16 * using the **current credentials** instead of a native Worker binding
17 * (`BrowserBinding`).
18 *
19 * Provide it on an {@link Action} (or any deploy-time Effect) to run the JSON
20 * quick actions — `content`, `markdown`, `scrape`, `links`, `snapshot`,
21 * `json` — with the same client you'd use inside a Worker; no Worker host, no
22 * `host.bind`, no minted token:
23 *
24 * @example Convert a page to Markdown from an Action
25 * ```typescript
26 * const Scrape = Alchemy.Action(
27 * "Scrape",
28 * Effect.gen(function* () {
29 * const browser = yield* Cloudflare.Browser("BROWSER");
30 * return Effect.fn(function* () {
31 * const { result } = yield* browser.markdown({
32 * url: "https://example.com",
33 * });
34 * return result;
35 * });
36 * }).pipe(Effect.provide(Cloudflare.Workers.BrowserLocal)),
37 * );
38 * ```
39 *
40 * `raw`, `fetch`, and the binary actions (`screenshot`/`pdf`) have no
41 * Cloudflare REST equivalent and die — see {@link makeHttpBrowserClient}.
42 */
43export const BrowserLocal = Layer.effect(
44 Browser,
45 Effect.gen(function* () {
46 // Account + credentials are ambient during stack-eval (the stack's
47 // providers layer). Capture the full context so the REST ops run with the
48 // current credentials — no `host.bind`, no minted token.
49 const { accountId } = yield* yield* CloudflareEnvironment;
50 const context = yield* Effect.context<
51 Credentials | HttpClient.HttpClient
52 >();
53 const auth: BrowserAuth = {
54 authorize: (eff) => eff.pipe(Effect.provideContext(context)),
55 accountId,
56 };
57
58 return Effect.fn(function* (_binding: BrowserBinding) {
59 // Browser Rendering is account-scoped; the binding carries no cloud
60 // resource id, so nothing to resolve — the client only needs the account
61 // + injected auth.
62 return makeHttpBrowserClient(auth);
63 });
64 }),
65);