From a33c6d26833bf1bd3ee95f223acf20615c4786f4 Mon Sep 17 00:00:00 2001 From: Gabriel Massadas <5445926+G4brym@users.noreply.github.com> Date: Wed, 15 Jan 2025 13:04:51 +0000 Subject: [PATCH] Add Browser Rendering JS wrapped binding (#3334) --- src/cloudflare/br.ts | 8 +++++ src/cloudflare/internal/br-api.ts | 28 +++++++++++++++ src/cloudflare/internal/test/br/BUILD.bazel | 22 ++++++++++++ .../internal/test/br/br-api-test.js | 21 +++++++++++ .../internal/test/br/br-api-test.py | 9 +++++ .../internal/test/br/br-api-test.wd-test | 36 +++++++++++++++++++ src/cloudflare/internal/test/br/br-mock.js | 16 +++++++++ .../test/br/python-br-api-test.wd-test | 36 +++++++++++++++++++ 8 files changed, 176 insertions(+) create mode 100644 src/cloudflare/br.ts create mode 100644 src/cloudflare/internal/br-api.ts create mode 100644 src/cloudflare/internal/test/br/BUILD.bazel create mode 100644 src/cloudflare/internal/test/br/br-api-test.js create mode 100644 src/cloudflare/internal/test/br/br-api-test.py create mode 100644 src/cloudflare/internal/test/br/br-api-test.wd-test create mode 100644 src/cloudflare/internal/test/br/br-mock.js create mode 100644 src/cloudflare/internal/test/br/python-br-api-test.wd-test diff --git a/src/cloudflare/br.ts b/src/cloudflare/br.ts new file mode 100644 index 00000000000..fe1249385d8 --- /dev/null +++ b/src/cloudflare/br.ts @@ -0,0 +1,8 @@ +// Copyright (c) 2025 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +// This binding is managed by the browser rendering team (aka brapi) +// https://developers.cloudflare.com/browser-rendering/ + +export { BrowserRendering } from 'cloudflare-internal:br-api'; diff --git a/src/cloudflare/internal/br-api.ts b/src/cloudflare/internal/br-api.ts new file mode 100644 index 00000000000..14d9ba4c9ba --- /dev/null +++ b/src/cloudflare/internal/br-api.ts @@ -0,0 +1,28 @@ +// Copyright (c) 2025 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +interface Fetcher { + fetch: typeof fetch; +} + +export class BrowserRendering { + private readonly fetcher: Fetcher; + + public constructor(fetcher: Fetcher) { + this.fetcher = fetcher; + } + + public async fetch( + input: RequestInfo | URL, + init?: RequestInit + ): Promise { + return this.fetcher.fetch(input, init); + } +} + +export default function makeBinding(env: { + fetcher: Fetcher; +}): BrowserRendering { + return new BrowserRendering(env.fetcher); +} diff --git a/src/cloudflare/internal/test/br/BUILD.bazel b/src/cloudflare/internal/test/br/BUILD.bazel new file mode 100644 index 00000000000..e4a38578b0b --- /dev/null +++ b/src/cloudflare/internal/test/br/BUILD.bazel @@ -0,0 +1,22 @@ +load("//:build/wd_test.bzl", "wd_test") +load("//src/workerd/server/tests/python:py_wd_test.bzl", "py_wd_test") + +wd_test( + src = "br-api-test.wd-test", + args = ["--experimental"], + data = glob(["*.js"]), +) + +py_wd_test( + size = "large", + src = "python-br-api-test.wd-test", + args = ["--experimental"], + data = glob([ + "*.js", + "*.py", + ]), + tags = [ + # TODO(someday): Fix asan failure for this, see https://github.com/cloudflare/workerd/pull/3140#discussion_r1858273318 + "no-asan", + ], +) diff --git a/src/cloudflare/internal/test/br/br-api-test.js b/src/cloudflare/internal/test/br/br-api-test.js new file mode 100644 index 00000000000..61261268612 --- /dev/null +++ b/src/cloudflare/internal/test/br/br-api-test.js @@ -0,0 +1,21 @@ +// Copyright (c) 2025 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +import * as assert from 'node:assert'; + +export const tests = { + async test(_, env) { + { + // Test legacy fetch + const resp = await env.browser.fetch('http://workers-binding.brapi/run', { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + inputs: { test: true }, + }), + }); + assert.deepStrictEqual(await resp.json(), { success: true }); + } + }, +}; diff --git a/src/cloudflare/internal/test/br/br-api-test.py b/src/cloudflare/internal/test/br/br-api-test.py new file mode 100644 index 00000000000..16a19bc6594 --- /dev/null +++ b/src/cloudflare/internal/test/br/br-api-test.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025 Cloudflare, Inc. +# Licensed under the Apache 2.0 license found in the LICENSE file or at: +# https://opensource.org/licenses/Apache-2.0 + + +async def test(context, env): + resp = await env.browser.fetch("http://workers-binding.brapi/run") + data = await resp.json() + assert data.success is True diff --git a/src/cloudflare/internal/test/br/br-api-test.wd-test b/src/cloudflare/internal/test/br/br-api-test.wd-test new file mode 100644 index 00000000000..b182e40750b --- /dev/null +++ b/src/cloudflare/internal/test/br/br-api-test.wd-test @@ -0,0 +1,36 @@ +using Workerd = import "/workerd/workerd.capnp"; + +const unitTests :Workerd.Config = ( + services = [ + ( name = "brapi-api-test", + worker = ( + modules = [ + (name = "worker", esModule = embed "br-api-test.js") + ], + compatibilityDate = "2024-12-30", + compatibilityFlags = ["nodejs_compat"], + bindings = [ + ( + name = "browser", + wrapped = ( + moduleName = "cloudflare-internal:br-api", + innerBindings = [( + name = "fetcher", + service = "br-mock" + )], + ) + ) + ], + ) + ), + ( name = "br-mock", + worker = ( + compatibilityDate = "2024-12-30", + compatibilityFlags = ["experimental", "nodejs_compat"], + modules = [ + (name = "worker", esModule = embed "br-mock.js") + ], + ) + ) + ] +); diff --git a/src/cloudflare/internal/test/br/br-mock.js b/src/cloudflare/internal/test/br/br-mock.js new file mode 100644 index 00000000000..06c105f775a --- /dev/null +++ b/src/cloudflare/internal/test/br/br-mock.js @@ -0,0 +1,16 @@ +// Copyright (c) 2025 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +export default { + async fetch(request, env, ctx) { + return Response.json( + { success: true }, + { + headers: { + 'content-type': 'application/json', + }, + } + ); + }, +}; diff --git a/src/cloudflare/internal/test/br/python-br-api-test.wd-test b/src/cloudflare/internal/test/br/python-br-api-test.wd-test new file mode 100644 index 00000000000..267efe9bdf5 --- /dev/null +++ b/src/cloudflare/internal/test/br/python-br-api-test.wd-test @@ -0,0 +1,36 @@ +using Workerd = import "/workerd/workerd.capnp"; + +const unitTests :Workerd.Config = ( + services = [ + ( name = "br-api-test", + worker = ( + modules = [ + (name = "worker.py", pythonModule = embed "br-api-test.py") + ], + compatibilityDate = "2024-06-03", + compatibilityFlags = ["nodejs_compat", "python_workers_development"], + bindings = [ + ( + name = "browser", + wrapped = ( + moduleName = "cloudflare-internal:br-api", + innerBindings = [( + name = "fetcher", + service = "br-mock" + )], + ) + ) + ], + ) + ), + ( name = "br-mock", + worker = ( + compatibilityDate = "2024-06-03", + compatibilityFlags = ["experimental", "nodejs_compat"], + modules = [ + (name = "worker", esModule = embed "br-mock.js") + ], + ) + ) + ] +);