
Anti-ALPR-surveillance PWA. The camera archive lives on the device.
they watching. we watching back.
An Android-first counter-surveillance PWA that tells a driver where the automated license plate readers are, before they drive past them.
It provides real-time proximity alerts against an offline-capable camera archive, local exposure history, an on-device queue of signed and hash-chained camera reports, and source-backed public-record context about the agencies operating cameras around you.
The deployed app has no report-upload endpoint. Reports stay in IndexedDB today. A submission gateway exists as operator code and is not a deployed route; that distinction is intentional throughout this README.
It deliberately does not do a sixth thing: it queries no vendor's system, and license plate values never leave the device.
darkroute/
├── apps/
│ ├── pwa/ React 19 + TypeScript + Vite PWA
│ ├── desktop/ developer console at api.darkroute.ai; its functions/ proxy /v1 and /api/v1 to the apex
│ └── android/ buildable Android Trusted Web Activity shell
├── packages/
│ ├── core/ pure geometry and alert engine; no DOM or I/O
│ └── api-client/ empty placeholder; the live contract is GET /api/v1/openapi.json
├── functions/
│ ├── cameras/ same-origin camera tiles from the CAMERA_TILES R2 binding
│ └── api/
│ ├── v1/ the public API: reads, /submit (opens a PR), /photo, openapi.json, _middleware
│ └── admin/ Cloudflare Access tester allowlist (private dev host only)
├── scripts/ camera pipeline, deploy, asset and policy checks
├── docs/ implementation, operations and public documentation
├── the design-gap ledger unresolved design decisions and stand-ins
└── installer.sh
The LoRa stack is Meshtastic's own, unmodified: this project builds no firmware and pairs with a
stock node. packages/api-client remains a placeholder; the production PWA does not use it.
The deployed product is a static PWA plus Cloudflare Pages Functions: the camera tile route, the
public API under /api/v1/ (see Public API), and two Access-gated administrative
routes on the private dev host. It has no FastAPI process, Postgres database, presence API or
deployed report endpoint; the one write path, POST /api/v1/submit, opens a public pull request
rather than writing to anything.
flowchart LR
OSM["OpenStreetMap"] --> PIPE["scheduled Node camera pipeline"]
PIPE --> R2[("R2 camera archive")]
R2 --> CAM["/cameras/* Pages Function"]
subgraph device["Browser or Android TWA"]
GPS["geolocation + other adapters"] --> CORE["packages/core alert engine"]
CORE --> UI["DRIVE / map / reports"]
UI <--> IDB[("IndexedDB + service-worker cache")]
end
CAM --> IDB
MAP["project tile host (PMTiles)"] --> UI
Camera tiles are requested from same-origin /cameras/*, served from the CAMERA_TILES R2 binding,
and cached for offline use. Basemap and speed archives are cross-origin range requests to the
project-operated public tile host. darkroute.ai is the public app. The
administrative Functions live outside this distribution entirely.
The curation tooling and the submission gateway are operator code, kept out of this distribution. Neither is a deployed runtime server, and nothing in this repository depends on either: the app builds, runs, tests and audits without them.
Geospatial calculation lives in packages/core; screens consume stores and adapters. Plate matching,
trip history and the report evidence chain remain on the device.
The archive is readable by anyone, without a key, at https://darkroute.ai/api/v1/, also served as
https://api.darkroute.ai/v1/ and https://api.darkroute.ai/api/v1/ through the developer console's
proxy. The machine-readable contract is GET /api/v1/openapi.json; the prose is
docs/public/API.md §1.3; the console to try it in is
api.darkroute.ai.
| Route | What it answers |
|---|---|
GET /api/v1/cameras | cameras in a bbox (≤ 1.5° a side, ≤ 24 tiles, ≤ 1000 rows), optional owner filter |
GET /api/v1/stats | archive size, generation, build and upstream timestamps |
GET /api/v1/abuse | documented cases and generation date; optional county fips |
GET /api/v1/news | automatically collected ALPR headlines, source links and collection status |
GET /api/v1/atlas | EFF Atlas agencies and vendors by county, with retrieval/check dates; optional fips |
GET /api/v1/monitoring | road-monitoring equipment inventories; optional kind and bbox |
GET /api/v1/monitoring/image?id=... | an available publisher photo for the selected traffic camera |
GET /api/v1/place | a US/PR place lookup, proxied so the geocoder never sees a driver's address |
GET /api/v1/route | a driving route that avoids up to 60 points; never cached |
GET /api/v1/doc/{name} | a published document from the public mirror, as Markdown |
POST /api/v1/submit | a correction, turned into an [unreviewed] pull request, nothing is written to the archive |
PUT /api/v1/photo | a photograph a submission refers to, keyed by its bytes; GET /api/v1/photo/{key} serves it back |