@kaiord/garmin-connect 4.9.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 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 { auth, service } = createGarminConnectClient();
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 tokenStore = createFileTokenStore("./tokens.json");
44
- const { auth, service } = createGarminConnectClient({ tokenStore });
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
- // Login (tokens are saved automatically)
47
- await auth.login("email@example.com", "password");
55
+ ```typescript
56
+ import { createGarminConnectClient } from "@kaiord/garmin-connect";
48
57
 
49
- // On next run, tokens are restored automatically
50
- // No need to login again if tokens are still valid
58
+ const client = createGarminConnectClient({
59
+ retry: { maxRetries: 3, baseDelay: 1000, maxDelay: 10000 },
60
+ });
51
61
  ```
52
62
 
53
- ### Custom Cookie-Aware Fetch
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 cookieFetch = createCookieFetch();
62
- const { auth, service } = createGarminConnectClient({ fetchFn: cookieFetch });
71
+ const client = createGarminConnectClient({
72
+ fetchFn: createCookieFetch(),
73
+ });
63
74
  ```
64
75
 
65
76
  ## API
66
77
 
67
- ### `createGarminConnectClient(options?): { auth, service }`
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 (defaults to in-memory)
85
+ - `tokenStore` - Token persistence store
86
+ - `logger` - Custom logger
87
+ - `retry` - Retry options: `{ maxRetries?, baseDelay?, maxDelay? }`
75
88
 
76
- ### `auth.login(email, password): Promise<void>`
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
- ### `service.list(options?): Promise<WorkoutSummary[]>`
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 structured workout to Garmin Connect.
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
- ### `createFileTokenStore(path): TokenStore`
135
+ ## Migration from v5.x
105
136
 
106
- Creates a file-based token store for persistent authentication.
137
+ ```typescript
138
+ // Before (v5.x)
139
+ const { auth, service } = createGarminConnectClient();
140
+ await auth.login(email, password);
107
141
 
