Skip to content

Commit 3310e9a

Browse files
Merge pull request #257 from TheAngryRaven/BETA
2.8.0
2 parents d5b7818 + 92afcc0 commit 3310e9a

83 files changed

Lines changed: 1387 additions & 189 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# =============================================================================
2-
# Build-time configuration for Dove's DataViewer / HackTheTrack
2+
# Build-time configuration for Dove's DataViewer / LapWing
33
# =============================================================================
44
#
55
# Two prefixes are accepted at build time (see vite.config.ts):
@@ -61,3 +61,10 @@ VITE_ENABLE_ADMIN="true"
6161
# Optional: Cloudflare Turnstile site key for the public contact form.
6262
# Leave empty to skip CAPTCHA.
6363
VITE_TURNSTILE_SITE_KEY=""
64+
65+
# Set to "true" ONLY for the native (Tauri/Android) shell build. Skips the
66+
# service worker, hides in-app purchases (paid plans are web-only — Google Play
67+
# billing policy), and routes external links through the system browser. The web
68+
# app must leave this unset/"false". See docs/android.md and src/lib/platform.ts.
69+
# VITE_IS_NATIVE="true"
70+
# HTT_IS_NATIVE="true"

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
blank_issues_enabled: false
22
contact_links:
3-
- name: Live App — HackTheTrack.net
4-
url: https://hackthetrack.net
3+
- name: Live App — LapWing
4+
url: https://lapwingdata.com
55
about: Try the app directly in your browser.
66
- name: DovesDataLogger (Hardware)
77
url: https://github.com/TheAngryRaven/DovesDataLogger

