GarminConnectClient.downloadActivityFit(activityId, outputDir) is the only route to an activity's GPS through this package, and it can only put the file on a disk:
async downloadActivityFit(activityId: number, outputDir: string): Promise<void> {
this.ensureLoggedIn()
await this.client.downloadOriginalActivityData({ activityId }, outputDir, 'zip')
}A server that wants to parse the recording — rather than keep it — has to make a temporary directory, let the library name the file, read it back and clean up:
const dir = await mkdtemp(join(tmpdir(), 'hq-garmin-'))
try {
await client.downloadActivityFit(activityId, dir)
const [name] = await readdir(dir) // the name is Garmin's to choose
const bytes = new Uint8Array(await Bun.file(join(dir, name)).arrayBuffer())
...
}
finally {
await rm(dir, { recursive: true, force: true })
}Three things that costs:
- Nothing is learned from the filesystem. The bytes are parsed in memory and thrown away; the disk round-trip exists only because the signature requires it.
- The caller cannot know the filename, so it has to read the directory and take whatever is there. That is fine for a directory it just made and wrong for any directory it did not.
- It leaks on the error path unless every caller remembers the
finally. A sync loop that forgets it fills/tmpan activity at a time.
Why the other methods are not an alternative
getActivityDetailsmaps to{ seconds, heartRate, power, speed }— the GPS in Garmin'sactivityDetailsresponse is dropped before the caller sees it.getActivityreturnsrecords: [](parseActivityResponsehardcodes it), so theActivity.recordswithposition.lat/lngthattoGeoJSONreads are never populated by the Connect client.
So the FIT download is the only way to a route, and it is the one method that cannot hand back what it fetched.
Suggestion
A sibling that returns the bytes, with the existing method kept as a thin wrapper over it:
downloadOriginalActivity(activityId: number, format?: 'zip' | 'fit'): Promise<{ bytes: Uint8Array, filename: string }>That also makes the filename explicit rather than something the caller rediscovers with readdir.
Happy to send a PR if the shape looks right.
Found while adding GPS tracks to synced activities in hq.training: Garmin's activity list carries every summary number and no coordinates, so the original recording is the only source for a route.