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.
73 lines
2.3 KiB
Ruby
73 lines
2.3 KiB
Ruby
require "json"
|
|
require "fileutils"
|
|
|
|
module Maptool
|
|
# JSON-file-backed store of Garmin LiveTrack sessions, appended to by a
|
|
# webhook whenever a new LiveTrack email arrives (see docs/deployment.adoc).
|
|
# The token is what actually lets us fetch the session's track later (see
|
|
# LiveTrackClient) — Garmin's session URL is /session/<id>/token/<token>,
|
|
# and the id alone isn't enough. Validity/expiry isn't tracked here —
|
|
# whoever fetches the track finds out then.
|
|
class LiveTrackStore
|
|
def initialize(path)
|
|
@path = path
|
|
@mutex = Mutex.new
|
|
FileUtils.mkdir_p(File.dirname(@path))
|
|
File.write(@path, "[]") unless File.exist?(@path)
|
|
end
|
|
|
|
# Records `session_id`/`token`, moving it to the front if already present.
|
|
def add(session_id, token)
|
|
@mutex.synchronize do
|
|
sessions = read
|
|
sessions.reject! { |s| s["id"] == session_id }
|
|
sessions.unshift({ "id" => session_id, "token" => token, "received_at" => Time.now.utc.iso8601 })
|
|
File.write(@path, JSON.pretty_generate(sessions))
|
|
end
|
|
end
|
|
|
|
# Most recently received first.
|
|
def list
|
|
read
|
|
end
|
|
|
|
def find(session_id)
|
|
read.find { |s| s["id"] == session_id }
|
|
end
|
|
|
|
private
|
|
|
|
def read
|
|
JSON.parse(File.read(@path))
|
|
rescue Errno::ENOENT, JSON::ParserError
|
|
[]
|
|
end
|
|
end
|
|
end
|
|
|
|
if __FILE__ == $0
|
|
require "tmpdir"
|
|
|
|
Dir.mktmpdir do |dir|
|
|
store = Maptool::LiveTrackStore.new(File.join(dir, "livetrack.json"))
|
|
raise "expected empty list" unless store.list == []
|
|
|
|
store.add("abc123", "tok-abc")
|
|
store.add("def456", "tok-def")
|
|
raise "expected 2 sessions" unless store.list.size == 2
|
|
raise "expected newest first" unless store.list.first["id"] == "def456"
|
|
raise "expected token stored" unless store.find("abc123")["token"] == "tok-abc"
|
|
raise "expected unknown id to be nil" unless store.find("nope").nil?
|
|
|
|
store.add("abc123", "tok-abc-2")
|
|
raise "expected re-added session moved to front, not duplicated" \
|
|
unless store.list.map { |s| s["id"] } == %w[abc123 def456]
|
|
raise "expected token updated on re-add" unless store.find("abc123")["token"] == "tok-abc-2"
|
|
|
|
reloaded = Maptool::LiveTrackStore.new(File.join(dir, "livetrack.json"))
|
|
raise "expected data to survive reload" unless reloaded.list.size == 2
|
|
end
|
|
|
|
puts "ok"
|
|
end
|