@kaiord/garmin-connect 5.0.0 → 6.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 +67 -29
- package/dist/index.d.ts +64 -21
- package/dist/index.js +246 -172
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,22 +17,21 @@ pnpm add @kaiord/garmin-connect
|
|
|
17
17
|
|
|
18
18
|
```typescript
|
|
19
19
|
import { createGarminConnectClient } from "@kaiord/garmin-connect";
|
|
20
|
-
import type { KRD } from "@kaiord/core";
|
|
21
20
|
|
|
22
|
-
const
|
|
21
|
+
const client = createGarminConnectClient();
|
|
23
22
|
|
|
24
23
|
// Login
|
|
25
|
-
await auth.login("email@example.com", "password");
|
|
24
|
+
await client.auth.login("email@example.com", "password");
|
|
26
25
|
|
|
27
26
|
// List workouts
|
|
28
|
-
const workouts = await service.list({ limit: 10 });
|
|
27
|
+
const workouts = await client.service.list({ limit: 10 });
|
|
29
28
|
|
|
30
29
|
// Push a KRD workout to Garmin Connect
|
|
31
|
-
const result = await service.push(krd);
|
|
30
|
+
const result = await client.service.push(krd);
|
|
32
31
|
console.log(`Pushed workout: ${result.name} (id: ${result.id})`);
|
|
33
32
|
```
|
|
34
33
|
|
|
35
|
-
### Token Persistence
|
|
34
|
+
### Token Persistence with Auto-Restore
|
|
36
35
|
|
|
37
36
|
```typescript
|
|
38
37
|
import {
|
|
@@ -40,17 +39,28 @@ import {
|
|
|
40
39
|
createFileTokenStore,
|
|
41
40
|
} from "@kaiord/garmin-connect";
|
|
42
41
|
|
|
43
|
-
const
|
|
44
|
-
|
|
42
|
+
const client = createGarminConnectClient({
|
|
43
|
+
tokenStore: createFileTokenStore("./tokens.json"),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Auto-restore tokens from store (login not needed if tokens are valid)
|
|
47
|
+
const { restored } = await client.init();
|
|
48
|
+
if (!restored) {
|
|
49
|
+
await client.auth.login("email@example.com", "password");
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### With Retry for Transient Failures
|
|
45
54
|
|
|
46
|
-
|
|
47
|
-
|
|
55
|
+
```typescript
|
|
56
|
+
import { createGarminConnectClient } from "@kaiord/garmin-connect";
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
|
|
58
|
+
const client = createGarminConnectClient({
|
|
59
|
+
retry: { maxRetries: 3, baseDelay: 1000, maxDelay: 10000 },
|
|
60
|
+
});
|
|
51
61
|
```
|
|
52
62
|
|
|
53
|
-
### Custom
|
|
63
|
+
### Custom Fetch Function
|
|
54
64
|
|
|
55
65
|
```typescript
|
|
56
66
|
import {
|
|
@@ -58,56 +68,84 @@ import {
|
|
|
58
68
|
createCookieFetch,
|
|
59
69
|
} from "@kaiord/garmin-connect";
|
|
60
70
|
|
|
61
|
-
const
|
|
62
|
-
|
|
71
|
+
const client = createGarminConnectClient({
|
|
72
|
+
fetchFn: createCookieFetch(),
|
|
73
|
+
});
|
|
63
74
|
```
|
|
64
75
|
|
|
65
76
|
## API
|
|
66
77
|
|
|
67
|
-
### `createGarminConnectClient(options?):
|
|
78
|
+
### `createGarminConnectClient(options?): GarminConnectClient`
|
|
68
79
|
|
|
69
80
|
Creates a Garmin Connect client with authentication and workout service.
|
|
70
81
|
|
|
71
82
|
**Options:**
|
|
72
83
|
|
|
73
84
|
- `fetchFn` - Custom fetch function (defaults to cookie-aware fetch)
|
|
74
|
-
- `tokenStore` - Token persistence store
|
|
85
|
+
- `tokenStore` - Token persistence store
|
|
86
|
+
- `logger` - Custom logger
|
|
87
|
+
- `retry` - Retry options: `{ maxRetries?, baseDelay?, maxDelay? }`
|
|
75
88
|
|
|
76
|
-
|
|
89
|
+
**Returns:** `{ auth, service, init }`
|
|
90
|
+
|
|
91
|
+
### `client.init(): Promise<{ restored: boolean }>`
|
|
92
|
+
|
|
93
|
+
Auto-restores tokens from the token store. Returns `{ restored: true }` if valid tokens were found. Idempotent: no-op if tokens are already in memory.
|
|
94
|
+
|
|
95
|
+
### `client.auth.login(email, password): Promise<void>`
|
|
77
96
|
|
|
78
97
|
Authenticates with Garmin Connect via SSO.
|
|
79
98
|
|
|
80
|
-
### `auth.is_authenticated(): boolean`
|
|
99
|
+
### `client.auth.is_authenticated(): boolean`
|
|
81
100
|
|
|
82
|
-
Checks if the client has valid authentication tokens.
|
|
101
|
+
Checks if the client has valid (non-expired) authentication tokens.
|
|
83
102
|
|
|
84
|
-
### `auth.export_tokens(): Promise<TokenData>`
|
|
103
|
+
### `client.auth.export_tokens(): Promise<TokenData>`
|
|
85
104
|
|
|
86
105
|
Exports current tokens for external storage.
|
|
87
106
|
|
|
88
|
-
### `auth.restore_tokens(tokens): Promise<void>`
|
|
107
|
+
### `client.auth.restore_tokens(tokens): Promise<void>`
|
|
89
108
|
|
|
90
109
|
Restores previously exported tokens.
|
|
91
110
|
|
|
92
|
-
### `
|
|
111
|
+
### `client.auth.logout(): Promise<void>`
|
|
112
|
+
|
|
113
|
+
Clears all tokens from memory and token store.
|
|
114
|
+
|
|
115
|
+
### `client.service.list(options?): Promise<WorkoutSummary[]>`
|
|
93
116
|
|
|
94
117
|
Lists workouts from Garmin Connect.
|
|
95
118
|
|
|
96
|
-
### `service.push(krd): Promise<PushResult>`
|
|
119
|
+
### `client.service.push(krd): Promise<PushResult>`
|
|
97
120
|
|
|
98
|
-
Pushes a KRD
|
|
121
|
+
Pushes a KRD-structured workout to Garmin Connect.
|
|
122
|
+
|
|
123
|
+
### `createFileTokenStore(path?): TokenStore`
|
|
124
|
+
|
|
125
|
+
Creates a file-based token store. Defaults to `~/.kaiord/garmin-tokens.json`.
|
|
126
|
+
|
|
127
|
+
### `createMemoryTokenStore(): TokenStore`
|
|
128
|
+
|
|
129
|
+
Creates an in-memory token store (tokens lost on process exit).
|
|
99
130
|
|
|
100
131
|
### `createCookieFetch(): typeof fetch`
|
|
101
132
|
|
|
102
133
|
Creates a cookie-aware fetch wrapper for SSO authentication flows.
|
|
103
134
|
|
|
104
|
-
|
|
135
|
+
## Migration from v5.x
|
|
105
136
|
|
|
106
|
-
|
|
137
|
+
```typescript
|
|
138
|
+
// Before (v5.x)
|
|
139
|
+
const { auth, service } = createGarminConnectClient();
|
|
140
|
+
await auth.login(email, password);
|
|
107
141
|
|
|
108
|
-
|
|
142
|
+
// After (v6.x)
|
|
143
|
+
const client = createGarminConnectClient({ tokenStore });
|
|
144
|
+
const { restored } = await client.init();
|
|
145
|
+
if (!restored) await client.auth.login(email, password);
|
|
146
|
+
```
|
|
109
147
|
|
|
110
|
-
|
|
148
|
+
See the [design document](../../openspec/changes/refactor-garmin-auth/design.md) for the full migration guide.
|
|
111
149
|
|
|
112
150
|
## License
|
|
113
151
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { WorkoutService, Logger, TokenStore, AuthProvider } from '@kaiord/core';
|
|
1
|
+
import { WorkoutService, Logger, AuthProvider, TokenStore } from '@kaiord/core';
|
|
3
2
|
export { ListOptions, PushResult, TokenData, TokenStore, WorkoutSummary } from '@kaiord/core';
|
|
4
3
|
|
|
4
|
+
type FetchFn = typeof globalThis.fetch;
|
|
5
5
|
type OAuth1Token = {
|
|
6
6
|
oauth_token: string;
|
|
7
7
|
oauth_token_secret: string;
|
|
@@ -14,33 +14,76 @@ type OAuth2Token = {
|
|
|
14
14
|
refresh_token_expires_in: number;
|
|
15
15
|
expires_at: number;
|
|
16
16
|
};
|
|
17
|
-
type GarminHttpClient = {
|
|
18
|
-
get: <T>(url: string) => Promise<T>;
|
|
19
|
-
post: <T>(url: string, body: unknown) => Promise<T>;
|
|
20
|
-
del: <T>(url: string) => Promise<T>;
|
|
21
|
-
setTokens: (oauth1: OAuth1Token, oauth2: OAuth2Token) => void;
|
|
22
|
-
clearTokens: () => void;
|
|
23
|
-
getOAuth2Token: () => OAuth2Token | undefined;
|
|
24
|
-
};
|
|
25
17
|
|
|
26
18
|
type GarminWorkoutClient = Pick<WorkoutService, "push" | "list">;
|
|
27
19
|
|
|
28
|
-
type
|
|
20
|
+
type RetryOptions = {
|
|
21
|
+
maxRetries?: number;
|
|
22
|
+
baseDelay?: number;
|
|
23
|
+
maxDelay?: number;
|
|
24
|
+
randomFn?: () => number;
|
|
29
25
|
logger?: Logger;
|
|
30
|
-
tokenStore?: TokenStore;
|
|
31
|
-
fetchFn?: typeof globalThis.fetch;
|
|
32
26
|
};
|
|
33
|
-
|
|
27
|
+
|
|
28
|
+
type InitResult = {
|
|
29
|
+
restored: boolean;
|
|
30
|
+
};
|
|
31
|
+
type GarminConnectClient = {
|
|
34
32
|
auth: AuthProvider;
|
|
35
|
-
|
|
33
|
+
service: GarminWorkoutClient;
|
|
34
|
+
init: () => Promise<InitResult>;
|
|
35
|
+
};
|
|
36
|
+
type GarminConnectClientOptions = {
|
|
37
|
+
logger?: Logger;
|
|
38
|
+
tokenStore?: TokenStore;
|
|
39
|
+
fetchFn?: FetchFn;
|
|
40
|
+
retry?: RetryOptions;
|
|
36
41
|
};
|
|
37
|
-
declare const createGarminAuthProvider: (options?: GarminAuthProviderOptions) => GarminAuthProviderResult;
|
|
38
42
|
|
|
39
|
-
type
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
type TokenManager = {
|
|
44
|
+
getAccessToken: () => string | undefined;
|
|
45
|
+
getOAuth1Token: () => OAuth1Token | undefined;
|
|
46
|
+
getOAuth2Token: () => OAuth2Token | undefined;
|
|
47
|
+
getGeneration: () => number;
|
|
48
|
+
isAuthenticated: () => boolean;
|
|
49
|
+
setTokens: (oauth1: OAuth1Token, oauth2: OAuth2Token) => Promise<void>;
|
|
50
|
+
clearTokens: () => Promise<void>;
|
|
51
|
+
refresh: () => Promise<void>;
|
|
52
|
+
init: () => Promise<{
|
|
53
|
+
restored: boolean;
|
|
54
|
+
}>;
|
|
55
|
+
};
|
|
56
|
+
type TokenReader = Pick<TokenManager, "getAccessToken" | "getGeneration" | "refresh" | "isAuthenticated">;
|
|
57
|
+
type RefreshFn = (oauth1: OAuth1Token) => Promise<OAuth2Token>;
|
|
58
|
+
|
|
59
|
+
type GarminAuthProviderOptions = {
|
|
60
|
+
tokenManager: TokenManager;
|
|
61
|
+
logger?: Logger;
|
|
62
|
+
fetchFn?: FetchFn;
|
|
43
63
|
};
|
|
64
|
+
declare const createGarminAuthProvider: (options: GarminAuthProviderOptions) => AuthProvider;
|
|
65
|
+
|
|
66
|
+
type Options = {
|
|
67
|
+
refreshFn: RefreshFn;
|
|
68
|
+
logger: Logger;
|
|
69
|
+
tokenStore?: TokenStore;
|
|
70
|
+
};
|
|
71
|
+
declare const createTokenManager: (options: Options) => TokenManager;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Create a Garmin Connect client with auth, workout service, and optional token auto-restore.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* const client = createGarminConnectClient({
|
|
79
|
+
* tokenStore: createFileTokenStore(),
|
|
80
|
+
* retry: { maxRetries: 3 },
|
|
81
|
+
* });
|
|
82
|
+
* const { restored } = await client.init();
|
|
83
|
+
* if (!restored) await client.auth.login(email, password);
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
declare const createGarminConnectClient: (options?: GarminConnectClientOptions) => GarminConnectClient;
|
|
44
87
|
|
|
45
88
|
declare const createCookieFetch: () => typeof globalThis.fetch;
|
|
46
89
|
|
|
@@ -48,4 +91,4 @@ declare const createFileTokenStore: (filePath?: string) => TokenStore;
|
|
|
48
91
|
|
|
49
92
|
declare const createMemoryTokenStore: () => TokenStore;
|
|
50
93
|
|
|
51
|
-
export { type GarminWorkoutClient, createCookieFetch, createFileTokenStore, createGarminAuthProvider, createGarminConnectClient, createMemoryTokenStore };
|
|
94
|
+
export { type GarminConnectClient, type GarminConnectClientOptions, type GarminWorkoutClient, type InitResult, type RetryOptions, type TokenReader, createCookieFetch, createFileTokenStore, createGarminAuthProvider, createGarminConnectClient, createMemoryTokenStore, createTokenManager };
|
package/dist/index.js
CHANGED
|
@@ -10,45 +10,6 @@ import { join, dirname } from 'path';
|
|
|
10
10
|
|
|
11
11
|
// src/adapters/auth/garmin-auth-provider.ts
|
|
12
12
|
var createCookieFetch = () => fetchCookie(globalThis.fetch);
|
|
13
|
-
var makeRequest = (url, init, refresh, fetchFn) => {
|
|
14
|
-
if (!refresh.state.oauth2Token) {
|
|
15
|
-
throw createServiceApiError("Token unavailable after refresh", 401);
|
|
16
|
-
}
|
|
17
|
-
return fetchFn(url, {
|
|
18
|
-
...init,
|
|
19
|
-
headers: {
|
|
20
|
-
...init?.headers,
|
|
21
|
-
Authorization: `Bearer ${refresh.state.oauth2Token.access_token}`
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
};
|
|
25
|
-
var authFetch = async (url, init, refresh, fetchFn) => {
|
|
26
|
-
if (!refresh.state.oauth2Token) {
|
|
27
|
-
throw createServiceApiError("Not authenticated", 401);
|
|
28
|
-
}
|
|
29
|
-
if (refresh.state.oauth2Token.expires_at < Math.floor(Date.now() / 1e3)) {
|
|
30
|
-
await refresh.ensureFreshToken();
|
|
31
|
-
}
|
|
32
|
-
const res = await makeRequest(url, init, refresh, fetchFn);
|
|
33
|
-
if (res.status === 401) {
|
|
34
|
-
await refresh.ensureFreshToken();
|
|
35
|
-
const retry = await makeRequest(url, init, refresh, fetchFn);
|
|
36
|
-
if (!retry.ok) {
|
|
37
|
-
throw createServiceApiError(
|
|
38
|
-
`API request failed after token refresh: ${retry.statusText}`,
|
|
39
|
-
retry.status
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
return retry;
|
|
43
|
-
}
|
|
44
|
-
if (!res.ok) {
|
|
45
|
-
throw createServiceApiError(
|
|
46
|
-
`API request failed: ${res.statusText}`,
|
|
47
|
-
res.status
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
return res;
|
|
51
|
-
};
|
|
52
13
|
|
|
53
14
|
// src/adapters/http/urls.ts
|
|
54
15
|
var GARMIN_SSO_ORIGIN = "https://sso.garmin.com";
|
|
@@ -250,98 +211,6 @@ var garminSso = async (username, password, logger, fetchFn) => {
|
|
|
250
211
|
logger.info("Garmin Connect SSO login successful");
|
|
251
212
|
return { oauth1, oauth2 };
|
|
252
213
|
};
|
|
253
|
-
|
|
254
|
-
// src/adapters/http/token-refresh.ts
|
|
255
|
-
var notifyAll = (subs, token) => {
|
|
256
|
-
subs.forEach((s) => s.resolve(token));
|
|
257
|
-
};
|
|
258
|
-
var rejectAll = (subs, error) => {
|
|
259
|
-
subs.forEach((s) => s.reject(error));
|
|
260
|
-
};
|
|
261
|
-
var getOrFetchConsumer = async (state, fetchFn) => {
|
|
262
|
-
if (state.consumer) return state.consumer;
|
|
263
|
-
state.consumer = await fetchOAuthConsumer(fetchFn);
|
|
264
|
-
return state.consumer;
|
|
265
|
-
};
|
|
266
|
-
var doRefreshToken = async (state, fetchFn, logger) => {
|
|
267
|
-
if (!state.oauth1Token || !state.oauth2Token) {
|
|
268
|
-
throw createServiceApiError("No tokens available for refresh", 401);
|
|
269
|
-
}
|
|
270
|
-
const cons = await getOrFetchConsumer(state, fetchFn);
|
|
271
|
-
state.oauth2Token = await exchangeOAuth2(state.oauth1Token, cons, fetchFn);
|
|
272
|
-
logger.info("OAuth2 token refreshed");
|
|
273
|
-
};
|
|
274
|
-
var createTokenRefreshManager = (fetchFn, logger) => {
|
|
275
|
-
const state = {
|
|
276
|
-
oauth1Token: void 0,
|
|
277
|
-
oauth2Token: void 0,
|
|
278
|
-
consumer: void 0
|
|
279
|
-
};
|
|
280
|
-
let isRefreshing = false;
|
|
281
|
-
let subscribers = [];
|
|
282
|
-
return {
|
|
283
|
-
state,
|
|
284
|
-
ensureFreshToken: async () => {
|
|
285
|
-
if (isRefreshing) {
|
|
286
|
-
await new Promise((resolve, reject) => {
|
|
287
|
-
subscribers.push({ resolve, reject });
|
|
288
|
-
});
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
isRefreshing = true;
|
|
292
|
-
try {
|
|
293
|
-
await doRefreshToken(state, fetchFn, logger);
|
|
294
|
-
if (state.oauth2Token)
|
|
295
|
-
notifyAll(subscribers, state.oauth2Token.access_token);
|
|
296
|
-
subscribers = [];
|
|
297
|
-
} catch (error) {
|
|
298
|
-
rejectAll(subscribers, error);
|
|
299
|
-
subscribers = [];
|
|
300
|
-
throw error;
|
|
301
|
-
} finally {
|
|
302
|
-
isRefreshing = false;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
};
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
// src/adapters/http/garmin-http-client.ts
|
|
309
|
-
var createGarminHttpClient = (logger, fetchFn = globalThis.fetch) => {
|
|
310
|
-
const refresh = createTokenRefreshManager(fetchFn, logger);
|
|
311
|
-
const fetch = (url, init) => authFetch(url, init, refresh, fetchFn);
|
|
312
|
-
return {
|
|
313
|
-
get: async (url) => {
|
|
314
|
-
const res = await fetch(url);
|
|
315
|
-
return await res.json();
|
|
316
|
-
},
|
|
317
|
-
post: async (url, body) => {
|
|
318
|
-
const res = await fetch(url, {
|
|
319
|
-
method: "POST",
|
|
320
|
-
headers: { "Content-Type": "application/json" },
|
|
321
|
-
body: body !== null ? JSON.stringify(body) : void 0
|
|
322
|
-
});
|
|
323
|
-
return await res.json();
|
|
324
|
-
},
|
|
325
|
-
del: async (url) => {
|
|
326
|
-
const res = await fetch(url, {
|
|
327
|
-
method: "POST",
|
|
328
|
-
headers: { "X-Http-Method-Override": "DELETE" }
|
|
329
|
-
});
|
|
330
|
-
const text = await res.text();
|
|
331
|
-
return text ? JSON.parse(text) : void 0;
|
|
332
|
-
},
|
|
333
|
-
setTokens: (o1, o2) => {
|
|
334
|
-
refresh.state.oauth1Token = o1;
|
|
335
|
-
refresh.state.oauth2Token = o2;
|
|
336
|
-
},
|
|
337
|
-
clearTokens: () => {
|
|
338
|
-
refresh.state.oauth1Token = void 0;
|
|
339
|
-
refresh.state.oauth2Token = void 0;
|
|
340
|
-
refresh.state.consumer = void 0;
|
|
341
|
-
},
|
|
342
|
-
getOAuth2Token: () => refresh.state.oauth2Token
|
|
343
|
-
};
|
|
344
|
-
};
|
|
345
214
|
var oauth1TokenSchema = z.object({
|
|
346
215
|
oauth_token: z.string(),
|
|
347
216
|
oauth_token_secret: z.string()
|
|
@@ -360,57 +229,132 @@ var garminTokensSchema = z.object({
|
|
|
360
229
|
});
|
|
361
230
|
|
|
362
231
|
// src/adapters/auth/garmin-auth-provider.ts
|
|
363
|
-
var buildAuthProvider = (
|
|
232
|
+
var buildAuthProvider = (tm, logger, fetchFn) => ({
|
|
364
233
|
login: async (username, password) => {
|
|
365
|
-
const result = await garminSso(username, password,
|
|
366
|
-
|
|
367
|
-
ctx.httpClient.setTokens(result.oauth1, result.oauth2);
|
|
368
|
-
if (ctx.tokenStore) {
|
|
369
|
-
await ctx.tokenStore.save({
|
|
370
|
-
oauth1: result.oauth1,
|
|
371
|
-
oauth2: result.oauth2
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
},
|
|
375
|
-
is_authenticated: () => {
|
|
376
|
-
const token = ctx.httpClient.getOAuth2Token();
|
|
377
|
-
if (!token) return false;
|
|
378
|
-
return token.expires_at > Math.floor(Date.now() / 1e3);
|
|
234
|
+
const result = await garminSso(username, password, logger, fetchFn);
|
|
235
|
+
await tm.setTokens(result.oauth1, result.oauth2);
|
|
379
236
|
},
|
|
237
|
+
is_authenticated: () => tm.isAuthenticated(),
|
|
380
238
|
export_tokens: async () => {
|
|
381
|
-
const
|
|
382
|
-
|
|
239
|
+
const oauth1 = tm.getOAuth1Token();
|
|
240
|
+
const oauth2 = tm.getOAuth2Token();
|
|
241
|
+
if (!oauth1 || !oauth2) {
|
|
383
242
|
throw createServiceAuthError("No tokens to export");
|
|
384
243
|
}
|
|
385
|
-
return { oauth1:
|
|
244
|
+
return { oauth1: { ...oauth1 }, oauth2: { ...oauth2 } };
|
|
386
245
|
},
|
|
387
246
|
restore_tokens: async (data) => {
|
|
388
247
|
const parsed = garminTokensSchema.parse(data);
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
ctx.logger.info("Tokens restored from stored session");
|
|
248
|
+
await tm.setTokens(parsed.oauth1, parsed.oauth2);
|
|
249
|
+
logger.info("Tokens restored from stored session");
|
|
392
250
|
},
|
|
393
251
|
logout: async () => {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
if (ctx.tokenStore) await ctx.tokenStore.clear();
|
|
397
|
-
ctx.logger.info("Logged out from Garmin Connect");
|
|
252
|
+
await tm.clearTokens();
|
|
253
|
+
logger.info("Logged out from Garmin Connect");
|
|
398
254
|
}
|
|
399
255
|
});
|
|
400
256
|
var createGarminAuthProvider = (options) => {
|
|
401
|
-
const logger = options
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
logger,
|
|
411
|
-
|
|
257
|
+
const logger = options.logger ?? createConsoleLogger();
|
|
258
|
+
const fetchFn = options.fetchFn ?? createCookieFetch();
|
|
259
|
+
return buildAuthProvider(options.tokenManager, logger, fetchFn);
|
|
260
|
+
};
|
|
261
|
+
var persistBestEffort = async (store, oauth1, oauth2, logger) => {
|
|
262
|
+
if (!store) return;
|
|
263
|
+
try {
|
|
264
|
+
await store.save({ oauth1, oauth2 });
|
|
265
|
+
} catch (error) {
|
|
266
|
+
logger.warn("Failed to persist tokens", {
|
|
267
|
+
errorName: error instanceof Error ? error.name : typeof error
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
var isExpired = (oauth2) => !oauth2 || oauth2.expires_at <= Date.now() / 1e3;
|
|
272
|
+
var doRefresh = (s, refreshFn, logger, tokenStore) => {
|
|
273
|
+
if (!s.oauth1) {
|
|
274
|
+
throw createServiceApiError("No OAuth1 token for refresh", 401);
|
|
275
|
+
}
|
|
276
|
+
const currentOAuth1 = s.oauth1;
|
|
277
|
+
const generationAtStart = s.generation;
|
|
278
|
+
s.refreshPromise = refreshFn(currentOAuth1).then(async (newOAuth2) => {
|
|
279
|
+
if (s.generation !== generationAtStart) return;
|
|
280
|
+
s.oauth2 = newOAuth2;
|
|
281
|
+
s.generation++;
|
|
282
|
+
logger.info("Token refreshed", { generation: s.generation });
|
|
283
|
+
await persistBestEffort(tokenStore, currentOAuth1, newOAuth2, logger);
|
|
284
|
+
}).finally(() => {
|
|
285
|
+
s.refreshPromise = void 0;
|
|
412
286
|
});
|
|
413
|
-
return
|
|
287
|
+
return s.refreshPromise;
|
|
288
|
+
};
|
|
289
|
+
var restoreFromStore = async (s, tokenStore, logger) => {
|
|
290
|
+
const data = await tokenStore.load();
|
|
291
|
+
if (!data) return { restored: false };
|
|
292
|
+
if (s.oauth1 && s.oauth2) return { restored: false };
|
|
293
|
+
const parsed = garminTokensSchema.safeParse(data);
|
|
294
|
+
if (!parsed.success) return { restored: false };
|
|
295
|
+
s.oauth1 = parsed.data.oauth1;
|
|
296
|
+
s.oauth2 = parsed.data.oauth2;
|
|
297
|
+
s.generation++;
|
|
298
|
+
if (isExpired(s.oauth2)) logger.warn("Restored tokens are expired");
|
|
299
|
+
logger.info("Tokens restored from store", { generation: s.generation });
|
|
300
|
+
return { restored: true };
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// src/adapters/token/token-manager.ts
|
|
304
|
+
var createTokenManager = (options) => {
|
|
305
|
+
const { refreshFn, logger, tokenStore } = options;
|
|
306
|
+
const s = {
|
|
307
|
+
oauth1: void 0,
|
|
308
|
+
oauth2: void 0,
|
|
309
|
+
generation: 0,
|
|
310
|
+
refreshPromise: void 0
|
|
311
|
+
};
|
|
312
|
+
return {
|
|
313
|
+
getAccessToken: () => s.oauth2?.access_token,
|
|
314
|
+
getOAuth1Token: () => s.oauth1 ? { ...s.oauth1 } : void 0,
|
|
315
|
+
getOAuth2Token: () => s.oauth2 ? { ...s.oauth2 } : void 0,
|
|
316
|
+
getGeneration: () => s.generation,
|
|
317
|
+
isAuthenticated: () => !!s.oauth2 && !isExpired(s.oauth2),
|
|
318
|
+
setTokens: async (o1, o2) => {
|
|
319
|
+
s.oauth1 = o1;
|
|
320
|
+
s.oauth2 = o2;
|
|
321
|
+
s.generation++;
|
|
322
|
+
logger.info("Tokens set", { generation: s.generation });
|
|
323
|
+
await persistBestEffort(tokenStore, o1, o2, logger);
|
|
324
|
+
},
|
|
325
|
+
clearTokens: async () => {
|
|
326
|
+
s.oauth1 = void 0;
|
|
327
|
+
s.oauth2 = void 0;
|
|
328
|
+
s.refreshPromise = void 0;
|
|
329
|
+
s.generation++;
|
|
330
|
+
logger.info("Tokens cleared");
|
|
331
|
+
if (tokenStore) await tokenStore.clear();
|
|
332
|
+
},
|
|
333
|
+
refresh: async () => {
|
|
334
|
+
if (s.refreshPromise) return s.refreshPromise;
|
|
335
|
+
return doRefresh(s, refreshFn, logger, tokenStore);
|
|
336
|
+
},
|
|
337
|
+
init: async () => {
|
|
338
|
+
if (s.oauth1 && s.oauth2) return { restored: false };
|
|
339
|
+
if (!tokenStore) return { restored: false };
|
|
340
|
+
return restoreFromStore(s, tokenStore, logger);
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
// src/adapters/client/build-refresh-fn.ts
|
|
346
|
+
var buildRefreshFn = (fetchFn) => {
|
|
347
|
+
let consumer;
|
|
348
|
+
return async (oauth1) => {
|
|
349
|
+
consumer ??= await fetchOAuthConsumer(fetchFn);
|
|
350
|
+
try {
|
|
351
|
+
return await exchangeOAuth2(oauth1, consumer, fetchFn);
|
|
352
|
+
} catch {
|
|
353
|
+
consumer = void 0;
|
|
354
|
+
consumer = await fetchOAuthConsumer(fetchFn);
|
|
355
|
+
return exchangeOAuth2(oauth1, consumer, fetchFn);
|
|
356
|
+
}
|
|
357
|
+
};
|
|
414
358
|
};
|
|
415
359
|
|
|
416
360
|
// src/adapters/mappers/workout-summary.mapper.ts
|
|
@@ -479,12 +423,142 @@ var createGarminWorkoutService = (httpClient, logger) => {
|
|
|
479
423
|
list: (opts) => listWorkouts(httpClient, log, opts)
|
|
480
424
|
};
|
|
481
425
|
};
|
|
426
|
+
var sendRequest = (url, init, token, fetchFn) => fetchFn(url, {
|
|
427
|
+
...init,
|
|
428
|
+
headers: { ...init?.headers, Authorization: `Bearer ${token}` }
|
|
429
|
+
});
|
|
430
|
+
var getTokenOrThrow = (reader, msg) => {
|
|
431
|
+
const token = reader.getAccessToken();
|
|
432
|
+
if (!token) throw createServiceApiError(msg, 401);
|
|
433
|
+
return token;
|
|
434
|
+
};
|
|
435
|
+
var handleNonOk = (res, prefix) => {
|
|
436
|
+
throw createServiceApiError(`${prefix}: ${res.statusText}`, res.status);
|
|
437
|
+
};
|
|
438
|
+
var authFetch = async (url, init, reader, fetchFn) => {
|
|
439
|
+
if (!reader.isAuthenticated()) await reader.refresh();
|
|
440
|
+
const gen = reader.getGeneration();
|
|
441
|
+
const token = getTokenOrThrow(reader, "Not authenticated");
|
|
442
|
+
const res = await sendRequest(url, init, token, fetchFn);
|
|
443
|
+
if (res.status !== 401) {
|
|
444
|
+
if (!res.ok) handleNonOk(res, "API request failed");
|
|
445
|
+
return res;
|
|
446
|
+
}
|
|
447
|
+
if (reader.getGeneration() === gen) await reader.refresh();
|
|
448
|
+
const freshToken = getTokenOrThrow(reader, "Token unavailable after refresh");
|
|
449
|
+
const retry = await sendRequest(url, init, freshToken, fetchFn);
|
|
450
|
+
if (!retry.ok) handleNonOk(retry, "API request failed after token refresh");
|
|
451
|
+
return retry;
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
// src/adapters/http/garmin-http-client.ts
|
|
455
|
+
var createGarminHttpClient = (tokenReader, fetchFn, logger) => {
|
|
456
|
+
const fetch = (url, init) => authFetch(url, init, tokenReader, fetchFn);
|
|
457
|
+
logger.debug("HTTP client created");
|
|
458
|
+
return {
|
|
459
|
+
get: async (url) => {
|
|
460
|
+
const res = await fetch(url);
|
|
461
|
+
return await res.json();
|
|
462
|
+
},
|
|
463
|
+
post: async (url, body) => {
|
|
464
|
+
const res = await fetch(url, {
|
|
465
|
+
method: "POST",
|
|
466
|
+
headers: { "Content-Type": "application/json" },
|
|
467
|
+
body: body !== null ? JSON.stringify(body) : void 0
|
|
468
|
+
});
|
|
469
|
+
return await res.json();
|
|
470
|
+
},
|
|
471
|
+
del: async (url) => {
|
|
472
|
+
const res = await fetch(url, {
|
|
473
|
+
method: "POST",
|
|
474
|
+
headers: { "X-Http-Method-Override": "DELETE" }
|
|
475
|
+
});
|
|
476
|
+
const text = await res.text();
|
|
477
|
+
return text ? JSON.parse(text) : void 0;
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
// src/adapters/http/retry.ts
|
|
483
|
+
var isRetryable = (status) => status === 429 || status >= 500 && status <= 599;
|
|
484
|
+
var computeDelay = (attempt, baseDelay, maxDelay, randomFn) => randomFn() * Math.min(maxDelay, baseDelay * 2 ** attempt);
|
|
485
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
486
|
+
var waitAndLog = (opts, attempt, message, info) => {
|
|
487
|
+
const delay = computeDelay(
|
|
488
|
+
attempt,
|
|
489
|
+
opts.baseDelay,
|
|
490
|
+
opts.maxDelay,
|
|
491
|
+
opts.randomFn
|
|
492
|
+
);
|
|
493
|
+
opts.logger?.debug(message, { ...info, delay: Math.round(delay) });
|
|
494
|
+
return sleep(delay);
|
|
495
|
+
};
|
|
496
|
+
var handleRetryableResponse = async (attempt, response, opts) => {
|
|
497
|
+
if (attempt < opts.maxRetries && isRetryable(response.status)) {
|
|
498
|
+
await waitAndLog(opts, attempt, "Retrying request", {
|
|
499
|
+
attempt: attempt + 1,
|
|
500
|
+
status: response.status
|
|
501
|
+
});
|
|
502
|
+
return true;
|
|
503
|
+
}
|
|
504
|
+
return false;
|
|
505
|
+
};
|
|
506
|
+
var handleRetryableError = async (attempt, error, opts) => {
|
|
507
|
+
if (attempt < opts.maxRetries && error instanceof TypeError) {
|
|
508
|
+
await waitAndLog(opts, attempt, "Retrying request after network error", {
|
|
509
|
+
attempt: attempt + 1,
|
|
510
|
+
error: error.message
|
|
511
|
+
});
|
|
512
|
+
return true;
|
|
513
|
+
}
|
|
514
|
+
return false;
|
|
515
|
+
};
|
|
516
|
+
var withRetry = (fetchFn, options) => {
|
|
517
|
+
const opts = {
|
|
518
|
+
maxRetries: options?.maxRetries ?? 3,
|
|
519
|
+
baseDelay: options?.baseDelay ?? 1e3,
|
|
520
|
+
maxDelay: options?.maxDelay ?? 1e4,
|
|
521
|
+
randomFn: options?.randomFn ?? Math.random,
|
|
522
|
+
logger: options?.logger
|
|
523
|
+
};
|
|
524
|
+
return async (input, init) => {
|
|
525
|
+
for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
|
|
526
|
+
try {
|
|
527
|
+
const response = await fetchFn(input, init);
|
|
528
|
+
if (await handleRetryableResponse(attempt, response, opts)) continue;
|
|
529
|
+
return response;
|
|
530
|
+
} catch (error) {
|
|
531
|
+
if (await handleRetryableError(attempt, error, opts)) continue;
|
|
532
|
+
throw error;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
throw new Error("Unexpected retry exhaustion");
|
|
536
|
+
};
|
|
537
|
+
};
|
|
482
538
|
|
|
483
539
|
// src/adapters/client/garmin-connect-client.ts
|
|
484
540
|
var createGarminConnectClient = (options) => {
|
|
485
|
-
const
|
|
486
|
-
const
|
|
487
|
-
|
|
541
|
+
const logger = options?.logger ?? createConsoleLogger();
|
|
542
|
+
const rawFetchFn = options?.fetchFn ?? createCookieFetch();
|
|
543
|
+
const retryFetchFn = options?.retry ? withRetry(rawFetchFn, { ...options.retry, logger }) : rawFetchFn;
|
|
544
|
+
const refreshFn = buildRefreshFn(rawFetchFn);
|
|
545
|
+
const tokenManager = createTokenManager({
|
|
546
|
+
refreshFn,
|
|
547
|
+
logger,
|
|
548
|
+
tokenStore: options?.tokenStore
|
|
549
|
+
});
|
|
550
|
+
const auth = createGarminAuthProvider({
|
|
551
|
+
tokenManager,
|
|
552
|
+
logger,
|
|
553
|
+
fetchFn: rawFetchFn
|
|
554
|
+
});
|
|
555
|
+
const httpClient = createGarminHttpClient(tokenManager, retryFetchFn, logger);
|
|
556
|
+
const service = createGarminWorkoutService(httpClient, logger);
|
|
557
|
+
return {
|
|
558
|
+
auth,
|
|
559
|
+
service,
|
|
560
|
+
init: () => tokenManager.init()
|
|
561
|
+
};
|
|
488
562
|
};
|
|
489
563
|
var DEFAULT_PATH = join(homedir(), ".kaiord", "garmin-tokens.json");
|
|
490
564
|
var createFileTokenStore = (filePath = DEFAULT_PATH) => ({
|
|
@@ -525,6 +599,6 @@ var createMemoryTokenStore = () => {
|
|
|
525
599
|
};
|
|
526
600
|
};
|
|
527
601
|
|
|
528
|
-
export { createCookieFetch, createFileTokenStore, createGarminAuthProvider, createGarminConnectClient, createMemoryTokenStore };
|
|
602
|
+
export { createCookieFetch, createFileTokenStore, createGarminAuthProvider, createGarminConnectClient, createMemoryTokenStore, createTokenManager };
|
|
529
603
|
//# sourceMappingURL=index.js.map
|
|
530
604
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapters/http/cookie-fetch.ts","../src/adapters/http/garmin-auth-fetch.ts","../src/adapters/http/urls.ts","../src/adapters/http/oauth-consumer.ts","../src/adapters/http/sso-validators.ts","../src/adapters/http/sso-login.ts","../src/adapters/http/oauth-signer.ts","../src/adapters/http/sso-oauth.ts","../src/adapters/http/garmin-sso.ts","../src/adapters/http/token-refresh.ts","../src/adapters/http/garmin-http-client.ts","../src/adapters/schemas/garmin-token.schema.ts","../src/adapters/auth/garmin-auth-provider.ts","../src/adapters/mappers/workout-summary.mapper.ts","../src/adapters/schemas/workout-response.schema.ts","../src/adapters/client/garmin-workout-service.ts","../src/adapters/client/garmin-connect-client.ts","../src/adapters/token-store/file-token-store.ts","../src/adapters/token-store/memory-token-store.ts"],"names":["createServiceAuthError","createServiceApiError","z","createConsoleLogger"],"mappings":"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","file":"index.js","sourcesContent":["import fetchCookie from \"fetch-cookie\";\n\nexport const createCookieFetch = (): typeof globalThis.fetch =>\n fetchCookie(globalThis.fetch) as typeof globalThis.fetch;\n","import { createServiceApiError } from \"@kaiord/core\";\nimport type { TokenRefreshManager } from \"./token-refresh\";\nimport type { FetchFn } from \"./types\";\n\nconst makeRequest = (\n url: string,\n init: RequestInit | undefined,\n refresh: TokenRefreshManager,\n fetchFn: FetchFn\n): Promise<Response> => {\n if (!refresh.state.oauth2Token) {\n throw createServiceApiError(\"Token unavailable after refresh\", 401);\n }\n return fetchFn(url, {\n ...init,\n headers: {\n ...init?.headers,\n Authorization: `Bearer ${refresh.state.oauth2Token.access_token}`,\n },\n });\n};\n\nexport const authFetch = async (\n url: string,\n init: RequestInit | undefined,\n refresh: TokenRefreshManager,\n fetchFn: FetchFn\n): Promise<Response> => {\n if (!refresh.state.oauth2Token) {\n throw createServiceApiError(\"Not authenticated\", 401);\n }\n if (refresh.state.oauth2Token.expires_at < Math.floor(Date.now() / 1000)) {\n await refresh.ensureFreshToken();\n }\n const res = await makeRequest(url, init, refresh, fetchFn);\n if (res.status === 401) {\n await refresh.ensureFreshToken();\n const retry = await makeRequest(url, init, refresh, fetchFn);\n if (!retry.ok) {\n throw createServiceApiError(\n `API request failed after token refresh: ${retry.statusText}`,\n retry.status\n );\n }\n return retry;\n }\n if (!res.ok) {\n throw createServiceApiError(\n `API request failed: ${res.statusText}`,\n res.status\n );\n }\n return res;\n};\n","export const GARMIN_SSO_ORIGIN = \"https://sso.garmin.com\";\nexport const GARMIN_SSO_EMBED = \"https://sso.garmin.com/sso/embed\";\nexport const SIGNIN_URL = \"https://sso.garmin.com/sso/signin\";\nexport const GC_MODERN = \"https://connect.garmin.com/modern\";\nexport const API_BASE = \"https://connectapi.garmin.com\";\nexport const OAUTH_URL = `${API_BASE}/oauth-service/oauth`;\nexport const WORKOUT_URL = `${API_BASE}/workout-service`;\n/** OAuth consumer credentials hosted by the garth project (third-party). */\nexport const OAUTH_CONSUMER_URL =\n \"https://thegarth.s3.amazonaws.com/oauth_consumer.json\";\n\nexport const USER_AGENT_MOBILE = \"com.garmin.android.apps.connectmobile\";\nexport const USER_AGENT_BROWSER =\n \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36\";\n","import { createServiceAuthError } from \"@kaiord/core\";\nimport { OAUTH_CONSUMER_URL } from \"./urls\";\nimport type { FetchFn, OAuthConsumer } from \"./types\";\n\nexport const fetchOAuthConsumer = async (\n fetchFn: FetchFn\n): Promise<OAuthConsumer> => {\n const res = await fetchFn(OAUTH_CONSUMER_URL);\n if (!res.ok) {\n throw createServiceAuthError(\n `Failed to fetch OAuth consumer: ${res.status} ${res.statusText}`\n );\n }\n const data = (await res.json()) as {\n consumer_key: string;\n consumer_secret: string;\n };\n return { key: data.consumer_key, secret: data.consumer_secret };\n};\n","import { createServiceAuthError } from \"@kaiord/core\";\nimport type { Logger } from \"@kaiord/core\";\n\nconst ACCOUNT_LOCKED_RE = /var status\\s*=\\s*\"([^\"]*)\"/;\nconst PAGE_TITLE_RE = /<title>([^<]*)<\\/title>/;\n\nexport const checkAccountLocked = (html: string): void => {\n const match = ACCOUNT_LOCKED_RE.exec(html);\n if (match && match[1] === \"ACCOUNT_LOCKED\") {\n throw createServiceAuthError(\n `Account locked: ${match[1]}. Unlock via Garmin Connect web.`\n );\n }\n};\n\nexport const checkPageTitle = (html: string, logger: Logger): void => {\n const match = PAGE_TITLE_RE.exec(html);\n if (match?.[1]?.includes(\"Update Phone Number\")) {\n throw createServiceAuthError(\"Login failed: phone number update required.\");\n }\n if (match) {\n logger.debug(\"Login page title\", { title: match[1] });\n }\n};\n","import { createServiceAuthError } from \"@kaiord/core\";\nimport { checkAccountLocked, checkPageTitle } from \"./sso-validators\";\nimport {\n GARMIN_SSO_EMBED,\n GC_MODERN,\n GARMIN_SSO_ORIGIN,\n SIGNIN_URL,\n USER_AGENT_BROWSER,\n} from \"./urls\";\nimport type { FetchFn } from \"./types\";\nimport type { Logger } from \"@kaiord/core\";\n\nconst CSRF_RE = /name=\"_csrf\"\\s+value=\"(.+?)\"/;\nconst TICKET_RE = /ticket=([^\"]+)\"/;\n\nconst fetchCsrfToken = async (fetchFn: FetchFn): Promise<string> => {\n const signinParams = new URLSearchParams({\n id: \"gauth-widget\",\n embedWidget: \"true\",\n locale: \"en\",\n gauthHost: GARMIN_SSO_EMBED,\n });\n const csrfRes = await fetchFn(`${SIGNIN_URL}?${signinParams}`);\n if (!csrfRes.ok) {\n throw createServiceAuthError(\n `SSO login page returned ${csrfRes.status}: ${csrfRes.statusText}`\n );\n }\n const csrfHtml = await csrfRes.text();\n const csrfMatch = CSRF_RE.exec(csrfHtml);\n if (!csrfMatch) {\n throw createServiceAuthError(\"CSRF token not found on login page\");\n }\n return csrfMatch[1];\n};\n\nconst submitLogin = async (\n username: string,\n password: string,\n csrf: string,\n fetchFn: FetchFn\n): Promise<string> => {\n const loginParams = new URLSearchParams({\n id: \"gauth-widget\",\n embedWidget: \"true\",\n clientId: \"GarminConnect\",\n locale: \"en\",\n gauthHost: GARMIN_SSO_EMBED,\n service: GARMIN_SSO_EMBED,\n source: GARMIN_SSO_EMBED,\n redirectAfterAccountLoginUrl: GARMIN_SSO_EMBED,\n redirectAfterAccountCreationUrl: GARMIN_SSO_EMBED,\n });\n const body = new URLSearchParams({\n username,\n password,\n embed: \"true\",\n _csrf: csrf,\n });\n const loginRes = await fetchFn(`${SIGNIN_URL}?${loginParams}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Dnt: \"1\",\n Origin: GARMIN_SSO_ORIGIN,\n Referer: SIGNIN_URL,\n \"User-Agent\": USER_AGENT_BROWSER,\n },\n body: body.toString(),\n });\n return loginRes.text();\n};\n\nexport const getLoginTicket = async (\n username: string,\n password: string,\n fetchFn: FetchFn,\n logger: Logger\n): Promise<string> => {\n const embedParams = new URLSearchParams({\n clientId: \"GarminConnect\",\n locale: \"en\",\n service: GC_MODERN,\n });\n await fetchFn(`${GARMIN_SSO_EMBED}?${embedParams}`);\n\n const csrf = await fetchCsrfToken(fetchFn);\n const loginHtml = await submitLogin(username, password, csrf, fetchFn);\n\n checkAccountLocked(loginHtml);\n checkPageTitle(loginHtml, logger);\n\n const ticketMatch = TICKET_RE.exec(loginHtml);\n if (!ticketMatch) {\n throw createServiceAuthError(\n \"Login failed: ticket not found. Check username and password.\"\n );\n }\n return ticketMatch[1];\n};\n","import { createHmac } from \"node:crypto\";\nimport OAuth from \"oauth-1.0a\";\nimport type { OAuthConsumer } from \"./types\";\n\nexport type { OAuthConsumer } from \"./types\";\nexport type OAuthToken = { key: string; secret: string };\n\nexport type OAuthSigner = {\n toHeader: (\n request: { url: string; method: string },\n token?: OAuthToken\n ) => Record<string, string>;\n};\n\nexport const createOAuthSigner = (consumer: OAuthConsumer): OAuthSigner => {\n const oauth = new OAuth({\n consumer,\n signature_method: \"HMAC-SHA1\",\n hash_function(baseString: string, key: string) {\n return createHmac(\"sha1\", key).update(baseString).digest(\"base64\");\n },\n });\n\n return {\n toHeader: (request, token) => {\n const authorized = token\n ? oauth.authorize(request, token)\n : oauth.authorize(request);\n const header = oauth.toHeader(authorized);\n return Object.fromEntries(Object.entries(header));\n },\n };\n};\n","import { createServiceAuthError } from \"@kaiord/core\";\nimport { createOAuthSigner } from \"./oauth-signer\";\nimport { GARMIN_SSO_EMBED, OAUTH_URL, USER_AGENT_MOBILE } from \"./urls\";\nimport type { FetchFn, OAuthConsumer } from \"./types\";\nimport type { OAuth1Token, OAuth2Token } from \"./types\";\n\nexport const getOAuth1Token = async (\n ticket: string,\n consumer: OAuthConsumer,\n fetchFn: FetchFn\n): Promise<OAuth1Token> => {\n const signer = createOAuthSigner(consumer);\n const params = new URLSearchParams({\n ticket,\n \"login-url\": GARMIN_SSO_EMBED,\n \"accepts-mfa-tokens\": \"true\",\n });\n const url = `${OAUTH_URL}/preauthorized?${params}`;\n const headers = signer.toHeader({ url, method: \"GET\" });\n\n const res = await fetchFn(url, {\n headers: { ...headers, \"User-Agent\": USER_AGENT_MOBILE },\n });\n if (!res.ok) {\n throw createServiceAuthError(\n `OAuth1 token request failed: ${res.status} ${res.statusText}`\n );\n }\n const text = await res.text();\n const parsed = new URLSearchParams(text);\n\n const oauthToken = parsed.get(\"oauth_token\");\n const oauthTokenSecret = parsed.get(\"oauth_token_secret\");\n if (!oauthToken || !oauthTokenSecret) {\n throw createServiceAuthError(\"OAuth1 token exchange failed\");\n }\n\n return { oauth_token: oauthToken, oauth_token_secret: oauthTokenSecret };\n};\n\nexport const exchangeOAuth2 = async (\n oauth1: OAuth1Token,\n consumer: OAuthConsumer,\n fetchFn: FetchFn\n): Promise<OAuth2Token> => {\n const signer = createOAuthSigner(consumer);\n const baseUrl = `${OAUTH_URL}/exchange/user/2.0`;\n const token = {\n key: oauth1.oauth_token,\n secret: oauth1.oauth_token_secret,\n };\n const authHeader = signer.toHeader({ url: baseUrl, method: \"POST\" }, token);\n\n const res = await fetchFn(baseUrl, {\n method: \"POST\",\n headers: {\n ...authHeader,\n \"User-Agent\": USER_AGENT_MOBILE,\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n });\n if (!res.ok) {\n throw createServiceAuthError(\n `OAuth2 exchange failed: ${res.status} ${res.statusText}`\n );\n }\n const data = (await res.json()) as OAuth2Token;\n\n return {\n ...data,\n expires_at: Math.floor(Date.now() / 1000) + data.expires_in,\n };\n};\n","import { fetchOAuthConsumer } from \"./oauth-consumer\";\nimport { getLoginTicket } from \"./sso-login\";\nimport { getOAuth1Token, exchangeOAuth2 as exchange } from \"./sso-oauth\";\nimport type { FetchFn, OAuth1Token, OAuth2Token } from \"./types\";\nimport type { Logger } from \"@kaiord/core\";\n\nexport type { OAuth1Token, OAuth2Token } from \"./types\";\nexport type SsoResult = { oauth1: OAuth1Token; oauth2: OAuth2Token };\n\n/**\n * Garmin Connect SSO login flow.\n * The fetchFn must be cookie-aware (persist cookies across requests).\n * Use fetch-cookie or similar wrapper for Node.js environments.\n */\nexport const garminSso = async (\n username: string,\n password: string,\n logger: Logger,\n fetchFn: FetchFn\n): Promise<SsoResult> => {\n logger.info(\"Starting Garmin Connect SSO login\");\n\n const consumer = await fetchOAuthConsumer(fetchFn);\n const ticket = await getLoginTicket(username, password, fetchFn, logger);\n logger.debug(\"SSO ticket obtained\");\n\n const oauth1 = await getOAuth1Token(ticket, consumer, fetchFn);\n logger.debug(\"OAuth1 token obtained\");\n\n const oauth2 = await exchange(oauth1, consumer, fetchFn);\n logger.info(\"Garmin Connect SSO login successful\");\n\n return { oauth1, oauth2 };\n};\n\nexport { exchangeOAuth2 } from \"./sso-oauth\";\n","import { createServiceApiError } from \"@kaiord/core\";\nimport { exchangeOAuth2 } from \"./garmin-sso\";\nimport { fetchOAuthConsumer } from \"./oauth-consumer\";\nimport type { FetchFn, OAuthConsumer, OAuth1Token, OAuth2Token } from \"./types\";\nimport type { Logger } from \"@kaiord/core\";\n\ntype RefreshSubscriber = {\n resolve: (token: string) => void;\n reject: (error: unknown) => void;\n};\n\nexport type TokenState = {\n oauth1Token: OAuth1Token | undefined;\n oauth2Token: OAuth2Token | undefined;\n consumer: OAuthConsumer | undefined;\n};\n\nexport type TokenRefreshManager = {\n ensureFreshToken: () => Promise<void>;\n state: TokenState;\n};\n\nconst notifyAll = (subs: RefreshSubscriber[], token: string): void => {\n subs.forEach((s) => s.resolve(token));\n};\n\nconst rejectAll = (subs: RefreshSubscriber[], error: unknown): void => {\n subs.forEach((s) => s.reject(error));\n};\n\nconst getOrFetchConsumer = async (\n state: TokenState,\n fetchFn: FetchFn\n): Promise<OAuthConsumer> => {\n if (state.consumer) return state.consumer;\n state.consumer = await fetchOAuthConsumer(fetchFn);\n return state.consumer;\n};\n\nconst doRefreshToken = async (\n state: TokenState,\n fetchFn: FetchFn,\n logger: Logger\n): Promise<void> => {\n if (!state.oauth1Token || !state.oauth2Token) {\n throw createServiceApiError(\"No tokens available for refresh\", 401);\n }\n const cons = await getOrFetchConsumer(state, fetchFn);\n state.oauth2Token = await exchangeOAuth2(state.oauth1Token, cons, fetchFn);\n logger.info(\"OAuth2 token refreshed\");\n};\n\nexport const createTokenRefreshManager = (\n fetchFn: FetchFn,\n logger: Logger\n): TokenRefreshManager => {\n const state: TokenState = {\n oauth1Token: undefined,\n oauth2Token: undefined,\n consumer: undefined,\n };\n let isRefreshing = false;\n let subscribers: RefreshSubscriber[] = [];\n\n return {\n state,\n ensureFreshToken: async (): Promise<void> => {\n if (isRefreshing) {\n await new Promise<string>((resolve, reject) => {\n subscribers.push({ resolve, reject });\n });\n return;\n }\n isRefreshing = true;\n try {\n await doRefreshToken(state, fetchFn, logger);\n if (state.oauth2Token)\n notifyAll(subscribers, state.oauth2Token.access_token);\n subscribers = [];\n } catch (error) {\n rejectAll(subscribers, error);\n subscribers = [];\n throw error;\n } finally {\n isRefreshing = false;\n }\n },\n };\n};\n","import { authFetch } from \"./garmin-auth-fetch\";\nimport { createTokenRefreshManager } from \"./token-refresh\";\nimport type {\n FetchFn,\n GarminHttpClient,\n OAuth1Token,\n OAuth2Token,\n} from \"./types\";\nimport type { Logger } from \"@kaiord/core\";\n\nexport type { GarminHttpClient } from \"./types\";\n\nexport const createGarminHttpClient = (\n logger: Logger,\n fetchFn: FetchFn = globalThis.fetch\n): GarminHttpClient => {\n const refresh = createTokenRefreshManager(fetchFn, logger);\n const fetch = (url: string, init?: RequestInit) =>\n authFetch(url, init, refresh, fetchFn);\n\n return {\n get: async <T>(url: string): Promise<T> => {\n const res = await fetch(url);\n return (await res.json()) as T;\n },\n post: async <T>(url: string, body: unknown): Promise<T> => {\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: body !== null ? JSON.stringify(body) : undefined,\n });\n return (await res.json()) as T;\n },\n del: async <T>(url: string): Promise<T> => {\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"X-Http-Method-Override\": \"DELETE\" },\n });\n const text = await res.text();\n return (text ? JSON.parse(text) : undefined) as T;\n },\n setTokens: (o1: OAuth1Token, o2: OAuth2Token): void => {\n refresh.state.oauth1Token = o1;\n refresh.state.oauth2Token = o2;\n },\n clearTokens: (): void => {\n refresh.state.oauth1Token = undefined;\n refresh.state.oauth2Token = undefined;\n refresh.state.consumer = undefined;\n },\n getOAuth2Token: () => refresh.state.oauth2Token,\n };\n};\n","import { z } from \"zod\";\n\nexport const oauth1TokenSchema = z.object({\n oauth_token: z.string(),\n oauth_token_secret: z.string(),\n});\n\nexport const oauth2TokenSchema = z.object({\n access_token: z.string(),\n refresh_token: z.string(),\n token_type: z.string(),\n expires_in: z.number(),\n refresh_token_expires_in: z.number(),\n expires_at: z.number(),\n});\n\nexport const garminTokensSchema = z.object({\n oauth1: oauth1TokenSchema,\n oauth2: oauth2TokenSchema,\n});\n","import { createConsoleLogger, createServiceAuthError } from \"@kaiord/core\";\nimport { createCookieFetch } from \"../http/cookie-fetch\";\nimport { createGarminHttpClient } from \"../http/garmin-http-client\";\nimport { garminSso } from \"../http/garmin-sso\";\nimport { garminTokensSchema } from \"../schemas/garmin-token.schema\";\nimport type { GarminHttpClient } from \"../http/garmin-http-client\";\nimport type { AuthProvider, Logger, TokenStore } from \"@kaiord/core\";\n\nexport type GarminAuthProviderOptions = {\n logger?: Logger;\n tokenStore?: TokenStore;\n fetchFn?: typeof globalThis.fetch;\n};\n\nexport type GarminAuthProviderResult = {\n auth: AuthProvider;\n httpClient: GarminHttpClient;\n};\n\ntype AuthState = {\n oauth1: { oauth_token: string; oauth_token_secret: string } | undefined;\n};\n\ntype AuthContext = {\n httpClient: GarminHttpClient;\n state: AuthState;\n tokenStore: TokenStore | undefined;\n logger: Logger;\n fetchFn: typeof globalThis.fetch;\n};\n\nconst buildAuthProvider = (ctx: AuthContext): AuthProvider => ({\n login: async (username, password) => {\n const result = await garminSso(username, password, ctx.logger, ctx.fetchFn);\n ctx.state.oauth1 = result.oauth1;\n ctx.httpClient.setTokens(result.oauth1, result.oauth2);\n if (ctx.tokenStore) {\n await ctx.tokenStore.save({\n oauth1: result.oauth1,\n oauth2: result.oauth2,\n });\n }\n },\n is_authenticated: () => {\n const token = ctx.httpClient.getOAuth2Token();\n if (!token) return false;\n return token.expires_at > Math.floor(Date.now() / 1000);\n },\n export_tokens: async () => {\n const token = ctx.httpClient.getOAuth2Token();\n if (!token || !ctx.state.oauth1) {\n throw createServiceAuthError(\"No tokens to export\");\n }\n return { oauth1: ctx.state.oauth1, oauth2: token };\n },\n restore_tokens: async (data) => {\n const parsed = garminTokensSchema.parse(data);\n ctx.state.oauth1 = parsed.oauth1;\n ctx.httpClient.setTokens(parsed.oauth1, parsed.oauth2);\n ctx.logger.info(\"Tokens restored from stored session\");\n },\n logout: async () => {\n ctx.state.oauth1 = undefined;\n ctx.httpClient.clearTokens();\n if (ctx.tokenStore) await ctx.tokenStore.clear();\n ctx.logger.info(\"Logged out from Garmin Connect\");\n },\n});\n\nexport const createGarminAuthProvider = (\n options?: GarminAuthProviderOptions\n): GarminAuthProviderResult => {\n const logger = options?.logger ?? createConsoleLogger();\n const tokenStore = options?.tokenStore;\n const fetchFn = options?.fetchFn ?? createCookieFetch();\n const httpClient = createGarminHttpClient(logger, fetchFn);\n const state: AuthState = { oauth1: undefined };\n const auth = buildAuthProvider({\n httpClient,\n state,\n tokenStore,\n logger,\n fetchFn,\n });\n return { auth, httpClient };\n};\n","import type { WorkoutSummary } from \"@kaiord/core\";\n\nexport const mapToWorkoutSummary = (garminWorkout: {\n workoutId?: number | string;\n workoutName?: string;\n sportType?: { sportTypeKey?: string };\n createdDate?: number | string;\n updatedDate?: number | string;\n}): WorkoutSummary => ({\n id: String(garminWorkout.workoutId ?? \"\"),\n name: garminWorkout.workoutName ?? \"Unnamed\",\n sport: garminWorkout.sportType?.sportTypeKey ?? \"unknown\",\n created_at: garminWorkout.createdDate\n ? new Date(garminWorkout.createdDate).toISOString()\n : \"\",\n updated_at: garminWorkout.updatedDate\n ? new Date(garminWorkout.updatedDate).toISOString()\n : \"\",\n});\n","import { z } from \"zod\";\n\n/** GET /workout-service/workouts?start=N&limit=N - List workout summaries */\nexport const garminWorkoutSummarySchema = z.object({\n workoutId: z.number().or(z.string()),\n workoutName: z.string().optional(),\n sportType: z.object({ sportTypeKey: z.string().optional() }).optional(),\n createdDate: z.number().or(z.string()).optional(),\n updatedDate: z.number().or(z.string()).optional(),\n});\n\n/** POST /workout-service/workout - Push a workout (returns created workout) */\nexport const garminPushResponseSchema = z.object({\n workoutId: z.number().or(z.string()),\n workoutName: z.string().optional(),\n});\n","import {\n createConsoleLogger,\n toText,\n createServiceApiError,\n} from \"@kaiord/core\";\nimport { createGarminWriter } from \"@kaiord/garmin\";\nimport { WORKOUT_URL } from \"../http/urls\";\nimport { mapToWorkoutSummary } from \"../mappers/workout-summary.mapper\";\nimport {\n garminPushResponseSchema,\n garminWorkoutSummarySchema,\n} from \"../schemas/workout-response.schema\";\nimport type { GarminHttpClient } from \"../http/garmin-http-client\";\nimport type {\n KRD,\n ListOptions,\n Logger,\n PushResult,\n WorkoutService,\n WorkoutSummary,\n} from \"@kaiord/core\";\n\nexport type GarminWorkoutClient = Pick<WorkoutService, \"push\" | \"list\">;\n\nconst pushWorkout = async (\n krd: KRD,\n httpClient: GarminHttpClient,\n garminWriter: ReturnType<typeof createGarminWriter>,\n log: Logger\n): Promise<PushResult> => {\n try {\n log.info(\"Pushing workout to Garmin Connect\");\n const gcnJson = await toText(krd, garminWriter, log);\n const payload = JSON.parse(gcnJson) as Record<string, unknown>;\n\n const raw = await httpClient.post<unknown>(\n `${WORKOUT_URL}/workout`,\n payload\n );\n const result = garminPushResponseSchema.parse(raw);\n\n return {\n id: String(result.workoutId),\n name: result.workoutName ?? \"Workout\",\n url: `https://connect.garmin.com/modern/workout/${result.workoutId}`,\n };\n } catch (error) {\n throw createServiceApiError(\"Failed to push workout\", undefined, error);\n }\n};\n\nconst listWorkouts = async (\n httpClient: GarminHttpClient,\n log: Logger,\n options?: ListOptions\n): Promise<WorkoutSummary[]> => {\n try {\n log.info(\"Listing workouts from Garmin Connect\");\n const start = options?.offset ?? 0;\n const limit = options?.limit ?? 20;\n const params = new URLSearchParams({\n start: String(start),\n limit: String(limit),\n });\n\n const raw = await httpClient.get<unknown>(\n `${WORKOUT_URL}/workouts?${params}`\n );\n const workouts = garminWorkoutSummarySchema.array().parse(raw);\n return workouts.map(mapToWorkoutSummary);\n } catch (error) {\n throw createServiceApiError(\"Failed to list workouts\", undefined, error);\n }\n};\n\nexport const createGarminWorkoutService = (\n httpClient: GarminHttpClient,\n logger?: Logger\n): GarminWorkoutClient => {\n const log = logger ?? createConsoleLogger();\n const garminWriter = createGarminWriter(log);\n\n return {\n push: (krd) => pushWorkout(krd, httpClient, garminWriter, log),\n list: (opts) => listWorkouts(httpClient, log, opts),\n };\n};\n","import { createGarminWorkoutService } from \"./garmin-workout-service\";\nimport { createGarminAuthProvider } from \"../auth/garmin-auth-provider\";\nimport type { GarminAuthProviderOptions } from \"../auth/garmin-auth-provider\";\n\nexport type GarminConnectClientOptions = GarminAuthProviderOptions;\n\nexport const createGarminConnectClient = (\n options?: GarminConnectClientOptions\n) => {\n const { auth, httpClient } = createGarminAuthProvider(options);\n const service = createGarminWorkoutService(httpClient, options?.logger);\n return { auth, service };\n};\n","import { readFile, writeFile, mkdir, unlink } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport type { TokenData, TokenStore } from \"@kaiord/core\";\n\nconst DEFAULT_PATH = join(homedir(), \".kaiord\", \"garmin-tokens.json\");\n\nexport const createFileTokenStore = (\n filePath: string = DEFAULT_PATH\n): TokenStore => ({\n save: async (tokens: TokenData) => {\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, JSON.stringify(tokens, null, 2), {\n encoding: \"utf-8\",\n mode: 0o600,\n });\n },\n\n load: async (): Promise<TokenData | null> => {\n try {\n const content = await readFile(filePath, \"utf-8\");\n return JSON.parse(content) as TokenData;\n } catch {\n return null;\n }\n },\n\n clear: async () => {\n try {\n await unlink(filePath);\n } catch {\n // File may not exist\n }\n },\n});\n","import type { TokenData, TokenStore } from \"@kaiord/core\";\n\nexport const createMemoryTokenStore = (): TokenStore => {\n let stored: TokenData | null = null;\n\n return {\n save: async (tokens: TokenData) => {\n stored = tokens;\n },\n load: async () => stored,\n clear: async () => {\n stored = null;\n },\n };\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/adapters/http/cookie-fetch.ts","../src/adapters/http/urls.ts","../src/adapters/http/oauth-consumer.ts","../src/adapters/http/sso-validators.ts","../src/adapters/http/sso-login.ts","../src/adapters/http/oauth-signer.ts","../src/adapters/http/sso-oauth.ts","../src/adapters/http/garmin-sso.ts","../src/adapters/schemas/garmin-token.schema.ts","../src/adapters/auth/garmin-auth-provider.ts","../src/adapters/token/token-manager.helpers.ts","../src/adapters/token/token-manager.ts","../src/adapters/client/build-refresh-fn.ts","../src/adapters/mappers/workout-summary.mapper.ts","../src/adapters/schemas/workout-response.schema.ts","../src/adapters/client/garmin-workout-service.ts","../src/adapters/http/garmin-auth-fetch.ts","../src/adapters/http/garmin-http-client.ts","../src/adapters/http/retry.ts","../src/adapters/client/garmin-connect-client.ts","../src/adapters/token-store/file-token-store.ts","../src/adapters/token-store/memory-token-store.ts"],"names":["createServiceAuthError","z","createServiceApiError","createConsoleLogger"],"mappings":"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","file":"index.js","sourcesContent":["import fetchCookie from \"fetch-cookie\";\n\nexport const createCookieFetch = (): typeof globalThis.fetch =>\n fetchCookie(globalThis.fetch) as typeof globalThis.fetch;\n","export const GARMIN_SSO_ORIGIN = \"https://sso.garmin.com\";\nexport const GARMIN_SSO_EMBED = \"https://sso.garmin.com/sso/embed\";\nexport const SIGNIN_URL = \"https://sso.garmin.com/sso/signin\";\nexport const GC_MODERN = \"https://connect.garmin.com/modern\";\nexport const API_BASE = \"https://connectapi.garmin.com\";\nexport const OAUTH_URL = `${API_BASE}/oauth-service/oauth`;\nexport const WORKOUT_URL = `${API_BASE}/workout-service`;\n/** OAuth consumer credentials hosted by the garth project (third-party). */\nexport const OAUTH_CONSUMER_URL =\n \"https://thegarth.s3.amazonaws.com/oauth_consumer.json\";\n\nexport const USER_AGENT_MOBILE = \"com.garmin.android.apps.connectmobile\";\nexport const USER_AGENT_BROWSER =\n \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36\";\n","import { createServiceAuthError } from \"@kaiord/core\";\nimport { OAUTH_CONSUMER_URL } from \"./urls\";\nimport type { FetchFn, OAuthConsumer } from \"./types\";\n\nexport const fetchOAuthConsumer = async (\n fetchFn: FetchFn\n): Promise<OAuthConsumer> => {\n const res = await fetchFn(OAUTH_CONSUMER_URL);\n if (!res.ok) {\n throw createServiceAuthError(\n `Failed to fetch OAuth consumer: ${res.status} ${res.statusText}`\n );\n }\n const data = (await res.json()) as {\n consumer_key: string;\n consumer_secret: string;\n };\n return { key: data.consumer_key, secret: data.consumer_secret };\n};\n","import { createServiceAuthError } from \"@kaiord/core\";\nimport type { Logger } from \"@kaiord/core\";\n\nconst ACCOUNT_LOCKED_RE = /var status\\s*=\\s*\"([^\"]*)\"/;\nconst PAGE_TITLE_RE = /<title>([^<]*)<\\/title>/;\n\nexport const checkAccountLocked = (html: string): void => {\n const match = ACCOUNT_LOCKED_RE.exec(html);\n if (match && match[1] === \"ACCOUNT_LOCKED\") {\n throw createServiceAuthError(\n `Account locked: ${match[1]}. Unlock via Garmin Connect web.`\n );\n }\n};\n\nexport const checkPageTitle = (html: string, logger: Logger): void => {\n const match = PAGE_TITLE_RE.exec(html);\n if (match?.[1]?.includes(\"Update Phone Number\")) {\n throw createServiceAuthError(\"Login failed: phone number update required.\");\n }\n if (match) {\n logger.debug(\"Login page title\", { title: match[1] });\n }\n};\n","import { createServiceAuthError } from \"@kaiord/core\";\nimport { checkAccountLocked, checkPageTitle } from \"./sso-validators\";\nimport {\n GARMIN_SSO_EMBED,\n GC_MODERN,\n GARMIN_SSO_ORIGIN,\n SIGNIN_URL,\n USER_AGENT_BROWSER,\n} from \"./urls\";\nimport type { FetchFn } from \"./types\";\nimport type { Logger } from \"@kaiord/core\";\n\nconst CSRF_RE = /name=\"_csrf\"\\s+value=\"(.+?)\"/;\nconst TICKET_RE = /ticket=([^\"]+)\"/;\n\nconst fetchCsrfToken = async (fetchFn: FetchFn): Promise<string> => {\n const signinParams = new URLSearchParams({\n id: \"gauth-widget\",\n embedWidget: \"true\",\n locale: \"en\",\n gauthHost: GARMIN_SSO_EMBED,\n });\n const csrfRes = await fetchFn(`${SIGNIN_URL}?${signinParams}`);\n if (!csrfRes.ok) {\n throw createServiceAuthError(\n `SSO login page returned ${csrfRes.status}: ${csrfRes.statusText}`\n );\n }\n const csrfHtml = await csrfRes.text();\n const csrfMatch = CSRF_RE.exec(csrfHtml);\n if (!csrfMatch) {\n throw createServiceAuthError(\"CSRF token not found on login page\");\n }\n return csrfMatch[1];\n};\n\nconst submitLogin = async (\n username: string,\n password: string,\n csrf: string,\n fetchFn: FetchFn\n): Promise<string> => {\n const loginParams = new URLSearchParams({\n id: \"gauth-widget\",\n embedWidget: \"true\",\n clientId: \"GarminConnect\",\n locale: \"en\",\n gauthHost: GARMIN_SSO_EMBED,\n service: GARMIN_SSO_EMBED,\n source: GARMIN_SSO_EMBED,\n redirectAfterAccountLoginUrl: GARMIN_SSO_EMBED,\n redirectAfterAccountCreationUrl: GARMIN_SSO_EMBED,\n });\n const body = new URLSearchParams({\n username,\n password,\n embed: \"true\",\n _csrf: csrf,\n });\n const loginRes = await fetchFn(`${SIGNIN_URL}?${loginParams}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Dnt: \"1\",\n Origin: GARMIN_SSO_ORIGIN,\n Referer: SIGNIN_URL,\n \"User-Agent\": USER_AGENT_BROWSER,\n },\n body: body.toString(),\n });\n return loginRes.text();\n};\n\nexport const getLoginTicket = async (\n username: string,\n password: string,\n fetchFn: FetchFn,\n logger: Logger\n): Promise<string> => {\n const embedParams = new URLSearchParams({\n clientId: \"GarminConnect\",\n locale: \"en\",\n service: GC_MODERN,\n });\n await fetchFn(`${GARMIN_SSO_EMBED}?${embedParams}`);\n\n const csrf = await fetchCsrfToken(fetchFn);\n const loginHtml = await submitLogin(username, password, csrf, fetchFn);\n\n checkAccountLocked(loginHtml);\n checkPageTitle(loginHtml, logger);\n\n const ticketMatch = TICKET_RE.exec(loginHtml);\n if (!ticketMatch) {\n throw createServiceAuthError(\n \"Login failed: ticket not found. Check username and password.\"\n );\n }\n return ticketMatch[1];\n};\n","import { createHmac } from \"node:crypto\";\nimport OAuth from \"oauth-1.0a\";\nimport type { OAuthConsumer } from \"./types\";\n\nexport type { OAuthConsumer } from \"./types\";\nexport type OAuthToken = { key: string; secret: string };\n\nexport type OAuthSigner = {\n toHeader: (\n request: { url: string; method: string },\n token?: OAuthToken\n ) => Record<string, string>;\n};\n\nexport const createOAuthSigner = (consumer: OAuthConsumer): OAuthSigner => {\n const oauth = new OAuth({\n consumer,\n signature_method: \"HMAC-SHA1\",\n hash_function(baseString: string, key: string) {\n return createHmac(\"sha1\", key).update(baseString).digest(\"base64\");\n },\n });\n\n return {\n toHeader: (request, token) => {\n const authorized = token\n ? oauth.authorize(request, token)\n : oauth.authorize(request);\n const header = oauth.toHeader(authorized);\n return Object.fromEntries(Object.entries(header));\n },\n };\n};\n","import { createServiceAuthError } from \"@kaiord/core\";\nimport { createOAuthSigner } from \"./oauth-signer\";\nimport { GARMIN_SSO_EMBED, OAUTH_URL, USER_AGENT_MOBILE } from \"./urls\";\nimport type { FetchFn, OAuthConsumer } from \"./types\";\nimport type { OAuth1Token, OAuth2Token } from \"./types\";\n\nexport const getOAuth1Token = async (\n ticket: string,\n consumer: OAuthConsumer,\n fetchFn: FetchFn\n): Promise<OAuth1Token> => {\n const signer = createOAuthSigner(consumer);\n const params = new URLSearchParams({\n ticket,\n \"login-url\": GARMIN_SSO_EMBED,\n \"accepts-mfa-tokens\": \"true\",\n });\n const url = `${OAUTH_URL}/preauthorized?${params}`;\n const headers = signer.toHeader({ url, method: \"GET\" });\n\n const res = await fetchFn(url, {\n headers: { ...headers, \"User-Agent\": USER_AGENT_MOBILE },\n });\n if (!res.ok) {\n throw createServiceAuthError(\n `OAuth1 token request failed: ${res.status} ${res.statusText}`\n );\n }\n const text = await res.text();\n const parsed = new URLSearchParams(text);\n\n const oauthToken = parsed.get(\"oauth_token\");\n const oauthTokenSecret = parsed.get(\"oauth_token_secret\");\n if (!oauthToken || !oauthTokenSecret) {\n throw createServiceAuthError(\"OAuth1 token exchange failed\");\n }\n\n return { oauth_token: oauthToken, oauth_token_secret: oauthTokenSecret };\n};\n\nexport const exchangeOAuth2 = async (\n oauth1: OAuth1Token,\n consumer: OAuthConsumer,\n fetchFn: FetchFn\n): Promise<OAuth2Token> => {\n const signer = createOAuthSigner(consumer);\n const baseUrl = `${OAUTH_URL}/exchange/user/2.0`;\n const token = {\n key: oauth1.oauth_token,\n secret: oauth1.oauth_token_secret,\n };\n const authHeader = signer.toHeader({ url: baseUrl, method: \"POST\" }, token);\n\n const res = await fetchFn(baseUrl, {\n method: \"POST\",\n headers: {\n ...authHeader,\n \"User-Agent\": USER_AGENT_MOBILE,\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n });\n if (!res.ok) {\n throw createServiceAuthError(\n `OAuth2 exchange failed: ${res.status} ${res.statusText}`\n );\n }\n const data = (await res.json()) as OAuth2Token;\n\n return {\n ...data,\n expires_at: Math.floor(Date.now() / 1000) + data.expires_in,\n };\n};\n","import { fetchOAuthConsumer } from \"./oauth-consumer\";\nimport { getLoginTicket } from \"./sso-login\";\nimport { getOAuth1Token, exchangeOAuth2 as exchange } from \"./sso-oauth\";\nimport type { FetchFn, OAuth1Token, OAuth2Token } from \"./types\";\nimport type { Logger } from \"@kaiord/core\";\n\nexport type { OAuth1Token, OAuth2Token } from \"./types\";\nexport type SsoResult = { oauth1: OAuth1Token; oauth2: OAuth2Token };\n\n/**\n * Garmin Connect SSO login flow.\n * The fetchFn must be cookie-aware (persist cookies across requests).\n * Use fetch-cookie or similar wrapper for Node.js environments.\n */\nexport const garminSso = async (\n username: string,\n password: string,\n logger: Logger,\n fetchFn: FetchFn\n): Promise<SsoResult> => {\n logger.info(\"Starting Garmin Connect SSO login\");\n\n const consumer = await fetchOAuthConsumer(fetchFn);\n const ticket = await getLoginTicket(username, password, fetchFn, logger);\n logger.debug(\"SSO ticket obtained\");\n\n const oauth1 = await getOAuth1Token(ticket, consumer, fetchFn);\n logger.debug(\"OAuth1 token obtained\");\n\n const oauth2 = await exchange(oauth1, consumer, fetchFn);\n logger.info(\"Garmin Connect SSO login successful\");\n\n return { oauth1, oauth2 };\n};\n\nexport { exchangeOAuth2 } from \"./sso-oauth\";\n","import { z } from \"zod\";\n\nexport const oauth1TokenSchema = z.object({\n oauth_token: z.string(),\n oauth_token_secret: z.string(),\n});\n\nexport const oauth2TokenSchema = z.object({\n access_token: z.string(),\n refresh_token: z.string(),\n token_type: z.string(),\n expires_in: z.number(),\n refresh_token_expires_in: z.number(),\n expires_at: z.number(),\n});\n\nexport const garminTokensSchema = z.object({\n oauth1: oauth1TokenSchema,\n oauth2: oauth2TokenSchema,\n});\n","import { createConsoleLogger, createServiceAuthError } from \"@kaiord/core\";\nimport { createCookieFetch } from \"../http/cookie-fetch\";\nimport { garminSso } from \"../http/garmin-sso\";\nimport { garminTokensSchema } from \"../schemas/garmin-token.schema\";\nimport type { FetchFn } from \"../http/types\";\nimport type { TokenManager } from \"../token/token-manager.types\";\nimport type { AuthProvider, Logger } from \"@kaiord/core\";\n\nexport type GarminAuthProviderOptions = {\n tokenManager: TokenManager;\n logger?: Logger;\n fetchFn?: FetchFn;\n};\n\nconst buildAuthProvider = (\n tm: TokenManager,\n logger: Logger,\n fetchFn: FetchFn\n): AuthProvider => ({\n login: async (username, password) => {\n const result = await garminSso(username, password, logger, fetchFn);\n await tm.setTokens(result.oauth1, result.oauth2);\n },\n is_authenticated: () => tm.isAuthenticated(),\n export_tokens: async () => {\n const oauth1 = tm.getOAuth1Token();\n const oauth2 = tm.getOAuth2Token();\n if (!oauth1 || !oauth2) {\n throw createServiceAuthError(\"No tokens to export\");\n }\n return { oauth1: { ...oauth1 }, oauth2: { ...oauth2 } };\n },\n restore_tokens: async (data) => {\n const parsed = garminTokensSchema.parse(data);\n await tm.setTokens(parsed.oauth1, parsed.oauth2);\n logger.info(\"Tokens restored from stored session\");\n },\n logout: async () => {\n await tm.clearTokens();\n logger.info(\"Logged out from Garmin Connect\");\n },\n});\n\nexport const createGarminAuthProvider = (\n options: GarminAuthProviderOptions\n): AuthProvider => {\n const logger = options.logger ?? createConsoleLogger();\n const fetchFn = options.fetchFn ?? createCookieFetch();\n return buildAuthProvider(options.tokenManager, logger, fetchFn);\n};\n","import { createServiceApiError } from \"@kaiord/core\";\nimport { garminTokensSchema } from \"../schemas/garmin-token.schema\";\nimport type { RefreshFn } from \"./token-manager.types\";\nimport type { OAuth1Token, OAuth2Token } from \"../http/types\";\nimport type { Logger, TokenStore } from \"@kaiord/core\";\n\nexport type TokenState = {\n oauth1: OAuth1Token | undefined;\n oauth2: OAuth2Token | undefined;\n generation: number;\n refreshPromise: Promise<void> | undefined;\n};\n\nexport const persistBestEffort = async (\n store: TokenStore | undefined,\n oauth1: OAuth1Token,\n oauth2: OAuth2Token,\n logger: Logger\n): Promise<void> => {\n if (!store) return;\n try {\n await store.save({ oauth1, oauth2 });\n } catch (error) {\n logger.warn(\"Failed to persist tokens\", {\n errorName: error instanceof Error ? error.name : typeof error,\n });\n }\n};\n\nexport const isExpired = (oauth2: OAuth2Token | undefined): boolean =>\n !oauth2 || oauth2.expires_at <= Date.now() / 1000;\n\nexport const doRefresh = (\n s: TokenState,\n refreshFn: RefreshFn,\n logger: Logger,\n tokenStore: TokenStore | undefined\n): Promise<void> => {\n if (!s.oauth1) {\n throw createServiceApiError(\"No OAuth1 token for refresh\", 401);\n }\n const currentOAuth1 = s.oauth1;\n const generationAtStart = s.generation;\n s.refreshPromise = refreshFn(currentOAuth1)\n .then(async (newOAuth2) => {\n if (s.generation !== generationAtStart) return;\n s.oauth2 = newOAuth2;\n s.generation++;\n logger.info(\"Token refreshed\", { generation: s.generation });\n await persistBestEffort(tokenStore, currentOAuth1, newOAuth2, logger);\n })\n .finally(() => {\n s.refreshPromise = undefined;\n });\n return s.refreshPromise;\n};\n\nexport const restoreFromStore = async (\n s: TokenState,\n tokenStore: TokenStore,\n logger: Logger\n): Promise<{ restored: boolean }> => {\n const data = await tokenStore.load();\n if (!data) return { restored: false };\n if (s.oauth1 && s.oauth2) return { restored: false };\n const parsed = garminTokensSchema.safeParse(data);\n if (!parsed.success) return { restored: false };\n s.oauth1 = parsed.data.oauth1;\n s.oauth2 = parsed.data.oauth2;\n s.generation++;\n if (isExpired(s.oauth2)) logger.warn(\"Restored tokens are expired\");\n logger.info(\"Tokens restored from store\", { generation: s.generation });\n return { restored: true };\n};\n","import {\n doRefresh,\n isExpired,\n persistBestEffort,\n restoreFromStore,\n} from \"./token-manager.helpers\";\nimport type { TokenState } from \"./token-manager.helpers\";\nimport type { RefreshFn, TokenManager } from \"./token-manager.types\";\nimport type { Logger, TokenStore } from \"@kaiord/core\";\n\ntype Options = {\n refreshFn: RefreshFn;\n logger: Logger;\n tokenStore?: TokenStore;\n};\n\nexport const createTokenManager = (options: Options): TokenManager => {\n const { refreshFn, logger, tokenStore } = options;\n const s: TokenState = {\n oauth1: undefined,\n oauth2: undefined,\n generation: 0,\n refreshPromise: undefined,\n };\n\n return {\n getAccessToken: () => s.oauth2?.access_token,\n getOAuth1Token: () => (s.oauth1 ? { ...s.oauth1 } : undefined),\n getOAuth2Token: () => (s.oauth2 ? { ...s.oauth2 } : undefined),\n getGeneration: () => s.generation,\n isAuthenticated: () => !!s.oauth2 && !isExpired(s.oauth2),\n setTokens: async (o1, o2) => {\n s.oauth1 = o1;\n s.oauth2 = o2;\n s.generation++;\n logger.info(\"Tokens set\", { generation: s.generation });\n await persistBestEffort(tokenStore, o1, o2, logger);\n },\n clearTokens: async () => {\n s.oauth1 = undefined;\n s.oauth2 = undefined;\n s.refreshPromise = undefined;\n s.generation++;\n logger.info(\"Tokens cleared\");\n if (tokenStore) await tokenStore.clear();\n },\n refresh: async () => {\n if (s.refreshPromise) return s.refreshPromise;\n return doRefresh(s, refreshFn, logger, tokenStore);\n },\n init: async () => {\n if (s.oauth1 && s.oauth2) return { restored: false };\n if (!tokenStore) return { restored: false };\n return restoreFromStore(s, tokenStore, logger);\n },\n };\n};\n","import { fetchOAuthConsumer } from \"../http/oauth-consumer\";\nimport { exchangeOAuth2 } from \"../http/sso-oauth\";\nimport type { FetchFn, OAuthConsumer } from \"../http/types\";\nimport type { RefreshFn } from \"../token/token-manager.types\";\n\nexport const buildRefreshFn = (fetchFn: FetchFn): RefreshFn => {\n let consumer: OAuthConsumer | undefined;\n\n return async (oauth1) => {\n consumer ??= await fetchOAuthConsumer(fetchFn);\n try {\n return await exchangeOAuth2(oauth1, consumer, fetchFn);\n } catch {\n consumer = undefined;\n consumer = await fetchOAuthConsumer(fetchFn);\n return exchangeOAuth2(oauth1, consumer, fetchFn);\n }\n };\n};\n","import type { WorkoutSummary } from \"@kaiord/core\";\n\nexport const mapToWorkoutSummary = (garminWorkout: {\n workoutId?: number | string;\n workoutName?: string;\n sportType?: { sportTypeKey?: string };\n createdDate?: number | string;\n updatedDate?: number | string;\n}): WorkoutSummary => ({\n id: String(garminWorkout.workoutId ?? \"\"),\n name: garminWorkout.workoutName ?? \"Unnamed\",\n sport: garminWorkout.sportType?.sportTypeKey ?? \"unknown\",\n created_at: garminWorkout.createdDate\n ? new Date(garminWorkout.createdDate).toISOString()\n : \"\",\n updated_at: garminWorkout.updatedDate\n ? new Date(garminWorkout.updatedDate).toISOString()\n : \"\",\n});\n","import { z } from \"zod\";\n\n/** GET /workout-service/workouts?start=N&limit=N - List workout summaries */\nexport const garminWorkoutSummarySchema = z.object({\n workoutId: z.number().or(z.string()),\n workoutName: z.string().optional(),\n sportType: z.object({ sportTypeKey: z.string().optional() }).optional(),\n createdDate: z.number().or(z.string()).optional(),\n updatedDate: z.number().or(z.string()).optional(),\n});\n\n/** POST /workout-service/workout - Push a workout (returns created workout) */\nexport const garminPushResponseSchema = z.object({\n workoutId: z.number().or(z.string()),\n workoutName: z.string().optional(),\n});\n","import {\n createConsoleLogger,\n toText,\n createServiceApiError,\n} from \"@kaiord/core\";\nimport { createGarminWriter } from \"@kaiord/garmin\";\nimport { WORKOUT_URL } from \"../http/urls\";\nimport { mapToWorkoutSummary } from \"../mappers/workout-summary.mapper\";\nimport {\n garminPushResponseSchema,\n garminWorkoutSummarySchema,\n} from \"../schemas/workout-response.schema\";\nimport type { GarminHttpClient } from \"../http/types\";\nimport type {\n KRD,\n ListOptions,\n Logger,\n PushResult,\n WorkoutService,\n WorkoutSummary,\n} from \"@kaiord/core\";\n\nexport type GarminWorkoutClient = Pick<WorkoutService, \"push\" | \"list\">;\n\nconst pushWorkout = async (\n krd: KRD,\n httpClient: GarminHttpClient,\n garminWriter: ReturnType<typeof createGarminWriter>,\n log: Logger\n): Promise<PushResult> => {\n try {\n log.info(\"Pushing workout to Garmin Connect\");\n const gcnJson = await toText(krd, garminWriter, log);\n const payload = JSON.parse(gcnJson) as Record<string, unknown>;\n\n const raw = await httpClient.post<unknown>(\n `${WORKOUT_URL}/workout`,\n payload\n );\n const result = garminPushResponseSchema.parse(raw);\n\n return {\n id: String(result.workoutId),\n name: result.workoutName ?? \"Workout\",\n url: `https://connect.garmin.com/modern/workout/${result.workoutId}`,\n };\n } catch (error) {\n throw createServiceApiError(\"Failed to push workout\", undefined, error);\n }\n};\n\nconst listWorkouts = async (\n httpClient: GarminHttpClient,\n log: Logger,\n options?: ListOptions\n): Promise<WorkoutSummary[]> => {\n try {\n log.info(\"Listing workouts from Garmin Connect\");\n const start = options?.offset ?? 0;\n const limit = options?.limit ?? 20;\n const params = new URLSearchParams({\n start: String(start),\n limit: String(limit),\n });\n\n const raw = await httpClient.get<unknown>(\n `${WORKOUT_URL}/workouts?${params}`\n );\n const workouts = garminWorkoutSummarySchema.array().parse(raw);\n return workouts.map(mapToWorkoutSummary);\n } catch (error) {\n throw createServiceApiError(\"Failed to list workouts\", undefined, error);\n }\n};\n\nexport const createGarminWorkoutService = (\n httpClient: GarminHttpClient,\n logger?: Logger\n): GarminWorkoutClient => {\n const log = logger ?? createConsoleLogger();\n const garminWriter = createGarminWriter(log);\n\n return {\n push: (krd) => pushWorkout(krd, httpClient, garminWriter, log),\n list: (opts) => listWorkouts(httpClient, log, opts),\n };\n};\n","import { createServiceApiError } from \"@kaiord/core\";\nimport type { FetchFn } from \"./types\";\nimport type { TokenReader } from \"../token/token-manager.types\";\n\nconst sendRequest = (\n url: string,\n init: RequestInit | undefined,\n token: string,\n fetchFn: FetchFn\n): Promise<Response> =>\n fetchFn(url, {\n ...init,\n headers: { ...init?.headers, Authorization: `Bearer ${token}` },\n });\n\nconst getTokenOrThrow = (reader: TokenReader, msg: string): string => {\n const token = reader.getAccessToken();\n if (!token) throw createServiceApiError(msg, 401);\n return token;\n};\n\nconst handleNonOk = (res: Response, prefix: string): never => {\n throw createServiceApiError(`${prefix}: ${res.statusText}`, res.status);\n};\n\nexport const authFetch = async (\n url: string,\n init: RequestInit | undefined,\n reader: TokenReader,\n fetchFn: FetchFn\n): Promise<Response> => {\n if (!reader.isAuthenticated()) await reader.refresh();\n const gen = reader.getGeneration();\n const token = getTokenOrThrow(reader, \"Not authenticated\");\n const res = await sendRequest(url, init, token, fetchFn);\n\n if (res.status !== 401) {\n if (!res.ok) handleNonOk(res, \"API request failed\");\n return res;\n }\n\n if (reader.getGeneration() === gen) await reader.refresh();\n const freshToken = getTokenOrThrow(reader, \"Token unavailable after refresh\");\n const retry = await sendRequest(url, init, freshToken, fetchFn);\n if (!retry.ok) handleNonOk(retry, \"API request failed after token refresh\");\n return retry;\n};\n","import { authFetch } from \"./garmin-auth-fetch\";\nimport type { FetchFn, GarminHttpClient } from \"./types\";\nimport type { TokenReader } from \"../token/token-manager.types\";\nimport type { Logger } from \"@kaiord/core\";\n\nexport type { GarminHttpClient } from \"./types\";\n\nexport const createGarminHttpClient = (\n tokenReader: TokenReader,\n fetchFn: FetchFn,\n logger: Logger\n): GarminHttpClient => {\n const fetch = (url: string, init?: RequestInit) =>\n authFetch(url, init, tokenReader, fetchFn);\n\n logger.debug(\"HTTP client created\");\n\n return {\n get: async <T>(url: string): Promise<T> => {\n const res = await fetch(url);\n return (await res.json()) as T;\n },\n post: async <T>(url: string, body: unknown): Promise<T> => {\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: body !== null ? JSON.stringify(body) : undefined,\n });\n return (await res.json()) as T;\n },\n del: async <T>(url: string): Promise<T> => {\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"X-Http-Method-Override\": \"DELETE\" },\n });\n const text = await res.text();\n return (text ? JSON.parse(text) : undefined) as T;\n },\n };\n};\n","import type { FetchFn } from \"./types\";\nimport type { Logger } from \"@kaiord/core\";\n\nexport type RetryOptions = {\n maxRetries?: number;\n baseDelay?: number;\n maxDelay?: number;\n randomFn?: () => number;\n logger?: Logger;\n};\n\ntype ResolvedOptions = {\n maxRetries: number;\n baseDelay: number;\n maxDelay: number;\n randomFn: () => number;\n logger?: Logger;\n};\n\nconst isRetryable = (status: number): boolean =>\n status === 429 || (status >= 500 && status <= 599);\n\nconst computeDelay = (\n attempt: number,\n baseDelay: number,\n maxDelay: number,\n randomFn: () => number\n): number => randomFn() * Math.min(maxDelay, baseDelay * 2 ** attempt);\n\nconst sleep = (ms: number): Promise<void> =>\n new Promise((resolve) => setTimeout(resolve, ms));\n\nconst waitAndLog = (\n opts: ResolvedOptions,\n attempt: number,\n message: string,\n info: Record<string, unknown>\n): Promise<void> => {\n const delay = computeDelay(\n attempt,\n opts.baseDelay,\n opts.maxDelay,\n opts.randomFn\n );\n opts.logger?.debug(message, { ...info, delay: Math.round(delay) });\n return sleep(delay);\n};\n\nconst handleRetryableResponse = async (\n attempt: number,\n response: Response,\n opts: ResolvedOptions\n): Promise<boolean> => {\n if (attempt < opts.maxRetries && isRetryable(response.status)) {\n await waitAndLog(opts, attempt, \"Retrying request\", {\n attempt: attempt + 1,\n status: response.status,\n });\n return true;\n }\n return false;\n};\n\nconst handleRetryableError = async (\n attempt: number,\n error: unknown,\n opts: ResolvedOptions\n): Promise<boolean> => {\n if (attempt < opts.maxRetries && error instanceof TypeError) {\n await waitAndLog(opts, attempt, \"Retrying request after network error\", {\n attempt: attempt + 1,\n error: (error as Error).message,\n });\n return true;\n }\n return false;\n};\n\nexport const withRetry = (\n fetchFn: FetchFn,\n options?: RetryOptions\n): FetchFn => {\n const opts: ResolvedOptions = {\n maxRetries: options?.maxRetries ?? 3,\n baseDelay: options?.baseDelay ?? 1000,\n maxDelay: options?.maxDelay ?? 10000,\n randomFn: options?.randomFn ?? Math.random,\n logger: options?.logger,\n };\n\n return async (input, init?) => {\n for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {\n try {\n const response = await fetchFn(input, init);\n if (await handleRetryableResponse(attempt, response, opts)) continue;\n return response;\n } catch (error) {\n if (await handleRetryableError(attempt, error, opts)) continue;\n throw error;\n }\n }\n /* istanbul ignore next -- unreachable */\n throw new Error(\"Unexpected retry exhaustion\");\n };\n};\n","import { createConsoleLogger } from \"@kaiord/core\";\nimport { buildRefreshFn } from \"./build-refresh-fn\";\nimport { createGarminWorkoutService } from \"./garmin-workout-service\";\nimport { createGarminAuthProvider } from \"../auth/garmin-auth-provider\";\nimport { createCookieFetch } from \"../http/cookie-fetch\";\nimport { createGarminHttpClient } from \"../http/garmin-http-client\";\nimport { withRetry } from \"../http/retry\";\nimport { createTokenManager } from \"../token/token-manager\";\nimport type {\n GarminConnectClient,\n GarminConnectClientOptions,\n} from \"./garmin-connect-client.types\";\n\nexport type {\n GarminConnectClient,\n GarminConnectClientOptions,\n InitResult,\n} from \"./garmin-connect-client.types\";\n\n/**\n * Create a Garmin Connect client with auth, workout service, and optional token auto-restore.\n *\n * @example\n * ```ts\n * const client = createGarminConnectClient({\n * tokenStore: createFileTokenStore(),\n * retry: { maxRetries: 3 },\n * });\n * const { restored } = await client.init();\n * if (!restored) await client.auth.login(email, password);\n * ```\n */\nexport const createGarminConnectClient = (\n options?: GarminConnectClientOptions\n): GarminConnectClient => {\n const logger = options?.logger ?? createConsoleLogger();\n const rawFetchFn = options?.fetchFn ?? createCookieFetch();\n const retryFetchFn = options?.retry\n ? withRetry(rawFetchFn, { ...options.retry, logger })\n : rawFetchFn;\n\n const refreshFn = buildRefreshFn(rawFetchFn);\n const tokenManager = createTokenManager({\n refreshFn,\n logger,\n tokenStore: options?.tokenStore,\n });\n\n const auth = createGarminAuthProvider({\n tokenManager,\n logger,\n fetchFn: rawFetchFn,\n });\n const httpClient = createGarminHttpClient(tokenManager, retryFetchFn, logger);\n const service = createGarminWorkoutService(httpClient, logger);\n\n return {\n auth,\n service,\n init: () => tokenManager.init(),\n };\n};\n","import { readFile, writeFile, mkdir, unlink } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport type { TokenData, TokenStore } from \"@kaiord/core\";\n\nconst DEFAULT_PATH = join(homedir(), \".kaiord\", \"garmin-tokens.json\");\n\nexport const createFileTokenStore = (\n filePath: string = DEFAULT_PATH\n): TokenStore => ({\n save: async (tokens: TokenData) => {\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, JSON.stringify(tokens, null, 2), {\n encoding: \"utf-8\",\n mode: 0o600,\n });\n },\n\n load: async (): Promise<TokenData | null> => {\n try {\n const content = await readFile(filePath, \"utf-8\");\n return JSON.parse(content) as TokenData;\n } catch {\n return null;\n }\n },\n\n clear: async () => {\n try {\n await unlink(filePath);\n } catch {\n // File may not exist\n }\n },\n});\n","import type { TokenData, TokenStore } from \"@kaiord/core\";\n\nexport const createMemoryTokenStore = (): TokenStore => {\n let stored: TokenData | null = null;\n\n return {\n save: async (tokens: TokenData) => {\n stored = tokens;\n },\n load: async () => stored,\n clear: async () => {\n stored = null;\n },\n };\n};\n"]}
|