@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 +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +227 -140
- package/dist/index.js.map +1 -1
- package/package.json +10 -7
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](
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
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
|
|
81
|
-
const
|
|
155
|
+
var getLoginTicket = async (username, password, fetchFn, logger) => {
|
|
156
|
+
const embedParams = new URLSearchParams({
|
|
82
157
|
id: "gauth-widget",
|
|
83
158
|
embedWidget: "true",
|
|
84
|
-
|
|
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
|
|
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
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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.
|
|
208
|
-
const oauth1 = await getOAuth1Token(ticket, consumer, fetchFn);
|
|
209
|
-
logger.
|
|
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": "
|
|
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/
|
|
23
|
-
"@kaiord/
|
|
22
|
+
"@kaiord/core": "^7.1.1",
|
|
23
|
+
"@kaiord/garmin": "^7.1.1"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@types/node": "^25.
|
|
27
|
-
"@vitest/coverage-v8": "^4.
|
|
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": "^
|
|
31
|
-
"vitest": "^4.
|
|
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",
|