- Extract shared fitToGpx into connected-services/fit.ts (FIT is a Garmin open standard used by Wahoo, Coros, Garmin — not provider-specific) - Add importActivity() to sync/imports.server.ts so providers call one function instead of createActivity + recordImport separately; eliminates direct dependency on activities.server.ts from provider adapters - Update wahoo importer + webhook to use both shared helpers - Extract useHostElection(yjs) hook from useRouting so host election is independently testable without mounting the full routing stack - Add setDb() to journal db.ts for module-level injection in unit tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
38 lines
1.3 KiB
TypeScript
38 lines
1.3 KiB
TypeScript
// Shared FIT file → GPX converter for provider importers.
|
|
//
|
|
// FIT is an open standard (Garmin/ANT+) used by Wahoo, Garmin, Coros, and
|
|
// others. This module is shared across all providers that produce FIT files
|
|
// so the conversion logic lives in one place.
|
|
|
|
import FitParser from "fit-file-parser";
|
|
import { generateGpx } from "@trails-cool/gpx";
|
|
|
|
export async function fitToGpx(buffer: Buffer, name: string): Promise<string | null> {
|
|
const parsed = await new Promise<Record<string, unknown>>((resolve, reject) => {
|
|
const parser = new FitParser({ force: true });
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
parser.parse(buffer as any, (error: unknown, data: any) => {
|
|
if (error) reject(error);
|
|
else resolve(data ?? {});
|
|
});
|
|
});
|
|
|
|
const records = (parsed.records ?? []) as Array<{
|
|
position_lat?: number;
|
|
position_long?: number;
|
|
altitude?: number;
|
|
timestamp?: string | Date;
|
|
}>;
|
|
|
|
const trackPoints = records
|
|
.filter((r) => r.position_lat != null && r.position_long != null)
|
|
.map((r) => ({
|
|
lat: r.position_lat!,
|
|
lon: r.position_long!,
|
|
ele: r.altitude,
|
|
time: r.timestamp instanceof Date ? r.timestamp.toISOString() : r.timestamp,
|
|
}));
|
|
|
|
if (trackPoints.length < 2) return null;
|
|
return generateGpx({ name, tracks: [trackPoints] });
|
|
}
|