@dofek/trainerroad 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 +134 -0
- package/dist/client.d.ts +13 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +77 -0
- package/dist/client.js.map +1 -0
- package/dist/parsing.d.ts +13 -0
- package/dist/parsing.d.ts.map +1 -0
- package/dist/parsing.js +40 -0
- package/dist/parsing.js.map +1 -0
- package/dist/types.d.ts +31 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -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,134 @@
|
|
|
1
|
+
# @dofek/trainerroad
|
|
2
|
+
|
|
3
|
+
Unofficial TypeScript client for private TrainerRoad web endpoints. It signs in
|
|
4
|
+
through the observed HTML form flow and retrieves member information,
|
|
5
|
+
activities, and career data.
|
|
6
|
+
|
|
7
|
+
This package is not affiliated with, endorsed by, or supported by TrainerRoad.
|
|
8
|
+
Its authentication page and undocumented endpoints may change without notice.
|
|
9
|
+
|
|
10
|
+
## Requirements and installation
|
|
11
|
+
|
|
12
|
+
Requires Node.js 22.14 or newer and its built-in
|
|
13
|
+
[`fetch`](https://nodejs.org/api/globals.html#fetch) implementation.
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npm install @dofek/trainerroad
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
Save this as `example.mjs`, set `TRAINERROAD_USERNAME` and
|
|
22
|
+
`TRAINERROAD_PASSWORD`, then run `node example.mjs`.
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
import { TrainerRoadClient } from "@dofek/trainerroad";
|
|
26
|
+
|
|
27
|
+
const login = process.env.TRAINERROAD_USERNAME;
|
|
28
|
+
const password = process.env.TRAINERROAD_PASSWORD;
|
|
29
|
+
if (!login || !password) {
|
|
30
|
+
throw new Error("Set TRAINERROAD_USERNAME and TRAINERROAD_PASSWORD");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const { authCookie, username } = await TrainerRoadClient.signIn(
|
|
34
|
+
login,
|
|
35
|
+
password,
|
|
36
|
+
);
|
|
37
|
+
const client = new TrainerRoadClient(authCookie);
|
|
38
|
+
const activities = await client.getActivities(
|
|
39
|
+
username,
|
|
40
|
+
"2026-07-01",
|
|
41
|
+
"2026-07-07",
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
console.log(activities);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Dates passed to `getActivities` use `YYYY-MM-DD`.
|
|
48
|
+
|
|
49
|
+
## Public API
|
|
50
|
+
|
|
51
|
+
- `TrainerRoadClient.signIn(username, password, fetch?)` returns the observed
|
|
52
|
+
`SharedTrainerRoadAuth` cookie value and canonical account username.
|
|
53
|
+
- `new TrainerRoadClient(authCookie, fetch?)` creates a session-backed client.
|
|
54
|
+
- `client.getMemberInfo()` retrieves the current member's ID and username.
|
|
55
|
+
- `client.getActivities(username, startDate, endDate)` retrieves scheduled and
|
|
56
|
+
completed calendar activities.
|
|
57
|
+
- `client.getCareer(username)` retrieves career data such as FTP and weight.
|
|
58
|
+
|
|
59
|
+
Supported deep imports:
|
|
60
|
+
|
|
61
|
+
- `@dofek/trainerroad/client` — client class.
|
|
62
|
+
- `@dofek/trainerroad/parsing` — activity type mapping and
|
|
63
|
+
`parseTrainerRoadActivity`.
|
|
64
|
+
- `@dofek/trainerroad/types` — raw member, activity, and career interfaces.
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import { parseTrainerRoadActivity } from "@dofek/trainerroad/parsing";
|
|
68
|
+
import type { TrainerRoadActivity } from "@dofek/trainerroad/types";
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Authentication and persistence
|
|
72
|
+
|
|
73
|
+
The [current implementation](https://github.com/Asherlc/dofek/blob/main/packages/trainerroad-client/src/client.ts)
|
|
74
|
+
loads `/app/login`, extracts the `__RequestVerificationToken` CSRF field and
|
|
75
|
+
initial cookies, submits the login form, and returns the
|
|
76
|
+
`SharedTrainerRoadAuth` cookie set by the response. It does not use embedded
|
|
77
|
+
application credentials.
|
|
78
|
+
|
|
79
|
+
Treat `authCookie` as a password-equivalent session secret and keep it in
|
|
80
|
+
encrypted storage. The package cannot inspect its expiry and has no cookie
|
|
81
|
+
refresh endpoint. If an authenticated request begins failing, call `signIn`
|
|
82
|
+
again and replace the stored cookie. Never log or commit the cookie or account
|
|
83
|
+
password.
|
|
84
|
+
|
|
85
|
+
## Rate limits and errors
|
|
86
|
+
|
|
87
|
+
The shared
|
|
88
|
+
[rate-limit wrapper](https://github.com/Asherlc/dofek/blob/main/packages/provider-http/src/rate-limit.ts)
|
|
89
|
+
throws `ProviderRateLimitError` for `429` and
|
|
90
|
+
`ProviderServiceUnavailableError` for `502`, `503`, and `504`. Both expose
|
|
91
|
+
`providerId`, `statusCode`, `responseBody`, and `retryAfterSeconds`. The latter
|
|
92
|
+
follows the HTTP
|
|
93
|
+
[`Retry-After`](https://www.rfc-editor.org/rfc/rfc9110.html#name-retry-after)
|
|
94
|
+
header when present. Other unsuccessful API responses throw a regular `Error`;
|
|
95
|
+
a failed login without the expected session cookie also throws `Error`.
|
|
96
|
+
|
|
97
|
+
If your application handles these error classes directly, declare
|
|
98
|
+
`@dofek/provider-http` as a direct dependency:
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
npm install @dofek/provider-http
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import {
|
|
106
|
+
ProviderRateLimitError,
|
|
107
|
+
ProviderServiceUnavailableError,
|
|
108
|
+
} from "@dofek/provider-http/rate-limit";
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
await client.getCareer(username);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (
|
|
114
|
+
error instanceof ProviderRateLimitError ||
|
|
115
|
+
error instanceof ProviderServiceUnavailableError
|
|
116
|
+
) {
|
|
117
|
+
console.error(error.providerId, error.statusCode, error.retryAfterSeconds);
|
|
118
|
+
}
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Parsing behavior
|
|
124
|
+
|
|
125
|
+
`parseTrainerRoadActivity` maps the currently observed activity names and
|
|
126
|
+
`IsOutside` flag to canonical activity types. It treats `CompletedDate` as the
|
|
127
|
+
end time and derives the start by subtracting `Duration`.
|
|
128
|
+
|
|
129
|
+
## Project
|
|
130
|
+
|
|
131
|
+
- [Source](https://github.com/Asherlc/dofek/tree/main/packages/trainerroad-client)
|
|
132
|
+
- [Report an issue](https://github.com/Asherlc/dofek/issues)
|
|
133
|
+
- [Contribute a pull request](https://github.com/Asherlc/dofek/pulls)
|
|
134
|
+
- License: [MIT](./LICENSE)
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TrainerRoadActivity, TrainerRoadCareer, TrainerRoadMemberInfo } from "./types.ts";
|
|
2
|
+
export declare class TrainerRoadClient {
|
|
3
|
+
#private;
|
|
4
|
+
constructor(authCookie: string, fetchFn?: typeof globalThis.fetch);
|
|
5
|
+
getMemberInfo(): Promise<TrainerRoadMemberInfo>;
|
|
6
|
+
getActivities(username: string, startDate: string, endDate: string): Promise<TrainerRoadActivity[]>;
|
|
7
|
+
getCareer(username: string): Promise<TrainerRoadCareer>;
|
|
8
|
+
static signIn(username: string, password: string, fetchFn?: typeof globalThis.fetch): Promise<{
|
|
9
|
+
authCookie: string;
|
|
10
|
+
username: string;
|
|
11
|
+
}>;
|
|
12
|
+
}
|
|
13
|
+
//# 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,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAIhG,qBAAa,iBAAiB;;gBAIhB,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,OAAO,UAAU,CAAC,KAAwB;IAsB7E,aAAa,IAAI,OAAO,CAAC,qBAAqB,CAAC;IAI/C,aAAa,CACjB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAM3B,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;WAIhD,MAAM,CACjB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,OAAO,UAAU,CAAC,KAAwB,GAClD,OAAO,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAkDrD"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { createRateLimitAwareFetch } from "@dofek/provider-http/rate-limit";
|
|
2
|
+
const TRAINERROAD_BASE = "https://www.trainerroad.com";
|
|
3
|
+
export class TrainerRoadClient {
|
|
4
|
+
#authCookie;
|
|
5
|
+
#fetchFn;
|
|
6
|
+
constructor(authCookie, fetchFn = globalThis.fetch) {
|
|
7
|
+
this.#authCookie = authCookie;
|
|
8
|
+
this.#fetchFn = createRateLimitAwareFetch(fetchFn, { providerId: "trainerroad" });
|
|
9
|
+
}
|
|
10
|
+
async #get(path) {
|
|
11
|
+
const url = `${TRAINERROAD_BASE}${path}`;
|
|
12
|
+
const response = await this.#fetchFn(url, {
|
|
13
|
+
headers: {
|
|
14
|
+
Cookie: `SharedTrainerRoadAuth=${this.#authCookie}`,
|
|
15
|
+
Accept: "application/json",
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
if (!response.ok) {
|
|
19
|
+
const text = await response.text();
|
|
20
|
+
throw new Error(`TrainerRoad API error (${response.status}): ${text}`);
|
|
21
|
+
}
|
|
22
|
+
return response.json();
|
|
23
|
+
}
|
|
24
|
+
async getMemberInfo() {
|
|
25
|
+
return this.#get("/app/api/member-info");
|
|
26
|
+
}
|
|
27
|
+
async getActivities(username, startDate, endDate) {
|
|
28
|
+
return this.#get(`/app/api/calendar/activities/${username}?startDate=${startDate}&endDate=${endDate}`);
|
|
29
|
+
}
|
|
30
|
+
async getCareer(username) {
|
|
31
|
+
return this.#get(`/app/api/career/${username}/new`);
|
|
32
|
+
}
|
|
33
|
+
static async signIn(username, password, fetchFn = globalThis.fetch) {
|
|
34
|
+
const rateLimitFetchFn = createRateLimitAwareFetch(fetchFn, { providerId: "trainerroad" });
|
|
35
|
+
// First, get the CSRF token from the login page
|
|
36
|
+
const loginPageResponse = await rateLimitFetchFn(`${TRAINERROAD_BASE}/app/login`, {
|
|
37
|
+
redirect: "manual",
|
|
38
|
+
});
|
|
39
|
+
const loginPageHtml = await loginPageResponse.text();
|
|
40
|
+
// Extract __RequestVerificationToken from the page
|
|
41
|
+
const tokenMatch = loginPageHtml.match(/name="__RequestVerificationToken"\s+value="([^"]+)"/);
|
|
42
|
+
const csrfToken = tokenMatch?.[1] ?? "";
|
|
43
|
+
// Extract cookies from the login page response
|
|
44
|
+
const pageCookies = loginPageResponse.headers.getSetCookie?.() ?? [];
|
|
45
|
+
const cookieHeader = pageCookies.map((c) => c.split(";")[0]).join("; ");
|
|
46
|
+
// Submit login form
|
|
47
|
+
const loginResponse = await rateLimitFetchFn(`${TRAINERROAD_BASE}/app/login`, {
|
|
48
|
+
method: "POST",
|
|
49
|
+
headers: {
|
|
50
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
51
|
+
Cookie: cookieHeader,
|
|
52
|
+
},
|
|
53
|
+
body: new URLSearchParams({
|
|
54
|
+
Username: username,
|
|
55
|
+
Password: password,
|
|
56
|
+
__RequestVerificationToken: csrfToken,
|
|
57
|
+
}),
|
|
58
|
+
redirect: "manual",
|
|
59
|
+
});
|
|
60
|
+
// Extract auth cookie from response
|
|
61
|
+
const responseCookies = loginResponse.headers.getSetCookie?.() ?? [];
|
|
62
|
+
const authCookieEntry = responseCookies.find((c) => c.startsWith("SharedTrainerRoadAuth="));
|
|
63
|
+
if (!authCookieEntry) {
|
|
64
|
+
throw new Error("TrainerRoad login failed — no auth cookie returned");
|
|
65
|
+
}
|
|
66
|
+
// authCookieEntry was matched by startsWith("SharedTrainerRoadAuth="), so it
|
|
67
|
+
// always contains "=" — take everything after the first one (the value may
|
|
68
|
+
// itself contain "=") up to the first attribute separator.
|
|
69
|
+
const firstEqualsIndex = authCookieEntry.indexOf("=");
|
|
70
|
+
const authCookieValue = authCookieEntry.slice(firstEqualsIndex + 1).split(";")[0] ?? "";
|
|
71
|
+
// Get username from member info
|
|
72
|
+
const client = new TrainerRoadClient(authCookieValue, fetchFn);
|
|
73
|
+
const memberInfo = await client.getMemberInfo();
|
|
74
|
+
return { authCookie: authCookieValue, username: memberInfo.Username };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# 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,13 @@
|
|
|
1
|
+
import type { CanonicalActivityType } from "@dofek/training/training";
|
|
2
|
+
import type { TrainerRoadActivity } from "./types.ts";
|
|
3
|
+
export interface ParsedTrainerRoadActivity {
|
|
4
|
+
externalId: string;
|
|
5
|
+
activityType: CanonicalActivityType;
|
|
6
|
+
name: string;
|
|
7
|
+
startedAt: Date;
|
|
8
|
+
endedAt: Date;
|
|
9
|
+
raw: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
export declare function mapTrainerRoadActivityType(activityType: string, isOutside: boolean): CanonicalActivityType;
|
|
12
|
+
export declare function parseTrainerRoadActivity(act: TrainerRoadActivity): ParsedTrainerRoadActivity;
|
|
13
|
+
//# 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;AACtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD,MAAM,WAAW,yBAAyB;IACxC,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,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B;AAED,wBAAgB,0BAA0B,CACxC,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,OAAO,GACjB,qBAAqB,CAQvB;AAED,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,mBAAmB,GAAG,yBAAyB,CA4B5F"}
|
package/dist/parsing.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export function mapTrainerRoadActivityType(activityType, isOutside) {
|
|
2
|
+
const type = activityType.toLowerCase();
|
|
3
|
+
if (type.includes("ride") || type.includes("cycling")) {
|
|
4
|
+
return isOutside ? "cycling" : "virtual_cycling";
|
|
5
|
+
}
|
|
6
|
+
if (type.includes("run"))
|
|
7
|
+
return "running";
|
|
8
|
+
if (type.includes("swim"))
|
|
9
|
+
return "swimming";
|
|
10
|
+
return "other";
|
|
11
|
+
}
|
|
12
|
+
export function parseTrainerRoadActivity(act) {
|
|
13
|
+
const completedAt = new Date(act.CompletedDate);
|
|
14
|
+
const startedAt = new Date(completedAt.getTime() - act.Duration * 1000);
|
|
15
|
+
return {
|
|
16
|
+
externalId: String(act.Id),
|
|
17
|
+
activityType: mapTrainerRoadActivityType(act.ActivityType, act.IsOutside),
|
|
18
|
+
name: act.WorkoutName,
|
|
19
|
+
startedAt,
|
|
20
|
+
endedAt: completedAt,
|
|
21
|
+
raw: {
|
|
22
|
+
tss: act.Tss,
|
|
23
|
+
distanceMeters: act.DistanceInMeters,
|
|
24
|
+
normalizedPower: act.NormalizedPower,
|
|
25
|
+
avgPower: act.AveragePower,
|
|
26
|
+
maxPower: act.MaxPower,
|
|
27
|
+
avgHeartRate: act.AverageHeartRate,
|
|
28
|
+
maxHeartRate: act.MaxHeartRate,
|
|
29
|
+
avgCadence: act.AverageCadence,
|
|
30
|
+
maxCadence: act.MaxCadence,
|
|
31
|
+
calories: act.Calories,
|
|
32
|
+
elevationGain: act.ElevationGainInMeters,
|
|
33
|
+
avgSpeed: act.AverageSpeed,
|
|
34
|
+
maxSpeed: act.MaxSpeed,
|
|
35
|
+
intensityFactor: act.IfFactor,
|
|
36
|
+
isOutside: act.IsOutside,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=parsing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parsing.js","sourceRoot":"","sources":["../src/parsing.ts"],"names":[],"mappings":"AAYA,MAAM,UAAU,0BAA0B,CACxC,YAAoB,EACpB,SAAkB;IAElB,MAAM,IAAI,GAAG,YAAY,CAAC,WAAW,EAAE,CAAC;IACxC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACtD,OAAO,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,iBAAiB,CAAC;IACnD,CAAC;IACD,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC3C,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,UAAU,CAAC;IAC7C,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,GAAwB;IAC/D,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAChD,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;IAExE,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,YAAY,EAAE,0BAA0B,CAAC,GAAG,CAAC,YAAY,EAAE,GAAG,CAAC,SAAS,CAAC;QACzE,IAAI,EAAE,GAAG,CAAC,WAAW;QACrB,SAAS;QACT,OAAO,EAAE,WAAW;QACpB,GAAG,EAAE;YACH,GAAG,EAAE,GAAG,CAAC,GAAG;YACZ,cAAc,EAAE,GAAG,CAAC,gBAAgB;YACpC,eAAe,EAAE,GAAG,CAAC,eAAe;YACpC,QAAQ,EAAE,GAAG,CAAC,YAAY;YAC1B,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,YAAY,EAAE,GAAG,CAAC,gBAAgB;YAClC,YAAY,EAAE,GAAG,CAAC,YAAY;YAC9B,UAAU,EAAE,GAAG,CAAC,cAAc;YAC9B,UAAU,EAAE,GAAG,CAAC,UAAU;YAC1B,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,aAAa,EAAE,GAAG,CAAC,qBAAqB;YACxC,QAAQ,EAAE,GAAG,CAAC,YAAY;YAC1B,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,eAAe,EAAE,GAAG,CAAC,QAAQ;YAC7B,SAAS,EAAE,GAAG,CAAC,SAAS;SACzB;KACF,CAAC;AACJ,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface TrainerRoadMemberInfo {
|
|
2
|
+
MemberId: number;
|
|
3
|
+
Username: string;
|
|
4
|
+
}
|
|
5
|
+
export interface TrainerRoadActivity {
|
|
6
|
+
Id: number;
|
|
7
|
+
WorkoutName: string;
|
|
8
|
+
CompletedDate: string;
|
|
9
|
+
Duration: number;
|
|
10
|
+
Tss: number;
|
|
11
|
+
DistanceInMeters: number;
|
|
12
|
+
IsOutside: boolean;
|
|
13
|
+
ActivityType: string;
|
|
14
|
+
IfFactor: number;
|
|
15
|
+
NormalizedPower: number;
|
|
16
|
+
AveragePower: number;
|
|
17
|
+
MaxPower: number;
|
|
18
|
+
AverageHeartRate: number;
|
|
19
|
+
MaxHeartRate: number;
|
|
20
|
+
AverageCadence: number;
|
|
21
|
+
MaxCadence: number;
|
|
22
|
+
Calories: number;
|
|
23
|
+
ElevationGainInMeters: number;
|
|
24
|
+
AverageSpeed: number;
|
|
25
|
+
MaxSpeed: number;
|
|
26
|
+
}
|
|
27
|
+
export interface TrainerRoadCareer {
|
|
28
|
+
Ftp: number;
|
|
29
|
+
Weight: number;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dofek/trainerroad",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Unofficial TrainerRoad API client using reverse-engineered 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/trainerroad-client"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/Asherlc/dofek/tree/main/packages/trainerroad-client#readme",
|
|
13
|
+
"bugs": "https://github.com/Asherlc/dofek/issues",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"trainerroad",
|
|
16
|
+
"cycling",
|
|
17
|
+
"api-client",
|
|
18
|
+
"unofficial",
|
|
19
|
+
"typescript"
|
|
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
|
+
}
|