Skip to content

Repository files navigation

Apper — LangGraph + Cloudflare Browser Rendering

Single LangGraph graph with one node that uses the official Cloudflare Python library (AsyncCloudflare) to call Browser Rendering content.create. Use it to fetch fully rendered HTML for JavaScript-heavy pages (e.g. Handshake) using your session cookie when needed.

Setup

python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
cp .env.example .env   # set CF_ACCOUNT_ID, CF_API_TOKEN
cp secrets/handshake_cookies.json.example secrets/handshake_cookies.json
# Paste your real cookies into handshake_cookies.json (file is gitignored)

Cookies for Handshake

Approach When to use
HANDSHAKE_COOKIES_PATH Recommended. JSON file: array of { "name", "value", "domain", "path" } (same shape as browser devtools export / your snippet). Safe for long JWT-style values like hss-global.
HANDSHAKE_COOKIE One-line Cookie header (a=b; c=d) for quick tests.
Graph state cookies (list) or cookies_path (string) when invoking the graph from code.

If structured cookies are loaded from file or state, the node sends them via Cloudflare’s cookies API field. The auto HANDSHAKE_COOKIE header is skipped in that case to avoid duplicates; you can still set a Cookie header manually in extra_http_headers if you need both.

Run (CLI)

python -m server --url "https://example.com"
python -m server --url "https://nyu.joinhandshake.com"
  • Set HANDSHAKE_COOKIES_PATH in .env to your handshake_cookies.json (see setup above).
  • --wait-until load (default) — Handshake and similar SPAs often never reach networkidle0, which triggers a 30s navigation timeout; use load or domcontentloaded, or networkidle2 if you need stricter idleness.
  • --timeout-ms 60000 (default, max allowed) — raises Cloudflare’s navigation timeout from the API default (30s).
  • --no-goto-options uses API defaults (short timeout — often fails on heavy SPAs).
  • --json prints the final state as JSON (truncates very large HTML).

Console script: apper-render (same flags).

LangSmith tracing

Tracing is off by default. To send LangGraph runs to LangSmith:

  1. Set in .env (see .env.example):
    • LANGCHAIN_TRACING_V2=true
    • LANGCHAIN_API_KEY= (or LANGSMITH_API_KEY)
    • LANGCHAIN_PROJECT=apper (or LANGSMITH_PROJECT)
  2. Optional: LANGCHAIN_ENDPOINT / LANGSMITH_ENDPOINT (e.g. EU host).

build_graph() calls configure_langsmith(), which copies these settings into os.environ so LangChain/LangGraph pick them up. You can also call configure_langsmith() yourself before invoking the graph if you load settings another way.

Programmatic use

import asyncio
from server.graph import build_graph

async def main():
    graph = build_graph()
    return await graph.ainvoke(
        {
            "url": "https://…",
            "extra_http_headers": {"Cookie": "…"},
            "goto_options": {"wait_until": "load", "timeout": 60000},
        }
    )

asyncio.run(main())

Layout

Risks

  • Session cookies expire; rotate HANDSHAKE_COOKIE as needed.
  • Respect Cloudflare quotas and Handshake terms of use.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages