@kaiord/garmin-connect 6.0.0 → 7.1.1

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
@@ -145,7 +145,7 @@ const { restored } = await client.init();
145
145
  if (!restored) await client.auth.login(email, password);
146
146
  ```
147
147
 
148
- See the [design document](../../openspec/changes/refactor-garmin-auth/design.md) for the full migration guide.
148
+ See the [design document](https://github.com/pablo-albaladejo/kaiord/blob/main/openspec/changes/archive/2026-04-03-refactor-garmin-auth/design.md) for the full migration guide.
149
149
 
150
150
  ## License
151
151
 
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { WorkoutService, Logger, AuthProvider, TokenStore } from '@kaiord/core';
1
+ import { Logger, WorkoutService, AuthProvider, TokenStore } from '@kaiord/core';
2
2
  export { ListOptions, PushResult, TokenData, TokenStore, WorkoutSummary } from '@kaiord/core';
3
3
 
4
4
  type FetchFn = typeof globalThis.fetch;
@@ -15,8 +15,6 @@ type OAuth2Token = {
15
15
  expires_at: number;
16
16
  };
17
17
 
18
- type GarminWorkoutClient = Pick<WorkoutService, "push" | "list">;
19
-
20
18
  type RetryOptions = {
21
19
  maxRetries?: number;
22
20
  baseDelay?: number;
@@ -25,6 +23,8 @@ type RetryOptions = {
25
23
  logger?: Logger;
26
24
  };
27
25
 
26
+ type GarminWorkoutClient = WorkoutService;
27
+
28
28
  type InitResult = {
29
29
  restored: boolean;
30
30
  };
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import { createConsoleLogger, createServiceAuthError, createServiceApiError, toText } from '@kaiord/core';
1
+ import { createConsoleLogger, createServiceAuthError, createServiceApiError, fromText, toText } from '@kaiord/core';
2
2
  import fetchCookie from 'fetch-cookie';
3
3
  import { createHmac } from 'crypto';
4
4
  import OAuth from 'oauth-1.0a';
5
5
  import { z } from 'zod';
6
- import { createGarminWriter } from '@kaiord/garmin';
6
+ import { createGarminWriter, createGarminReader } from '@kaiord/garmin';
7
7
  import { unlink, readFile, mkdir, writeFile } from 'fs/promises';
8
8
  import { homedir } from 'os';
9
9
  import { join, dirname } from 'path';
@@ -15,27 +15,90 @@ var createCookieFetch = () => fetchCookie(globalThis.fetch);
15
15
  var GARMIN_SSO_ORIGIN = "https://sso.garmin.com";
16
16
  var GARMIN_SSO_EMBED = "https://sso.garmin.com/sso/embed";
17
17
  var SIGNIN_URL = "https://sso.garmin.com/sso/signin";
18
- var GC_MODERN = "https://connect.garmin.com/modern";
19
18
  var API_BASE = "https://connectapi.garmin.com";
20
19
  var OAUTH_URL = `${API_BASE}/oauth-service/oauth`;
21
20
  var WORKOUT_URL = `${API_BASE}/workout-service`;
22
21
  var OAUTH_CONSUMER_URL = "https://thegarth.s3.amazonaws.com/oauth_consumer.json";
23
22
  var USER_AGENT_MOBILE = "com.garmin.android.apps.connectmobile";
24
- var USER_AGENT_BROWSER = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36";
23
+ var USER_AGENT_SSO = "GCM-iOS-5.7.2.1";
25
24
 
26
25
  // src/adapters/http/oauth-consumer.ts
27
- var fetchOAuthConsumer = async (fetchFn) => {
26
+ var fetchOAuthConsumer = async (fetchFn, logger) => {
27
+ logger.debug("[SSO] Fetching OAuth consumer credentials");
28
28
  const res = await fetchFn(OAUTH_CONSUMER_URL);
29
29
  if (!res.ok) {
30
+ logger.error("[SSO] OAuth consumer fetch failed", { status: res.status });
30
31
  throw createServiceAuthError(
31
32
  `Failed to fetch OAuth consumer: ${res.status} ${res.statusText}`
32
33
  );
33
34
  }
35
+ logger.debug("[SSO] OAuth consumer fetch", { status: res.status });
34
36
  const data = await res.json();
35
37
  return { key: data.consumer_key, secret: data.consumer_secret };
36
38
  };
37
- var ACCOUNT_LOCKED_RE = /var status\s*=\s*"([^"]*)"/;
39
+
40
+ // src/adapters/http/sso-html-diagnostics.ts
38
41
  var PAGE_TITLE_RE = /<title>([^<]*)<\/title>/;
42
+ var extractTitle = (html) => {
43
+ const match = PAGE_TITLE_RE.exec(html);
44
+ return match?.[1] ?? "unknown";
45
+ };
46
+ var logCsrfResult = (logger, status, size, found) => {
47
+ logger.debug("[SSO] CSRF fetch", { status, size });
48
+ if (!found) logger.warn("[SSO] CSRF token not found in response");
49
+ };
50
+ var logLoginResponse = (logger, status, size) => {
51
+ logger.debug("[SSO] Login response", { status, size });
52
+ };
53
+ var logLoginHtmlDiagnostics = (html, ticketFound, logger) => {
54
+ const title = extractTitle(html);
55
+ const size = new TextEncoder().encode(html).byteLength;
56
+ logger.debug("[SSO] Page title", { title });
57
+ if (/\bmfa\b/i.test(html)) logger.warn("[SSO] MFA detected");
58
+ if (/\berror\b/i.test(html)) logger.warn("[SSO] Error indicators found");
59
+ if (ticketFound) {
60
+ logger.debug("[SSO] Ticket found in HTML");
61
+ } else {
62
+ logger.warn("[SSO] Ticket not found in HTML", { size, title });
63
+ }
64
+ };
65
+
66
+ // src/adapters/http/sso-submit.ts
67
+ var buildLoginParams = () => new URLSearchParams({
68
+ id: "gauth-widget",
69
+ embedWidget: "true",
70
+ gauthHost: GARMIN_SSO_EMBED,
71
+ service: GARMIN_SSO_EMBED,
72
+ source: GARMIN_SSO_EMBED,
73
+ redirectAfterAccountLoginUrl: GARMIN_SSO_EMBED,
74
+ redirectAfterAccountCreationUrl: GARMIN_SSO_EMBED
75
+ });
76
+ var submitLogin = async (input) => {
77
+ const { username, password, csrf, fetchFn, logger } = input;
78
+ logger.debug("[SSO] Submitting login");
79
+ const body = new URLSearchParams({
80
+ username,
81
+ password,
82
+ embed: "true",
83
+ _csrf: csrf
84
+ });
85
+ const loginRes = await fetchFn(`${SIGNIN_URL}?${buildLoginParams()}`, {
86
+ method: "POST",
87
+ headers: {
88
+ "Content-Type": "application/x-www-form-urlencoded",
89
+ Origin: GARMIN_SSO_ORIGIN,
90
+ Referer: `${SIGNIN_URL}?${buildLoginParams()}`,
91
+ "User-Agent": USER_AGENT_SSO
92
+ },
93
+ body: body.toString()
94
+ });
95
+ const html = await loginRes.text();
96
+ const size = new TextEncoder().encode(html).byteLength;
97
+ logLoginResponse(logger, loginRes.status, size);
98
+ return { html, status: loginRes.status };
99
+ };
100
+ var ACCOUNT_LOCKED_RE = /var status\s*=\s*"([^"]*)"/;
101
+ var PAGE_TITLE_RE2 = /<title>([^<]*)<\/title>/;
39
102
  var checkAccountLocked = (html) => {
40
103
  const match = ACCOUNT_LOCKED_RE.exec(html);
41
104
  if (match && match[1] === "ACCOUNT_LOCKED") {
@@ -45,7 +108,7 @@ var checkAccountLocked = (html) => {
45
108
  }
46
109
  };
47
110
  var checkPageTitle = (html, logger) => {
48
- const match = PAGE_TITLE_RE.exec(html);
111
+ const match = PAGE_TITLE_RE2.exec(html);
49
112
  if (match?.[1]?.includes("Update Phone Number")) {
50
113
  throw createServiceAuthError("Login failed: phone number update required.");
51
114
  }
@@ -57,69 +120,66 @@ var checkPageTitle = (html, logger) => {
57
120
  // src/adapters/http/sso-login.ts
58
121
  var CSRF_RE = /name="_csrf"\s+value="(.+?)"/;
59
122
  var TICKET_RE = /ticket=([^"]+)"/;
60
- var fetchCsrfToken = async (fetchFn) => {
61
- const signinParams = new URLSearchParams({
62
- id: "gauth-widget",
63
- embedWidget: "true",
64
- locale: "en",
65
- gauthHost: GARMIN_SSO_EMBED
123
+ var buildSigninParams = () => new URLSearchParams({
124
+ id: "gauth-widget",
125
+ embedWidget: "true",
126
+ gauthHost: GARMIN_SSO_EMBED,
127
+ service: GARMIN_SSO_EMBED,
128
+ source: GARMIN_SSO_EMBED,
129
+ redirectAfterAccountLoginUrl: GARMIN_SSO_EMBED,
130
+ redirectAfterAccountCreationUrl: GARMIN_SSO_EMBED
131
+ });
132
+ var fetchCsrfToken = async (fetchFn, embedUrl, logger) => {
133
+ logger.debug("[SSO] Fetching CSRF token");
134
+ const signinParams = buildSigninParams();
135
+ const csrfRes = await fetchFn(`${SIGNIN_URL}?${signinParams}`, {
136
+ headers: {
137
+ "User-Agent": USER_AGENT_SSO,
138
+ Referer: embedUrl
139
+ }
66
140
  });
67
- const csrfRes = await fetchFn(`${SIGNIN_URL}?${signinParams}`);
141
+ const csrfHtml = await csrfRes.text();
142
+ const csrfMatch = CSRF_RE.exec(csrfHtml);
143
+ const size = new TextEncoder().encode(csrfHtml).byteLength;
144
+ logCsrfResult(logger, csrfRes.status, size, !!csrfMatch);
68
145
  if (!csrfRes.ok) {
69
146
  throw createServiceAuthError(
70
147
  `SSO login page returned ${csrfRes.status}: ${csrfRes.statusText}`
71
148
  );
72
149
  }
73
- const csrfHtml = await csrfRes.text();
74
- const csrfMatch = CSRF_RE.exec(csrfHtml);
75
150
  if (!csrfMatch) {
76
151
  throw createServiceAuthError("CSRF token not found on login page");
77
152
  }
78
153
  return csrfMatch[1];
79
154
  };
80
- var submitLogin = async (username, password, csrf, fetchFn) => {
81
- const loginParams = new URLSearchParams({
155
+ var getLoginTicket = async (username, password, fetchFn, logger) => {
156
+ const embedParams = new URLSearchParams({
82
157
  id: "gauth-widget",
83
158
  embedWidget: "true",
84
- clientId: "GarminConnect",
85
- locale: "en",
86
- gauthHost: GARMIN_SSO_EMBED,
87
- service: GARMIN_SSO_EMBED,
88
- source: GARMIN_SSO_EMBED,
89
- redirectAfterAccountLoginUrl: GARMIN_SSO_EMBED,
90
- redirectAfterAccountCreationUrl: GARMIN_SSO_EMBED
159
+ gauthHost: "https://sso.garmin.com/sso"
91
160
  });
92
- const body = new URLSearchParams({
161
+ const embedUrl = `${GARMIN_SSO_EMBED}?${embedParams}`;
162
+ const embedRes = await fetchFn(embedUrl, {
163
+ headers: { "User-Agent": USER_AGENT_SSO }
164
+ });
165
+ logger.debug("[SSO] Embed bootstrap", { status: embedRes.status });
166
+ if (!embedRes.ok) {
167
+ throw createServiceAuthError(
168
+ `SSO embed bootstrap failed: ${embedRes.status} ${embedRes.statusText}`
169
+ );
170
+ }
171
+ const csrf = await fetchCsrfToken(fetchFn, embedUrl, logger);
172
+ const { html: loginHtml } = await submitLogin({
93
173
  username,
94
174
  password,
95
- embed: "true",
96
- _csrf: csrf
97
- });
98
- const loginRes = await fetchFn(`${SIGNIN_URL}?${loginParams}`, {
99
- method: "POST",
100
- headers: {
101
- "Content-Type": "application/x-www-form-urlencoded",
102
- Dnt: "1",
103
- Origin: GARMIN_SSO_ORIGIN,
104
- Referer: SIGNIN_URL,
105
- "User-Agent": USER_AGENT_BROWSER
106
- },
107
- body: body.toString()
108
- });
109
- return loginRes.text();
110
- };
111
- var getLoginTicket = async (username, password, fetchFn, logger) => {
112
- const embedParams = new URLSearchParams({
113
- clientId: "GarminConnect",
114
- locale: "en",
115
- service: GC_MODERN
175
+ csrf,
176
+ fetchFn,
177
+ logger
116
178
  });
117
- await fetchFn(`${GARMIN_SSO_EMBED}?${embedParams}`);
118
- const csrf = await fetchCsrfToken(fetchFn);
119
- const loginHtml = await submitLogin(username, password, csrf, fetchFn);
120
179
  checkAccountLocked(loginHtml);
121
180
  checkPageTitle(loginHtml, logger);
122
181
  const ticketMatch = TICKET_RE.exec(loginHtml);
182
+ logLoginHtmlDiagnostics(loginHtml, !!ticketMatch, logger);
123
183
  if (!ticketMatch) {
124
184
  throw createServiceAuthError(
125
185
  "Login failed: ticket not found. Check username and password."
@@ -145,7 +205,7 @@ var createOAuthSigner = (consumer) => {
145
205
  };
146
206
 
147
207
  // src/adapters/http/sso-oauth.ts
148
- var getOAuth1Token = async (ticket, consumer, fetchFn) => {
208
+ var getOAuth1Token = async (ticket, consumer, fetchFn, logger) => {
149
209
  const signer = createOAuthSigner(consumer);
150
210
  const params = new URLSearchParams({
151
211
  ticket,
@@ -157,6 +217,7 @@ var getOAuth1Token = async (ticket, consumer, fetchFn) => {
157
217
  const res = await fetchFn(url, {
158
218
  headers: { ...headers, "User-Agent": USER_AGENT_MOBILE }
159
219
  });
220
+ logger.debug("[SSO] OAuth1 token response", { status: res.status });
160
221
  if (!res.ok) {
161
222
  throw createServiceAuthError(
162
223
  `OAuth1 token request failed: ${res.status} ${res.statusText}`
@@ -171,7 +232,7 @@ var getOAuth1Token = async (ticket, consumer, fetchFn) => {
171
232
  }
172
233
  return { oauth_token: oauthToken, oauth_token_secret: oauthTokenSecret };
173
234
  };
174
- var exchangeOAuth2 = async (oauth1, consumer, fetchFn) => {
235
+ var exchangeOAuth2 = async (oauth1, consumer, fetchFn, logger) => {
175
236
  const signer = createOAuthSigner(consumer);
176
237
  const baseUrl = `${OAUTH_URL}/exchange/user/2.0`;
177
238
  const token = {
@@ -187,6 +248,7 @@ var exchangeOAuth2 = async (oauth1, consumer, fetchFn) => {
187
248
  "Content-Type": "application/x-www-form-urlencoded"
188
249
  }
189
250
  });
251
+ logger.debug("[SSO] OAuth2 exchange response", { status: res.status });
190
252
  if (!res.ok) {
191
253
  throw createServiceAuthError(
192
254
  `OAuth2 exchange failed: ${res.status} ${res.statusText}`
@@ -202,12 +264,14 @@ var exchangeOAuth2 = async (oauth1, consumer, fetchFn) => {
202
264
  // src/adapters/http/garmin-sso.ts
203
265
  var garminSso = async (username, password, logger, fetchFn) => {
204
266
  logger.info("Starting Garmin Connect SSO login");
205
- const consumer = await fetchOAuthConsumer(fetchFn);
267
+ logger.info("[SSO] Step 1/4: OAuth consumer");
268
+ const consumer = await fetchOAuthConsumer(fetchFn, logger);
269
+ logger.info("[SSO] Step 2/4: Login ticket");
206
270
  const ticket = await getLoginTicket(username, password, fetchFn, logger);
207
- logger.debug("SSO ticket obtained");
208
- const oauth1 = await getOAuth1Token(ticket, consumer, fetchFn);
209
- logger.debug("OAuth1 token obtained");
210
- const oauth2 = await exchangeOAuth2(oauth1, consumer, fetchFn);
271
+ logger.info("[SSO] Step 3/4: OAuth1 token");
272
+ const oauth1 = await getOAuth1Token(ticket, consumer, fetchFn, logger);
273
+ logger.info("[SSO] Step 4/4: OAuth2 exchange");
274
+ const oauth2 = await exchangeOAuth2(oauth1, consumer, fetchFn, logger);
211
275
  logger.info("Garmin Connect SSO login successful");
212
276
  return { oauth1, oauth2 };
213
277
  };
@@ -341,88 +405,6 @@ var createTokenManager = (options) => {
341
405
  }
342
406
  };
343
407
  };
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
- };
358
- };
359
-
360
- // src/adapters/mappers/workout-summary.mapper.ts
361
- var mapToWorkoutSummary = (garminWorkout) => ({
362
- id: String(garminWorkout.workoutId ?? ""),
363
- name: garminWorkout.workoutName ?? "Unnamed",
364
- sport: garminWorkout.sportType?.sportTypeKey ?? "unknown",
365
- created_at: garminWorkout.createdDate ? new Date(garminWorkout.createdDate).toISOString() : "",
366
- updated_at: garminWorkout.updatedDate ? new Date(garminWorkout.updatedDate).toISOString() : ""
367
- });
368
- var garminWorkoutSummarySchema = z.object({
369
- workoutId: z.number().or(z.string()),
370
- workoutName: z.string().optional(),
371
- sportType: z.object({ sportTypeKey: z.string().optional() }).optional(),
372
- createdDate: z.number().or(z.string()).optional(),
373
- updatedDate: z.number().or(z.string()).optional()
374
- });
375
- var garminPushResponseSchema = z.object({
376
- workoutId: z.number().or(z.string()),
377
- workoutName: z.string().optional()
378
- });
379
-
380
- // src/adapters/client/garmin-workout-service.ts
381
- var pushWorkout = async (krd, httpClient, garminWriter, log) => {
382
- try {
383
- log.info("Pushing workout to Garmin Connect");
384
- const gcnJson = await toText(krd, garminWriter, log);
385
- const payload = JSON.parse(gcnJson);
386
- const raw = await httpClient.post(
387
- `${WORKOUT_URL}/workout`,
388
- payload
389
- );
390
- const result = garminPushResponseSchema.parse(raw);
391
- return {
392
- id: String(result.workoutId),
393
- name: result.workoutName ?? "Workout",
394
- url: `https://connect.garmin.com/modern/workout/${result.workoutId}`
395
- };
396
- } catch (error) {
397
- throw createServiceApiError("Failed to push workout", void 0, error);
398
- }
399
- };
400
- var listWorkouts = async (httpClient, log, options) => {
401
- try {
402
- log.info("Listing workouts from Garmin Connect");
403
- const start = options?.offset ?? 0;
404
- const limit = options?.limit ?? 20;
405
- const params = new URLSearchParams({
406
- start: String(start),
407
- limit: String(limit)
408
- });
409
- const raw = await httpClient.get(
410
- `${WORKOUT_URL}/workouts?${params}`
411
- );
412
- const workouts = garminWorkoutSummarySchema.array().parse(raw);
413
- return workouts.map(mapToWorkoutSummary);
414
- } catch (error) {
415
- throw createServiceApiError("Failed to list workouts", void 0, error);
416
- }
417
- };
418
- var createGarminWorkoutService = (httpClient, logger) => {
419
- const log = logger ?? createConsoleLogger();
420
- const garminWriter = createGarminWriter(log);
421
- return {
422
- push: (krd) => pushWorkout(krd, httpClient, garminWriter, log),
423
- list: (opts) => listWorkouts(httpClient, log, opts)
424
- };
425
- };
426
408
  var sendRequest = (url, init, token, fetchFn) => fetchFn(url, {
427
409
  ...init,
428
410
  headers: { ...init?.headers, Authorization: `Bearer ${token}` }
@@ -536,12 +518,117 @@ var withRetry = (fetchFn, options) => {
536
518
  };
537
519
  };
538
520
 
521
+ // src/adapters/client/build-refresh-fn.ts
522
+ var buildRefreshFn = (fetchFn, logger) => {
523
+ let consumer;
524
+ return async (oauth1) => {
525
+ consumer ??= await fetchOAuthConsumer(fetchFn, logger);
526
+ try {
527
+ return await exchangeOAuth2(oauth1, consumer, fetchFn, logger);
528
+ } catch {
529
+ consumer = void 0;
530
+ consumer = await fetchOAuthConsumer(fetchFn, logger);
531
+ return exchangeOAuth2(oauth1, consumer, fetchFn, logger);
532
+ }
533
+ };
534
+ };
535
+
536
+ // src/adapters/mappers/workout-summary.mapper.ts
537
+ var mapToWorkoutSummary = (garminWorkout) => ({
538
+ id: String(garminWorkout.workoutId ?? ""),
539
+ name: garminWorkout.workoutName ?? "Unnamed",
540
+ sport: garminWorkout.sportType?.sportTypeKey ?? "unknown",
541
+ created_at: garminWorkout.createdDate ? new Date(garminWorkout.createdDate).toISOString() : "",
542
+ updated_at: garminWorkout.updatedDate ? new Date(garminWorkout.updatedDate).toISOString() : ""
543
+ });
544
+ var garminWorkoutSummarySchema = z.object({
545
+ workoutId: z.number().or(z.string()),
546
+ workoutName: z.string().optional(),
547
+ sportType: z.object({ sportTypeKey: z.string().optional() }).optional(),
548
+ createdDate: z.number().or(z.string()).optional(),
549
+ updatedDate: z.number().or(z.string()).optional()
550
+ });
551
+ var garminPushResponseSchema = z.object({
552
+ workoutId: z.number().or(z.string()),
553
+ workoutName: z.string().optional()
554
+ });
555
+ var pullWorkout = async (workoutId, httpClient, garminReader, log) => {
556
+ try {
557
+ log.info(`Pulling workout ${workoutId} from Garmin Connect`);
558
+ const raw = await httpClient.get(
559
+ `${WORKOUT_URL}/workout/${workoutId}`
560
+ );
561
+ const gcnJson = JSON.stringify(raw);
562
+ return await fromText(gcnJson, garminReader, log);
563
+ } catch (error) {
564
+ throw createServiceApiError("Failed to pull workout", void 0, error);
565
+ }
566
+ };
567
+ var removeWorkout = async (workoutId, httpClient, log) => {
568
+ try {
569
+ log.info(`Removing workout ${workoutId} from Garmin Connect`);
570
+ await httpClient.del(`${WORKOUT_URL}/workout/${workoutId}`);
571
+ } catch (error) {
572
+ throw createServiceApiError("Failed to remove workout", void 0, error);
573
+ }
574
+ };
575
+
576
+ // src/adapters/client/garmin-workout-service.ts
577
+ var pushWorkout = async (krd, httpClient, garminWriter, log) => {
578
+ try {
579
+ log.info("Pushing workout to Garmin Connect");
580
+ const gcnJson = await toText(krd, garminWriter, log);
581
+ const payload = JSON.parse(gcnJson);
582
+ const raw = await httpClient.post(
583
+ `${WORKOUT_URL}/workout`,
584
+ payload
585
+ );
586
+ const result = garminPushResponseSchema.parse(raw);
587
+ return {
588
+ id: String(result.workoutId),
589
+ name: result.workoutName ?? "Workout",
590
+ url: `https://connect.garmin.com/modern/workout/${result.workoutId}`
591
+ };
592
+ } catch (error) {
593
+ throw createServiceApiError("Failed to push workout", void 0, error);
594
+ }
595
+ };
596
+ var listWorkouts = async (httpClient, log, options) => {
597
+ try {
598
+ log.info("Listing workouts from Garmin Connect");
599
+ const start = options?.offset ?? 0;
600
+ const limit = options?.limit ?? 20;
601
+ const params = new URLSearchParams({
602
+ start: String(start),
603
+ limit: String(limit)
604
+ });
605
+ const raw = await httpClient.get(
606
+ `${WORKOUT_URL}/workouts?${params}`
607
+ );
608
+ const workouts = garminWorkoutSummarySchema.array().parse(raw);
609
+ return workouts.map(mapToWorkoutSummary);
610
+ } catch (error) {
611
+ throw createServiceApiError("Failed to list workouts", void 0, error);
612
+ }
613
+ };
614
+ var createGarminWorkoutService = (httpClient, logger) => {
615
+ const log = logger ?? createConsoleLogger();
616
+ const garminWriter = createGarminWriter(log);
617
+ const garminReader = createGarminReader(log);
618
+ return {
619
+ push: (krd) => pushWorkout(krd, httpClient, garminWriter, log),
620
+ pull: (id) => pullWorkout(id, httpClient, garminReader, log),
621
+ list: (opts) => listWorkouts(httpClient, log, opts),
622
+ remove: (id) => removeWorkout(id, httpClient, log)
623
+ };
624
+ };
625
+
539
626
  // src/adapters/client/garmin-connect-client.ts
540
627
  var createGarminConnectClient = (options) => {
541
628
  const logger = options?.logger ?? createConsoleLogger();
542
629
  const rawFetchFn = options?.fetchFn ?? createCookieFetch();
543
630
  const retryFetchFn = options?.retry ? withRetry(rawFetchFn, { ...options.retry, logger }) : rawFetchFn;
544
- const refreshFn = buildRefreshFn(rawFetchFn);
631
+ const refreshFn = buildRefreshFn(rawFetchFn, logger);
545
632
  const tokenManager = createTokenManager({
546
633
  refreshFn,
547
634
  logger,
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/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":";;;;;;;;;;;AAEO,IAAM,iBAAA,GAAoB,MAC/B,WAAA,CAAY,UAAA,CAAW,KAAK;;;ACHvB,IAAM,iBAAA,GAAoB,wBAAA;AAC1B,IAAM,gBAAA,GAAmB,kCAAA;AACzB,IAAM,UAAA,GAAa,mCAAA;AACnB,IAAM,SAAA,GAAY,mCAAA;AAClB,IAAM,QAAA,GAAW,+BAAA;AACjB,IAAM,SAAA,GAAY,GAAG,QAAQ,CAAA,oBAAA,CAAA;AAC7B,IAAM,WAAA,GAAc,GAAG,QAAQ,CAAA,gBAAA,CAAA;AAE/B,IAAM,kBAAA,GACX,uDAAA;AAEK,IAAM,iBAAA,GAAoB,uCAAA;AAC1B,IAAM,kBAAA,GACX,iHAAA;;;ACTK,IAAM,kBAAA,GAAqB,OAChC,OAAA,KAC2B;AAC3B,EAAA,MAAM,GAAA,GAAM,MAAM,OAAA,CAAQ,kBAAkB,CAAA;AAC5C,EAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,IAAA,MAAM,sBAAA;AAAA,MACJ,CAAA,gCAAA,EAAmC,GAAA,CAAI,MAAM,CAAA,CAAA,EAAI,IAAI,UAAU,CAAA;AAAA,KACjE;AAAA,EACF;AACA,EAAA,MAAM,IAAA,GAAQ,MAAM,GAAA,CAAI,IAAA,EAAK;AAI7B,EAAA,OAAO,EAAE,GAAA,EAAK,IAAA,CAAK,YAAA,EAAc,MAAA,EAAQ,KAAK,eAAA,EAAgB;AAChE,CAAA;ACfA,IAAM,iBAAA,GAAoB,4BAAA;AAC1B,IAAM,aAAA,GAAgB,yBAAA;AAEf,IAAM,kBAAA,GAAqB,CAAC,IAAA,KAAuB;AACxD,EAAA,MAAM,KAAA,GAAQ,iBAAA,CAAkB,IAAA,CAAK,IAAI,CAAA;AACzC,EAAA,IAAI,KAAA,IAAS,KAAA,CAAM,CAAC,CAAA,KAAM,gBAAA,EAAkB;AAC1C,IAAA,MAAMA,sBAAAA;AAAA,MACJ,CAAA,gBAAA,EAAmB,KAAA,CAAM,CAAC,CAAC,CAAA,gCAAA;AAAA,KAC7B;AAAA,EACF;AACF,CAAA;AAEO,IAAM,cAAA,GAAiB,CAAC,IAAA,EAAc,MAAA,KAAyB;AACpE,EAAA,MAAM,KAAA,GAAQ,aAAA,CAAc,IAAA,CAAK,IAAI,CAAA;AACrC,EAAA,IAAI,KAAA,GAAQ,CAAC,CAAA,EAAG,QAAA,CAAS,qBAAqB,CAAA,EAAG;AAC/C,IAAA,MAAMA,uBAAuB,6CAA6C,CAAA;AAAA,EAC5E;AACA,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,MAAA,CAAO,MAAM,kBAAA,EAAoB,EAAE,OAAO,KAAA,CAAM,CAAC,GAAG,CAAA;AAAA,EACtD;AACF,CAAA;;;ACXA,IAAM,OAAA,GAAU,8BAAA;AAChB,IAAM,SAAA,GAAY,iBAAA;AAElB,IAAM,cAAA,GAAiB,OAAO,OAAA,KAAsC;AAClE,EAAA,MAAM,YAAA,GAAe,IAAI,eAAA,CAAgB;AAAA,IACvC,EAAA,EAAI,cAAA;AAAA,IACJ,WAAA,EAAa,MAAA;AAAA,IACb,MAAA,EAAQ,IAAA;AAAA,IACR,SAAA,EAAW;AAAA,GACZ,CAAA;AACD,EAAA,MAAM,UAAU,MAAM,OAAA,CAAQ,GAAG,UAAU,CAAA,CAAA,EAAI,YAAY,CAAA,CAAE,CAAA;AAC7D,EAAA,IAAI,CAAC,QAAQ,EAAA,EAAI;AACf,IAAA,MAAMA,sBAAAA;AAAA,MACJ,CAAA,wBAAA,EAA2B,OAAA,CAAQ,MAAM,CAAA,EAAA,EAAK,QAAQ,UAAU,CAAA;AAAA,KAClE;AAAA,EACF;AACA,EAAA,MAAM,QAAA,GAAW,MAAM,OAAA,CAAQ,IAAA,EAAK;AACpC,EAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,IAAA,CAAK,QAAQ,CAAA;AACvC,EAAA,IAAI,CAAC,SAAA,EAAW;AACd,IAAA,MAAMA,uBAAuB,oCAAoC,CAAA;AAAA,EACnE;AACA,EAAA,OAAO,UAAU,CAAC,CAAA;AACpB,CAAA;AAEA,IAAM,WAAA,GAAc,OAClB,QAAA,EACA,QAAA,EACA,MACA,OAAA,KACoB;AACpB,EAAA,MAAM,WAAA,GAAc,IAAI,eAAA,CAAgB;AAAA,IACtC,EAAA,EAAI,cAAA;AAAA,IACJ,WAAA,EAAa,MAAA;AAAA,IACb,QAAA,EAAU,eAAA;AAAA,IACV,MAAA,EAAQ,IAAA;AAAA,IACR,SAAA,EAAW,gBAAA;AAAA,IACX,OAAA,EAAS,gBAAA;AAAA,IACT,MAAA,EAAQ,gBAAA;AAAA,IACR,4BAAA,EAA8B,gBAAA;AAAA,IAC9B,+BAAA,EAAiC;AAAA,GAClC,CAAA;AACD,EAAA,MAAM,IAAA,GAAO,IAAI,eAAA,CAAgB;AAAA,IAC/B,QAAA;AAAA,IACA,QAAA;AAAA,IACA,KAAA,EAAO,MAAA;AAAA,IACP,KAAA,EAAO;AAAA,GACR,CAAA;AACD,EAAA,MAAM,WAAW,MAAM,OAAA,CAAQ,GAAG,UAAU,CAAA,CAAA,EAAI,WAAW,CAAA,CAAA,EAAI;AAAA,IAC7D,MAAA,EAAQ,MAAA;AAAA,IACR,OAAA,EAAS;AAAA,MACP,cAAA,EAAgB,mCAAA;AAAA,MAChB,GAAA,EAAK,GAAA;AAAA,MACL,MAAA,EAAQ,iBAAA;AAAA,MACR,OAAA,EAAS,UAAA;AAAA,MACT,YAAA,EAAc;AAAA,KAChB;AAAA,IACA,IAAA,EAAM,KAAK,QAAA;AAAS,GACrB,CAAA;AACD,EAAA,OAAO,SAAS,IAAA,EAAK;AACvB,CAAA;AAEO,IAAM,cAAA,GAAiB,OAC5B,QAAA,EACA,QAAA,EACA,SACA,MAAA,KACoB;AACpB,EAAA,MAAM,WAAA,GAAc,IAAI,eAAA,CAAgB;AAAA,IACtC,QAAA,EAAU,eAAA;AAAA,IACV,MAAA,EAAQ,IAAA;AAAA,IACR,OAAA,EAAS;AAAA,GACV,CAAA;AACD,EAAA,MAAM,OAAA,CAAQ,CAAA,EAAG,gBAAgB,CAAA,CAAA,EAAI,WAAW,CAAA,CAAE,CAAA;AAElD,EAAA,MAAM,IAAA,GAAO,MAAM,cAAA,CAAe,OAAO,CAAA;AACzC,EAAA,MAAM,YAAY,MAAM,WAAA,CAAY,QAAA,EAAU,QAAA,EAAU,MAAM,OAAO,CAAA;AAErE,EAAA,kBAAA,CAAmB,SAAS,CAAA;AAC5B,EAAA,cAAA,CAAe,WAAW,MAAM,CAAA;AAEhC,EAAA,MAAM,WAAA,GAAc,SAAA,CAAU,IAAA,CAAK,SAAS,CAAA;AAC5C,EAAA,IAAI,CAAC,WAAA,EAAa;AAChB,IAAA,MAAMA,sBAAAA;AAAA,MACJ;AAAA,KACF;AAAA,EACF;AACA,EAAA,OAAO,YAAY,CAAC,CAAA;AACtB,CAAA;ACrFO,IAAM,iBAAA,GAAoB,CAAC,QAAA,KAAyC;AACzE,EAAA,MAAM,KAAA,GAAQ,IAAI,KAAA,CAAM;AAAA,IACtB,QAAA;AAAA,IACA,gBAAA,EAAkB,WAAA;AAAA,IAClB,aAAA,CAAc,YAAoB,GAAA,EAAa;AAC7C,MAAA,OAAO,UAAA,CAAW,QAAQ,GAAG,CAAA,CAAE,OAAO,UAAU,CAAA,CAAE,OAAO,QAAQ,CAAA;AAAA,IACnE;AAAA,GACD,CAAA;AAED,EAAA,OAAO;AAAA,IACL,QAAA,EAAU,CAAC,OAAA,EAAS,KAAA,KAAU;AAC5B,MAAA,MAAM,UAAA,GAAa,QACf,KAAA,CAAM,SAAA,CAAU,SAAS,KAAK,CAAA,GAC9B,KAAA,CAAM,SAAA,CAAU,OAAO,CAAA;AAC3B,MAAA,MAAM,MAAA,GAAS,KAAA,CAAM,QAAA,CAAS,UAAU,CAAA;AACxC,MAAA,OAAO,MAAA,CAAO,WAAA,CAAY,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAC,CAAA;AAAA,IAClD;AAAA,GACF;AACF,CAAA;;;AC1BO,IAAM,cAAA,GAAiB,OAC5B,MAAA,EACA,QAAA,EACA,OAAA,KACyB;AACzB,EAAA,MAAM,MAAA,GAAS,kBAAkB,QAAQ,CAAA;AACzC,EAAA,MAAM,MAAA,GAAS,IAAI,eAAA,CAAgB;AAAA,IACjC,MAAA;AAAA,IACA,WAAA,EAAa,gBAAA;AAAA,IACb,oBAAA,EAAsB;AAAA,GACvB,CAAA;AACD,EAAA,MAAM,GAAA,GAAM,CAAA,EAAG,SAAS,CAAA,eAAA,EAAkB,MAAM,CAAA,CAAA;AAChD,EAAA,MAAM,UAAU,MAAA,CAAO,QAAA,CAAS,EAAE,GAAA,EAAK,MAAA,EAAQ,OAAO,CAAA;AAEtD,EAAA,MAAM,GAAA,GAAM,MAAM,OAAA,CAAQ,GAAA,EAAK;AAAA,IAC7B,OAAA,EAAS,EAAE,GAAG,OAAA,EAAS,cAAc,iBAAA;AAAkB,GACxD,CAAA;AACD,EAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,IAAA,MAAMA,sBAAAA;AAAA,MACJ,CAAA,6BAAA,EAAgC,GAAA,CAAI,MAAM,CAAA,CAAA,EAAI,IAAI,UAAU,CAAA;AAAA,KAC9D;AAAA,EACF;AACA,EAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,EAAK;AAC5B,EAAA,MAAM,MAAA,GAAS,IAAI,eAAA,CAAgB,IAAI,CAAA;AAEvC,EAAA,MAAM,UAAA,GAAa,MAAA,CAAO,GAAA,CAAI,aAAa,CAAA;AAC3C,EAAA,MAAM,gBAAA,GAAmB,MAAA,CAAO,GAAA,CAAI,oBAAoB,CAAA;AACxD,EAAA,IAAI,CAAC,UAAA,IAAc,CAAC,gBAAA,EAAkB;AACpC,IAAA,MAAMA,uBAAuB,8BAA8B,CAAA;AAAA,EAC7D;AAEA,EAAA,OAAO,EAAE,WAAA,EAAa,UAAA,EAAY,kBAAA,EAAoB,gBAAA,EAAiB;AACzE,CAAA;AAEO,IAAM,cAAA,GAAiB,OAC5B,MAAA,EACA,QAAA,EACA,OAAA,KACyB;AACzB,EAAA,MAAM,MAAA,GAAS,kBAAkB,QAAQ,CAAA;AACzC,EAAA,MAAM,OAAA,GAAU,GAAG,SAAS,CAAA,kBAAA,CAAA;AAC5B,EAAA,MAAM,KAAA,GAAQ;AAAA,IACZ,KAAK,MAAA,CAAO,WAAA;AAAA,IACZ,QAAQ,MAAA,CAAO;AAAA,GACjB;AACA,EAAA,MAAM,UAAA,GAAa,OAAO,QAAA,CAAS,EAAE,KAAK,OAAA,EAAS,MAAA,EAAQ,MAAA,EAAO,EAAG,KAAK,CAAA;AAE1E,EAAA,MAAM,GAAA,GAAM,MAAM,OAAA,CAAQ,OAAA,EAAS;AAAA,IACjC,MAAA,EAAQ,MAAA;AAAA,IACR,OAAA,EAAS;AAAA,MACP,GAAG,UAAA;AAAA,MACH,YAAA,EAAc,iBAAA;AAAA,MACd,cAAA,EAAgB;AAAA;AAClB,GACD,CAAA;AACD,EAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,IAAA,MAAMA,sBAAAA;AAAA,MACJ,CAAA,wBAAA,EAA2B,GAAA,CAAI,MAAM,CAAA,CAAA,EAAI,IAAI,UAAU,CAAA;AAAA,KACzD;AAAA,EACF;AACA,EAAA,MAAM,IAAA,GAAQ,MAAM,GAAA,CAAI,IAAA,EAAK;AAE7B,EAAA,OAAO;AAAA,IACL,GAAG,IAAA;AAAA,IACH,UAAA,EAAY,KAAK,KAAA,CAAM,IAAA,CAAK,KAAI,GAAI,GAAI,IAAI,IAAA,CAAK;AAAA,GACnD;AACF,CAAA;;;AC1DO,IAAM,SAAA,GAAY,OACvB,QAAA,EACA,QAAA,EACA,QACA,OAAA,KACuB;AACvB,EAAA,MAAA,CAAO,KAAK,mCAAmC,CAAA;AAE/C,EAAA,MAAM,QAAA,GAAW,MAAM,kBAAA,CAAmB,OAAO,CAAA;AACjD,EAAA,MAAM,SAAS,MAAM,cAAA,CAAe,QAAA,EAAU,QAAA,EAAU,SAAS,MAAM,CAAA;AACvE,EAAA,MAAA,CAAO,MAAM,qBAAqB,CAAA;AAElC,EAAA,MAAM,MAAA,GAAS,MAAM,cAAA,CAAe,MAAA,EAAQ,UAAU,OAAO,CAAA;AAC7D,EAAA,MAAA,CAAO,MAAM,uBAAuB,CAAA;AAEpC,EAAA,MAAM,MAAA,GAAS,MAAM,cAAA,CAAS,MAAA,EAAQ,UAAU,OAAO,CAAA;AACvD,EAAA,MAAA,CAAO,KAAK,qCAAqC,CAAA;AAEjD,EAAA,OAAO,EAAE,QAAQ,MAAA,EAAO;AAC1B,CAAA;AC/BO,IAAM,iBAAA,GAAoB,EAAE,MAAA,CAAO;AAAA,EACxC,WAAA,EAAa,EAAE,MAAA,EAAO;AAAA,EACtB,kBAAA,EAAoB,EAAE,MAAA;AACxB,CAAC,CAAA;AAEM,IAAM,iBAAA,GAAoB,EAAE,MAAA,CAAO;AAAA,EACxC,YAAA,EAAc,EAAE,MAAA,EAAO;AAAA,EACvB,aAAA,EAAe,EAAE,MAAA,EAAO;AAAA,EACxB,UAAA,EAAY,EAAE,MAAA,EAAO;AAAA,EACrB,UAAA,EAAY,EAAE,MAAA,EAAO;AAAA,EACrB,wBAAA,EAA0B,EAAE,MAAA,EAAO;AAAA,EACnC,UAAA,EAAY,EAAE,MAAA;AAChB,CAAC,CAAA;AAEM,IAAM,kBAAA,GAAqB,EAAE,MAAA,CAAO;AAAA,EACzC,MAAA,EAAQ,iBAAA;AAAA,EACR,MAAA,EAAQ;AACV,CAAC,CAAA;;;ACLD,IAAM,iBAAA,GAAoB,CACxB,EAAA,EACA,MAAA,EACA,OAAA,MACkB;AAAA,EAClB,KAAA,EAAO,OAAO,QAAA,EAAU,QAAA,KAAa;AACnC,IAAA,MAAM,SAAS,MAAM,SAAA,CAAU,QAAA,EAAU,QAAA,EAAU,QAAQ,OAAO,CAAA;AAClE,IAAA,MAAM,EAAA,CAAG,SAAA,CAAU,MAAA,CAAO,MAAA,EAAQ,OAAO,MAAM,CAAA;AAAA,EACjD,CAAA;AAAA,EACA,gBAAA,EAAkB,MAAM,EAAA,CAAG,eAAA,EAAgB;AAAA,EAC3C,eAAe,YAAY;AACzB,IAAA,MAAM,MAAA,GAAS,GAAG,cAAA,EAAe;AACjC,IAAA,MAAM,MAAA,GAAS,GAAG,cAAA,EAAe;AACjC,IAAA,IAAI,CAAC,MAAA,IAAU,CAAC,MAAA,EAAQ;AACtB,MAAA,MAAMA,uBAAuB,qBAAqB,CAAA;AAAA,IACpD;AACA,IAAA,OAAO,EAAE,MAAA,EAAQ,EAAE,GAAG,MAAA,IAAU,MAAA,EAAQ,EAAE,GAAG,MAAA,EAAO,EAAE;AAAA,EACxD,CAAA;AAAA,EACA,cAAA,EAAgB,OAAO,IAAA,KAAS;AAC9B,IAAA,MAAM,MAAA,GAAS,kBAAA,CAAmB,KAAA,CAAM,IAAI,CAAA;AAC5C,IAAA,MAAM,EAAA,CAAG,SAAA,CAAU,MAAA,CAAO,MAAA,EAAQ,OAAO,MAAM,CAAA;AAC/C,IAAA,MAAA,CAAO,KAAK,qCAAqC,CAAA;AAAA,EACnD,CAAA;AAAA,EACA,QAAQ,YAAY;AAClB,IAAA,MAAM,GAAG,WAAA,EAAY;AACrB,IAAA,MAAA,CAAO,KAAK,gCAAgC,CAAA;AAAA,EAC9C;AACF,CAAA,CAAA;AAEO,IAAM,wBAAA,GAA2B,CACtC,OAAA,KACiB;AACjB,EAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,MAAA,IAAU,mBAAA,EAAoB;AACrD,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,OAAA,IAAW,iBAAA,EAAkB;AACrD,EAAA,OAAO,iBAAA,CAAkB,OAAA,CAAQ,YAAA,EAAc,MAAA,EAAQ,OAAO,CAAA;AAChE;ACpCO,IAAM,iBAAA,GAAoB,OAC/B,KAAA,EACA,MAAA,EACA,QACA,MAAA,KACkB;AAClB,EAAA,IAAI,CAAC,KAAA,EAAO;AACZ,EAAA,IAAI;AACF,IAAA,MAAM,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,QAAQ,CAAA;AAAA,EACrC,SAAS,KAAA,EAAO;AACd,IAAA,MAAA,CAAO,KAAK,0BAAA,EAA4B;AAAA,MACtC,SAAA,EAAW,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAO,OAAO;AAAA,KACzD,CAAA;AAAA,EACH;AACF,CAAA;AAEO,IAAM,SAAA,GAAY,CAAC,MAAA,KACxB,CAAC,UAAU,MAAA,CAAO,UAAA,IAAc,IAAA,CAAK,GAAA,EAAI,GAAI,GAAA;AAExC,IAAM,SAAA,GAAY,CACvB,CAAA,EACA,SAAA,EACA,QACA,UAAA,KACkB;AAClB,EAAA,IAAI,CAAC,EAAE,MAAA,EAAQ;AACb,IAAA,MAAM,qBAAA,CAAsB,+BAA+B,GAAG,CAAA;AAAA,EAChE;AACA,EAAA,MAAM,gBAAgB,CAAA,CAAE,MAAA;AACxB,EAAA,MAAM,oBAAoB,CAAA,CAAE,UAAA;AAC5B,EAAA,CAAA,CAAE,iBAAiB,SAAA,CAAU,aAAa,CAAA,CACvC,IAAA,CAAK,OAAO,SAAA,KAAc;AACzB,IAAA,IAAI,CAAA,CAAE,eAAe,iBAAA,EAAmB;AACxC,IAAA,CAAA,CAAE,MAAA,GAAS,SAAA;AACX,IAAA,CAAA,CAAE,UAAA,EAAA;AACF,IAAA,MAAA,CAAO,KAAK,iBAAA,EAAmB,EAAE,UAAA,EAAY,CAAA,CAAE,YAAY,CAAA;AAC3D,IAAA,MAAM,iBAAA,CAAkB,UAAA,EAAY,aAAA,EAAe,SAAA,EAAW,MAAM,CAAA;AAAA,EACtE,CAAC,CAAA,CACA,OAAA,CAAQ,MAAM;AACb,IAAA,CAAA,CAAE,cAAA,GAAiB,MAAA;AAAA,EACrB,CAAC,CAAA;AACH,EAAA,OAAO,CAAA,CAAE,cAAA;AACX,CAAA;AAEO,IAAM,gBAAA,GAAmB,OAC9B,CAAA,EACA,UAAA,EACA,MAAA,KACmC;AACnC,EAAA,MAAM,IAAA,GAAO,MAAM,UAAA,CAAW,IAAA,EAAK;AACnC,EAAA,IAAI,CAAC,IAAA,EAAM,OAAO,EAAE,UAAU,KAAA,EAAM;AACpC,EAAA,IAAI,EAAE,MAAA,IAAU,CAAA,CAAE,QAAQ,OAAO,EAAE,UAAU,KAAA,EAAM;AACnD,EAAA,MAAM,MAAA,GAAS,kBAAA,CAAmB,SAAA,CAAU,IAAI,CAAA;AAChD,EAAA,IAAI,CAAC,MAAA,CAAO,OAAA,EAAS,OAAO,EAAE,UAAU,KAAA,EAAM;AAC9C,EAAA,CAAA,CAAE,MAAA,GAAS,OAAO,IAAA,CAAK,MAAA;AACvB,EAAA,CAAA,CAAE,MAAA,GAAS,OAAO,IAAA,CAAK,MAAA;AACvB,EAAA,CAAA,CAAE,UAAA,EAAA;AACF,EAAA,IAAI,UAAU,CAAA,CAAE,MAAM,CAAA,EAAG,MAAA,CAAO,KAAK,6BAA6B,CAAA;AAClE,EAAA,MAAA,CAAO,KAAK,4BAAA,EAA8B,EAAE,UAAA,EAAY,CAAA,CAAE,YAAY,CAAA;AACtE,EAAA,OAAO,EAAE,UAAU,IAAA,EAAK;AAC1B,CAAA;;;ACzDO,IAAM,kBAAA,GAAqB,CAAC,OAAA,KAAmC;AACpE,EAAA,MAAM,EAAE,SAAA,EAAW,MAAA,EAAQ,UAAA,EAAW,GAAI,OAAA;AAC1C,EAAA,MAAM,CAAA,GAAgB;AAAA,IACpB,MAAA,EAAQ,MAAA;AAAA,IACR,MAAA,EAAQ,MAAA;AAAA,IACR,UAAA,EAAY,CAAA;AAAA,IACZ,cAAA,EAAgB;AAAA,GAClB;AAEA,EAAA,OAAO;AAAA,IACL,cAAA,EAAgB,MAAM,CAAA,CAAE,MAAA,EAAQ,YAAA;AAAA,IAChC,cAAA,EAAgB,MAAO,CAAA,CAAE,MAAA,GAAS,EAAE,GAAG,CAAA,CAAE,QAAO,GAAI,MAAA;AAAA,IACpD,cAAA,EAAgB,MAAO,CAAA,CAAE,MAAA,GAAS,EAAE,GAAG,CAAA,CAAE,QAAO,GAAI,MAAA;AAAA,IACpD,aAAA,EAAe,MAAM,CAAA,CAAE,UAAA;AAAA,IACvB,eAAA,EAAiB,MAAM,CAAC,CAAC,EAAE,MAAA,IAAU,CAAC,SAAA,CAAU,CAAA,CAAE,MAAM,CAAA;AAAA,IACxD,SAAA,EAAW,OAAO,EAAA,EAAI,EAAA,KAAO;AAC3B,MAAA,CAAA,CAAE,MAAA,GAAS,EAAA;AACX,MAAA,CAAA,CAAE,MAAA,GAAS,EAAA;AACX,MAAA,CAAA,CAAE,UAAA,EAAA;AACF,MAAA,MAAA,CAAO,KAAK,YAAA,EAAc,EAAE,UAAA,EAAY,CAAA,CAAE,YAAY,CAAA;AACtD,MAAA,MAAM,iBAAA,CAAkB,UAAA,EAAY,EAAA,EAAI,EAAA,EAAI,MAAM,CAAA;AAAA,IACpD,CAAA;AAAA,IACA,aAAa,YAAY;AACvB,MAAA,CAAA,CAAE,MAAA,GAAS,MAAA;AACX,MAAA,CAAA,CAAE,MAAA,GAAS,MAAA;AACX,MAAA,CAAA,CAAE,cAAA,GAAiB,MAAA;AACnB,MAAA,CAAA,CAAE,UAAA,EAAA;AACF,MAAA,MAAA,CAAO,KAAK,gBAAgB,CAAA;AAC5B,MAAA,IAAI,UAAA,EAAY,MAAM,UAAA,CAAW,KAAA,EAAM;AAAA,IACzC,CAAA;AAAA,IACA,SAAS,YAAY;AACnB,MAAA,IAAI,CAAA,CAAE,cAAA,EAAgB,OAAO,CAAA,CAAE,cAAA;AAC/B,MAAA,OAAO,SAAA,CAAU,CAAA,EAAG,SAAA,EAAW,MAAA,EAAQ,UAAU,CAAA;AAAA,IACnD,CAAA;AAAA,IACA,MAAM,YAAY;AAChB,MAAA,IAAI,EAAE,MAAA,IAAU,CAAA,CAAE,QAAQ,OAAO,EAAE,UAAU,KAAA,EAAM;AACnD,MAAA,IAAI,CAAC,UAAA,EAAY,OAAO,EAAE,UAAU,KAAA,EAAM;AAC1C,MAAA,OAAO,gBAAA,CAAiB,CAAA,EAAG,UAAA,EAAY,MAAM,CAAA;AAAA,IAC/C;AAAA,GACF;AACF;;;ACnDO,IAAM,cAAA,GAAiB,CAAC,OAAA,KAAgC;AAC7D,EAAA,IAAI,QAAA;AAEJ,EAAA,OAAO,OAAO,MAAA,KAAW;AACvB,IAAA,QAAA,KAAa,MAAM,mBAAmB,OAAO,CAAA;AAC7C,IAAA,IAAI;AACF,MAAA,OAAO,MAAM,cAAA,CAAe,MAAA,EAAQ,QAAA,EAAU,OAAO,CAAA;AAAA,IACvD,CAAA,CAAA,MAAQ;AACN,MAAA,QAAA,GAAW,MAAA;AACX,MAAA,QAAA,GAAW,MAAM,mBAAmB,OAAO,CAAA;AAC3C,MAAA,OAAO,cAAA,CAAe,MAAA,EAAQ,QAAA,EAAU,OAAO,CAAA;AAAA,IACjD;AAAA,EACF,CAAA;AACF,CAAA;;;AChBO,IAAM,mBAAA,GAAsB,CAAC,aAAA,MAMb;AAAA,EACrB,EAAA,EAAI,MAAA,CAAO,aAAA,CAAc,SAAA,IAAa,EAAE,CAAA;AAAA,EACxC,IAAA,EAAM,cAAc,WAAA,IAAe,SAAA;AAAA,EACnC,KAAA,EAAO,aAAA,CAAc,SAAA,EAAW,YAAA,IAAgB,SAAA;AAAA,EAChD,UAAA,EAAY,cAAc,WAAA,GACtB,IAAI,KAAK,aAAA,CAAc,WAAW,CAAA,CAAE,WAAA,EAAY,GAChD,EAAA;AAAA,EACJ,UAAA,EAAY,cAAc,WAAA,GACtB,IAAI,KAAK,aAAA,CAAc,WAAW,CAAA,CAAE,WAAA,EAAY,GAChD;AACN,CAAA,CAAA;ACfO,IAAM,0BAAA,GAA6BC,EAAE,MAAA,CAAO;AAAA,EACjD,WAAWA,CAAAA,CAAE,MAAA,GAAS,EAAA,CAAGA,CAAAA,CAAE,QAAQ,CAAA;AAAA,EACnC,WAAA,EAAaA,CAAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EACjC,SAAA,EAAWA,CAAAA,CAAE,MAAA,CAAO,EAAE,YAAA,EAAcA,CAAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS,EAAG,CAAA,CAAE,QAAA,EAAS;AAAA,EACtE,WAAA,EAAaA,EAAE,MAAA,EAAO,CAAE,GAAGA,CAAAA,CAAE,MAAA,EAAQ,CAAA,CAAE,QAAA,EAAS;AAAA,EAChD,WAAA,EAAaA,EAAE,MAAA,EAAO,CAAE,GAAGA,CAAAA,CAAE,MAAA,EAAQ,CAAA,CAAE,QAAA;AACzC,CAAC,CAAA;AAGM,IAAM,wBAAA,GAA2BA,EAAE,MAAA,CAAO;AAAA,EAC/C,WAAWA,CAAAA,CAAE,MAAA,GAAS,EAAA,CAAGA,CAAAA,CAAE,QAAQ,CAAA;AAAA,EACnC,WAAA,EAAaA,CAAAA,CAAE,MAAA,EAAO,CAAE,QAAA;AAC1B,CAAC,CAAA;;;ACSD,IAAM,WAAA,GAAc,OAClB,GAAA,EACA,UAAA,EACA,cACA,GAAA,KACwB;AACxB,EAAA,IAAI;AACF,IAAA,GAAA,CAAI,KAAK,mCAAmC,CAAA;AAC5C,IAAA,MAAM,OAAA,GAAU,MAAM,MAAA,CAAO,GAAA,EAAK,cAAc,GAAG,CAAA;AACnD,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA;AAElC,IAAA,MAAM,GAAA,GAAM,MAAM,UAAA,CAAW,IAAA;AAAA,MAC3B,GAAG,WAAW,CAAA,QAAA,CAAA;AAAA,MACd;AAAA,KACF;AACA,IAAA,MAAM,MAAA,GAAS,wBAAA,CAAyB,KAAA,CAAM,GAAG,CAAA;AAEjD,IAAA,OAAO;AAAA,MACL,EAAA,EAAI,MAAA,CAAO,MAAA,CAAO,SAAS,CAAA;AAAA,MAC3B,IAAA,EAAM,OAAO,WAAA,IAAe,SAAA;AAAA,MAC5B,GAAA,EAAK,CAAA,0CAAA,EAA6C,MAAA,CAAO,SAAS,CAAA;AAAA,KACpE;AAAA,EACF,SAAS,KAAA,EAAO;AACd,IAAA,MAAMC,qBAAAA,CAAsB,wBAAA,EAA0B,MAAA,EAAW,KAAK,CAAA;AAAA,EACxE;AACF,CAAA;AAEA,IAAM,YAAA,GAAe,OACnB,UAAA,EACA,GAAA,EACA,OAAA,KAC8B;AAC9B,EAAA,IAAI;AACF,IAAA,GAAA,CAAI,KAAK,sCAAsC,CAAA;AAC/C,IAAA,MAAM,KAAA,GAAQ,SAAS,MAAA,IAAU,CAAA;AACjC,IAAA,MAAM,KAAA,GAAQ,SAAS,KAAA,IAAS,EAAA;AAChC,IAAA,MAAM,MAAA,GAAS,IAAI,eAAA,CAAgB;AAAA,MACjC,KAAA,EAAO,OAAO,KAAK,CAAA;AAAA,MACnB,KAAA,EAAO,OAAO,KAAK;AAAA,KACpB,CAAA;AAED,IAAA,MAAM,GAAA,GAAM,MAAM,UAAA,CAAW,GAAA;AAAA,MAC3B,CAAA,EAAG,WAAW,CAAA,UAAA,EAAa,MAAM,CAAA;AAAA,KACnC;AACA,IAAA,MAAM,QAAA,GAAW,0BAAA,CAA2B,KAAA,EAAM,CAAE,MAAM,GAAG,CAAA;AAC7D,IAAA,OAAO,QAAA,CAAS,IAAI,mBAAmB,CAAA;AAAA,EACzC,SAAS,KAAA,EAAO;AACd,IAAA,MAAMA,qBAAAA,CAAsB,yBAAA,EAA2B,MAAA,EAAW,KAAK,CAAA;AAAA,EACzE;AACF,CAAA;AAEO,IAAM,0BAAA,GAA6B,CACxC,UAAA,EACA,MAAA,KACwB;AACxB,EAAA,MAAM,GAAA,GAAM,UAAUC,mBAAAA,EAAoB;AAC1C,EAAA,MAAM,YAAA,GAAe,mBAAmB,GAAG,CAAA;AAE3C,EAAA,OAAO;AAAA,IACL,MAAM,CAAC,GAAA,KAAQ,YAAY,GAAA,EAAK,UAAA,EAAY,cAAc,GAAG,CAAA;AAAA,IAC7D,MAAM,CAAC,IAAA,KAAS,YAAA,CAAa,UAAA,EAAY,KAAK,IAAI;AAAA,GACpD;AACF,CAAA;AClFA,IAAM,cAAc,CAClB,GAAA,EACA,MACA,KAAA,EACA,OAAA,KAEA,QAAQ,GAAA,EAAK;AAAA,EACX,GAAG,IAAA;AAAA,EACH,OAAA,EAAS,EAAE,GAAG,IAAA,EAAM,SAAS,aAAA,EAAe,CAAA,OAAA,EAAU,KAAK,CAAA,CAAA;AAC7D,CAAC,CAAA;AAEH,IAAM,eAAA,GAAkB,CAAC,MAAA,EAAqB,GAAA,KAAwB;AACpE,EAAA,MAAM,KAAA,GAAQ,OAAO,cAAA,EAAe;AACpC,EAAA,IAAI,CAAC,KAAA,EAAO,MAAMD,qBAAAA,CAAsB,KAAK,GAAG,CAAA;AAChD,EAAA,OAAO,KAAA;AACT,CAAA;AAEA,IAAM,WAAA,GAAc,CAAC,GAAA,EAAe,MAAA,KAA0B;AAC5D,EAAA,MAAMA,qBAAAA,CAAsB,GAAG,MAAM,CAAA,EAAA,EAAK,IAAI,UAAU,CAAA,CAAA,EAAI,IAAI,MAAM,CAAA;AACxE,CAAA;AAEO,IAAM,SAAA,GAAY,OACvB,GAAA,EACA,IAAA,EACA,QACA,OAAA,KACsB;AACtB,EAAA,IAAI,CAAC,MAAA,CAAO,eAAA,EAAgB,EAAG,MAAM,OAAO,OAAA,EAAQ;AACpD,EAAA,MAAM,GAAA,GAAM,OAAO,aAAA,EAAc;AACjC,EAAA,MAAM,KAAA,GAAQ,eAAA,CAAgB,MAAA,EAAQ,mBAAmB,CAAA;AACzD,EAAA,MAAM,MAAM,MAAM,WAAA,CAAY,GAAA,EAAK,IAAA,EAAM,OAAO,OAAO,CAAA;AAEvD,EAAA,IAAI,GAAA,CAAI,WAAW,GAAA,EAAK;AACtB,IAAA,IAAI,CAAC,GAAA,CAAI,EAAA,EAAI,WAAA,CAAY,KAAK,oBAAoB,CAAA;AAClD,IAAA,OAAO,GAAA;AAAA,EACT;AAEA,EAAA,IAAI,OAAO,aAAA,EAAc,KAAM,GAAA,EAAK,MAAM,OAAO,OAAA,EAAQ;AACzD,EAAA,MAAM,UAAA,GAAa,eAAA,CAAgB,MAAA,EAAQ,iCAAiC,CAAA;AAC5E,EAAA,MAAM,QAAQ,MAAM,WAAA,CAAY,GAAA,EAAK,IAAA,EAAM,YAAY,OAAO,CAAA;AAC9D,EAAA,IAAI,CAAC,KAAA,CAAM,EAAA,EAAI,WAAA,CAAY,OAAO,wCAAwC,CAAA;AAC1E,EAAA,OAAO,KAAA;AACT,CAAA;;;ACvCO,IAAM,sBAAA,GAAyB,CACpC,WAAA,EACA,OAAA,EACA,MAAA,KACqB;AACrB,EAAA,MAAM,KAAA,GAAQ,CAAC,GAAA,EAAa,IAAA,KAC1B,UAAU,GAAA,EAAK,IAAA,EAAM,aAAa,OAAO,CAAA;AAE3C,EAAA,MAAA,CAAO,MAAM,qBAAqB,CAAA;AAElC,EAAA,OAAO;AAAA,IACL,GAAA,EAAK,OAAU,GAAA,KAA4B;AACzC,MAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,GAAG,CAAA;AAC3B,MAAA,OAAQ,MAAM,IAAI,IAAA,EAAK;AAAA,IACzB,CAAA;AAAA,IACA,IAAA,EAAM,OAAU,GAAA,EAAa,IAAA,KAA8B;AACzD,MAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,QAC3B,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,QAC9C,MAAM,IAAA,KAAS,IAAA,GAAO,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA,GAAI;AAAA,OAC9C,CAAA;AACD,MAAA,OAAQ,MAAM,IAAI,IAAA,EAAK;AAAA,IACzB,CAAA;AAAA,IACA,GAAA,EAAK,OAAU,GAAA,KAA4B;AACzC,MAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,QAC3B,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS,EAAE,wBAAA,EAA0B,QAAA;AAAS,OAC/C,CAAA;AACD,MAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,EAAK;AAC5B,MAAA,OAAQ,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA,GAAI,MAAA;AAAA,IACpC;AAAA,GACF;AACF,CAAA;;;ACpBA,IAAM,cAAc,CAAC,MAAA,KACnB,WAAW,GAAA,IAAQ,MAAA,IAAU,OAAO,MAAA,IAAU,GAAA;AAEhD,IAAM,YAAA,GAAe,CACnB,OAAA,EACA,SAAA,EACA,QAAA,EACA,QAAA,KACW,QAAA,EAAS,GAAI,IAAA,CAAK,GAAA,CAAI,QAAA,EAAU,SAAA,GAAY,KAAK,OAAO,CAAA;AAErE,IAAM,KAAA,GAAQ,CAAC,EAAA,KACb,IAAI,OAAA,CAAQ,CAAC,OAAA,KAAY,UAAA,CAAW,OAAA,EAAS,EAAE,CAAC,CAAA;AAElD,IAAM,UAAA,GAAa,CACjB,IAAA,EACA,OAAA,EACA,SACA,IAAA,KACkB;AAClB,EAAA,MAAM,KAAA,GAAQ,YAAA;AAAA,IACZ,OAAA;AAAA,IACA,IAAA,CAAK,SAAA;AAAA,IACL,IAAA,CAAK,QAAA;AAAA,IACL,IAAA,CAAK;AAAA,GACP;AACA,EAAA,IAAA,CAAK,MAAA,EAAQ,KAAA,CAAM,OAAA,EAAS,EAAE,GAAG,IAAA,EAAM,KAAA,EAAO,IAAA,CAAK,KAAA,CAAM,KAAK,CAAA,EAAG,CAAA;AACjE,EAAA,OAAO,MAAM,KAAK,CAAA;AACpB,CAAA;AAEA,IAAM,uBAAA,GAA0B,OAC9B,OAAA,EACA,QAAA,EACA,IAAA,KACqB;AACrB,EAAA,IAAI,UAAU,IAAA,CAAK,UAAA,IAAc,WAAA,CAAY,QAAA,CAAS,MAAM,CAAA,EAAG;AAC7D,IAAA,MAAM,UAAA,CAAW,IAAA,EAAM,OAAA,EAAS,kBAAA,EAAoB;AAAA,MAClD,SAAS,OAAA,GAAU,CAAA;AAAA,MACnB,QAAQ,QAAA,CAAS;AAAA,KAClB,CAAA;AACD,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,OAAO,KAAA;AACT,CAAA;AAEA,IAAM,oBAAA,GAAuB,OAC3B,OAAA,EACA,KAAA,EACA,IAAA,KACqB;AACrB,EAAA,IAAI,OAAA,GAAU,IAAA,CAAK,UAAA,IAAc,KAAA,YAAiB,SAAA,EAAW;AAC3D,IAAA,MAAM,UAAA,CAAW,IAAA,EAAM,OAAA,EAAS,sCAAA,EAAwC;AAAA,MACtE,SAAS,OAAA,GAAU,CAAA;AAAA,MACnB,OAAQ,KAAA,CAAgB;AAAA,KACzB,CAAA;AACD,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,OAAO,KAAA;AACT,CAAA;AAEO,IAAM,SAAA,GAAY,CACvB,OAAA,EACA,OAAA,KACY;AACZ,EAAA,MAAM,IAAA,GAAwB;AAAA,IAC5B,UAAA,EAAY,SAAS,UAAA,IAAc,CAAA;AAAA,IACnC,SAAA,EAAW,SAAS,SAAA,IAAa,GAAA;AAAA,IACjC,QAAA,EAAU,SAAS,QAAA,IAAY,GAAA;AAAA,IAC/B,QAAA,EAAU,OAAA,EAAS,QAAA,IAAY,IAAA,CAAK,MAAA;AAAA,IACpC,QAAQ,OAAA,EAAS;AAAA,GACnB;AAEA,EAAA,OAAO,OAAO,OAAO,IAAA,KAAU;AAC7B,IAAA,KAAA,IAAS,OAAA,GAAU,CAAA,EAAG,OAAA,IAAW,IAAA,CAAK,YAAY,OAAA,EAAA,EAAW;AAC3D,MAAA,IAAI;AACF,QAAA,MAAM,QAAA,GAAW,MAAM,OAAA,CAAQ,KAAA,EAAO,IAAI,CAAA;AAC1C,QAAA,IAAI,MAAM,uBAAA,CAAwB,OAAA,EAAS,QAAA,EAAU,IAAI,CAAA,EAAG;AAC5D,QAAA,OAAO,QAAA;AAAA,MACT,SAAS,KAAA,EAAO;AACd,QAAA,IAAI,MAAM,oBAAA,CAAqB,OAAA,EAAS,KAAA,EAAO,IAAI,CAAA,EAAG;AACtD,QAAA,MAAM,KAAA;AAAA,MACR;AAAA,IACF;AAEA,IAAA,MAAM,IAAI,MAAM,6BAA6B,CAAA;AAAA,EAC/C,CAAA;AACF,CAAA;;;ACxEO,IAAM,yBAAA,GAA4B,CACvC,OAAA,KACwB;AACxB,EAAA,MAAM,MAAA,GAAS,OAAA,EAAS,MAAA,IAAUC,mBAAAA,EAAoB;AACtD,EAAA,MAAM,UAAA,GAAa,OAAA,EAAS,OAAA,IAAW,iBAAA,EAAkB;AACzD,EAAA,MAAM,YAAA,GAAe,OAAA,EAAS,KAAA,GAC1B,SAAA,CAAU,UAAA,EAAY,EAAE,GAAG,OAAA,CAAQ,KAAA,EAAO,MAAA,EAAQ,CAAA,GAClD,UAAA;AAEJ,EAAA,MAAM,SAAA,GAAY,eAAe,UAAU,CAAA;AAC3C,EAAA,MAAM,eAAe,kBAAA,CAAmB;AAAA,IACtC,SAAA;AAAA,IACA,MAAA;AAAA,IACA,YAAY,OAAA,EAAS;AAAA,GACtB,CAAA;AAED,EAAA,MAAM,OAAO,wBAAA,CAAyB;AAAA,IACpC,YAAA;AAAA,IACA,MAAA;AAAA,IACA,OAAA,EAAS;AAAA,GACV,CAAA;AACD,EAAA,MAAM,UAAA,GAAa,sBAAA,CAAuB,YAAA,EAAc,YAAA,EAAc,MAAM,CAAA;AAC5E,EAAA,MAAM,OAAA,GAAU,0BAAA,CAA2B,UAAA,EAAY,MAAM,CAAA;AAE7D,EAAA,OAAO;AAAA,IACL,IAAA;AAAA,IACA,OAAA;AAAA,IACA,IAAA,EAAM,MAAM,YAAA,CAAa,IAAA;AAAK,GAChC;AACF;ACxDA,IAAM,YAAA,GAAe,IAAA,CAAK,OAAA,EAAQ,EAAG,WAAW,oBAAoB,CAAA;AAE7D,IAAM,oBAAA,GAAuB,CAClC,QAAA,GAAmB,YAAA,MACH;AAAA,EAChB,IAAA,EAAM,OAAO,MAAA,KAAsB;AACjC,IAAA,MAAM,MAAM,OAAA,CAAQ,QAAQ,GAAG,EAAE,SAAA,EAAW,MAAM,CAAA;AAClD,IAAA,MAAM,UAAU,QAAA,EAAU,IAAA,CAAK,UAAU,MAAA,EAAQ,IAAA,EAAM,CAAC,CAAA,EAAG;AAAA,MACzD,QAAA,EAAU,OAAA;AAAA,MACV,IAAA,EAAM;AAAA,KACP,CAAA;AAAA,EACH,CAAA;AAAA,EAEA,MAAM,YAAuC;AAC3C,IAAA,IAAI;AACF,MAAA,MAAM,OAAA,GAAU,MAAM,QAAA,CAAS,QAAA,EAAU,OAAO,CAAA;AAChD,MAAA,OAAO,IAAA,CAAK,MAAM,OAAO,CAAA;AAAA,IAC3B,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF,CAAA;AAAA,EAEA,OAAO,YAAY;AACjB,IAAA,IAAI;AACF,MAAA,MAAM,OAAO,QAAQ,CAAA;AAAA,IACvB,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AACF,CAAA;;;AChCO,IAAM,yBAAyB,MAAkB;AACtD,EAAA,IAAI,MAAA,GAA2B,IAAA;AAE/B,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,OAAO,MAAA,KAAsB;AACjC,MAAA,MAAA,GAAS,MAAA;AAAA,IACX,CAAA;AAAA,IACA,MAAM,YAAY,MAAA;AAAA,IAClB,OAAO,YAAY;AACjB,MAAA,MAAA,GAAS,IAAA;AAAA,IACX;AAAA,GACF;AACF","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"]}
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-html-diagnostics.ts","../src/adapters/http/sso-submit.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/http/garmin-auth-fetch.ts","../src/adapters/http/garmin-http-client.ts","../src/adapters/http/retry.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/pull-workout.ts","../src/adapters/client/remove-workout.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":["PAGE_TITLE_RE","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 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_SSO = \"GCM-iOS-5.7.2.1\";\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 type { Logger } from \"@kaiord/core\";\nimport { createServiceAuthError } from \"@kaiord/core\";\n\nimport type { FetchFn, OAuthConsumer } from \"./types\";\nimport { OAUTH_CONSUMER_URL } from \"./urls\";\n\nexport const fetchOAuthConsumer = async (\n fetchFn: FetchFn,\n logger: Logger\n): Promise<OAuthConsumer> => {\n logger.debug(\"[SSO] Fetching OAuth consumer credentials\");\n const res = await fetchFn(OAUTH_CONSUMER_URL);\n if (!res.ok) {\n logger.error(\"[SSO] OAuth consumer fetch failed\", { status: res.status });\n throw createServiceAuthError(\n `Failed to fetch OAuth consumer: ${res.status} ${res.statusText}`\n );\n }\n logger.debug(\"[SSO] OAuth consumer fetch\", { status: res.status });\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 type { Logger } from \"@kaiord/core\";\n\nconst PAGE_TITLE_RE = /<title>([^<]*)<\\/title>/;\n\nconst extractTitle = (html: string): string => {\n const match = PAGE_TITLE_RE.exec(html);\n return match?.[1] ?? \"unknown\";\n};\n\nexport const logCsrfResult = (\n logger: Logger,\n status: number,\n size: number,\n found: boolean\n): void => {\n logger.debug(\"[SSO] CSRF fetch\", { status, size });\n if (!found) logger.warn(\"[SSO] CSRF token not found in response\");\n};\n\nexport const logLoginResponse = (\n logger: Logger,\n status: number,\n size: number\n): void => {\n logger.debug(\"[SSO] Login response\", { status, size });\n};\n\nexport const logLoginHtmlDiagnostics = (\n html: string,\n ticketFound: boolean,\n logger: Logger\n): void => {\n const title = extractTitle(html);\n const size = new TextEncoder().encode(html).byteLength;\n logger.debug(\"[SSO] Page title\", { title });\n if (/\\bmfa\\b/i.test(html)) logger.warn(\"[SSO] MFA detected\");\n if (/\\berror\\b/i.test(html)) logger.warn(\"[SSO] Error indicators found\");\n if (ticketFound) {\n logger.debug(\"[SSO] Ticket found in HTML\");\n } else {\n logger.warn(\"[SSO] Ticket not found in HTML\", { size, title });\n }\n};\n","import type { Logger } from \"@kaiord/core\";\n\nimport { logLoginResponse } from \"./sso-html-diagnostics\";\nimport type { FetchFn } from \"./types\";\nimport {\n GARMIN_SSO_EMBED,\n GARMIN_SSO_ORIGIN,\n SIGNIN_URL,\n USER_AGENT_SSO,\n} from \"./urls\";\n\nexport type LoginResult = { html: string; status: number };\n\ntype LoginInput = {\n username: string;\n password: string;\n csrf: string;\n fetchFn: FetchFn;\n logger: Logger;\n};\n\nconst buildLoginParams = (): URLSearchParams =>\n new URLSearchParams({\n id: \"gauth-widget\",\n embedWidget: \"true\",\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\nexport const submitLogin = async (input: LoginInput): Promise<LoginResult> => {\n const { username, password, csrf, fetchFn, logger } = input;\n logger.debug(\"[SSO] Submitting login\");\n const body = new URLSearchParams({\n username,\n password,\n embed: \"true\",\n _csrf: csrf,\n });\n const loginRes = await fetchFn(`${SIGNIN_URL}?${buildLoginParams()}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Origin: GARMIN_SSO_ORIGIN,\n Referer: `${SIGNIN_URL}?${buildLoginParams()}`,\n \"User-Agent\": USER_AGENT_SSO,\n },\n body: body.toString(),\n });\n const html = await loginRes.text();\n const size = new TextEncoder().encode(html).byteLength;\n logLoginResponse(logger, loginRes.status, size);\n return { html, status: loginRes.status };\n};\n","import type { Logger } from \"@kaiord/core\";\nimport { createServiceAuthError } 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 type { Logger } from \"@kaiord/core\";\nimport { createServiceAuthError } from \"@kaiord/core\";\n\nimport { logCsrfResult, logLoginHtmlDiagnostics } from \"./sso-html-diagnostics\";\nimport { submitLogin } from \"./sso-submit\";\nimport { checkAccountLocked, checkPageTitle } from \"./sso-validators\";\nimport type { FetchFn } from \"./types\";\nimport { GARMIN_SSO_EMBED, SIGNIN_URL, USER_AGENT_SSO } from \"./urls\";\n\nconst CSRF_RE = /name=\"_csrf\"\\s+value=\"(.+?)\"/;\nconst TICKET_RE = /ticket=([^\"]+)\"/;\n\nconst buildSigninParams = (): URLSearchParams =>\n new URLSearchParams({\n id: \"gauth-widget\",\n embedWidget: \"true\",\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\nconst fetchCsrfToken = async (\n fetchFn: FetchFn,\n embedUrl: string,\n logger: Logger\n): Promise<string> => {\n logger.debug(\"[SSO] Fetching CSRF token\");\n const signinParams = buildSigninParams();\n const csrfRes = await fetchFn(`${SIGNIN_URL}?${signinParams}`, {\n headers: {\n \"User-Agent\": USER_AGENT_SSO,\n Referer: embedUrl,\n },\n });\n const csrfHtml = await csrfRes.text();\n const csrfMatch = CSRF_RE.exec(csrfHtml);\n const size = new TextEncoder().encode(csrfHtml).byteLength;\n logCsrfResult(logger, csrfRes.status, size, !!csrfMatch);\n if (!csrfRes.ok) {\n throw createServiceAuthError(\n `SSO login page returned ${csrfRes.status}: ${csrfRes.statusText}`\n );\n }\n if (!csrfMatch) {\n throw createServiceAuthError(\"CSRF token not found on login page\");\n }\n return csrfMatch[1];\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 id: \"gauth-widget\",\n embedWidget: \"true\",\n gauthHost: \"https://sso.garmin.com/sso\",\n });\n const embedUrl = `${GARMIN_SSO_EMBED}?${embedParams}`;\n const embedRes = await fetchFn(embedUrl, {\n headers: { \"User-Agent\": USER_AGENT_SSO },\n });\n logger.debug(\"[SSO] Embed bootstrap\", { status: embedRes.status });\n if (!embedRes.ok) {\n throw createServiceAuthError(\n `SSO embed bootstrap failed: ${embedRes.status} ${embedRes.statusText}`\n );\n }\n\n const csrf = await fetchCsrfToken(fetchFn, embedUrl, logger);\n const { html: loginHtml } = await submitLogin({\n username,\n password,\n csrf,\n fetchFn,\n logger,\n });\n\n checkAccountLocked(loginHtml);\n checkPageTitle(loginHtml, logger);\n\n const ticketMatch = TICKET_RE.exec(loginHtml);\n logLoginHtmlDiagnostics(loginHtml, !!ticketMatch, logger);\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\";\n\nimport OAuth from \"oauth-1.0a\";\n\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 type { Logger } from \"@kaiord/core\";\nimport { createServiceAuthError } from \"@kaiord/core\";\n\nimport { createOAuthSigner } from \"./oauth-signer\";\nimport type { FetchFn, OAuthConsumer } from \"./types\";\nimport type { OAuth1Token, OAuth2Token } from \"./types\";\nimport { GARMIN_SSO_EMBED, OAUTH_URL, USER_AGENT_MOBILE } from \"./urls\";\n\nexport const getOAuth1Token = async (\n ticket: string,\n consumer: OAuthConsumer,\n fetchFn: FetchFn,\n logger: Logger\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 logger.debug(\"[SSO] OAuth1 token response\", { status: res.status });\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 logger: Logger\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 logger.debug(\"[SSO] OAuth2 exchange response\", { status: res.status });\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 type { Logger } from \"@kaiord/core\";\n\nimport { fetchOAuthConsumer } from \"./oauth-consumer\";\nimport { getLoginTicket } from \"./sso-login\";\nimport { exchangeOAuth2 as exchange, getOAuth1Token } from \"./sso-oauth\";\nimport type { FetchFn, OAuth1Token, OAuth2Token } from \"./types\";\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 logger.info(\"[SSO] Step 1/4: OAuth consumer\");\n const consumer = await fetchOAuthConsumer(fetchFn, logger);\n\n logger.info(\"[SSO] Step 2/4: Login ticket\");\n const ticket = await getLoginTicket(username, password, fetchFn, logger);\n\n logger.info(\"[SSO] Step 3/4: OAuth1 token\");\n const oauth1 = await getOAuth1Token(ticket, consumer, fetchFn, logger);\n\n logger.info(\"[SSO] Step 4/4: OAuth2 exchange\");\n const oauth2 = await exchange(oauth1, consumer, fetchFn, logger);\n\n logger.info(\"Garmin Connect SSO login successful\");\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 type { AuthProvider, Logger } from \"@kaiord/core\";\nimport { createConsoleLogger, createServiceAuthError } from \"@kaiord/core\";\n\nimport { createCookieFetch } from \"../http/cookie-fetch\";\nimport { garminSso } from \"../http/garmin-sso\";\nimport type { FetchFn } from \"../http/types\";\nimport { garminTokensSchema } from \"../schemas/garmin-token.schema\";\nimport type { TokenManager } from \"../token/token-manager.types\";\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 type { Logger, TokenStore } from \"@kaiord/core\";\nimport { createServiceApiError } from \"@kaiord/core\";\n\nimport type { OAuth1Token, OAuth2Token } from \"../http/types\";\nimport { garminTokensSchema } from \"../schemas/garmin-token.schema\";\nimport type { RefreshFn } from \"./token-manager.types\";\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 type { Logger, TokenStore } from \"@kaiord/core\";\n\nimport type { TokenState } from \"./token-manager.helpers\";\nimport {\n doRefresh,\n isExpired,\n persistBestEffort,\n restoreFromStore,\n} from \"./token-manager.helpers\";\nimport type { RefreshFn, TokenManager } from \"./token-manager.types\";\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 { createServiceApiError } from \"@kaiord/core\";\n\nimport type { TokenReader } from \"../token/token-manager.types\";\nimport type { FetchFn } from \"./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 type { Logger } from \"@kaiord/core\";\n\nimport type { TokenReader } from \"../token/token-manager.types\";\nimport { authFetch } from \"./garmin-auth-fetch\";\nimport type { FetchFn, GarminHttpClient } from \"./types\";\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 { Logger } from \"@kaiord/core\";\n\nimport type { FetchFn } from \"./types\";\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 type { Logger } from \"@kaiord/core\";\n\nimport { 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, logger: Logger): RefreshFn => {\n let consumer: OAuthConsumer | undefined;\n\n return async (oauth1) => {\n consumer ??= await fetchOAuthConsumer(fetchFn, logger);\n try {\n return await exchangeOAuth2(oauth1, consumer, fetchFn, logger);\n } catch {\n consumer = undefined;\n consumer = await fetchOAuthConsumer(fetchFn, logger);\n return exchangeOAuth2(oauth1, consumer, fetchFn, logger);\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 type { KRD, Logger } from \"@kaiord/core\";\nimport { createServiceApiError, fromText } from \"@kaiord/core\";\nimport type { createGarminReader } from \"@kaiord/garmin\";\n\nimport type { GarminHttpClient } from \"../http/types\";\nimport { WORKOUT_URL } from \"../http/urls\";\n\nexport const pullWorkout = async (\n workoutId: string,\n httpClient: GarminHttpClient,\n garminReader: ReturnType<typeof createGarminReader>,\n log: Logger\n): Promise<KRD> => {\n try {\n log.info(`Pulling workout ${workoutId} from Garmin Connect`);\n const raw = await httpClient.get<unknown>(\n `${WORKOUT_URL}/workout/${workoutId}`\n );\n const gcnJson = JSON.stringify(raw);\n return await fromText(gcnJson, garminReader, log);\n } catch (error) {\n throw createServiceApiError(\"Failed to pull workout\", undefined, error);\n }\n};\n","import type { Logger } from \"@kaiord/core\";\nimport { createServiceApiError } from \"@kaiord/core\";\n\nimport type { GarminHttpClient } from \"../http/types\";\nimport { WORKOUT_URL } from \"../http/urls\";\n\nexport const removeWorkout = async (\n workoutId: string,\n httpClient: GarminHttpClient,\n log: Logger\n): Promise<void> => {\n try {\n log.info(`Removing workout ${workoutId} from Garmin Connect`);\n await httpClient.del(`${WORKOUT_URL}/workout/${workoutId}`);\n } catch (error) {\n throw createServiceApiError(\"Failed to remove workout\", undefined, error);\n }\n};\n","import type {\n KRD,\n ListOptions,\n Logger,\n PushResult,\n WorkoutService,\n WorkoutSummary,\n} from \"@kaiord/core\";\nimport {\n createConsoleLogger,\n createServiceApiError,\n toText,\n} from \"@kaiord/core\";\nimport { createGarminReader, createGarminWriter } from \"@kaiord/garmin\";\n\nimport type { GarminHttpClient } from \"../http/types\";\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 { pullWorkout } from \"./pull-workout\";\nimport { removeWorkout } from \"./remove-workout\";\n\nexport type GarminWorkoutClient = WorkoutService;\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 const raw = await httpClient.post<unknown>(\n `${WORKOUT_URL}/workout`,\n payload\n );\n const result = garminPushResponseSchema.parse(raw);\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 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 const garminReader = createGarminReader(log);\n\n return {\n push: (krd) => pushWorkout(krd, httpClient, garminWriter, log),\n pull: (id) => pullWorkout(id, httpClient, garminReader, log),\n list: (opts) => listWorkouts(httpClient, log, opts),\n remove: (id) => removeWorkout(id, httpClient, log),\n };\n};\n","import { createConsoleLogger } from \"@kaiord/core\";\n\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 { buildRefreshFn } from \"./build-refresh-fn\";\nimport type {\n GarminConnectClient,\n GarminConnectClientOptions,\n} from \"./garmin-connect-client.types\";\nimport { createGarminWorkoutService } from \"./garmin-workout-service\";\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, logger);\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 { mkdir, readFile, unlink, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\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": "6.0.0",
3
+ "version": "7.1.1",
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,16 +19,16 @@
19
19
  "fetch-cookie": "^3.2.0",
20
20
  "oauth-1.0a": "^2.2.6",
21
21
  "zod": "^4.0.0",
22
- "@kaiord/garmin": "^5.0.0",
23
- "@kaiord/core": "^4.9.0"
22
+ "@kaiord/core": "^7.1.1",
23
+ "@kaiord/garmin": "^7.1.1"
24
24
  },
25
25
  "devDependencies": {
26
- "@types/node": "^25.0.0",
27
- "@vitest/coverage-v8": "^4.0.18",
26
+ "@types/node": "^25.6.0",
27
+ "@vitest/coverage-v8": "^4.1.5",
28
28
  "tsup": "^8.5.1",
29
29
  "tsx": "^4.21.0",
30
- "typescript": "^5.3.3",
31
- "vitest": "^4.0.18"
30
+ "typescript": "^6.0.3",
31
+ "vitest": "^4.1.5"
32
32
  },
33
33
  "repository": {
34
34
  "type": "git",
@@ -52,6 +52,9 @@
52
52
  ],
53
53
  "author": "Kaiord Contributors",
54
54
  "license": "MIT",
55
+ "engines": {
56
+ "node": ">=22.12.0"
57
+ },
55
58
  "scripts": {
56
59
  "build": "tsup",
57
60
  "test": "vitest --run",