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
@@ -6,6 +6,7 @@ require_relative "lib/maptool/gpx"
|
||||
require_relative "lib/maptool/gpx_store"
|
||||
require_relative "lib/maptool/gpsd_client"
|
||||
require_relative "lib/maptool/livetrack_store"
|
||||
require_relative "lib/maptool/livetrack_client"
|
||||
|
||||
module Maptool
|
||||
class App < Sinatra::Base
|
||||
@@ -228,14 +229,15 @@ module Maptool
|
||||
end
|
||||
|
||||
# Called by the Apps Script watching Gmail for Garmin LiveTrack emails —
|
||||
# it only extracts and forwards the session ID, nothing else.
|
||||
# it only extracts and forwards the session id/token, nothing else.
|
||||
post "/api/livetrack/session" do
|
||||
halt 403 if LIVETRACK_WEBHOOK_SECRET.empty? || params[:secret] != LIVETRACK_WEBHOOK_SECRET
|
||||
|
||||
session_id = params[:session_id].to_s.strip
|
||||
session_token = params[:session_token].to_s.strip
|
||||
halt 400, json(error: "session_id is required") if session_id.empty?
|
||||
|
||||
LIVETRACK_STORE.add(session_id)
|
||||
LIVETRACK_STORE.add(session_id, session_token)
|
||||
json ok: true
|
||||
end
|
||||
|
||||
@@ -243,6 +245,41 @@ module Maptool
|
||||
json sessions: LIVETRACK_STORE.list
|
||||
end
|
||||
|
||||
# Garmin's own API (livetrack.garmin.com/api/v2/...) 403s a plain server
|
||||
# request no matter what headers it gets — see LiveTrackClient — so this
|
||||
# fetches the session page itself and pulls the track out of that.
|
||||
get "/api/livetrack/sessions/:id/track" do
|
||||
session = LIVETRACK_STORE.find(params[:id])
|
||||
halt 404, json(error: "unknown session") unless session
|
||||
halt 404, json(error: "session has no token") if session["token"].to_s.empty?
|
||||
|
||||
begin
|
||||
points = LiveTrackClient.track_points(session["id"], session["token"])
|
||||
rescue LiveTrackClient::FetchError => e
|
||||
halt 502, json(error: "could not fetch track: #{e.message}")
|
||||
end
|
||||
|
||||
coordinates = points.filter_map { |p| p.dig("position", "lon") && p.dig("position", "lat") && [p["position"]["lon"], p["position"]["lat"]] }
|
||||
halt 502, json(error: "track has no positions") if coordinates.empty?
|
||||
last = points.last
|
||||
|
||||
json(
|
||||
type: "FeatureCollection",
|
||||
features: [
|
||||
{ type: "Feature", geometry: { type: "LineString", coordinates: coordinates }, properties: {} },
|
||||
{
|
||||
type: "Feature",
|
||||
geometry: { type: "Point", coordinates: coordinates.last },
|
||||
properties: {
|
||||
time: last["dateTime"],
|
||||
speedMetersPerSec: last["speedMetersPerSec"],
|
||||
heartRateBeatsPerMin: last["heartRateBeatsPerMin"],
|
||||
},
|
||||
},
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
get "/api/gnss/position" do
|
||||
GNSS_LATEST_MUTEX.synchronize do
|
||||
received_at = GNSS_LATEST[:received_at]
|
||||
|
||||
Reference in new issue
Block a user