CHANGELOG.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
> from git history and grouped by theme rather than exhaustive per-commit
1212
> detail.
1313
14+
## [2.8.0] - 2026-06-20
15+
16+
### Added
17+
- **Android app groundwork.** The same frontend can now also serve a native
18+
Android app (built with Tauri in a separate repo) while the web app is
19+
unchanged. A new platform layer (`isNativeApp()`) gates native-only behaviour:
20+
the service worker is skipped inside the native WebView, external links open in
21+
the system browser, and — to comply with Google Play's billing policy — paid
22+
plans are not sold or managed in-app (cloud **sync still works**; subscriptions
23+
are purchased and managed on the web). Set `VITE_IS_NATIVE=true` for the native
24+
build. See `docs/android.md`.
25+
- **Public account-deletion page.** A no-login page at **/delete-account** lets
26+
you request permanent deletion of your cloud account from the web (Google Play
27+
requires a public deletion URL), in addition to the existing in-app flow under
28+
Profile → Data & privacy.
29+
- **Import your data.** Profile → Data & privacy gains an **Import data** option
30+
that restores files and garage data from a previously downloaded data-export
31+
ZIP. This is the migration path when moving between origins (e.g. the old
32+
hackthetrack.net site → lapwingdata.com), where per-browser storage doesn't
33+
carry over. Existing files are kept; matching names are skipped. Works signed
34+
in or out (it's a local restore).
35+
36+
### Changed
37+
- **Renamed to LapWing.** The app's display name is now **LapWing** everywhere
38+
(the previous "HackTheTrack" / "HackTheTrack.net" branding).
39+
- **New domain — lapwingdata.com.** The site now lives at **lapwingdata.com**
40+
(canonical URLs, sitemap, social tags, in-app links). Production attaches the
41+
domain via a `custom_domain` route in `wrangler.jsonc`; the beta domain
42+
**beta.lapwingdata.com** is served by the `beta-proxy/` reverse-proxy Worker.
43+
- **Privacy Policy & Terms** now describe the Android app's web-only billing, the
44+
public deletion URL, and the app's foreground-only location use.
45+
1446
## [2.7.2] - 2026-06-20
1547

1648
### Added

CLAUDE.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
99
## Project Identity
1010

11-
**Dove's DataViewer / HackTheTrack** — Open-source, offline-first motorsport
11+
**Dove's DataViewer / LapWing** — Open-source, offline-first motorsport
1212
telemetry viewer.
13-
- Live: [hackthetrack.net](https://hackthetrack.net) | Beta: [beta.perchwerks.com](https://beta.perchwerks.com)
13+
- Live: [lapwingdata.com](https://lapwingdata.com) | Beta: [beta.lapwingdata.com](https://beta.lapwingdata.com)
1414
- Companion hardware: [DovesDataLogger](https://github.com/TheAngryRaven/DovesDataLogger) (nRF52840 GPS logger with BLE — Seeed XIAO nRF52840, `sense`/`nonsense` IMU variants)
1515
- PWA with full offline support via service worker + IndexedDB
1616

@@ -308,6 +308,12 @@ unless noted.
308308
- **Cloud sync, subscriptions, GDPR**: Supabase-backed, touch nothing in the core
309309
app per Rule 1 → `docs/backend.md`. Documents, logs, and lap snapshots draw from
310310
**one pooled per-tier byte budget** (`subscription_tiers.total_bytes`).
311+
- **Android / Tauri shell** (`lib/platform.ts`): the same bundle serves the web app
312+
and a native Android app (separate Tauri repo). `isNativeApp()` is the single
313+
gate — on native: **no service worker** (`main.tsx`), **no in-app purchases**
314+
(paid plans are web-only per Google Play policy; cloud sync still works), and
315+
external links open in the system browser (`openExternal`/`interceptExternal`).
316+
Public deletion URL at `/delete-account`. → `docs/android.md`.
311317

312318
---
313319

@@ -360,6 +366,7 @@ and the seeder: **→ `docs/i18n.md`**.
360366
| `VITE_ENABLE_ADMIN` | Client | `"true"` enables admin UI + `/admin`. `/login` is mounted when this OR `VITE_ENABLE_CLOUD` is on. |
361367
| `VITE_ENABLE_CLOUD` | Client | `"true"` enables public accounts (Cloud Sync + email sign-in + `/register` etc.). Default `"false"`. |
362368
| `VITE_ENABLE_GOOGLE_AUTH` | Client | `"true"` shows "Continue with Google". Requires `VITE_ENABLE_CLOUD`. Default `"false"`. |
369+
| `VITE_IS_NATIVE` | Client/Build | `"true"` ONLY for the native (Tauri/Android) shell build. Gates `isNativeApp()` (`lib/platform.ts`): no service worker, no in-app purchases (web-only billing — Google Play policy), external links via the system browser. Default `"false"`. → `docs/android.md`. |
363370
| `VITE_TURNSTILE_SITE_KEY` | Client | Cloudflare Turnstile site key (optional CAPTCHA) |
364371
| `TURNSTILE_SECRET_KEY` | Server (edge fn) | Turnstile secret — `???` |
365372
| `VITE_FIRMWARE_MANIFEST_URL` | Client | Override the logger firmware OTA manifest URL. Unset: `main` → production manifest, non-`main`/preview → beta channel (same `isPreviewBuild()` switch). |
@@ -370,8 +377,13 @@ and the seeder: **→ `docs/i18n.md`**.
370377
**PWA/deploy detail:** the active offline worker is `/service-worker.js` (registered
371378
outside preview/iframe contexts); `public/sw.js` is a legacy kill-switch. Static
372379
hosting is Cloudflare Workers (static-assets-only, `wrangler.jsonc`,
373-
`bun run build` then `wrangler deploy`). Per-branch preview backend: `vite.config.ts`
374-
`pick()` prefers `*_PREVIEW` Supabase creds on any non-`main` branch
380+
`bun run build` then `wrangler deploy`). Production `lapwingdata.com` attaches via
381+
a `custom_domain` route in `wrangler.jsonc` (auto DNS+TLS — don't also attach it in
382+
the dashboard). The beta domain `beta.lapwingdata.com` can't bind to a Branch
383+
Preview URL, so a separate thin reverse-proxy Worker in `beta-proxy/` owns it and
384+
forwards to `beta-dovesdataviewer.perchwerks.workers.dev` (deployed on its own; see
385+
`beta-proxy/README.md`). Per-branch preview backend: `vite.config.ts` `pick()`
386+
prefers `*_PREVIEW` Supabase creds on any non-`main` branch
375387
(`WORKERS_CI_BRANCH`/`CF_PAGES_BRANCH`), so beta deployments bake in a preview DB.
376388
`main` and local dev never read `_PREVIEW`. See README "Deployment".
377389

README.md

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
[![Build](https://github.com/TheAngryRaven/DovesDataViewer/actions/workflows/build.yml/badge.svg)](https://github.com/TheAngryRaven/DovesDataViewer/actions/workflows/build.yml)
1010
[![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/TheAngryRaven/9c0c31f9c333c565804b26643a2e3aec/raw/coverage-badge.json)](https://github.com/TheAngryRaven/DovesDataViewer/actions/workflows/coverage.yml)
1111

12-
🌐 **Live Demo:** [HackTheTrack.net](https://hackthetrack.net)
12+
🌐 **Live Demo:** [LapWing](https://lapwingdata.com)
1313
🔧 **Hardware Project:** [DovesDataLogger on GitHub](https://github.com/TheAngryRaven/DovesDataLogger)
1414

1515
**Now officially in BETA status**
@@ -59,7 +59,7 @@ This project is **100% open source**. The entire codebase—every feature, every
5959

6060
## Free Forever
6161

62-
- **Single file processing on HackTheTrack.net is always free**—no download or account required
62+
- **Single file processing on LapWing is always free**—no download or account required
6363
- **Self-hosting is always an option**—clone this repo and run it yourself
6464
- The only potential future paid feature: optional cloud storage for users who *want* hosted data retention on *my* infrastructure
6565

@@ -160,6 +160,7 @@ view. Older JSON with only `sector_2_*`/`sector_3_*` is read as the two majors.
160160
| `VITE_ENABLE_ADMIN` | No | Set to `true` to enable admin UI and `/admin` route. `/login` mounts when admin OR cloud is enabled. Default `false` — a fresh clone ships the public, offline-first app, not admin UI pointed at an upstream backend. |
161161
| `VITE_ENABLE_CLOUD` | No | Set to `true` to enable public user accounts: Cloud Sync panels, email sign-in/registration, `/register`, `/forgot-password`, `/reset-password`, `/auth/callback`. Default `false` — flag-off builds ship zero cloud auth code (offline-first invariant). |
162162
| `VITE_ENABLE_GOOGLE_AUTH` | No | Set to `true` to show the "Continue with Google" buttons (login, register, Profile). Requires `VITE_ENABLE_CLOUD`. Default `false`: Google sign-in currently routes through Lovable's hosted OAuth broker, so it stays hidden until native Supabase Google OAuth is configured (Google Cloud OAuth client + Supabase provider). |
163+
| `VITE_IS_NATIVE` | No | Set to `true` **only** for the native (Tauri/Android) shell build. Skips the service worker, hides in-app purchases (paid plans are web-only — Google Play billing policy; cloud sync still works), and opens external links in the system browser. The web app leaves this unset/`false`. See [`docs/android.md`](docs/android.md). |
163164
| `VITE_TURNSTILE_SITE_KEY` | No | Cloudflare Turnstile site key for track submission CAPTCHA |
164165
| `VITE_FIRMWARE_MANIFEST_URL` | No | Override the DovesDataLogger firmware OTA manifest URL used by the in-app firmware updater. When unset: `main` builds use the production manifest (`https://theangryraven.github.io/DovesDataLogger/manifest.json`); non-`main`/preview builds use the **beta channel** (`https://theangryraven.github.io/DovesDataLogger/beta/manifest.json`). Set this to force a specific channel on any branch. |
165166
| `TURNSTILE_SECRET_KEY` | No | Cloudflare Turnstile secret key (edge function secret — `???`) |
@@ -455,6 +456,30 @@ whenever that branch isn't `main`, and ignores them on `main` and in local dev.
455456
is also accepted. Add the Cloudflare preview URL to the preview branch's
456457
**Auth → Redirect URLs** so cloud sign-in works there.
457458

459+
#### Custom domains (production + beta)
460+
461+
- **Production — `lapwingdata.com`:** `wrangler.jsonc` declares a
462+
`custom_domain` route, so `wrangler deploy` provisions the DNS record and TLS
463+
certificate automatically. The zone `lapwingdata.com` must be in the same
464+
Cloudflare account; **do not also attach the domain by hand** in the dashboard
465+
or the bindings conflict.
466+
- **Beta — `beta.lapwingdata.com`:** custom domains can't attach to a Branch
467+
Preview URL, so a separate thin reverse-proxy Worker owns the beta hostname and
468+
forwards to the stable `beta` preview (`beta-dovesdataviewer.perchwerks.workers.dev`).
469+
It lives in [`beta-proxy/`](beta-proxy/README.md) and is deployed on its own
470+
(`cd beta-proxy && npm install && npm run deploy`). Keep Cloudflare Access
471+
**off** on the upstream preview URL — see that README.
472+
473+
### Android app (Tauri)
474+
475+
The same frontend also serves a native **Android** app built with Tauri (in a
476+
separate repo) — the web app is unchanged. Build the bundle with
477+
`VITE_IS_NATIVE=true` and the app skips the service worker, hides in-app purchases
478+
(paid plans stay web-only per Google Play's billing policy; cloud sync still
479+
works), and opens external links in the system browser. See
480+
[`docs/android.md`](docs/android.md) for the platform layer, the native bridge
481+
contract, the Data Safety form, and the Android permission set.
482+
458483
---
459484

460485
## Project Structure

SECURITY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
Dove's DataViewer is a continuously deployed web app. Security fixes are applied
66
to the latest release on the `main` branch, which is what powers
7-
[hackthetrack.net](https://hackthetrack.net). Older tagged releases are not
7+
[lapwingdata.com](https://lapwingdata.com). Older tagged releases are not
88
separately patched.
99

1010
| Version | Supported |

beta-proxy/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules/
2+
.wrangler/
3+
dist/
4+
package-lock.json

beta-proxy/README.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# beta-proxy
2+
3+
A minimal Cloudflare Worker that reverse-proxies **https://beta.lapwingdata.com**
4+
to the stable `beta` branch preview of the `dovesdataviewer` Worker.
5+
6+
## Why this exists
7+
8+
The `dovesdataviewer` Worker has non-production branch builds enabled. Pushes to
9+
the `beta` branch publish a **stable Branch Preview URL**:
10+
11+
```
12+
https://beta-dovesdataviewer.perchwerks.workers.dev
13+
```
14+
15+
that always serves the latest `beta` push. Cloudflare **custom domains cannot be
16+
attached directly to a preview URL**, so this thin proxy Worker owns
17+
`beta.lapwingdata.com` and forwards every request to the preview URL, returning
18+
the upstream response unchanged (it also rewrites the `Location` header on 3xx
19+
redirects so the preview hostname never leaks to the client).
20+
21+
The upstream host is a single top-level constant (`UPSTREAM_HOST`) in
22+
[`src/index.ts`](src/index.ts) — change it there if the target ever moves. (It
23+
stays on `perchwerks.workers.dev` because the preview URL is derived from the
24+
Worker name + the account's workers.dev subdomain, which the new public domain
25+
doesn't change.)
26+
27+
## Prerequisites
28+
29+
- The zone **lapwingdata.com** is already managed in this Cloudflare account.
30+
- Node.js installed locally.
31+
32+
## Deploy
33+
34+
```bash
35+
cd beta-proxy
36+
npm install
37+
38+
# Authenticate (opens a browser; one-time per machine)
39+
npx wrangler login
40+
41+
# Validate config + bundle without deploying
42+
npm run dry-run
43+
44+
# Deploy for real
45+
npm run deploy
46+
```
47+
48+
> **Note:** this project lives inside the `dovesdataviewer` repo, which has its
49+
> own root `wrangler.jsonc`. Wrangler's config discovery walks *up* the tree and
50+
> would otherwise pick up that parent config, so the npm scripts (and any direct
51+
> invocation) must pass `--config ./wrangler.toml`. The `npm run deploy` /
52+
> `npm run dry-run` scripts already do this — if you call wrangler directly, run
53+
> `npx wrangler deploy --config ./wrangler.toml`.
54+
55+
The `custom_domain` route in [`wrangler.toml`](wrangler.toml) makes Cloudflare
56+
**automatically provision the DNS record and TLS certificate** for
57+
`beta.lapwingdata.com` on first deploy. **Do not create any DNS records by hand**
58+
doing so will conflict with the custom-domain binding.
59+
60+
## ⚠️ Cloudflare Access must be OFF on the upstream preview URL
61+
62+
This proxy makes a server-side `fetch()` to
63+
`beta-dovesdataviewer.perchwerks.workers.dev`. If **Cloudflare Access** (Zero
64+
Trust) is enabled on that preview hostname (or on the `*.workers.dev` route),
65+
the proxy's `fetch` will be redirected to an Access login wall and your users
66+
will see a Cloudflare login page instead of the app.
67+
68+
**Keep Cloudflare Access disabled on the upstream preview URL** for this proxy
69+
to work. If you need to gate `beta.lapwingdata.com`, add Access on *this* proxy's
70+
custom domain instead, not on the upstream.

beta-proxy/package.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "beta-proxy",
3+
"version": "1.0.0",
4+
"private": true,
5+
"description": "Reverse-proxy Worker serving the dovesdataviewer beta branch preview at beta.lapwingdata.com",
6+
"scripts": {
7+
"deploy": "wrangler deploy --config ./wrangler.toml",
8+
"dry-run": "wrangler deploy --dry-run --config ./wrangler.toml"
9+
},
10+
"devDependencies": {
11+
"@cloudflare/workers-types": "^4.20250601.0",
12+
"wrangler": "^4.20.0"
13+
}
14+
}

beta-proxy/src/index.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* beta-proxy — a thin reverse-proxy Worker.
3+
*
4+
* Cloudflare custom domains cannot attach to a *Branch Preview* URL, so this
5+
* Worker owns beta.lapwingdata.com and transparently forwards every request to
6+
* the stable `beta` branch preview of the `dovesdataviewer` Worker.
7+
*
8+
* Change the deployment target by editing UPSTREAM_HOST below.
9+
*/
10+
11+
// The stable Branch Preview hostname for the `beta` branch of `dovesdataviewer`.
12+
// It's derived from the Worker name + account workers.dev subdomain, which are
13+
// unchanged by the new public domain — so this stays on perchwerks.workers.dev.
14+
const UPSTREAM_HOST = "beta-dovesdataviewer.perchwerks.workers.dev";
15+
16+
// The public hostname this proxy serves on (used to rewrite redirect targets so
17+
// the preview host never leaks back to the client).
18+
const PUBLIC_HOST = "beta.lapwingdata.com";
19+
20+
export default {
21+
async fetch(request: Request): Promise<Response> {
22+
const url = new URL(request.url);
23+
24+
// Forward to the upstream preview host, preserving path + query string.
25+
url.hostname = UPSTREAM_HOST;
26+
url.protocol = "https:";
27+
url.port = "";
28+
29+
// Reconstruct the request so all methods, headers, and the body are
30+
// forwarded verbatim. `redirect: "manual"` lets us rewrite Location
31+
// headers ourselves rather than letting fetch follow them.
32+
const upstreamRequest = new Request(url.toString(), request);
33+
34+
const upstreamResponse = await fetch(upstreamRequest, { redirect: "manual" });
35+
36+
// On 3xx redirects, swap the upstream .workers.dev host in the Location
37+
// header for our public host so clients never see the preview hostname.
38+
if (upstreamResponse.status >= 300 && upstreamResponse.status < 400) {
39+
const location = upstreamResponse.headers.get("Location");
40+
if (location) {
41+
const rewritten = rewriteLocation(location);
42+
if (rewritten !== location) {
43+
const headers = new Headers(upstreamResponse.headers);
44+
headers.set("Location", rewritten);
45+
return new Response(upstreamResponse.body, {
46+
status: upstreamResponse.status,
47+
statusText: upstreamResponse.statusText,
48+
headers,
49+
});
50+
}
51+
}
52+
}
53+
54+
return upstreamResponse;
55+
},
56+
} satisfies ExportedHandler;
57+
58+
/**
59+
* Rewrite a Location header value, replacing the upstream preview host with the
60+
* public host. Handles both absolute URLs and bare/relative values (which are
61+
* returned unchanged).
62+
*/
63+
function rewriteLocation(location: string): string {
64+
try {
65+
const target = new URL(location);
66+
if (target.hostname === UPSTREAM_HOST) {
67+
target.hostname = PUBLIC_HOST;
68+
return target.toString();
69+
}
70+
return location;
71+
} catch {
72+
// Relative Location (e.g. "/path") — nothing to rewrite.
73+
return location;
74+
}
75+
}

0 commit comments

Comments
 (0)