# Cloudflare Worker

One Cloudflare Worker serves the ProductLense script from your domain and, on the Scale plan, forwards events with the visitor's real IP address.

If your domain is on Cloudflare, one Worker handles both requests. It caches the script at the edge and reads the visitor's address from `CF-Connecting-IP`, which Cloudflare sets and visitors cannot forge.

## The Worker

```ts title="src/index.ts"
interface Env {
  PL_API_KEY?: string;
}

type Event = Record<string, unknown>;

export default {
  async fetch(req: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const url = new URL(req.url);

    if (url.pathname === '/js/a.js') {
      const cache = caches.default;
      const hit = await cache.match(req);
      if (hit) return hit;
      const res = await fetch('https://api.productlense.app/a.js');
      if (res.ok) ctx.waitUntil(cache.put(req, res.clone()));
      return res;
    }

    if (url.pathname === '/m/api/e' && req.method === 'POST' && env.PL_API_KEY) {
      const text = await req.text();
      if (text.length > 8192) return new Response(null, { status: 413 });
      let body: { b?: Event[] } & Event;
      try {
        body = JSON.parse(text);
      } catch {
        return new Response(null, { status: 400 });
      }
      const extra = { ip: req.headers.get('CF-Connecting-IP') ?? '', ua: req.headers.get('User-Agent') ?? '' };
      const out = body.b ? { ...body, b: body.b.map((e) => ({ ...e, ...extra })) } : { ...body, ...extra };
      const r = await fetch('https://api.productlense.app/api/v1/events', {
        method: 'POST',
        headers: { Authorization: `Bearer ${env.PL_API_KEY}`, 'Content-Type': 'application/json' },
        body: JSON.stringify(out),
      });
      return new Response(null, { status: r.status });
    }

    return new Response('Not found', { status: 404 });
  },
};
```

```toml title="wrangler.toml"
name = "productlense-proxy"
main = "src/index.ts"
compatibility_date = "2026-10-01"
routes = [
  { pattern = "example.com/js/a.js", zone_name = "example.com" },
  { pattern = "example.com/m/api/e", zone_name = "example.com" },
]
```

The script response keeps ProductLense's `Cache-Control: public, max-age=86400`, so the edge cache refreshes at least once a day.

## Use only the script part (any plan)

Without `PL_API_KEY` the Worker serves only the script. Point the tag at it and send events straight to ProductLense:

```html
<script defer data-site="SITE_ID" data-api="https://api.productlense.app" src="/js/a.js"></script>
```

## Forward events too (Scale plan)

1. Create an API key with the `events` scope (see [Authentication and API keys](/docs/api/authentication)).
2. Store it as a secret: `npx wrangler secret put PL_API_KEY`, then `npx wrangler deploy`.
3. Change the tag to `data-api="https://example.com/m"`.

## Check it works

1. Open `https://example.com/js/a.js`: it returns JavaScript, and a second load shows `cf-cache-status: HIT`.
2. Load a page with the developer tools open. The Network tab shows a `POST` to `api/e` with status `204`.
3. Open the Live page in the ProductLense dashboard. Your visit appears within a few seconds, with your real country.
