---
name: IP-based geo from real client IP
overview: Move location resolution to a server-side flow so the visitor's real public IP (as seen by your origin) is used for GeoIP, producing reliable city/region/country data without GPS.
todos:
  - id: api-geo-endpoint
    content: Add PHP endpoint that reads real client IP (REMOTE_ADDR / trusted proxy headers) and returns JSON geo via provider or local DB
    status: pending
  - id: shop-core-fetch
    content: Replace or supplement client ipapi/ipwho calls in shop-core.js with fetch to same-origin api/geo (or pass IP only server-side)
    status: pending
  - id: meta-firebase
    content: Ensure gatherAllIntel saves normalized city/region/country/location from server response into visitors meta
    status: pending
  - id: dashboard-fallback
    content: Keep dashboard-ui visitorLocationLabelFromMeta fallbacks (country + meta.location) for old rows
    status: pending
isProject: true
---

# IP-based location using real client IP

## User direction (iteration)

The approach should be **IP-based** so that the system collects the **real IP** and uses it to obtain **accurate geo data** (not GPS, not hidden prompts). This aligns with the previous plan: **no silent GPS**; all precision is approximate (city/region/country).

## Why server-side IP is better than browser-only `ipapi.co/json/`

- Today [`shop-core.js`](shop-core.js) calls third-party APIs **from the browser**. Those APIs still return geo for the visitor's public IP, but:
  - Ad blockers / network failures hurt reliability.
  - Rate limits and CORS are client-dependent.
- A **same-origin PHP endpoint** on your hosting sees the TCP connection's client IP as **`REMOTE_ADDR`** (and optionally `X-Forwarded-For` / `CF-Connecting-IP` when behind a reverse proxy, **only if trusted**). That is the canonical "real IP" for your server.
- Geo lookup **on the server** using that IP string gives one consistent pipeline: **real IP → geo fields → Firebase `meta`**.

## Architecture

```mermaid
sequenceDiagram
  participant Browser
  participant Origin as YourPHP
  participant Geo as GeoProvider_or_MMDB
  Browser->>Origin: GET /api/geo-ip.php (same origin, credentials optional)
  Note over Origin: IP = REMOTE_ADDR or trusted forwarded header
  Origin->>Geo: lookup(IP)
  Geo-->>Origin: city region country
  Origin-->>Browser: JSON
  Browser->>Browser: gatherAllIntel merges into meta
```

## Implementation outline

### 1. New endpoint (e.g. [`api/geo-ip.php`](api/geo-ip.php) or next to existing proxies)

- Read client IP:
  - Prefer `REMOTE_ADDR`.
  - If behind Cloudflare / known proxy, use documented header (e.g. `CF-Connecting-IP`) **only** when deployment is fixed; avoid blindly trusting arbitrary `X-Forwarded-For`.
- Call one of:
  - HTTP API (ipapi, ipinfo, etc.) **server-to-server** with the IP as query param; or
  - Local **MaxMind GeoLite2** (no per-request third-party call; best for production).
- Return JSON: `{ ip, city, region, country, isp }` (normalize empty → omit or `"Unknown"` consistently).

### 2. [`shop-core.js`](shop-core.js)

- In `fetchGeoFromProviders()` (or a renamed function), **first** `fetch()` the same-origin geo endpoint (e.g. `api/geo-ip.php` resolved via existing `new URL('api/...', window.location.href)` pattern).
- Keep **client fallbacks** (ipapi / ipwho) only if server endpoint fails (optional), so old hosts still work.

### 3. Firebase `visitors` meta

- No change to shape: `gatherAllIntel()` already maps `geo` into `meta`; ensure server response matches those keys.

### 4. Dashboard

- Apply [`dashboard-ui.js`](dashboard-ui.js) label helper with **`country` + `meta.location` fallbacks** so older visitors without city still show a region/country bucket.

## Security and policy

- Log/store IP only for analytics / fraud / delivery context as disclosed in your privacy notice.
- Do not claim street-level accuracy; IP geo is **approximate**.

## Out of scope

- GPS / `navigator.geolocation` without consent.
- Using IP for anything that violates local law; follow applicable regulations.
