Sign up

Proxy

Proxy with Firebase Hosting

On this page

Firebase Hosting rewrites your two paths to a Cloud Function, which forwards the visitor's IP.

Set it up

  1. Add the function

    Cloud Functions need the Blaze (pay as you go) plan. Use Node.js 18 or later for the built-in fetch.

    functions/index.js
    // functions/index.js · Cloud Functions for Firebase (2nd gen)
    const { onRequest } = require("firebase-functions/v2/https");
    
    exports.metricraven = onRequest({ region: "us-central1" }, async (req, res) => {
      const script = req.path === "/a/script" && req.method === "GET";
      const event = req.path === "/a/event" && req.method === "POST";
      if (!script && !event) return res.status(404).end();
      const headers = {};
      for (const name of ["content-type", "origin", "user-agent"]) {
        if (req.headers[name]) headers[name] = req.headers[name];
      }
      // Hosting's CDN puts the visitor's address here; confirm it on your project (see below).
      const ip = req.headers["fastly-client-ip"] || req.headers["x-forwarded-for"]?.split(",")[0]?.trim();
      if (ip) headers["x-visitor-ip"] = ip;
      try {
        const upstream = await fetch(script ? "https://metricraven.com/js/script.js" : "https://metricraven.com/api/v1/collect", {
          method: req.method,
          headers,
          body: event ? req.rawBody : undefined,
          signal: AbortSignal.timeout(15000),
        });
        res
          .status(upstream.status)
          .set("Content-Type", upstream.headers.get("content-type") || "application/octet-stream")
          .set("Cache-Control", script ? "public, max-age=3600" : "no-store")
          .send(Buffer.from(await upstream.arrayBuffer()));
      } catch {
        res.status(502).end();
      }
    });
  2. Rewrite the paths to it

    Add the two rewrites to firebase.json, above any ** catch-all, and keep the region the same as the function's:

    firebase.json
    {
      "hosting": {
        "public": "public",
        "rewrites": [
          { "source": "/a/script", "function": { "functionId": "metricraven", "region": "us-central1" } },
          { "source": "/a/event", "function": { "functionId": "metricraven", "region": "us-central1" } }
        ]
      }
    }
  3. Deploy

    terminal
    firebase deploy --only functions,hosting
  4. Point your script tag at your own paths

    Replace your MetricRaven tag with this one. src loads the tracker from your domain and data-api sends its events there too.

    Your website's shared layout
    <script defer src="/a/script" data-api="/a/event" data-site-id="YOUR_SITE_ID"></script>

Verify it works

Open your live site. In the browser's Network panel, /a/script loads the tracker and /a/event answers with ok: true. Nothing goes to metricraven.com from the browser.

In Traffic, your own visit should show your real country and city. If every visit appears to come from your server's or host's location, the visitor IP isn't being forwarded.

Notes

  • Firebase doesn't document which header carries the visitor IP through a Hosting rewrite. The function reads fastly-client-ip, then the first x-forwarded-for entry. After deploying, confirm your own visit shows your city; if not, log req.headers once and pick the header with your IP.
  • A Hosting rewrite with a destination URL can't forward to another domain with a per-visitor header, which is why a function is needed.

How the proxy works and which headers it must send: Use your own domain.

Details

  • The proxy forwards only Content-Type, Origin, User-Agent and the visitor's IP as x-visitor-ip. Your site's cookies and auth headers never leave your server.
  • Origin matters: MetricRaven checks it against your site's allowed domains. A missing or wrong origin is refused with domain_not_allowed.
  • MetricRaven allows about 120 events per minute per visitor IP. Without x-visitor-ip, all of your visitors share the proxy's IP, so busy sites lose events as well as locations.
  • A proxy doesn't guarantee that every blocker will allow tracking.