| 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Layer from "effect/Layer"; |
| 3 | import type * as HttpClient from "effect/http/HttpClient"; |
| 4 | import { CloudflareEnvironment } from "../CloudflareEnvironment.ts"; |
| 5 | import type { Credentials } from "../Credentials.ts"; |
| 6 | import { Browser } from "./Browser.ts"; |
| 7 | import type { BrowserBinding } from "./BrowserBinding.ts"; |
| 8 | import { |
| 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 | */ |
| 43 | export 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 | ); |