108
- ### `createMemoryTokenStore(): TokenStore`
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
- Creates an in-memory token store (tokens lost on process exit).
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 * as _kaiord_core from '@kaiord/core';
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 GarminAuthProviderOptions = {
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
- type GarminAuthProviderResult = {
27
+
28
+ type InitResult = {
29
+ restored: boolean;
30
+ };
31
+ type GarminConnectClient = {
34
32
  auth: AuthProvider;
35
- httpClient: GarminHttpClient;
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 GarminConnectClientOptions = GarminAuthProviderOptions;
40
- declare const createGarminConnectClient: (options?: GarminConnectClientOptions) => {
41
- auth: _kaiord_core.AuthProvider;
42
- service: GarminWorkoutClient;
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
@@ -211,142 +211,6 @@ var garminSso = async (username, password, logger, fetchFn) => {
211
211
  logger.info("Garmin Connect SSO login successful");
212
212
  return { oauth1, oauth2 };
213
213
  };
214
-
215
- // src/adapters/http/token-refresh.ts
216
- var createTokenRefreshManager = (fetchFn, logger) => {
217
- const state = {
218
- oauth1Token: void 0,
219
- oauth2Token: void 0,
220
- consumer: void 0
221
- };
222
- let isRefreshing = false;
223
- let subscribers = [];
224
- const getConsumer = async () => {
225
- if (state.consumer) return state.consumer;
226
- state.consumer = await fetchOAuthConsumer(fetchFn);
227
- return state.consumer;
228
- };
229
- const refreshToken = async () => {
230
- if (!state.oauth1Token || !state.oauth2Token) {
231
- throw createServiceApiError("No tokens available for refresh", 401);
232
- }
233
- const cons = await getConsumer();
234
- state.oauth2Token = await exchangeOAuth2(state.oauth1Token, cons, fetchFn);
235
- logger.info("OAuth2 token refreshed");
236
- };
237
- const waitForRefresh = () => new Promise((resolve, reject) => {
238
- subscribers.push({ resolve, reject });
239
- });
240
- const notifySubscribers = () => {
241
- if (!state.oauth2Token) return;
242
- const token = state.oauth2Token.access_token;
243
- subscribers.forEach((s) => {
244
- s.resolve(token);
245
- });
246
- subscribers = [];
247
- };
248
- const rejectSubscribers = (error) => {
249
- subscribers.forEach((s) => {
250
- s.reject(error);
251
- });
252
- subscribers = [];
253
- };
254
- return {
255
- state,
256
- ensureFreshToken: async () => {
257
- if (isRefreshing) {
258
- await waitForRefresh();
259
- return;
260
- }
261
- isRefreshing = true;
262
- try {
263
- await refreshToken();
264
- notifySubscribers();
265
- } catch (error) {
266
- rejectSubscribers(error);
267
- throw error;
268
- } finally {
269
- isRefreshing = false;
270
- }
271
- }
272
- };
273
- };
274
-
275
- // src/adapters/http/garmin-http-client.ts
276
- var createGarminHttpClient = (logger, fetchFn = globalThis.fetch) => {
277
- const refresh = createTokenRefreshManager(fetchFn, logger);
278
- const makeRequest = (url, init) => {
279
- if (!refresh.state.oauth2Token) {
280
- throw createServiceApiError("Token unavailable after refresh", 401);
281
- }
282
- return fetchFn(url, {
283
- ...init,
284
- headers: {
285
- ...init?.headers,
286
- Authorization: `Bearer ${refresh.state.oauth2Token.access_token}`
287
- }
288
- });
289
- };
290
- const authFetch = async (url, init) => {
291
- if (!refresh.state.oauth2Token) {
292
- throw createServiceApiError("Not authenticated", 401);
293
- }
294
- if (refresh.state.oauth2Token.expires_at < Math.floor(Date.now() / 1e3)) {
295
- await refresh.ensureFreshToken();
296
- }
297
- const res = await makeRequest(url, init);
298
- if (res.status === 401) {
299
- await refresh.ensureFreshToken();
300
- const retry = await makeRequest(url, init);
301
- if (!retry.ok) {
302
- throw createServiceApiError(
303
- `API request failed after token refresh: ${retry.statusText}`,
304
- retry.status
305
- );
306
- }
307
- return retry;
308
- }
309
- if (!res.ok) {
310
- throw createServiceApiError(
311
- `API request failed: ${res.statusText}`,
312
- res.status
313
- );
314
- }
315
- return res;
316
- };
317
- return {
318
- get: async (url) => {
319
- const res = await authFetch(url);
320
- return await res.json();
321
- },
322
- post: async (url, body) => {
323
- const res = await authFetch(url, {
324
- method: "POST",
325
- headers: { "Content-Type": "application/json" },
326
- body: body !== null ? JSON.stringify(body) : void 0
327
- });
328
- return await res.json();
329
- },
330
- del: async (url) => {
331
- const res = await authFetch(url, {
332
- method: "POST",
333
- headers: { "X-Http-Method-Override": "DELETE" }
334
- });
335
- const text = await res.text();
336
- return text ? JSON.parse(text) : void 0;
337
- },
338
- setTokens: (o1, o2) => {
339
- refresh.state.oauth1Token = o1;
340
- refresh.state.oauth2Token = o2;
341
- },
342
- clearTokens: () => {
343
- refresh.state.oauth1Token = void 0;
344
- refresh.state.oauth2Token = void 0;
345
- refresh.state.consumer = void 0;
346
- },
347
- getOAuth2Token: () => refresh.state.oauth2Token
348
- };
349
- };
350
214
  var oauth1TokenSchema = z.object({
351
215
  oauth_token: z.string(),
352
216
  oauth_token_secret: z.string()
@@ -365,52 +229,132 @@ var garminTokensSchema = z.object({
365
229
  });
366
230
 
367
231
  // src/adapters/auth/garmin-auth-provider.ts
232
+ var buildAuthProvider = (tm, logger, fetchFn) => ({
233
+ login: async (username, password) => {
234
+ const result = await garminSso(username, password, logger, fetchFn);
235
+ await tm.setTokens(result.oauth1, result.oauth2);
236
+ },
237
+ is_authenticated: () => tm.isAuthenticated(),
238
+ export_tokens: async () => {
239
+ const oauth1 = tm.getOAuth1Token();
240
+ const oauth2 = tm.getOAuth2Token();
241
+ if (!oauth1 || !oauth2) {
242
+ throw createServiceAuthError("No tokens to export");
243
+ }
244
+ return { oauth1: { ...oauth1 }, oauth2: { ...oauth2 } };
245
+ },
246
+ restore_tokens: async (data) => {
247
+ const parsed = garminTokensSchema.parse(data);
248
+ await tm.setTokens(parsed.oauth1, parsed.oauth2);
249
+ logger.info("Tokens restored from stored session");
250
+ },
251
+ logout: async () => {
252
+ await tm.clearTokens();
253
+ logger.info("Logged out from Garmin Connect");
254
+ }
255
+ });
368
256
  var createGarminAuthProvider = (options) => {
369
- const logger = options?.logger ?? createConsoleLogger();
370
- const tokenStore = options?.tokenStore;
371
- const fetchFn = options?.fetchFn ?? createCookieFetch();
372
- const httpClient = createGarminHttpClient(logger, fetchFn);
373
- let currentOAuth1;
374
- const auth = {
375
- login: async (username, password) => {
376
- const result = await garminSso(username, password, logger, fetchFn);
377
- currentOAuth1 = result.oauth1;
378
- httpClient.setTokens(result.oauth1, result.oauth2);
379
- if (tokenStore) {
380
- await tokenStore.save({
381
- oauth1: result.oauth1,
382
- oauth2: result.oauth2
383
- });
384
- }
385
- },
386
- is_authenticated: () => {
387
- const token = httpClient.getOAuth2Token();
388
- if (!token) return false;
389
- return token.expires_at > Math.floor(Date.now() / 1e3);
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;
286
+ });
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);
390
324
  },
391
- export_tokens: async () => {
392
- const token = httpClient.getOAuth2Token();
393
- if (!token || !currentOAuth1) {
394
- throw createServiceAuthError("No tokens to export");
395
- }
396
- return { oauth1: currentOAuth1, oauth2: token };
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();
397
332
  },
398
- restore_tokens: async (data) => {
399
- const parsed = garminTokensSchema.parse(data);
400
- currentOAuth1 = parsed.oauth1;
401
- httpClient.setTokens(parsed.oauth1, parsed.oauth2);
402
- logger.info("Tokens restored from stored session");
333
+ refresh: async () => {
334
+ if (s.refreshPromise) return s.refreshPromise;
335
+ return doRefresh(s, refreshFn, logger, tokenStore);
403
336
  },
404
- logout: async () => {
405
- currentOAuth1 = void 0;
406
- httpClient.clearTokens();
407
- if (tokenStore) {
408
- await tokenStore.clear();
409
- }
410
- logger.info("Logged out from Garmin Connect");
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);
411
356
  }
412
357
  };
413
- return { auth, httpClient };
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 { auth, httpClient } = createGarminAuthProvider(options);
486
- const service = createGarminWorkoutService(httpClient, options?.logger);
487
- return { auth, service };
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/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","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\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 const getConsumer = async (): Promise<OAuthConsumer> => {\n if (state.consumer) return state.consumer;\n state.consumer = await fetchOAuthConsumer(fetchFn);\n return state.consumer;\n };\n\n const refreshToken = async (): Promise<void> => {\n if (!state.oauth1Token || !state.oauth2Token) {\n throw createServiceApiError(\"No tokens available for refresh\", 401);\n }\n const cons = await getConsumer();\n state.oauth2Token = await exchangeOAuth2(state.oauth1Token, cons, fetchFn);\n logger.info(\"OAuth2 token refreshed\");\n };\n\n const waitForRefresh = (): Promise<string> =>\n new Promise((resolve, reject) => {\n subscribers.push({ resolve, reject });\n });\n\n const notifySubscribers = (): void => {\n if (!state.oauth2Token) return;\n const token = state.oauth2Token.access_token;\n subscribers.forEach((s) => {\n s.resolve(token);\n });\n subscribers = [];\n };\n\n const rejectSubscribers = (error: unknown): void => {\n subscribers.forEach((s) => {\n s.reject(error);\n });\n subscribers = [];\n };\n\n return {\n state,\n ensureFreshToken: async (): Promise<void> => {\n if (isRefreshing) {\n await waitForRefresh();\n return;\n }\n isRefreshing = true;\n try {\n await refreshToken();\n notifySubscribers();\n } catch (error) {\n rejectSubscribers(error);\n throw error;\n } finally {\n isRefreshing = false;\n }\n },\n };\n};\n","import { createServiceApiError } from \"@kaiord/core\";\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\n const makeRequest = (url: string, init?: RequestInit): 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\n const authFetch = async (\n url: string,\n init?: RequestInit\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\n const res = await makeRequest(url, init);\n\n if (res.status === 401) {\n await refresh.ensureFreshToken();\n const retry = await makeRequest(url, init);\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\n return {\n get: async <T>(url: string): Promise<T> => {\n const res = await authFetch(url);\n return (await res.json()) as T;\n },\n post: async <T>(url: string, body: unknown): Promise<T> => {\n const res = await authFetch(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 authFetch(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\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 let currentOAuth1:\n | { oauth_token: string; oauth_token_secret: string }\n | undefined;\n\n const auth: AuthProvider = {\n login: async (username, password) => {\n const result = await garminSso(username, password, logger, fetchFn);\n currentOAuth1 = result.oauth1;\n httpClient.setTokens(result.oauth1, result.oauth2);\n if (tokenStore) {\n await tokenStore.save({\n oauth1: result.oauth1,\n oauth2: result.oauth2,\n });\n }\n },\n\n is_authenticated: () => {\n const token = httpClient.getOAuth2Token();\n if (!token) return false;\n return token.expires_at > Math.floor(Date.now() / 1000);\n },\n\n export_tokens: async () => {\n const token = httpClient.getOAuth2Token();\n if (!token || !currentOAuth1) {\n throw createServiceAuthError(\"No tokens to export\");\n }\n return { oauth1: currentOAuth1, oauth2: token };\n },\n\n restore_tokens: async (data) => {\n const parsed = garminTokensSchema.parse(data);\n currentOAuth1 = parsed.oauth1;\n httpClient.setTokens(parsed.oauth1, parsed.oauth2);\n logger.info(\"Tokens restored from stored session\");\n },\n\n logout: async () => {\n currentOAuth1 = undefined;\n httpClient.clearTokens();\n if (tokenStore) {\n await tokenStore.clear();\n }\n logger.info(\"Logged out from Garmin Connect\");\n },\n };\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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaiord/garmin-connect",
3
- "version": "4.9.0",
3
+ "version": "6.0.0",
4
4
  "description": "Garmin Connect API client for the Kaiord health & fitness data framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -19,8 +19,8 @@
19
19
  "fetch-cookie": "^3.2.0",
20
20
  "oauth-1.0a": "^2.2.6",
21
21
  "zod": "^4.0.0",
22
- "@kaiord/core": "^4.9.0",
23
- "@kaiord/garmin": "^4.8.1"
22
+ "@kaiord/garmin": "^5.0.0",
23
+ "@kaiord/core": "^4.9.0"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@types/node": "^25.0.0",