suunto-api-wrapper 1.0.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/README.md +163 -0
- package/dist/index.d.mts +511 -0
- package/dist/index.d.ts +511 -0
- package/dist/index.js +414 -0
- package/dist/index.mjs +361 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# suunto-api-wrapper
|
|
2
|
+
|
|
3
|
+
A small, typed TypeScript client for the **Suunto app API** (which is served by
|
|
4
|
+
the Sports Tracker backend at `api.sports-tracker.com`).
|
|
5
|
+
|
|
6
|
+
It handles the annoying parts and exposes the endpoints as
|
|
7
|
+
typed, resource‑grouped methods:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
const suunto = await SuuntoClient.login({ email, password });
|
|
11
|
+
|
|
12
|
+
const workouts = await suunto.workouts.own({ limit: 10 });
|
|
13
|
+
const profile = await suunto.users.byName("someuser");
|
|
14
|
+
const gear = await suunto.gear.latest("someuser");
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
No runtime dependencies — it's built on Node's native `fetch`.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## ⚠️ Disclaimer
|
|
22
|
+
|
|
23
|
+
This is an **unofficial** library. It is **not affiliated with, endorsed by, or
|
|
24
|
+
supported by Suunto or Sports Tracker** in any way.
|
|
25
|
+
|
|
26
|
+
- It talks to an **undocumented API** that can change or break without
|
|
27
|
+
notice.
|
|
28
|
+
- Use it **only with your own account and your own data**, at your own risk as it may break Suunto or Sports Tracker TOS. Be
|
|
29
|
+
respectful of the service: don't hammer the API or use it for scraping/abuse.
|
|
30
|
+
|
|
31
|
+
If Suunto or Sports Tracker request it, this project will comply.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
This package isn't published to npm yet. Install it from source:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
git clone <this-repo> suunto-api-wrapper
|
|
41
|
+
cd suunto-api-wrapper
|
|
42
|
+
npm install
|
|
43
|
+
npm run build # emits ESM + CJS + .d.ts into dist/
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Then consume it from another project (e.g. via a local path or `npm link`):
|
|
47
|
+
|
|
48
|
+
```jsonc
|
|
49
|
+
// package.json
|
|
50
|
+
{
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"suunto-api-wrapper": "file:../suunto-api-wrapper"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The package ships both **ESM** and **CommonJS** builds with full type
|
|
58
|
+
declarations, so `import` and `require` both work:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import { SuuntoClient } from "suunto-api-wrapper"; // ESM
|
|
62
|
+
const { SuuntoClient } = require("suunto-api-wrapper"); // CJS
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Usage
|
|
68
|
+
|
|
69
|
+
### Authenticating
|
|
70
|
+
|
|
71
|
+
`SuuntoClient.login` performs the email/password login, stores the returned
|
|
72
|
+
session key, and returns a ready‑to‑use client. Every request it makes is authed.
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { SuuntoClient } from "suunto-api-wrapper";
|
|
76
|
+
|
|
77
|
+
const suunto = await SuuntoClient.login({
|
|
78
|
+
email: "you@example.com",
|
|
79
|
+
password: "your-password",
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
console.log(suunto.sessionKey); // the active session key
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Resource namespaces
|
|
86
|
+
|
|
87
|
+
The client groups endpoints by resource. Each call returns the parsed JSON
|
|
88
|
+
payload, typed to the real API response shape (an envelope of
|
|
89
|
+
`{ error, payload, metadata }`).
|
|
90
|
+
|
|
91
|
+
| Namespace | Method | Endpoint |
|
|
92
|
+
| ------------------ | ------------------------------ | ----------------------------------------- |
|
|
93
|
+
| `suunto.workouts` | `.own(params?)` | your own workouts |
|
|
94
|
+
| | `.public(username, params?)` | a user's public workouts |
|
|
95
|
+
| `suunto.users` | `.byName(username)` | a user's public profile |
|
|
96
|
+
| | `.search(terms)` | search for users |
|
|
97
|
+
| `suunto.gear` | `.latest(username, params?)` | a user's latest gear |
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
// Workouts
|
|
101
|
+
const own = await suunto.workouts.own({ limit: 20, offset: 0, since: 0 });
|
|
102
|
+
const publicItems = await suunto.workouts.public("someuser", { limit: 40 });
|
|
103
|
+
|
|
104
|
+
// Users
|
|
105
|
+
const profile = await suunto.users.byName("someuser");
|
|
106
|
+
const matches = await suunto.users.search("john");
|
|
107
|
+
|
|
108
|
+
// Gear
|
|
109
|
+
const gear = await suunto.gear.latest("someuser", { allTypes: true });
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The response payloads are fully typed. For example, workout `extensions` are a
|
|
113
|
+
discriminated union you can narrow on:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
for (const w of own.payload) {
|
|
117
|
+
for (const ext of w.extensions) {
|
|
118
|
+
if (ext.type === "WeatherExtension") {
|
|
119
|
+
console.log(ext.temperature, ext.humidity); // typed!
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Unauthenticated access
|
|
126
|
+
|
|
127
|
+
Some endpoints (like fetching a public profile) don't require login. Use the
|
|
128
|
+
`unauthenticated()` factory to create a client that sends **no** credentials:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
const guest = SuuntoClient.unauthenticated();
|
|
132
|
+
const profile = await guest.users.byName("someuser");
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Escape hatch: the raw HTTP client
|
|
136
|
+
|
|
137
|
+
For endpoints not yet wrapped, reach the underlying HTTP client directly. It
|
|
138
|
+
already carries the auth headers, retries, and timeouts:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
const res = await suunto.http.get("/apiserver/v1/some/other/endpoint", {
|
|
142
|
+
query: { foo: "bar" },
|
|
143
|
+
});
|
|
144
|
+
console.log(res.status, res.data);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Development
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
npm run build # bundle to dist/ (ESM + CJS + types) with tsup
|
|
153
|
+
npm run dev # rebuild on change (tsup --watch)
|
|
154
|
+
npm run typecheck # tsc --noEmit
|
|
155
|
+
npm test # run the vitest suite once
|
|
156
|
+
npm run test:watch # vitest in watch mode
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
ISC.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
type RequestBody = NonNullable<RequestInit["body"]>;
|
|
2
|
+
type QueryValue = string | number | boolean | null | undefined;
|
|
3
|
+
type Query = Record<string, QueryValue | QueryValue[]>;
|
|
4
|
+
interface HttpClientOptions {
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
headers?: Record<string, string>;
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
retries?: number;
|
|
9
|
+
retryBackoffMs?: number;
|
|
10
|
+
fetch?: typeof fetch;
|
|
11
|
+
beforeRequest?: (ctx: RequestContext) => void | RequestContext | Promise<void | RequestContext>;
|
|
12
|
+
}
|
|
13
|
+
interface RequestContext {
|
|
14
|
+
method: string;
|
|
15
|
+
url: string;
|
|
16
|
+
headers: Record<string, string>;
|
|
17
|
+
body?: RequestBody | null;
|
|
18
|
+
}
|
|
19
|
+
interface RequestOptions {
|
|
20
|
+
query?: Query;
|
|
21
|
+
headers?: Record<string, string>;
|
|
22
|
+
json?: unknown;
|
|
23
|
+
body?: RequestBody | null;
|
|
24
|
+
timeoutMs?: number;
|
|
25
|
+
retries?: number;
|
|
26
|
+
signal?: AbortSignal;
|
|
27
|
+
}
|
|
28
|
+
interface HttpResponse<T> {
|
|
29
|
+
data: T;
|
|
30
|
+
status: number;
|
|
31
|
+
headers: Headers;
|
|
32
|
+
}
|
|
33
|
+
declare class HttpError extends Error {
|
|
34
|
+
readonly status: number;
|
|
35
|
+
readonly url: string;
|
|
36
|
+
readonly method: string;
|
|
37
|
+
readonly body: unknown;
|
|
38
|
+
constructor(message: string, init: {
|
|
39
|
+
status: number;
|
|
40
|
+
url: string;
|
|
41
|
+
method: string;
|
|
42
|
+
body: unknown;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
declare class HttpClient {
|
|
47
|
+
private readonly baseUrl;
|
|
48
|
+
private readonly defaultHeaders;
|
|
49
|
+
private readonly timeoutMs;
|
|
50
|
+
private readonly retries;
|
|
51
|
+
private readonly retryBackoffMs;
|
|
52
|
+
private readonly fetchImpl;
|
|
53
|
+
private readonly beforeRequest?;
|
|
54
|
+
constructor(options?: HttpClientOptions);
|
|
55
|
+
get<T>(path: string, options?: RequestOptions): Promise<HttpResponse<T>>;
|
|
56
|
+
delete<T>(path: string, options?: RequestOptions): Promise<HttpResponse<T>>;
|
|
57
|
+
post<T>(path: string, options?: RequestOptions): Promise<HttpResponse<T>>;
|
|
58
|
+
put<T>(path: string, options?: RequestOptions): Promise<HttpResponse<T>>;
|
|
59
|
+
patch<T>(path: string, options?: RequestOptions): Promise<HttpResponse<T>>;
|
|
60
|
+
request<T>(method: string, path: string, options?: RequestOptions): Promise<HttpResponse<T>>;
|
|
61
|
+
private attempt;
|
|
62
|
+
private toResult;
|
|
63
|
+
private backoff;
|
|
64
|
+
private buildUrl;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
declare function generateXtotp(email: string, now?: number): string;
|
|
68
|
+
declare function secondsUntilRollover(now?: number): number;
|
|
69
|
+
|
|
70
|
+
interface LoginOptions {
|
|
71
|
+
email: string;
|
|
72
|
+
password: string;
|
|
73
|
+
version?: string;
|
|
74
|
+
userAgent?: string;
|
|
75
|
+
baseUrl?: string;
|
|
76
|
+
fetch?: typeof fetch;
|
|
77
|
+
timeoutMs?: number;
|
|
78
|
+
}
|
|
79
|
+
interface LoginResponse {
|
|
80
|
+
sessionkey?: string;
|
|
81
|
+
[key: string]: unknown;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
declare const SPORTS_TRACKER_API = "https://api.sports-tracker.com";
|
|
85
|
+
declare const DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36";
|
|
86
|
+
declare function login(options: LoginOptions): Promise<LoginResponse>;
|
|
87
|
+
declare function sessionTokenFrom(response: LoginResponse): string | undefined;
|
|
88
|
+
|
|
89
|
+
interface GetWorkoutsParams {
|
|
90
|
+
limit?: number;
|
|
91
|
+
sortonst?: boolean;
|
|
92
|
+
}
|
|
93
|
+
interface GetOwnWorkoutsParams {
|
|
94
|
+
since?: number;
|
|
95
|
+
offset?: number;
|
|
96
|
+
limit?: number;
|
|
97
|
+
}
|
|
98
|
+
/** GPS coordinate: x = longitude, y = latitude. */
|
|
99
|
+
interface Position {
|
|
100
|
+
x: number;
|
|
101
|
+
y: number;
|
|
102
|
+
}
|
|
103
|
+
interface Cadence {
|
|
104
|
+
max: number;
|
|
105
|
+
avg: number;
|
|
106
|
+
}
|
|
107
|
+
interface HrData {
|
|
108
|
+
workoutMaxHR: number;
|
|
109
|
+
workoutAvgHR: number;
|
|
110
|
+
userMaxHR: number;
|
|
111
|
+
avg: number;
|
|
112
|
+
hrmax: number;
|
|
113
|
+
max: number;
|
|
114
|
+
}
|
|
115
|
+
interface Gear {
|
|
116
|
+
manufacturer: string;
|
|
117
|
+
name: string;
|
|
118
|
+
displayName: string;
|
|
119
|
+
serialNumber: string;
|
|
120
|
+
softwareVersion: string;
|
|
121
|
+
hardwareVersion: string;
|
|
122
|
+
productType: string;
|
|
123
|
+
}
|
|
124
|
+
interface HeartRateRecovery {
|
|
125
|
+
comparisonLevel: string;
|
|
126
|
+
drop: number;
|
|
127
|
+
level: string;
|
|
128
|
+
}
|
|
129
|
+
interface TssEntry {
|
|
130
|
+
calculationMethod: string;
|
|
131
|
+
trainingStressScore: number;
|
|
132
|
+
intensityFactor: number | null;
|
|
133
|
+
normalizedPower: number | null;
|
|
134
|
+
averageGradeAdjustedPace: number | null;
|
|
135
|
+
}
|
|
136
|
+
interface RouteRanking {
|
|
137
|
+
originalRanking: number;
|
|
138
|
+
originalNumberOfWorkouts: number;
|
|
139
|
+
}
|
|
140
|
+
interface Rankings {
|
|
141
|
+
totalTimeOnRouteRanking: RouteRanking;
|
|
142
|
+
}
|
|
143
|
+
interface ActivityCounts {
|
|
144
|
+
timeCategory: number;
|
|
145
|
+
currentCount: number;
|
|
146
|
+
previousCount: number | null;
|
|
147
|
+
firstInCount: number | null;
|
|
148
|
+
activityCount: number | null;
|
|
149
|
+
activityTypeCount: number | null;
|
|
150
|
+
}
|
|
151
|
+
interface CumulativeAchievement {
|
|
152
|
+
description: number;
|
|
153
|
+
activityCounts: ActivityCounts;
|
|
154
|
+
}
|
|
155
|
+
interface PersonalBestAchievement {
|
|
156
|
+
timeCategory: number;
|
|
157
|
+
valueCategory: number;
|
|
158
|
+
since: string | null;
|
|
159
|
+
}
|
|
160
|
+
interface ClientCalculatedAchievements {
|
|
161
|
+
cumulativeAchievements: CumulativeAchievement[];
|
|
162
|
+
personalBestAchievements: PersonalBestAchievement[];
|
|
163
|
+
}
|
|
164
|
+
interface WorkoutComment {
|
|
165
|
+
key: string;
|
|
166
|
+
timestamp: number;
|
|
167
|
+
username: string;
|
|
168
|
+
realname: string;
|
|
169
|
+
profilePictureUrl: string | null;
|
|
170
|
+
comment: string;
|
|
171
|
+
}
|
|
172
|
+
interface WorkoutReaction {
|
|
173
|
+
utfCode: string;
|
|
174
|
+
count: number;
|
|
175
|
+
userReacted: boolean;
|
|
176
|
+
}
|
|
177
|
+
interface IntensityZone {
|
|
178
|
+
totalTime: number;
|
|
179
|
+
lowerLimit: number;
|
|
180
|
+
}
|
|
181
|
+
interface IntensityZones {
|
|
182
|
+
zone1: IntensityZone;
|
|
183
|
+
zone2: IntensityZone;
|
|
184
|
+
zone3: IntensityZone;
|
|
185
|
+
zone4: IntensityZone;
|
|
186
|
+
zone5: IntensityZone;
|
|
187
|
+
}
|
|
188
|
+
interface FitnessExtension {
|
|
189
|
+
type: "FitnessExtension";
|
|
190
|
+
maxHeartRate: number | null;
|
|
191
|
+
vo2Max: number | null;
|
|
192
|
+
estimatedVo2Max: number | null;
|
|
193
|
+
fitnessAge: number | null;
|
|
194
|
+
}
|
|
195
|
+
interface IntensityExtension {
|
|
196
|
+
type: "IntensityExtension";
|
|
197
|
+
zones: {
|
|
198
|
+
heartRate: IntensityZones | null;
|
|
199
|
+
speed: IntensityZones | null;
|
|
200
|
+
power: IntensityZones | null;
|
|
201
|
+
};
|
|
202
|
+
physiologicalThresholds: unknown | null;
|
|
203
|
+
overallIntensity: unknown | null;
|
|
204
|
+
}
|
|
205
|
+
interface SummaryExtension {
|
|
206
|
+
type: "SummaryExtension";
|
|
207
|
+
avgSpeed: number | null;
|
|
208
|
+
avgPower: number | null;
|
|
209
|
+
maxPower: number | null;
|
|
210
|
+
avgVerticalOscillation: number | null;
|
|
211
|
+
avgStrideLength: number | null;
|
|
212
|
+
avgGroundContactTime: number | null;
|
|
213
|
+
avgCadence: number | null;
|
|
214
|
+
maxCadence: number | null;
|
|
215
|
+
ascent: number | null;
|
|
216
|
+
descent: number | null;
|
|
217
|
+
ascentTime: number | null;
|
|
218
|
+
descentTime: number | null;
|
|
219
|
+
pte: number | null;
|
|
220
|
+
peakEpoc: number | null;
|
|
221
|
+
performanceLevel: number | null;
|
|
222
|
+
recoveryTime: number | null;
|
|
223
|
+
weather: unknown | null;
|
|
224
|
+
minTemperature: number | null;
|
|
225
|
+
avgTemperature: number | null;
|
|
226
|
+
maxTemperature: number | null;
|
|
227
|
+
workoutType: unknown | null;
|
|
228
|
+
/** 1–5 rating, or null if not set. */
|
|
229
|
+
feeling: number | null;
|
|
230
|
+
tags: unknown | null;
|
|
231
|
+
gear: Gear | null;
|
|
232
|
+
additionalGears: unknown | null;
|
|
233
|
+
exerciseId: string;
|
|
234
|
+
apps: unknown[];
|
|
235
|
+
repetitionCount: number | null;
|
|
236
|
+
lacticThHr: number | null;
|
|
237
|
+
avgAscentSpeed: number | null;
|
|
238
|
+
maxAscentSpeed: number | null;
|
|
239
|
+
avgDescentSpeed: number | null;
|
|
240
|
+
maxDescentSpeed: number | null;
|
|
241
|
+
avgDistancePerStroke: number | null;
|
|
242
|
+
fatConsumption: number | null;
|
|
243
|
+
carbohydrateConsumption: number | null;
|
|
244
|
+
avgLeftGroundContactBalance: number | null;
|
|
245
|
+
avgRightGroundContactBalance: number | null;
|
|
246
|
+
lacticThPace: number | null;
|
|
247
|
+
avgFlightTime: number | null;
|
|
248
|
+
avgContactTimeRatio: number | null;
|
|
249
|
+
teamSportId: unknown | null;
|
|
250
|
+
heartRateRecovery: HeartRateRecovery | null;
|
|
251
|
+
finalEndurance: unknown | null;
|
|
252
|
+
minimumEndurance: unknown | null;
|
|
253
|
+
curEnduranceDistance: unknown | null;
|
|
254
|
+
minEnduranceDistance: unknown | null;
|
|
255
|
+
enduranceValid: unknown | null;
|
|
256
|
+
}
|
|
257
|
+
/** Present on outdoor workouts that have weather data. */
|
|
258
|
+
interface WeatherExtension {
|
|
259
|
+
type: "WeatherExtension";
|
|
260
|
+
weatherIcon: string;
|
|
261
|
+
temperature: number;
|
|
262
|
+
windSpeed: number;
|
|
263
|
+
windDirection: number;
|
|
264
|
+
humidity: number;
|
|
265
|
+
}
|
|
266
|
+
/** Present only on pool-swimming workouts (activityId 21). */
|
|
267
|
+
interface SwimmingHeaderExtension {
|
|
268
|
+
type: "SwimmingHeaderExtension";
|
|
269
|
+
avgSwolf: number;
|
|
270
|
+
avgStrokeRate: number;
|
|
271
|
+
poolLength: number;
|
|
272
|
+
breaststrokeGlideTime: number | null;
|
|
273
|
+
ventilationFrequency: number | null;
|
|
274
|
+
avgFreestyleBreathAngle: number | null;
|
|
275
|
+
maxFreestyleBreathAngle: number | null;
|
|
276
|
+
freestyleGlideAngle: number | null;
|
|
277
|
+
avgBreaststrokeBreathAngle: number | null;
|
|
278
|
+
maxBreaststrokeBreathAngle: number | null;
|
|
279
|
+
freestyleDuration: number | null;
|
|
280
|
+
breaststrokeDuration: number | null;
|
|
281
|
+
otherStylesDuration: number | null;
|
|
282
|
+
freestylePercent: number | null;
|
|
283
|
+
breaststrokePercent: number | null;
|
|
284
|
+
otherStylesPercent: number | null;
|
|
285
|
+
breaststrokeHeadAngle: number | null;
|
|
286
|
+
}
|
|
287
|
+
type WorkoutExtension = FitnessExtension | IntensityExtension | SummaryExtension | WeatherExtension | SwimmingHeaderExtension;
|
|
288
|
+
interface Workout {
|
|
289
|
+
username: string;
|
|
290
|
+
sharingFlags: number;
|
|
291
|
+
/** Suunto activity type ID (e.g. 2 = cycling, 11 = trail running, 21 = pool swim, 36 = gym, 99 = other). */
|
|
292
|
+
activityId: number;
|
|
293
|
+
key: string;
|
|
294
|
+
startTime: number;
|
|
295
|
+
stopTime: number;
|
|
296
|
+
totalTime: number;
|
|
297
|
+
estimatedFloorsClimbed: number;
|
|
298
|
+
totalDistance: number;
|
|
299
|
+
totalAscent: number;
|
|
300
|
+
totalDescent: number;
|
|
301
|
+
startPosition: Position;
|
|
302
|
+
stopPosition: Position;
|
|
303
|
+
centerPosition: Position;
|
|
304
|
+
maxSpeed: number;
|
|
305
|
+
/** Encoded polyline string. Absent on indoor/GPS-less workouts. */
|
|
306
|
+
polyline?: string;
|
|
307
|
+
stepCount: number;
|
|
308
|
+
recoveryTime: number;
|
|
309
|
+
cumulativeRecoveryTime: number;
|
|
310
|
+
rankings: Rankings;
|
|
311
|
+
extensions: WorkoutExtension[];
|
|
312
|
+
/** Absent on pool-swimming workouts. */
|
|
313
|
+
minAltitude?: number;
|
|
314
|
+
/** Absent on pool-swimming workouts. */
|
|
315
|
+
maxAltitude?: number;
|
|
316
|
+
isManuallyAdded: boolean;
|
|
317
|
+
tss: TssEntry;
|
|
318
|
+
tssList: TssEntry[];
|
|
319
|
+
suuntoTags: string[];
|
|
320
|
+
clientCalculatedAchievements: ClientCalculatedAchievements;
|
|
321
|
+
workoutKey: string;
|
|
322
|
+
visibilityFacebook: boolean;
|
|
323
|
+
visibilityTwitter: boolean;
|
|
324
|
+
viewCount: number;
|
|
325
|
+
visibilityGroups: boolean;
|
|
326
|
+
pictureCount: number;
|
|
327
|
+
commentCount: number;
|
|
328
|
+
reactionCount: number;
|
|
329
|
+
created: number;
|
|
330
|
+
timeInZone0: number;
|
|
331
|
+
timeInZone1: number;
|
|
332
|
+
timeInZone2: number;
|
|
333
|
+
timeInZone3: number;
|
|
334
|
+
timeInZone4: number;
|
|
335
|
+
timeInZone5: number;
|
|
336
|
+
visibilityExplore: boolean;
|
|
337
|
+
avgPace: number;
|
|
338
|
+
visibilityFriends: boolean;
|
|
339
|
+
energyConsumption: number;
|
|
340
|
+
avgSpeed: number;
|
|
341
|
+
hrdata: HrData;
|
|
342
|
+
cadence: Cadence;
|
|
343
|
+
timeOffsetInMinutes: number;
|
|
344
|
+
lastModified: number;
|
|
345
|
+
/** Only present when commentCount > 0. */
|
|
346
|
+
comments?: WorkoutComment[];
|
|
347
|
+
/** Only present when reactionCount > 0. */
|
|
348
|
+
reactions?: WorkoutReaction[];
|
|
349
|
+
}
|
|
350
|
+
interface WorkoutsResponse {
|
|
351
|
+
error: string | null;
|
|
352
|
+
payload: Workout[];
|
|
353
|
+
metadata: {
|
|
354
|
+
workoutcount: string;
|
|
355
|
+
until: string;
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
declare function getWorkouts(client: HttpClient, username: string, params?: GetWorkoutsParams): Promise<WorkoutsResponse>;
|
|
360
|
+
declare function getOwnWorkouts(client: HttpClient, params?: GetOwnWorkoutsParams): Promise<WorkoutsResponse>;
|
|
361
|
+
/** Workout endpoints, bound to an {@link HttpClient}. Accessed via `suunto.workouts`. */
|
|
362
|
+
declare class WorkoutsResource {
|
|
363
|
+
private readonly client;
|
|
364
|
+
constructor(client: HttpClient);
|
|
365
|
+
/** The authenticated user's own workouts. */
|
|
366
|
+
own(params?: GetOwnWorkoutsParams): Promise<WorkoutsResponse>;
|
|
367
|
+
/** A given user's public workouts. */
|
|
368
|
+
public(username: string, params?: GetWorkoutsParams): Promise<WorkoutsResponse>;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
interface UserProfile {
|
|
372
|
+
username: string;
|
|
373
|
+
createdDate: number;
|
|
374
|
+
lastModified: number;
|
|
375
|
+
lastLogin: number;
|
|
376
|
+
realName: string;
|
|
377
|
+
/** ISO 3166-1 alpha-2 country code. */
|
|
378
|
+
country: string;
|
|
379
|
+
gender: string;
|
|
380
|
+
uuid: string;
|
|
381
|
+
blocked: boolean;
|
|
382
|
+
showLocale: boolean;
|
|
383
|
+
followersCount: number;
|
|
384
|
+
followingCount: number;
|
|
385
|
+
currentBlobStorageLocation: string;
|
|
386
|
+
defaultBinaryStorageLocation: string;
|
|
387
|
+
}
|
|
388
|
+
interface UserProfileResponse {
|
|
389
|
+
error: string | null;
|
|
390
|
+
payload: UserProfile;
|
|
391
|
+
metadata: {
|
|
392
|
+
ts: string;
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
/** User shape returned by the search endpoint (differs slightly from {@link UserProfile}). */
|
|
396
|
+
interface SearchUser {
|
|
397
|
+
username: string;
|
|
398
|
+
createdDate: number;
|
|
399
|
+
lastModified: number;
|
|
400
|
+
lastLogin: number;
|
|
401
|
+
realName: string;
|
|
402
|
+
/** ISO 3166-1 country code. Mostly alpha-2, but legacy records may use alpha-3 (e.g. "FRA"). */
|
|
403
|
+
country?: string;
|
|
404
|
+
city?: string;
|
|
405
|
+
gender: string;
|
|
406
|
+
uuid: string;
|
|
407
|
+
imageKey?: string;
|
|
408
|
+
profileImageUrl?: string;
|
|
409
|
+
showLocale: boolean;
|
|
410
|
+
defaultBinaryStorageLocation: string;
|
|
411
|
+
currentBlobStorageLocation: string;
|
|
412
|
+
key: string;
|
|
413
|
+
}
|
|
414
|
+
interface UserSearchResult {
|
|
415
|
+
/** Relationship to the searching user, e.g. "STRANGER". */
|
|
416
|
+
connection: string;
|
|
417
|
+
user: SearchUser;
|
|
418
|
+
workout: unknown | null;
|
|
419
|
+
}
|
|
420
|
+
interface UserSearchResponse {
|
|
421
|
+
error: string | null;
|
|
422
|
+
payload: UserSearchResult[];
|
|
423
|
+
metadata: {
|
|
424
|
+
ts: string;
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Fetch a user's public profile by username. Unauthenticated — no session
|
|
430
|
+
* required.
|
|
431
|
+
*/
|
|
432
|
+
declare function getUserByName(client: HttpClient, username: string): Promise<UserProfileResponse>;
|
|
433
|
+
/** Search for users by name/username. */
|
|
434
|
+
declare function searchUsers(client: HttpClient, searchTerms: string): Promise<UserSearchResponse>;
|
|
435
|
+
/** User endpoints, bound to an {@link HttpClient}. Accessed via `suunto.users`. */
|
|
436
|
+
declare class UsersResource {
|
|
437
|
+
private readonly client;
|
|
438
|
+
constructor(client: HttpClient);
|
|
439
|
+
/** A user's public profile, by username. */
|
|
440
|
+
byName(username: string): Promise<UserProfileResponse>;
|
|
441
|
+
/** Search for users by name/username. */
|
|
442
|
+
search(searchTerms: string): Promise<UserSearchResponse>;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
interface GetLatestGearParams {
|
|
446
|
+
/** Include all gear types, not just the default. Default: true. */
|
|
447
|
+
allTypes?: boolean;
|
|
448
|
+
}
|
|
449
|
+
interface GearSummary {
|
|
450
|
+
serialNumber: string;
|
|
451
|
+
displayName: string;
|
|
452
|
+
productType: string;
|
|
453
|
+
}
|
|
454
|
+
interface GearResponse {
|
|
455
|
+
error: string | null;
|
|
456
|
+
payload: GearSummary[];
|
|
457
|
+
metadata: {
|
|
458
|
+
ts: string;
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/** A user's latest gear. */
|
|
463
|
+
declare function getLatestGear(client: HttpClient, username: string, params?: GetLatestGearParams): Promise<GearResponse>;
|
|
464
|
+
/** Gear endpoints, bound to an {@link HttpClient}. Accessed via `suunto.gear`. */
|
|
465
|
+
declare class GearResource {
|
|
466
|
+
private readonly client;
|
|
467
|
+
constructor(client: HttpClient);
|
|
468
|
+
/** A user's latest (?) gear. */
|
|
469
|
+
latest(username: string, params?: GetLatestGearParams): Promise<GearResponse>;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
interface SuuntoClientOptions extends Omit<HttpClientOptions, "beforeRequest"> {
|
|
473
|
+
/**
|
|
474
|
+
* Account email. Drives the `x-totp` header. Omit it (e.g. via
|
|
475
|
+
* {@link SuuntoClient.unauthenticated}) to use only unauthenticated endpoints
|
|
476
|
+
* such as `users.byName`.
|
|
477
|
+
*/
|
|
478
|
+
email?: string;
|
|
479
|
+
/** Session key from login — sent as the `Sttauthorization` header. */
|
|
480
|
+
sessionKey?: string;
|
|
481
|
+
userAgent?: string;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* The main entry point. Owns the authenticated {@link HttpClient} and exposes
|
|
485
|
+
* the API grouped by resource.
|
|
486
|
+
*
|
|
487
|
+
* @example
|
|
488
|
+
* ```ts
|
|
489
|
+
* const suunto = await SuuntoClient.login({ email, password });
|
|
490
|
+
* const workouts = await suunto.workouts.own({ limit: 5 });
|
|
491
|
+
* ```
|
|
492
|
+
*/
|
|
493
|
+
declare class SuuntoClient {
|
|
494
|
+
/** The underlying HTTP client, for advanced or not-yet-wrapped endpoints. */
|
|
495
|
+
readonly http: HttpClient;
|
|
496
|
+
/** Session key in use, if the client was authenticated. */
|
|
497
|
+
readonly sessionKey?: string;
|
|
498
|
+
readonly workouts: WorkoutsResource;
|
|
499
|
+
readonly users: UsersResource;
|
|
500
|
+
readonly gear: GearResource;
|
|
501
|
+
constructor(options: SuuntoClientOptions);
|
|
502
|
+
/** Log in with email/password and return an authenticated client. */
|
|
503
|
+
static login(options: LoginOptions & Omit<SuuntoClientOptions, "email" | "sessionKey">): Promise<SuuntoClient>;
|
|
504
|
+
/**
|
|
505
|
+
* Create a client with no credentials, for unauthenticated endpoints only
|
|
506
|
+
* (e.g. {@link UsersResource.byName}). No `x-totp` or session header is sent.
|
|
507
|
+
*/
|
|
508
|
+
static unauthenticated(options?: Omit<SuuntoClientOptions, "email" | "sessionKey">): SuuntoClient;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
export { type ActivityCounts, type Cadence, type ClientCalculatedAchievements, type CumulativeAchievement, DEFAULT_USER_AGENT, type FitnessExtension, type Gear, GearResource, type GearResponse, type GearSummary, type GetLatestGearParams, type GetOwnWorkoutsParams, type GetWorkoutsParams, type HeartRateRecovery, type HrData, HttpClient, type HttpClientOptions, HttpError, type HttpResponse, type IntensityExtension, type IntensityZone, type IntensityZones, type LoginOptions, type LoginResponse, type PersonalBestAchievement, type Position, type Query, type Rankings, type RequestBody, type RequestContext, type RequestOptions, type RouteRanking, SPORTS_TRACKER_API, type SearchUser, type SummaryExtension, SuuntoClient, type SuuntoClientOptions, type SwimmingHeaderExtension, type TssEntry, type UserProfile, type UserProfileResponse, type UserSearchResponse, type UserSearchResult, UsersResource, type WeatherExtension, type Workout, type WorkoutComment, type WorkoutExtension, type WorkoutReaction, WorkoutsResource, type WorkoutsResponse, generateXtotp, getLatestGear, getOwnWorkouts, getUserByName, getWorkouts, login, searchUsers, secondsUntilRollover, sessionTokenFrom };
|