Render a session's track on the map
Clicking a LiveTrack session now fetches and draws its track (line + latest position). Garmin's own API 403s any plain server-side request (Cloudflare bot protection), so LiveTrackClient fetches the session page instead and pulls the track out of the server-rendered data embedded in it. The webhook now captures the session's token too, since the id alone isn't enough to fetch anything.
This commit is contained in:
1 parent
7083cec36c
commit
f4b5237f01
7 files changed
+326
-29
No files matched your search
+23
-11
@@ -109,13 +109,25 @@ usernames, e.g. `randy,friend1,friend2` — not their emails, the
|
||||
|
||||
== Garmin LiveTrack session webhook
|
||||
|
||||
`POST /api/livetrack/session` (`session_id`, `secret` form params) records a
|
||||
Garmin LiveTrack session ID to `data/livetrack.json`; `GET
|
||||
`POST /api/livetrack/session` (`session_id`, `session_token`, `secret` form
|
||||
params) records a Garmin LiveTrack session to `data/livetrack.json`; `GET
|
||||
/api/livetrack/sessions` lists them, newest first. Intended caller: a Google
|
||||
Apps Script bound to the Gmail account that receives LiveTrack emails,
|
||||
triggered on a timer, extracting the session ID from the email and posting
|
||||
it here — the endpoint itself doesn't know anything about email or Garmin's
|
||||
API, just the ID string.
|
||||
triggered on a timer, extracting the session id/token from the email and
|
||||
posting them here — the endpoint itself doesn't know anything about email or
|
||||
Garmin's API, just those two strings.
|
||||
|
||||
`GET /api/livetrack/sessions/:id/track` fetches and returns that session's
|
||||
track as GeoJSON (a `LineString` plus a `Point` at the latest position) —
|
||||
see `LiveTrackClient` (`lib/maptool/livetrack_client.rb`) for how, since
|
||||
there's no usable REST call for this: Garmin's own API
|
||||
(`livetrack.garmin.com/api/v2/...`) 403s a plain server-side HTTP request no
|
||||
matter what headers it's given (Cloudflare bot protection, fingerprinting
|
||||
at the TLS/HTTP level rather than anything header-based) — but the session
|
||||
page itself isn't blocked, and Garmin's Next.js frontend renders the full
|
||||
track server-side into it (as React Server Component data, not a follow-up
|
||||
API call), so the client fetches that page and pulls the track out of the
|
||||
embedded payload instead.
|
||||
|
||||
`secret` must match the `LIVETRACK_WEBHOOK_SECRET` repo secret (added to
|
||||
`.env` the same way as the others above). Unlike the other secrets, this one
|
||||
@@ -142,12 +154,12 @@ function checkLiveTrackEmail() {
|
||||
// part, only text/html, and the link only exists as an href attribute
|
||||
// — the auto-generated plain-text conversion isn't reliable for it.
|
||||
const body = thread.getMessages()[0].getBody();
|
||||
const match = body.match(/https:\/\/livetrack\.garmin\.com\/session\/([\w-]+)/);
|
||||
const match = body.match(/https:\/\/livetrack\.garmin\.com\/session\/([\w-]+)\/token\/([\w-]+)/);
|
||||
|
||||
if (match) {
|
||||
UrlFetchApp.fetch(WEBHOOK_URL, {
|
||||
method: "post",
|
||||
payload: { session_id: match[1], secret: WEBHOOK_SECRET }
|
||||
payload: { session_id: match[1], session_token: match[2], secret: WEBHOOK_SECRET }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -168,11 +180,11 @@ every 5 minutes.
|
||||
|
||||
Verified against a real "started an activity" LiveTrack email
|
||||
(2026-09-06): sender `noreply@garmin.com`, link
|
||||
`https://livetrack.garmin.com/session/<uuid>/token/<token>` — the regex
|
||||
above only captures the `<uuid>` part, stopping at the next `/`. Garmin
|
||||
also sends other LiveTrack notifications (e.g. activity ended) that
|
||||
`https://livetrack.garmin.com/session/<uuid>/token/<token>`, and against
|
||||
the resulting session page/track fetch (`LiveTrackClient`, same day).
|
||||
Garmin also sends other LiveTrack notifications (e.g. activity ended) that
|
||||
weren't checked — if one turns out to use a different link shape, the
|
||||
webhook simply won't receive that session ID; nothing breaks.
|
||||
webhook simply won't receive that session; nothing breaks.
|
||||
|
||||
== CI deploy job
|
||||
|
||||
|
||||
Reference in new issue
Block a user