@dofek/trainingpeaks 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +158 -0
- package/dist/client.d.ts +51 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +190 -0
- package/dist/client.js.map +1 -0
- package/dist/parsing.d.ts +40 -0
- package/dist/parsing.d.ts.map +1 -0
- package/dist/parsing.js +53 -0
- package/dist/parsing.js.map +1 -0
- package/dist/sports.d.ts +10 -0
- package/dist/sports.d.ts.map +1 -0
- package/dist/sports.js +25 -0
- package/dist/sports.js.map +1 -0
- package/dist/types.d.ts +138 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Asher Cohen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# @dofek/trainingpeaks
|
|
2
|
+
|
|
3
|
+
Unofficial TypeScript client for private TrainingPeaks web endpoints. It reads
|
|
4
|
+
athlete workouts, profile data, Performance Management Chart data, personal
|
|
5
|
+
records, calendar notes, and workout analysis.
|
|
6
|
+
|
|
7
|
+
This package is not affiliated with, endorsed by, or supported by
|
|
8
|
+
TrainingPeaks. Its cookie flow and undocumented endpoints may change without
|
|
9
|
+
notice.
|
|
10
|
+
|
|
11
|
+
## Requirements and installation
|
|
12
|
+
|
|
13
|
+
Requires Node.js 22.14 or newer and its built-in
|
|
14
|
+
[`fetch`](https://nodejs.org/api/globals.html#fetch) implementation.
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
npm install @dofek/trainingpeaks
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick start
|
|
21
|
+
|
|
22
|
+
Log in to `app.trainingpeaks.com` in a browser and copy the value of its
|
|
23
|
+
`Production_tpAuth` cookie. Save this example as `example.mjs`, set
|
|
24
|
+
`TRAININGPEAKS_AUTH_COOKIE` and `TRAININGPEAKS_ATHLETE_ID`, then run
|
|
25
|
+
`node example.mjs`.
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
import { TrainingPeaksConnectClient } from "@dofek/trainingpeaks";
|
|
29
|
+
|
|
30
|
+
const savedCookie = process.env.TRAININGPEAKS_AUTH_COOKIE;
|
|
31
|
+
const athleteId = Number(process.env.TRAININGPEAKS_ATHLETE_ID);
|
|
32
|
+
if (!savedCookie || !Number.isInteger(athleteId)) {
|
|
33
|
+
throw new Error("Set TRAININGPEAKS_AUTH_COOKIE and TRAININGPEAKS_ATHLETE_ID");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const refreshedCookie =
|
|
37
|
+
await TrainingPeaksConnectClient.refreshCookie(savedCookie);
|
|
38
|
+
const { accessToken, expiresIn } =
|
|
39
|
+
await TrainingPeaksConnectClient.exchangeCookieForToken(refreshedCookie);
|
|
40
|
+
const client = new TrainingPeaksConnectClient(accessToken);
|
|
41
|
+
const workouts = await client.getWorkouts(
|
|
42
|
+
athleteId,
|
|
43
|
+
"2026-07-01",
|
|
44
|
+
"2026-07-07",
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
console.log({ expiresIn, workouts: workouts.length });
|
|
48
|
+
// Replace the saved cookie with refreshedCookie in encrypted storage.
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Dates passed to range methods use `YYYY-MM-DD`.
|
|
52
|
+
|
|
53
|
+
## Public API
|
|
54
|
+
|
|
55
|
+
Authentication:
|
|
56
|
+
|
|
57
|
+
- `TrainingPeaksConnectClient.refreshCookie(cookie, fetch?)` returns the
|
|
58
|
+
replacement `Production_tpAuth` cookie from the observed refresh endpoint.
|
|
59
|
+
- `TrainingPeaksConnectClient.exchangeCookieForToken(cookie, fetch?)` returns
|
|
60
|
+
`accessToken` and `expiresIn` in seconds.
|
|
61
|
+
|
|
62
|
+
Data:
|
|
63
|
+
|
|
64
|
+
- `getUser()`
|
|
65
|
+
- `getWorkouts(athleteId, startDate, endDate)` and
|
|
66
|
+
`getWorkout(athleteId, workoutId)`
|
|
67
|
+
- `getWorkoutFitUrl(athleteId, workoutId)` — constructs the FIT download URL;
|
|
68
|
+
the caller remains responsible for making an authenticated download request.
|
|
69
|
+
- `getPerformanceManagement(athleteId, startDate, endDate, options?)`
|
|
70
|
+
- `getPersonalRecords(athleteId, sport, recordType, startDate?, endDate?)`,
|
|
71
|
+
where `sport` is `"Bike"` or `"Run"`.
|
|
72
|
+
- `getCalendarNotes(athleteId, startDate, endDate)`
|
|
73
|
+
- `getWorkoutAnalysis(workoutId, athleteId)`
|
|
74
|
+
|
|
75
|
+
Supported deep imports:
|
|
76
|
+
|
|
77
|
+
- `@dofek/trainingpeaks/client` — client class.
|
|
78
|
+
- `@dofek/trainingpeaks/parsing` — workout and Performance Management Chart
|
|
79
|
+
parsers plus decimal-hour conversion.
|
|
80
|
+
- `@dofek/trainingpeaks/sports` — sport mapping table and mapper.
|
|
81
|
+
- `@dofek/trainingpeaks/types` — raw token, workout, analysis, profile, record,
|
|
82
|
+
calendar-note, and chart interfaces.
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { parseTrainingPeaksWorkout } from "@dofek/trainingpeaks/parsing";
|
|
86
|
+
import type { TrainingPeaksWorkout } from "@dofek/trainingpeaks/types";
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Authentication and persistence
|
|
90
|
+
|
|
91
|
+
The [current implementation](https://github.com/Asherlc/dofek/blob/main/packages/trainingpeaks-connect/src/client.ts)
|
|
92
|
+
does not accept a username and password or use embedded application
|
|
93
|
+
credentials. It exchanges the browser-created `Production_tpAuth` cookie for a
|
|
94
|
+
bearer access token.
|
|
95
|
+
|
|
96
|
+
Treat both values as password-equivalent secrets. Persist the latest cookie and
|
|
97
|
+
token expiry in encrypted storage. The package does not refresh bearer tokens
|
|
98
|
+
directly: refresh the saved cookie, exchange it for a new token, replace the
|
|
99
|
+
stored cookie, and construct a new client. If cookie refresh fails because the
|
|
100
|
+
browser session is no longer accepted, log in through the TrainingPeaks site
|
|
101
|
+
again and capture a new cookie.
|
|
102
|
+
|
|
103
|
+
## Request constraints
|
|
104
|
+
|
|
105
|
+
The currently observed workout endpoint accepts at most 90 days per
|
|
106
|
+
`getWorkouts` call, as recorded alongside the
|
|
107
|
+
[implemented method](https://github.com/Asherlc/dofek/blob/main/packages/trainingpeaks-connect/src/client.ts).
|
|
108
|
+
The client does not validate or split longer spans, so callers performing
|
|
109
|
+
historical syncs must divide them into windows of 90 days or less.
|
|
110
|
+
|
|
111
|
+
Each client instance targets a minimum 150 ms interval between its API calls.
|
|
112
|
+
This is local pacing, not a guarantee against upstream throttling; coordinate
|
|
113
|
+
concurrent clients separately.
|
|
114
|
+
|
|
115
|
+
## Rate limits and errors
|
|
116
|
+
|
|
117
|
+
The shared
|
|
118
|
+
[rate-limit wrapper](https://github.com/Asherlc/dofek/blob/main/packages/provider-http/src/rate-limit.ts)
|
|
119
|
+
throws `ProviderRateLimitError` for `429` and
|
|
120
|
+
`ProviderServiceUnavailableError` for `502`, `503`, and `504`. Both expose
|
|
121
|
+
`providerId`, `statusCode`, `responseBody`, and `retryAfterSeconds`. The latter
|
|
122
|
+
follows the HTTP
|
|
123
|
+
[`Retry-After`](https://www.rfc-editor.org/rfc/rfc9110.html#name-retry-after)
|
|
124
|
+
header when present. Other unsuccessful responses throw a regular `Error`
|
|
125
|
+
containing the response status and body.
|
|
126
|
+
|
|
127
|
+
If your application handles these error classes directly, declare
|
|
128
|
+
`@dofek/provider-http` as a direct dependency:
|
|
129
|
+
|
|
130
|
+
```sh
|
|
131
|
+
npm install @dofek/provider-http
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import {
|
|
136
|
+
ProviderRateLimitError,
|
|
137
|
+
ProviderServiceUnavailableError,
|
|
138
|
+
} from "@dofek/provider-http/rate-limit";
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
await client.getUser();
|
|
142
|
+
} catch (error) {
|
|
143
|
+
if (
|
|
144
|
+
error instanceof ProviderRateLimitError ||
|
|
145
|
+
error instanceof ProviderServiceUnavailableError
|
|
146
|
+
) {
|
|
147
|
+
console.error(error.providerId, error.statusCode, error.retryAfterSeconds);
|
|
148
|
+
}
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Project
|
|
154
|
+
|
|
155
|
+
- [Source](https://github.com/Asherlc/dofek/tree/main/packages/trainingpeaks-connect)
|
|
156
|
+
- [Report an issue](https://github.com/Asherlc/dofek/issues)
|
|
157
|
+
- [Contribute a pull request](https://github.com/Asherlc/dofek/pulls)
|
|
158
|
+
- License: [MIT](./LICENSE)
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { TrainingPeaksCalendarNote, TrainingPeaksPersonalRecord, TrainingPeaksPmcEntry, TrainingPeaksPmcRequest, TrainingPeaksUser, TrainingPeaksWorkout, TrainingPeaksWorkoutAnalysis } from "./types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Exchange a Production_tpAuth cookie for a Bearer access token.
|
|
4
|
+
*
|
|
5
|
+
* The cookie must be obtained by logging into app.trainingpeaks.com
|
|
6
|
+
* in a browser and copying the `Production_tpAuth` cookie value.
|
|
7
|
+
*/
|
|
8
|
+
declare function exchangeCookieForToken(cookie: string, fetchFn?: typeof globalThis.fetch): Promise<{
|
|
9
|
+
accessToken: string;
|
|
10
|
+
expiresIn: number;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* Refresh the Production_tpAuth cookie.
|
|
14
|
+
* Returns the new cookie value from the Set-Cookie header.
|
|
15
|
+
*/
|
|
16
|
+
declare function refreshCookie(cookie: string, fetchFn?: typeof globalThis.fetch): Promise<string>;
|
|
17
|
+
export declare class TrainingPeaksConnectClient {
|
|
18
|
+
#private;
|
|
19
|
+
constructor(accessToken: string, fetchFn?: typeof globalThis.fetch);
|
|
20
|
+
static exchangeCookieForToken: typeof exchangeCookieForToken;
|
|
21
|
+
static refreshCookie: typeof refreshCookie;
|
|
22
|
+
getUser(): Promise<TrainingPeaksUser>;
|
|
23
|
+
/**
|
|
24
|
+
* Get workouts in a date range. Max 90 days per request.
|
|
25
|
+
* Dates in YYYY-MM-DD format.
|
|
26
|
+
*/
|
|
27
|
+
getWorkouts(athleteId: number, startDate: string, endDate: string): Promise<TrainingPeaksWorkout[]>;
|
|
28
|
+
/** Get a single workout by ID. */
|
|
29
|
+
getWorkout(athleteId: number, workoutId: number): Promise<TrainingPeaksWorkout>;
|
|
30
|
+
/** Get FIT file download URL for a workout. */
|
|
31
|
+
getWorkoutFitUrl(athleteId: number, workoutId: number): string;
|
|
32
|
+
/**
|
|
33
|
+
* Get CTL/ATL/TSB (fitness/fatigue/form) data for a date range.
|
|
34
|
+
* This is the Performance Management Chart data.
|
|
35
|
+
*/
|
|
36
|
+
getPerformanceManagement(athleteId: number, startDate: string, endDate: string, options?: Partial<TrainingPeaksPmcRequest>): Promise<TrainingPeaksPmcEntry[]>;
|
|
37
|
+
/**
|
|
38
|
+
* Get personal records for a sport type.
|
|
39
|
+
* @param sport - "Bike" or "Run"
|
|
40
|
+
* @param recordType - e.g., "power5sec", "power20min", "speed5K"
|
|
41
|
+
*/
|
|
42
|
+
getPersonalRecords(athleteId: number, sport: "Bike" | "Run", recordType: string, startDate?: string, endDate?: string): Promise<TrainingPeaksPersonalRecord[]>;
|
|
43
|
+
getCalendarNotes(athleteId: number, startDate: string, endDate: string): Promise<TrainingPeaksCalendarNote[]>;
|
|
44
|
+
/**
|
|
45
|
+
* Get detailed workout analysis with time-series data, zones, and laps.
|
|
46
|
+
* Uses the separate analysis API at api.peakswaresb.com.
|
|
47
|
+
*/
|
|
48
|
+
getWorkoutAnalysis(workoutId: number, athleteId: number): Promise<TrainingPeaksWorkoutAnalysis>;
|
|
49
|
+
}
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EACzB,2BAA2B,EAC3B,qBAAqB,EACrB,uBAAuB,EAEvB,iBAAiB,EACjB,oBAAoB,EACpB,4BAA4B,EAC7B,MAAM,YAAY,CAAC;AAkBpB;;;;;GAKG;AACH,iBAAe,sBAAsB,CACnC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,OAAO,UAAU,CAAC,KAAwB,GAClD,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAsBrD;AAED;;;GAGG;AACH,iBAAe,aAAa,CAC1B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,OAAO,UAAU,CAAC,KAAwB,GAClD,OAAO,CAAC,MAAM,CAAC,CAsBjB;AAMD,qBAAa,0BAA0B;;gBAKzB,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,OAAO,UAAU,CAAC,KAAwB;IAOpF,MAAM,CAAC,sBAAsB,gCAA0B;IACvD,MAAM,CAAC,aAAa,uBAAiB;IAiE/B,OAAO,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAM3C;;;OAGG;IACG,WAAW,CACf,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAOlC,kCAAkC;IAC5B,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAOrF,+CAA+C;IAC/C,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM;IAM9D;;;OAGG;IACG,wBAAwB,CAC5B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,OAAO,CAAC,uBAAuB,CAAM,GAC7C,OAAO,CAAC,qBAAqB,EAAE,CAAC;IAkBnC;;;;OAIG;IACG,kBAAkB,CACtB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GAAG,KAAK,EACrB,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,2BAA2B,EAAE,CAAC;IAcnC,gBAAgB,CACpB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,yBAAyB,EAAE,CAAC;IASvC;;;OAGG;IACG,kBAAkB,CACtB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,4BAA4B,CAAC;CAOzC"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { createRateLimitAwareFetch } from "@dofek/provider-http/rate-limit";
|
|
2
|
+
// ============================================================
|
|
3
|
+
// TrainingPeaks internal API URLs
|
|
4
|
+
// ============================================================
|
|
5
|
+
const TP_API_BASE = "https://tpapi.trainingpeaks.com";
|
|
6
|
+
const TP_ANALYSIS_BASE = "https://api.peakswaresb.com";
|
|
7
|
+
const TP_HOME_BASE = "https://home.trainingpeaks.com";
|
|
8
|
+
const TP_APP_ORIGIN = "https://app.trainingpeaks.com";
|
|
9
|
+
/** Minimum delay between requests (ms) to avoid rate limiting */
|
|
10
|
+
const REQUEST_DELAY_MS = 150;
|
|
11
|
+
// ============================================================
|
|
12
|
+
// Authentication
|
|
13
|
+
// ============================================================
|
|
14
|
+
/**
|
|
15
|
+
* Exchange a Production_tpAuth cookie for a Bearer access token.
|
|
16
|
+
*
|
|
17
|
+
* The cookie must be obtained by logging into app.trainingpeaks.com
|
|
18
|
+
* in a browser and copying the `Production_tpAuth` cookie value.
|
|
19
|
+
*/
|
|
20
|
+
async function exchangeCookieForToken(cookie, fetchFn = globalThis.fetch) {
|
|
21
|
+
const rateLimitFetchFn = createRateLimitAwareFetch(fetchFn, { providerId: "trainingpeaks" });
|
|
22
|
+
const response = await rateLimitFetchFn(`${TP_API_BASE}/users/v3/token`, {
|
|
23
|
+
headers: {
|
|
24
|
+
Cookie: `Production_tpAuth=${cookie}`,
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
const text = await response.text();
|
|
29
|
+
throw new Error(`TrainingPeaks token exchange failed (${response.status}): ${text}`);
|
|
30
|
+
}
|
|
31
|
+
const data = await response.json();
|
|
32
|
+
if (!data.success) {
|
|
33
|
+
throw new Error("TrainingPeaks token exchange returned success=false");
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
accessToken: data.token.access_token,
|
|
37
|
+
expiresIn: data.token.expires_in,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Refresh the Production_tpAuth cookie.
|
|
42
|
+
* Returns the new cookie value from the Set-Cookie header.
|
|
43
|
+
*/
|
|
44
|
+
async function refreshCookie(cookie, fetchFn = globalThis.fetch) {
|
|
45
|
+
const rateLimitFetchFn = createRateLimitAwareFetch(fetchFn, { providerId: "trainingpeaks" });
|
|
46
|
+
const response = await rateLimitFetchFn(`${TP_HOME_BASE}/refresh`, {
|
|
47
|
+
headers: {
|
|
48
|
+
Cookie: `Production_tpAuth=${cookie}`,
|
|
49
|
+
},
|
|
50
|
+
redirect: "manual",
|
|
51
|
+
});
|
|
52
|
+
const setCookies = "getSetCookie" in response.headers && typeof response.headers.getSetCookie === "function"
|
|
53
|
+
? response.headers.getSetCookie()
|
|
54
|
+
: (response.headers.get("set-cookie")?.split(/,(?=\s*\w+=)/) ?? []);
|
|
55
|
+
for (const header of setCookies) {
|
|
56
|
+
if (header.includes("Production_tpAuth=")) {
|
|
57
|
+
const match = header.match(/Production_tpAuth=([^;]+)/);
|
|
58
|
+
if (match?.[1])
|
|
59
|
+
return match[1];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
throw new Error("TrainingPeaks cookie refresh did not return a new Production_tpAuth cookie");
|
|
63
|
+
}
|
|
64
|
+
// ============================================================
|
|
65
|
+
// TrainingPeaks Connect Client
|
|
66
|
+
// ============================================================
|
|
67
|
+
export class TrainingPeaksConnectClient {
|
|
68
|
+
#accessToken;
|
|
69
|
+
#fetchFn;
|
|
70
|
+
#lastRequestTime = 0;
|
|
71
|
+
constructor(accessToken, fetchFn = globalThis.fetch) {
|
|
72
|
+
this.#accessToken = accessToken;
|
|
73
|
+
this.#fetchFn = createRateLimitAwareFetch(fetchFn, { providerId: "trainingpeaks" });
|
|
74
|
+
}
|
|
75
|
+
// ---- Static auth methods ----
|
|
76
|
+
static exchangeCookieForToken = exchangeCookieForToken;
|
|
77
|
+
static refreshCookie = refreshCookie;
|
|
78
|
+
// ---- Private helpers ----
|
|
79
|
+
async #throttle() {
|
|
80
|
+
const now = Date.now();
|
|
81
|
+
const elapsed = now - this.#lastRequestTime;
|
|
82
|
+
if (elapsed < REQUEST_DELAY_MS) {
|
|
83
|
+
await new Promise((resolve) => setTimeout(resolve, REQUEST_DELAY_MS - elapsed));
|
|
84
|
+
}
|
|
85
|
+
this.#lastRequestTime = Date.now();
|
|
86
|
+
}
|
|
87
|
+
async #get(base, path, params) {
|
|
88
|
+
await this.#throttle();
|
|
89
|
+
const url = new URL(`${base}${path}`);
|
|
90
|
+
if (params) {
|
|
91
|
+
for (const [key, value] of Object.entries(params)) {
|
|
92
|
+
url.searchParams.set(key, value);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const response = await this.#fetchFn(url.toString(), {
|
|
96
|
+
headers: {
|
|
97
|
+
Authorization: `Bearer ${this.#accessToken}`,
|
|
98
|
+
Accept: "application/json",
|
|
99
|
+
Origin: TP_APP_ORIGIN,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
if (!response.ok) {
|
|
103
|
+
const text = await response.text();
|
|
104
|
+
throw new Error(`TrainingPeaks API error (${response.status}): ${text}`);
|
|
105
|
+
}
|
|
106
|
+
const result = await response.json();
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
async #post(base, path, body) {
|
|
110
|
+
await this.#throttle();
|
|
111
|
+
const response = await this.#fetchFn(`${base}${path}`, {
|
|
112
|
+
method: "POST",
|
|
113
|
+
headers: {
|
|
114
|
+
Authorization: `Bearer ${this.#accessToken}`,
|
|
115
|
+
"Content-Type": "application/json",
|
|
116
|
+
Accept: "application/json",
|
|
117
|
+
Origin: TP_APP_ORIGIN,
|
|
118
|
+
},
|
|
119
|
+
body: JSON.stringify(body),
|
|
120
|
+
});
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
const text = await response.text();
|
|
123
|
+
throw new Error(`TrainingPeaks API error (${response.status}): ${text}`);
|
|
124
|
+
}
|
|
125
|
+
const result = await response.json();
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
// ---- User profile ----
|
|
129
|
+
async getUser() {
|
|
130
|
+
return this.#get(TP_API_BASE, "/users/v3/user");
|
|
131
|
+
}
|
|
132
|
+
// ---- Workouts ----
|
|
133
|
+
/**
|
|
134
|
+
* Get workouts in a date range. Max 90 days per request.
|
|
135
|
+
* Dates in YYYY-MM-DD format.
|
|
136
|
+
*/
|
|
137
|
+
async getWorkouts(athleteId, startDate, endDate) {
|
|
138
|
+
return this.#get(TP_API_BASE, `/fitness/v6/athletes/${athleteId}/workouts/${startDate}/${endDate}`);
|
|
139
|
+
}
|
|
140
|
+
/** Get a single workout by ID. */
|
|
141
|
+
async getWorkout(athleteId, workoutId) {
|
|
142
|
+
return this.#get(TP_API_BASE, `/fitness/v6/athletes/${athleteId}/workouts/${workoutId}`);
|
|
143
|
+
}
|
|
144
|
+
/** Get FIT file download URL for a workout. */
|
|
145
|
+
getWorkoutFitUrl(athleteId, workoutId) {
|
|
146
|
+
return `${TP_API_BASE}/fitness/v6/athletes/${athleteId}/workouts/${workoutId}/fordevice/fit`;
|
|
147
|
+
}
|
|
148
|
+
// ---- Performance Management Chart ----
|
|
149
|
+
/**
|
|
150
|
+
* Get CTL/ATL/TSB (fitness/fatigue/form) data for a date range.
|
|
151
|
+
* This is the Performance Management Chart data.
|
|
152
|
+
*/
|
|
153
|
+
async getPerformanceManagement(athleteId, startDate, endDate, options = {}) {
|
|
154
|
+
const body = {
|
|
155
|
+
atlConstant: options.atlConstant ?? 7,
|
|
156
|
+
atlStart: options.atlStart ?? 0,
|
|
157
|
+
ctlConstant: options.ctlConstant ?? 42,
|
|
158
|
+
ctlStart: options.ctlStart ?? 0,
|
|
159
|
+
workoutTypes: options.workoutTypes ?? [],
|
|
160
|
+
};
|
|
161
|
+
return this.#post(TP_API_BASE, `/fitness/v1/athletes/${athleteId}/reporting/performancedata/${startDate}/${endDate}`, body);
|
|
162
|
+
}
|
|
163
|
+
// ---- Personal Records ----
|
|
164
|
+
/**
|
|
165
|
+
* Get personal records for a sport type.
|
|
166
|
+
* @param sport - "Bike" or "Run"
|
|
167
|
+
* @param recordType - e.g., "power5sec", "power20min", "speed5K"
|
|
168
|
+
*/
|
|
169
|
+
async getPersonalRecords(athleteId, sport, recordType, startDate, endDate) {
|
|
170
|
+
const params = { prType: recordType };
|
|
171
|
+
if (startDate)
|
|
172
|
+
params.startDate = startDate;
|
|
173
|
+
if (endDate)
|
|
174
|
+
params.endDate = endDate;
|
|
175
|
+
return this.#get(TP_API_BASE, `/personalrecord/v2/athletes/${athleteId}/${sport}`, params);
|
|
176
|
+
}
|
|
177
|
+
// ---- Calendar Notes ----
|
|
178
|
+
async getCalendarNotes(athleteId, startDate, endDate) {
|
|
179
|
+
return this.#get(TP_API_BASE, `/fitness/v1/athletes/${athleteId}/calendarNote/${startDate}/${endDate}`);
|
|
180
|
+
}
|
|
181
|
+
// ---- Workout Analysis ----
|
|
182
|
+
/**
|
|
183
|
+
* Get detailed workout analysis with time-series data, zones, and laps.
|
|
184
|
+
* Uses the separate analysis API at api.peakswaresb.com.
|
|
185
|
+
*/
|
|
186
|
+
async getWorkoutAnalysis(workoutId, athleteId) {
|
|
187
|
+
return this.#post(TP_ANALYSIS_BASE, "/workout-analysis/v1/analyze", { workoutId, viewingPersonId: athleteId });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { CanonicalActivityType } from "@dofek/training/training";
|
|
2
|
+
import type { TrainingPeaksPmcEntry, TrainingPeaksWorkout } from "./types.ts";
|
|
3
|
+
export interface ParsedWorkout {
|
|
4
|
+
externalId: string;
|
|
5
|
+
activityType: CanonicalActivityType;
|
|
6
|
+
name: string;
|
|
7
|
+
startedAt: Date;
|
|
8
|
+
endedAt: Date;
|
|
9
|
+
completed: boolean;
|
|
10
|
+
distanceMeters?: number;
|
|
11
|
+
durationSeconds?: number;
|
|
12
|
+
averageHeartRate?: number;
|
|
13
|
+
maxHeartRate?: number;
|
|
14
|
+
averagePower?: number;
|
|
15
|
+
normalizedPower?: number;
|
|
16
|
+
cadenceAverage?: number;
|
|
17
|
+
elevationGain?: number;
|
|
18
|
+
trainingStressScore?: number;
|
|
19
|
+
intensityFactor?: number;
|
|
20
|
+
feeling?: number;
|
|
21
|
+
rpe?: number;
|
|
22
|
+
raw: TrainingPeaksWorkout;
|
|
23
|
+
}
|
|
24
|
+
export interface ParsedPerformanceManagement {
|
|
25
|
+
date: string;
|
|
26
|
+
tss: number;
|
|
27
|
+
fitness: number;
|
|
28
|
+
fatigue: number;
|
|
29
|
+
form: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Convert TrainingPeaks decimal hours to seconds.
|
|
33
|
+
* TrainingPeaks stores totalTime as decimal hours (e.g., 1.25 = 1h15m).
|
|
34
|
+
*/
|
|
35
|
+
export declare function decimalHoursToSeconds(hours: number): number;
|
|
36
|
+
/** Parse a TrainingPeaks workout into our normalized format. */
|
|
37
|
+
export declare function parseTrainingPeaksWorkout(raw: TrainingPeaksWorkout): ParsedWorkout;
|
|
38
|
+
/** Parse a PMC data point into our normalized format. */
|
|
39
|
+
export declare function parseTrainingPeaksPmc(entry: TrainingPeaksPmcEntry): ParsedPerformanceManagement;
|
|
40
|
+
//# sourceMappingURL=parsing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parsing.d.ts","sourceRoot":"","sources":["../src/parsing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEtE,OAAO,KAAK,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAM9E,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,qBAAqB,CAAC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,IAAI,CAAC;IAChB,OAAO,EAAE,IAAI,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,oBAAoB,CAAC;CAC3B;AAED,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAMD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,gEAAgE;AAChE,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,oBAAoB,GAAG,aAAa,CA+BlF;AAED,yDAAyD;AACzD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,qBAAqB,GAAG,2BAA2B,CAQ/F"}
|
package/dist/parsing.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { mapTrainingPeaksSport } from "./sports.js";
|
|
2
|
+
// ============================================================
|
|
3
|
+
// Parsing functions
|
|
4
|
+
// ============================================================
|
|
5
|
+
/**
|
|
6
|
+
* Convert TrainingPeaks decimal hours to seconds.
|
|
7
|
+
* TrainingPeaks stores totalTime as decimal hours (e.g., 1.25 = 1h15m).
|
|
8
|
+
*/
|
|
9
|
+
export function decimalHoursToSeconds(hours) {
|
|
10
|
+
return Math.round(hours * 3600);
|
|
11
|
+
}
|
|
12
|
+
/** Parse a TrainingPeaks workout into our normalized format. */
|
|
13
|
+
export function parseTrainingPeaksWorkout(raw) {
|
|
14
|
+
// Use startTime if available, fall back to workoutDay
|
|
15
|
+
const startTimeStr = raw.startTime ?? raw.startTimePlanned ?? raw.workoutDay;
|
|
16
|
+
const startedAt = new Date(startTimeStr);
|
|
17
|
+
// Calculate end time from actual or planned duration
|
|
18
|
+
const totalTimeHours = raw.totalTime ?? raw.totalTimePlanned ?? 0;
|
|
19
|
+
const durationSeconds = decimalHoursToSeconds(totalTimeHours);
|
|
20
|
+
const endedAt = new Date(startedAt.getTime() + durationSeconds * 1000);
|
|
21
|
+
return {
|
|
22
|
+
externalId: String(raw.workoutId),
|
|
23
|
+
activityType: mapTrainingPeaksSport(raw.workoutTypeFamilyId),
|
|
24
|
+
name: raw.title,
|
|
25
|
+
startedAt,
|
|
26
|
+
endedAt,
|
|
27
|
+
completed: raw.completed,
|
|
28
|
+
distanceMeters: raw.distance,
|
|
29
|
+
durationSeconds: durationSeconds > 0 ? durationSeconds : undefined,
|
|
30
|
+
averageHeartRate: raw.heartRateAverage,
|
|
31
|
+
maxHeartRate: raw.heartRateMaximum,
|
|
32
|
+
averagePower: raw.powerAverage,
|
|
33
|
+
normalizedPower: raw.normalizedPowerActual,
|
|
34
|
+
cadenceAverage: raw.cadenceAverage,
|
|
35
|
+
elevationGain: raw.elevationGain,
|
|
36
|
+
trainingStressScore: raw.tssActual ?? raw.tssPlanned,
|
|
37
|
+
intensityFactor: raw.if ?? raw.ifPlanned,
|
|
38
|
+
feeling: raw.feeling,
|
|
39
|
+
rpe: raw.rpe,
|
|
40
|
+
raw,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** Parse a PMC data point into our normalized format. */
|
|
44
|
+
export function parseTrainingPeaksPmc(entry) {
|
|
45
|
+
return {
|
|
46
|
+
date: entry.workoutDay,
|
|
47
|
+
tss: entry.tssActual,
|
|
48
|
+
fitness: entry.ctl,
|
|
49
|
+
fatigue: entry.atl,
|
|
50
|
+
form: entry.tsb,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=parsing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parsing.js","sourceRoot":"","sources":["../src/parsing.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAqCpD,+DAA+D;AAC/D,oBAAoB;AACpB,+DAA+D;AAE/D;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAa;IACjD,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;AAClC,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,yBAAyB,CAAC,GAAyB;IACjE,sDAAsD;IACtD,MAAM,YAAY,GAAG,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,gBAAgB,IAAI,GAAG,CAAC,UAAU,CAAC;IAC7E,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,CAAC;IAEzC,qDAAqD;IACrD,MAAM,cAAc,GAAG,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,gBAAgB,IAAI,CAAC,CAAC;IAClE,MAAM,eAAe,GAAG,qBAAqB,CAAC,cAAc,CAAC,CAAC;IAC9D,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,eAAe,GAAG,IAAI,CAAC,CAAC;IAEvE,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC;QACjC,YAAY,EAAE,qBAAqB,CAAC,GAAG,CAAC,mBAAmB,CAAC;QAC5D,IAAI,EAAE,GAAG,CAAC,KAAK;QACf,SAAS;QACT,OAAO;QACP,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,cAAc,EAAE,GAAG,CAAC,QAAQ;QAC5B,eAAe,EAAE,eAAe,GAAG,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS;QAClE,gBAAgB,EAAE,GAAG,CAAC,gBAAgB;QACtC,YAAY,EAAE,GAAG,CAAC,gBAAgB;QAClC,YAAY,EAAE,GAAG,CAAC,YAAY;QAC9B,eAAe,EAAE,GAAG,CAAC,qBAAqB;QAC1C,cAAc,EAAE,GAAG,CAAC,cAAc;QAClC,aAAa,EAAE,GAAG,CAAC,aAAa;QAChC,mBAAmB,EAAE,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,UAAU;QACpD,eAAe,EAAE,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,SAAS;QACxC,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,GAAG;KACJ,CAAC;AACJ,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,qBAAqB,CAAC,KAA4B;IAChE,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,UAAU;QACtB,GAAG,EAAE,KAAK,CAAC,SAAS;QACpB,OAAO,EAAE,KAAK,CAAC,GAAG;QAClB,OAAO,EAAE,KAAK,CAAC,GAAG;QAClB,IAAI,EAAE,KAAK,CAAC,GAAG;KAChB,CAAC;AACJ,CAAC"}
|
package/dist/sports.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CanonicalActivityType } from "@dofek/training/training";
|
|
2
|
+
/**
|
|
3
|
+
* TrainingPeaks workout type family IDs → normalized sport type.
|
|
4
|
+
*/
|
|
5
|
+
export declare const TRAINING_PEAKS_SPORT_MAP: Record<number, CanonicalActivityType>;
|
|
6
|
+
/**
|
|
7
|
+
* Map a TrainingPeaks workoutTypeFamilyId to a normalized sport type.
|
|
8
|
+
*/
|
|
9
|
+
export declare function mapTrainingPeaksSport(familyId: number): CanonicalActivityType;
|
|
10
|
+
//# sourceMappingURL=sports.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sports.d.ts","sourceRoot":"","sources":["../src/sports.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEtE;;GAEG;AACH,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAc1E,CAAC;AAEF;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,qBAAqB,CAE7E"}
|
package/dist/sports.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TrainingPeaks workout type family IDs → normalized sport type.
|
|
3
|
+
*/
|
|
4
|
+
export const TRAINING_PEAKS_SPORT_MAP = {
|
|
5
|
+
1: "swimming",
|
|
6
|
+
2: "cycling",
|
|
7
|
+
3: "running",
|
|
8
|
+
4: "walking",
|
|
9
|
+
5: "rowing",
|
|
10
|
+
6: "skiing",
|
|
11
|
+
7: "strength",
|
|
12
|
+
8: "yoga",
|
|
13
|
+
9: "hiking",
|
|
14
|
+
10: "other",
|
|
15
|
+
11: "triathlon",
|
|
16
|
+
12: "other",
|
|
17
|
+
13: "cardio",
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Map a TrainingPeaks workoutTypeFamilyId to a normalized sport type.
|
|
21
|
+
*/
|
|
22
|
+
export function mapTrainingPeaksSport(familyId) {
|
|
23
|
+
return TRAINING_PEAKS_SPORT_MAP[familyId] ?? "other";
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=sports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sports.js","sourceRoot":"","sources":["../src/sports.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAA0C;IAC7E,CAAC,EAAE,UAAU;IACb,CAAC,EAAE,SAAS;IACZ,CAAC,EAAE,SAAS;IACZ,CAAC,EAAE,SAAS;IACZ,CAAC,EAAE,QAAQ;IACX,CAAC,EAAE,QAAQ;IACX,CAAC,EAAE,UAAU;IACb,CAAC,EAAE,MAAM;IACT,CAAC,EAAE,QAAQ;IACX,EAAE,EAAE,OAAO;IACX,EAAE,EAAE,WAAW;IACf,EAAE,EAAE,OAAO;IACX,EAAE,EAAE,QAAQ;CACb,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAgB;IACpD,OAAO,wBAAwB,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC;AACvD,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/** Token response from /users/v3/token */
|
|
2
|
+
export interface TrainingPeaksTokenResponse {
|
|
3
|
+
success: boolean;
|
|
4
|
+
token: {
|
|
5
|
+
access_token: string;
|
|
6
|
+
expires_in: number;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
/** User profile from /users/v3/user */
|
|
10
|
+
export interface TrainingPeaksUser {
|
|
11
|
+
user: {
|
|
12
|
+
personId: number;
|
|
13
|
+
firstName: string;
|
|
14
|
+
lastName: string;
|
|
15
|
+
email: string;
|
|
16
|
+
athletes: Array<{
|
|
17
|
+
athleteId: number;
|
|
18
|
+
}>;
|
|
19
|
+
settings: {
|
|
20
|
+
account: {
|
|
21
|
+
isPremium: boolean;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** Workout from /fitness/v6/athletes/{id}/workouts */
|
|
27
|
+
export interface TrainingPeaksWorkout {
|
|
28
|
+
workoutId: number;
|
|
29
|
+
athleteId: number;
|
|
30
|
+
workoutDay: string;
|
|
31
|
+
title: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
completed: boolean;
|
|
34
|
+
workoutTypeFamilyId: number;
|
|
35
|
+
workoutTypeValueId: number;
|
|
36
|
+
totalTimePlanned?: number;
|
|
37
|
+
totalTime?: number;
|
|
38
|
+
distancePlanned?: number;
|
|
39
|
+
distance?: number;
|
|
40
|
+
tssPlanned?: number;
|
|
41
|
+
tssActual?: number;
|
|
42
|
+
tssCalculationMethod?: string;
|
|
43
|
+
ifPlanned?: number;
|
|
44
|
+
if?: number;
|
|
45
|
+
powerAverage?: number;
|
|
46
|
+
normalizedPowerActual?: number;
|
|
47
|
+
heartRateAverage?: number;
|
|
48
|
+
heartRateMaximum?: number;
|
|
49
|
+
cadenceAverage?: number;
|
|
50
|
+
elevationGain?: number;
|
|
51
|
+
elevationLoss?: number;
|
|
52
|
+
calories?: number;
|
|
53
|
+
feeling?: number;
|
|
54
|
+
rpe?: number;
|
|
55
|
+
coachComments?: string;
|
|
56
|
+
athleteComments?: string;
|
|
57
|
+
startTime?: string;
|
|
58
|
+
startTimePlanned?: string;
|
|
59
|
+
lastModifiedDate?: string;
|
|
60
|
+
tags?: string[];
|
|
61
|
+
workoutFileFormats?: string[];
|
|
62
|
+
}
|
|
63
|
+
/** Performance Management Chart data point */
|
|
64
|
+
export interface TrainingPeaksPmcEntry {
|
|
65
|
+
workoutDay: string;
|
|
66
|
+
tssActual: number;
|
|
67
|
+
ctl: number;
|
|
68
|
+
atl: number;
|
|
69
|
+
tsb: number;
|
|
70
|
+
}
|
|
71
|
+
/** PMC request body */
|
|
72
|
+
export interface TrainingPeaksPmcRequest {
|
|
73
|
+
atlConstant: number;
|
|
74
|
+
atlStart: number;
|
|
75
|
+
ctlConstant: number;
|
|
76
|
+
ctlStart: number;
|
|
77
|
+
workoutTypes: number[];
|
|
78
|
+
}
|
|
79
|
+
/** Personal record entry */
|
|
80
|
+
export interface TrainingPeaksPersonalRecord {
|
|
81
|
+
workoutId: number;
|
|
82
|
+
workoutDay: string;
|
|
83
|
+
title: string;
|
|
84
|
+
value: number;
|
|
85
|
+
rank: number;
|
|
86
|
+
duration?: number;
|
|
87
|
+
distance?: number;
|
|
88
|
+
}
|
|
89
|
+
/** Calendar note */
|
|
90
|
+
export interface TrainingPeaksCalendarNote {
|
|
91
|
+
id: number;
|
|
92
|
+
athleteId: number;
|
|
93
|
+
date: string;
|
|
94
|
+
title: string;
|
|
95
|
+
description?: string;
|
|
96
|
+
}
|
|
97
|
+
/** Workout analysis response from api.peakswaresb.com */
|
|
98
|
+
export interface TrainingPeaksWorkoutAnalysis {
|
|
99
|
+
totals: {
|
|
100
|
+
duration?: number;
|
|
101
|
+
distance?: number;
|
|
102
|
+
calories?: number;
|
|
103
|
+
tss?: number;
|
|
104
|
+
normalizedPower?: number;
|
|
105
|
+
intensityFactor?: number;
|
|
106
|
+
averagePower?: number;
|
|
107
|
+
averageHeartRate?: number;
|
|
108
|
+
averageCadence?: number;
|
|
109
|
+
elevationGain?: number;
|
|
110
|
+
work?: number;
|
|
111
|
+
};
|
|
112
|
+
channels?: TrainingPeaksDataChannel[];
|
|
113
|
+
laps?: TrainingPeaksLapData[];
|
|
114
|
+
}
|
|
115
|
+
/** Time-series data channel */
|
|
116
|
+
export interface TrainingPeaksDataChannel {
|
|
117
|
+
name: string;
|
|
118
|
+
min: number;
|
|
119
|
+
max: number;
|
|
120
|
+
avg: number;
|
|
121
|
+
zones?: Array<{
|
|
122
|
+
start: number;
|
|
123
|
+
end: number;
|
|
124
|
+
duration: number;
|
|
125
|
+
}>;
|
|
126
|
+
}
|
|
127
|
+
/** Lap data */
|
|
128
|
+
export interface TrainingPeaksLapData {
|
|
129
|
+
lapNumber: number;
|
|
130
|
+
duration: number;
|
|
131
|
+
distance?: number;
|
|
132
|
+
averagePower?: number;
|
|
133
|
+
normalizedPower?: number;
|
|
134
|
+
averageHeartRate?: number;
|
|
135
|
+
averageCadence?: number;
|
|
136
|
+
elevationGain?: number;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// TrainingPeaks internal API response types
|
|
3
|
+
// Reverse-engineered from app.trainingpeaks.com
|
|
4
|
+
// ============================================================
|
|
5
|
+
export {};
|
|
6
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,4CAA4C;AAC5C,gDAAgD;AAChD,+DAA+D"}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dofek/trainingpeaks",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Unofficial TrainingPeaks internal API client using cookie-based authentication",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Asherlc/dofek.git",
|
|
10
|
+
"directory": "packages/trainingpeaks-connect"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/Asherlc/dofek/tree/main/packages/trainingpeaks-connect#readme",
|
|
13
|
+
"bugs": "https://github.com/Asherlc/dofek/issues",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"trainingpeaks",
|
|
16
|
+
"cycling",
|
|
17
|
+
"running",
|
|
18
|
+
"api-client",
|
|
19
|
+
"unofficial"
|
|
20
|
+
],
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=22.14.0"
|
|
23
|
+
},
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE"
|
|
29
|
+
],
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/client.d.ts",
|
|
33
|
+
"import": "./dist/client.js"
|
|
34
|
+
},
|
|
35
|
+
"./*": {
|
|
36
|
+
"types": "./dist/*.d.ts",
|
|
37
|
+
"import": "./dist/*.js"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"test:watch": "vitest --watch",
|
|
46
|
+
"build": "tsc",
|
|
47
|
+
"prepack": "pnpm run build",
|
|
48
|
+
"lint": "biome check .",
|
|
49
|
+
"typecheck": "tsc --noEmit"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@dofek/provider-http": "0.1.0",
|
|
53
|
+
"@dofek/training": "0.1.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"typescript": "6.0.3",
|
|
57
|
+
"vitest": "3.2.6"
|
|
58
|
+
},
|
|
59
|
+
"gitHead": "ac95258c753c7e75b57da7ae47426d22b78e8c01"
|
|
60
|
+
}
|