# Firebase Hosting

Firebase Hosting cannot rewrite to another domain, so a Cloud Function serves the ProductLense script and forwards events behind your hosting rewrites.

Firebase Hosting rewrites can point only at your own Cloud Functions or Cloud Run services, not at another domain. So one small function does both jobs: it serves the script, and on the Scale plan it forwards events. Read the [proxy overview](/docs/proxy/overview) first for what each one fixes.

## The function

```ts title="functions/src/index.ts"
import { defineSecret } from 'firebase-functions/params';
import { onRequest } from 'firebase-functions/v2/https';

const PL_API_KEY = defineSecret('PL_API_KEY');
type Event = Record<string, unknown>;

export const productlense = onRequest({ secrets: [PL_API_KEY], region: 'us-central1' }, async (req, res) => {
  if (req.method === 'GET' && req.path === '/js/a.js') {
    const r = await fetch('https://api.productlense.app/a.js');
    if (!r.ok) {
      res.sendStatus(502);
      return;
    }
    // Firebase Hosting's CDN caches this response, so the function runs about once a day per edge.
    res.set('Cache-Control', 'public, max-age=86400').type('application/javascript').send(await r.text());
    return;
  }

  if (req.method === 'POST' && req.path === '/m/api/e') {
    const text = req.rawBody?.toString('utf8') ?? '';
    if (text.length > 8192) {
      res.sendStatus(413);
      return;
    }
    let body: { b?: Event[] } & Event;
    try {
      body = JSON.parse(text);
    } catch {
      res.sendStatus(400);
      return;
    }
    // Behind Firebase Hosting the visitor's address is the first x-forwarded-for entry.
    const ip = String(req.headers['x-forwarded-for'] ?? '').split(',')[0]?.trim() ?? '';
    const extra = { ip, ua: req.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 ${PL_API_KEY.value()}`, 'Content-Type': 'application/json' },
      body: JSON.stringify(out),
    });
    res.set('Cache-Control', 'no-store').sendStatus(r.status);
    return;
  }

  res.sendStatus(404);
});
```

```json title="firebase.json"
{
  "hosting": {
    "public": "public",
    "rewrites": [
      { "source": "/js/a.js", "function": { "functionId": "productlense", "region": "us-central1" } },
      { "source": "/m/api/e", "function": { "functionId": "productlense", "region": "us-central1" } }
    ]
  }
}
```

Set the secret with `firebase functions:secrets:set PL_API_KEY` (a key with the `events` scope; see [Authentication and API keys](/docs/api/authentication)), then `firebase deploy`. Without the Scale plan, leave out the `/m/api/e` rewrite and use only the script.

## Use it

```html tab="Script only (any plan)"
<script defer data-site="SITE_ID" data-api="https://api.productlense.app" src="/js/a.js"></script>
```

```html tab="Script and events (Scale)"
<script defer data-site="SITE_ID" data-api="https://example.com/m" src="/js/a.js"></script>
```

## Check it works

1. Open `https://example.com/js/a.js`: it returns JavaScript.
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.
