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 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.
@@ -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 };