@nativesquare/soma 0.6.0 → 0.7.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.
Files changed (49) hide show
  1. package/dist/client/index.d.ts +151 -53
  2. package/dist/client/index.d.ts.map +1 -1
  3. package/dist/client/index.js +162 -69
  4. package/dist/client/index.js.map +1 -1
  5. package/dist/component/_generated/component.d.ts +130 -17
  6. package/dist/component/_generated/component.d.ts.map +1 -1
  7. package/dist/component/garmin.d.ts +61 -43
  8. package/dist/component/garmin.d.ts.map +1 -1
  9. package/dist/component/garmin.js +228 -122
  10. package/dist/component/garmin.js.map +1 -1
  11. package/dist/component/public.d.ts +363 -0
  12. package/dist/component/public.d.ts.map +1 -1
  13. package/dist/component/public.js +124 -0
  14. package/dist/component/public.js.map +1 -1
  15. package/dist/component/schema.d.ts +7 -9
  16. package/dist/component/schema.d.ts.map +1 -1
  17. package/dist/component/schema.js +9 -10
  18. package/dist/component/schema.js.map +1 -1
  19. package/dist/component/strava.d.ts +0 -1
  20. package/dist/component/strava.d.ts.map +1 -1
  21. package/dist/component/strava.js +0 -1
  22. package/dist/component/strava.js.map +1 -1
  23. package/dist/component/validators/enums.d.ts +2 -2
  24. package/dist/garmin/auth.d.ts +55 -46
  25. package/dist/garmin/auth.d.ts.map +1 -1
  26. package/dist/garmin/auth.js +82 -122
  27. package/dist/garmin/auth.js.map +1 -1
  28. package/dist/garmin/client.d.ts +64 -17
  29. package/dist/garmin/client.d.ts.map +1 -1
  30. package/dist/garmin/client.js +148 -29
  31. package/dist/garmin/client.js.map +1 -1
  32. package/dist/garmin/index.d.ts +3 -3
  33. package/dist/garmin/index.d.ts.map +1 -1
  34. package/dist/garmin/index.js +4 -4
  35. package/dist/garmin/index.js.map +1 -1
  36. package/dist/garmin/plannedWorkout.d.ts +12 -0
  37. package/dist/garmin/plannedWorkout.d.ts.map +1 -0
  38. package/dist/garmin/plannedWorkout.js +267 -0
  39. package/dist/garmin/plannedWorkout.js.map +1 -0
  40. package/dist/garmin/types.d.ts +78 -6
  41. package/dist/garmin/types.d.ts.map +1 -1
  42. package/package.json +1 -1
  43. package/src/client/index.ts +147 -0
  44. package/src/component/_generated/component.ts +142 -0
  45. package/src/component/garmin.ts +145 -0
  46. package/src/component/public.ts +135 -0
  47. package/src/garmin/client.ts +171 -0
  48. package/src/garmin/plannedWorkout.ts +333 -0
  49. package/src/garmin/types.ts +143 -0
@@ -1,155 +1,115 @@
1
- // ─── Garmin OAuth 1.0a Helpers ───────────────────────────────────────────────
2
- // Pure helper functions for the Garmin OAuth 1.0a three-legged flow.
3
- // Uses the Web Crypto API for HMAC-SHA1 signing and global `fetch`.
4
- const OAUTH_BASE_URL = "https://connectapi.garmin.com";
5
- const AUTH_CONFIRM_URL = "https://connect.garmin.com/oauthConfirm";
6
- // ─── OAuth 1.0a Signature ───────────────────────────────────────────────────
1
+ // ─── Garmin OAuth 2.0 PKCE Helpers ──────────────────────────────────────────
2
+ // Pure helper functions for the Garmin OAuth 2.0 PKCE flow.
3
+ // Uses the Web Crypto API for SHA-256 challenge generation and global `fetch`.
4
+ const AUTH_URL = "https://connect.garmin.com/oauth2Confirm";
5
+ const TOKEN_URL = "https://diauth.garmin.com/di-oauth2-service/oauth/token";
6
+ // ─── PKCE Helpers ───────────────────────────────────────────────────────────
7
+ const PKCE_CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
7
8
  /**
8
- * Generate a random nonce for OAuth 1.0a requests.
9
+ * Generate a cryptographically random code verifier for PKCE.
10
+ * Returns a 64-character string from the unreserved character set.
9
11
  */
10
- export function generateNonce() {
11
- const bytes = new Uint8Array(16);
12
+ export function generateCodeVerifier(length = 64) {
13
+ const bytes = new Uint8Array(length);
12
14
  crypto.getRandomValues(bytes);
13
- return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
14
- }
15
- /**
16
- * Get the current Unix timestamp in seconds.
17
- */
18
- export function getTimestamp() {
19
- return String(Math.floor(Date.now() / 1000));
15
+ return Array.from(bytes, (b) => PKCE_CHARSET[b % PKCE_CHARSET.length]).join("");
20
16
  }
21
17
  /**
22
- * Percent-encode a string per RFC 3986 (used by OAuth 1.0a).
23
- * Unlike encodeURIComponent, this also encodes `!`, `*`, `'`, `(`, `)`.
18
+ * Generate a random state parameter for CSRF protection.
24
19
  */
25
- export function percentEncode(str) {
26
- return encodeURIComponent(str).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
20
+ export function generateState() {
21
+ const bytes = new Uint8Array(32);
22
+ crypto.getRandomValues(bytes);
23
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
27
24
  }
28
25
  /**
29
- * Build the OAuth 1.0a signature base string and compute the HMAC-SHA1 signature.
30
- *
31
- * @param method - HTTP method (e.g., "POST", "GET")
32
- * @param url - The base URL (without query string)
33
- * @param params - All OAuth + request parameters sorted alphabetically
34
- * @param consumerSecret - The application's consumer secret
35
- * @param tokenSecret - The token secret (empty string for request token step)
26
+ * Compute the S256 code challenge from a code verifier.
27
+ * Returns `base64url(sha256(verifier))`.
36
28
  */
37
- export async function buildOAuthSignature(method, url, params, consumerSecret, tokenSecret = "") {
38
- const sortedKeys = Object.keys(params).sort();
39
- const paramString = sortedKeys
40
- .map((key) => `${percentEncode(key)}=${percentEncode(params[key])}`)
41
- .join("&");
42
- const signatureBaseString = [
43
- method.toUpperCase(),
44
- percentEncode(url),
45
- percentEncode(paramString),
46
- ].join("&");
47
- const signingKey = `${percentEncode(consumerSecret)}&${percentEncode(tokenSecret)}`;
29
+ export async function generateCodeChallenge(verifier) {
48
30
  const encoder = new TextEncoder();
49
- const key = await crypto.subtle.importKey("raw", encoder.encode(signingKey), { name: "HMAC", hash: "SHA-1" }, false, ["sign"]);
50
- const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(signatureBaseString));
51
- return btoa(String.fromCharCode(...new Uint8Array(signature)));
31
+ const digest = await crypto.subtle.digest("SHA-256", encoder.encode(verifier));
32
+ const base64 = btoa(String.fromCharCode(...new Uint8Array(digest)));
33
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
52
34
  }
53
35
  /**
54
- * Build the `Authorization: OAuth ...` header value from OAuth parameters.
36
+ * Build the Garmin OAuth 2.0 authorization URL.
37
+ *
38
+ * Redirect the user to this URL to begin the OAuth flow. After the user
39
+ * grants access, Garmin will redirect back to `redirectUri` with `code`
40
+ * and `state` query parameters.
55
41
  */
56
- export function buildOAuthHeader(params) {
57
- const entries = Object.entries(params)
58
- .map(([key, value]) => `${percentEncode(key)}="${percentEncode(value)}"`)
59
- .join(", ");
60
- return `OAuth ${entries}`;
42
+ export function buildAuthUrl(opts) {
43
+ const params = new URLSearchParams({
44
+ client_id: opts.clientId,
45
+ response_type: "code",
46
+ code_challenge: opts.codeChallenge,
47
+ code_challenge_method: "S256",
48
+ });
49
+ if (opts.redirectUri) {
50
+ params.set("redirect_uri", opts.redirectUri);
51
+ }
52
+ if (opts.state) {
53
+ params.set("state", opts.state);
54
+ }
55
+ return `${AUTH_URL}?${params.toString()}`;
61
56
  }
62
57
  /**
63
- * Obtain an unauthorized request token from Garmin.
58
+ * Exchange an authorization code for access and refresh tokens.
64
59
  *
65
- * This is Step 1 of the OAuth 1.0a three-legged flow:
66
- * 1. Your server calls this function to get a temporary request token
67
- * 2. Redirect the user to the returned `authUrl`
68
- * 3. After the user authorizes, exchange the verifier for an access token
60
+ * Call this from your OAuth callback endpoint after receiving the `code`
61
+ * query parameter from Garmin.
69
62
  *
70
- * @returns The request token, token secret, and the authorization URL
63
+ * @returns The token response including `access_token`, `refresh_token`,
64
+ * `expires_in`, and `refresh_token_expires_in`.
71
65
  */
72
- export async function getRequestToken(opts) {
73
- const url = `${OAUTH_BASE_URL}/oauth-service/oauth/request_token`;
74
- const nonce = generateNonce();
75
- const timestamp = getTimestamp();
76
- const oauthParams = {
77
- oauth_consumer_key: opts.consumerKey,
78
- oauth_nonce: nonce,
79
- oauth_signature_method: "HMAC-SHA1",
80
- oauth_timestamp: timestamp,
81
- oauth_version: "1.0",
82
- };
83
- if (opts.callbackUrl) {
84
- oauthParams.oauth_callback = opts.callbackUrl;
66
+ export async function exchangeCode(opts) {
67
+ const body = new URLSearchParams({
68
+ grant_type: "authorization_code",
69
+ client_id: opts.clientId,
70
+ client_secret: opts.clientSecret,
71
+ code: opts.code,
72
+ code_verifier: opts.codeVerifier,
73
+ });
74
+ if (opts.redirectUri) {
75
+ body.set("redirect_uri", opts.redirectUri);
85
76
  }
86
- const signature = await buildOAuthSignature("POST", url, oauthParams, opts.consumerSecret);
87
- oauthParams.oauth_signature = signature;
88
- const response = await fetch(url, {
77
+ const response = await fetch(TOKEN_URL, {
89
78
  method: "POST",
90
- headers: {
91
- Authorization: buildOAuthHeader(oauthParams),
92
- },
79
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
80
+ body: body.toString(),
93
81
  });
94
82
  if (!response.ok) {
95
- const body = await response.text().catch(() => "");
96
- throw new Error(`Garmin OAuth error (getRequestToken): ${response.status} ${response.statusText} — ${body}`);
83
+ const text = await response.text().catch(() => "");
84
+ throw new Error(`Garmin OAuth error (exchangeCode): ${response.status} ${response.statusText} — ${text}`);
97
85
  }
98
- const responseText = await response.text();
99
- const parsed = new URLSearchParams(responseText);
100
- const oauthToken = parsed.get("oauth_token");
101
- const oauthTokenSecret = parsed.get("oauth_token_secret");
102
- if (!oauthToken || !oauthTokenSecret) {
103
- throw new Error(`Garmin OAuth error: unexpected response format — ${responseText}`);
104
- }
105
- const authUrl = opts.callbackUrl
106
- ? `${AUTH_CONFIRM_URL}?oauth_token=${encodeURIComponent(oauthToken)}&oauth_callback=${encodeURIComponent(opts.callbackUrl)}`
107
- : `${AUTH_CONFIRM_URL}?oauth_token=${encodeURIComponent(oauthToken)}`;
108
- return {
109
- oauthToken,
110
- oauthTokenSecret,
111
- authUrl,
112
- };
86
+ return (await response.json());
113
87
  }
114
88
  /**
115
- * Exchange a request token + verifier for a permanent access token.
89
+ * Refresh an expired access token using a refresh token.
90
+ *
91
+ * Garmin access tokens expire after ~24 hours. Call this when the token
92
+ * is near expiry to obtain a fresh access token. A new refresh token
93
+ * is returned each time.
116
94
  *
117
- * This is Step 3 of the OAuth 1.0a three-legged flow.
118
- * The returned access token and secret are permanent — Garmin tokens
119
- * do not expire and there is no refresh flow.
95
+ * @returns A new token response with fresh `access_token` and `refresh_token`.
120
96
  */
121
- export async function getAccessToken(opts) {
122
- const url = `${OAUTH_BASE_URL}/oauth-service/oauth/access_token`;
123
- const nonce = generateNonce();
124
- const timestamp = getTimestamp();
125
- const oauthParams = {
126
- oauth_consumer_key: opts.consumerKey,
127
- oauth_nonce: nonce,
128
- oauth_signature_method: "HMAC-SHA1",
129
- oauth_timestamp: timestamp,
130
- oauth_token: opts.token,
131
- oauth_verifier: opts.verifier,
132
- oauth_version: "1.0",
133
- };
134
- const signature = await buildOAuthSignature("POST", url, oauthParams, opts.consumerSecret, opts.tokenSecret);
135
- oauthParams.oauth_signature = signature;
136
- const response = await fetch(url, {
97
+ export async function refreshToken(opts) {
98
+ const body = new URLSearchParams({
99
+ grant_type: "refresh_token",
100
+ client_id: opts.clientId,
101
+ client_secret: opts.clientSecret,
102
+ refresh_token: opts.refreshToken,
103
+ });
104
+ const response = await fetch(TOKEN_URL, {
137
105
  method: "POST",
138
- headers: {
139
- Authorization: buildOAuthHeader(oauthParams),
140
- },
106
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
107
+ body: body.toString(),
141
108
  });
142
109
  if (!response.ok) {
143
- const body = await response.text().catch(() => "");
144
- throw new Error(`Garmin OAuth error (getAccessToken): ${response.status} ${response.statusText} — ${body}`);
145
- }
146
- const responseText = await response.text();
147
- const parsed = new URLSearchParams(responseText);
148
- const oauthToken = parsed.get("oauth_token");
149
- const oauthTokenSecret = parsed.get("oauth_token_secret");
150
- if (!oauthToken || !oauthTokenSecret) {
151
- throw new Error(`Garmin OAuth error: unexpected access token response — ${responseText}`);
110
+ const text = await response.text().catch(() => "");
111
+ throw new Error(`Garmin OAuth error (refreshToken): ${response.status} ${response.statusText} — ${text}`);
152
112
  }
153
- return { oauthToken, oauthTokenSecret };
113
+ return (await response.json());
154
114
  }
155
115
  //# sourceMappingURL=auth.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/garmin/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/garmin/auth.ts"],"names":[],"mappings":"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"}
@@ -1,33 +1,24 @@
1
- import type { GarminActivity, GarminDailySummary, GarminSleep, GarminBodyComposition, GarminMenstrualCycleData } from "./types.js";
1
+ import type { GarminActivity, GarminDailySummary, GarminSleep, GarminBodyComposition, GarminMenstrualCycleData, GarminWorkout, GarminWorkoutSchedule } from "./types.js";
2
2
  export interface GarminClientOptions {
3
- /** Your application's consumer key (from Garmin Developer Portal). */
4
- consumerKey: string;
5
- /** Your application's consumer secret. */
6
- consumerSecret: string;
7
- /** The user's permanent OAuth access token. */
3
+ /** The user's OAuth 2.0 access token. */
8
4
  accessToken: string;
9
- /** The user's permanent OAuth token secret. */
10
- tokenSecret: string;
11
5
  /**
12
6
  * Base URL of the Garmin Health API.
13
- * Defaults to `https://apis.garmin.com`.
7
+ * @default "https://apis.garmin.com"
14
8
  */
15
9
  baseUrl?: string;
16
10
  }
17
11
  /**
18
12
  * A lightweight client for the Garmin Health API.
19
13
  *
20
- * All requests are signed with OAuth 1.0a. Time-range parameters
14
+ * All requests are authenticated with a Bearer token. Time-range parameters
21
15
  * use Unix epoch seconds for `uploadStartTimeInSeconds` and
22
16
  * `uploadEndTimeInSeconds`.
23
17
  *
24
18
  * @example
25
19
  * ```ts
26
20
  * const client = new GarminClient({
27
- * consumerKey: "your_key",
28
- * consumerSecret: "your_secret",
29
- * accessToken: "user_token",
30
- * tokenSecret: "user_secret",
21
+ * accessToken: "user_access_token",
31
22
  * });
32
23
  *
33
24
  * const dailies = await client.getDailies({
@@ -37,10 +28,7 @@ export interface GarminClientOptions {
37
28
  * ```
38
29
  */
39
30
  export declare class GarminClient {
40
- private readonly consumerKey;
41
- private readonly consumerSecret;
42
31
  private readonly accessToken;
43
- private readonly tokenSecret;
44
32
  private readonly baseUrl;
45
33
  constructor(opts: GarminClientOptions);
46
34
  /**
@@ -83,7 +71,66 @@ export declare class GarminClient {
83
71
  * @param params - Time range for the backfill
84
72
  */
85
73
  requestBackfill(summaryType: string, params: TimeRangeParams): Promise<void>;
74
+ /**
75
+ * Check which permissions the user has granted.
76
+ *
77
+ * Garmin API: `GET /userPermissions/`
78
+ */
79
+ getUserPermissions(): Promise<string[]>;
80
+ /**
81
+ * Create a workout in Garmin Connect.
82
+ *
83
+ * Garmin API: `POST /workoutportal/workout/v2`
84
+ * Note: uses a different base path than other Training API endpoints.
85
+ */
86
+ createWorkout(workout: GarminWorkout): Promise<GarminWorkout>;
87
+ /**
88
+ * Retrieve a workout by ID.
89
+ *
90
+ * Garmin API: `GET /training-api/workout/v2/{workoutId}`
91
+ */
92
+ getWorkout(workoutId: number): Promise<GarminWorkout>;
93
+ /**
94
+ * Update a workout by ID.
95
+ *
96
+ * Garmin API: `PUT /training-api/workout/v2/{workoutId}`
97
+ */
98
+ updateWorkout(workoutId: number, workout: GarminWorkout): Promise<GarminWorkout>;
99
+ /**
100
+ * Delete a workout by ID.
101
+ *
102
+ * Garmin API: `DELETE /training-api/workout/v2/{workoutId}`
103
+ */
104
+ deleteWorkout(workoutId: number): Promise<void>;
105
+ /**
106
+ * Schedule a workout to a specific date on the user's calendar.
107
+ *
108
+ * Garmin API: `POST /training-api/schedule/`
109
+ */
110
+ createSchedule(workoutId: number, date: string): Promise<GarminWorkoutSchedule>;
111
+ /**
112
+ * Retrieve workout schedules for a date range.
113
+ *
114
+ * Garmin API: `GET /training-api/schedule?startDate=...&endDate=...`
115
+ */
116
+ getSchedulesByDate(startDate: string, endDate: string): Promise<GarminWorkoutSchedule[]>;
117
+ /**
118
+ * Delete a workout schedule by ID.
119
+ *
120
+ * Garmin API: `DELETE /training-api/schedule/{scheduleId}`
121
+ */
122
+ deleteSchedule(scheduleId: number): Promise<void>;
123
+ /**
124
+ * Delete the user's registration with Garmin.
125
+ *
126
+ * Must be called when the user disconnects or deletes their account
127
+ * to comply with Garmin's API requirements.
128
+ */
129
+ deleteUserRegistration(): Promise<void>;
86
130
  private get;
131
+ private post;
132
+ private put;
133
+ private del;
87
134
  }
88
135
  export interface TimeRangeParams {
89
136
  /** Start of the time range as Unix epoch seconds. */
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/garmin/client.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,cAAc,EACd,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,wBAAwB,EACzB,MAAM,YAAY,CAAC;AAUpB,MAAM,WAAW,mBAAmB;IAClC,sEAAsE;IACtE,WAAW,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,cAAc,EAAE,MAAM,CAAC;IACvB,+CAA+C;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,+CAA+C;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,IAAI,EAAE,mBAAmB;IAUrC;;;;OAIG;IACG,UAAU,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IASxE;;;;OAIG;IACG,aAAa,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IASvE;;;;OAIG;IACG,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAShE;;;;OAIG;IACG,mBAAmB,CACvB,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,qBAAqB,EAAE,CAAC;IASnC;;;;OAIG;IACG,qBAAqB,CACzB,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,wBAAwB,EAAE,CAAC;IAStC;;;;;;;;OAQG;IACG,eAAe,CACnB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,IAAI,CAAC;YAUF,GAAG;CAoDlB;AAID,MAAM,WAAW,eAAe;IAC9B,qDAAqD;IACrD,wBAAwB,EAAE,MAAM,CAAC;IACjC,mDAAmD;IACnD,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAWD,qBAAa,cAAe,SAAQ,KAAK;aAGrB,MAAM,EAAE,MAAM;aACd,IAAI,EAAE,MAAM;gBAF5B,OAAO,EAAE,MAAM,EACC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM;CAK/B"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/garmin/client.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,cAAc,EACd,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,wBAAwB,EACxB,aAAa,EACb,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAIpB,MAAM,WAAW,mBAAmB;IAClC,yCAAyC;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,IAAI,EAAE,mBAAmB;IAOrC;;;;OAIG;IACG,UAAU,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IASxE;;;;OAIG;IACG,aAAa,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IASvE;;;;OAIG;IACG,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAShE;;;;OAIG;IACG,mBAAmB,CACvB,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,qBAAqB,EAAE,CAAC;IASnC;;;;OAIG;IACG,qBAAqB,CACzB,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,wBAAwB,EAAE,CAAC;IAStC;;;;;;;;OAQG;IACG,eAAe,CACnB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,IAAI,CAAC;IAUhB;;;;OAIG;IACG,kBAAkB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAI7C;;;;;OAKG;IACG,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;IAInE;;;;OAIG;IACG,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAM3D;;;;OAIG;IACG,aAAa,CACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,aAAa,CAAC;IAOzB;;;;OAIG;IACG,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrD;;;;OAIG;IACG,cAAc,CAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,qBAAqB,CAAC;IAOjC;;;;OAIG;IACG,kBAAkB,CACtB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,qBAAqB,EAAE,CAAC;IAOnC;;;;OAIG;IACG,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMvD;;;;;OAKG;IACG,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;YAqB/B,GAAG;YA8BH,IAAI;YA+BJ,GAAG;YAwBH,GAAG;CAkBlB;AAID,MAAM,WAAW,eAAe;IAC9B,qDAAqD;IACrD,wBAAwB,EAAE,MAAM,CAAC;IACjC,mDAAmD;IACnD,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAWD,qBAAa,cAAe,SAAQ,KAAK;aAGrB,MAAM,EAAE,MAAM;aACd,IAAI,EAAE,MAAM;gBAF5B,OAAO,EAAE,MAAM,EACC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM;CAK/B"}
@@ -1,23 +1,18 @@
1
1
  // ─── Garmin Health API Client ────────────────────────────────────────────────
2
2
  // Lightweight, fetch-based client for the Garmin Health API.
3
- // Every request is signed with OAuth 1.0a using the consumer and user tokens.
4
- // Uses the Web Crypto API for HMAC-SHA1 signing and global `fetch`.
5
- import { generateNonce, getTimestamp, buildOAuthSignature, buildOAuthHeader, } from "./auth.js";
3
+ // Authenticates requests with an OAuth 2.0 Bearer token.
6
4
  const DEFAULT_BASE_URL = "https://apis.garmin.com";
7
5
  /**
8
6
  * A lightweight client for the Garmin Health API.
9
7
  *
10
- * All requests are signed with OAuth 1.0a. Time-range parameters
8
+ * All requests are authenticated with a Bearer token. Time-range parameters
11
9
  * use Unix epoch seconds for `uploadStartTimeInSeconds` and
12
10
  * `uploadEndTimeInSeconds`.
13
11
  *
14
12
  * @example
15
13
  * ```ts
16
14
  * const client = new GarminClient({
17
- * consumerKey: "your_key",
18
- * consumerSecret: "your_secret",
19
- * accessToken: "user_token",
20
- * tokenSecret: "user_secret",
15
+ * accessToken: "user_access_token",
21
16
  * });
22
17
  *
23
18
  * const dailies = await client.getDailies({
@@ -27,16 +22,10 @@ const DEFAULT_BASE_URL = "https://apis.garmin.com";
27
22
  * ```
28
23
  */
29
24
  export class GarminClient {
30
- consumerKey;
31
- consumerSecret;
32
25
  accessToken;
33
- tokenSecret;
34
26
  baseUrl;
35
27
  constructor(opts) {
36
- this.consumerKey = opts.consumerKey;
37
- this.consumerSecret = opts.consumerSecret;
38
28
  this.accessToken = opts.accessToken;
39
- this.tokenSecret = opts.tokenSecret;
40
29
  this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
41
30
  }
42
31
  // ─── Daily Summaries ────────────────────────────────────────────────────
@@ -98,6 +87,98 @@ export class GarminClient {
98
87
  const query = timeRangeQuery(params);
99
88
  await this.get(`/wellness-api/rest/backfill/${summaryType}`, query);
100
89
  }
90
+ // ─── Training API V2 ────────────────────────────────────────────────
91
+ /**
92
+ * Check which permissions the user has granted.
93
+ *
94
+ * Garmin API: `GET /userPermissions/`
95
+ */
96
+ async getUserPermissions() {
97
+ return this.get("/userPermissions/");
98
+ }
99
+ /**
100
+ * Create a workout in Garmin Connect.
101
+ *
102
+ * Garmin API: `POST /workoutportal/workout/v2`
103
+ * Note: uses a different base path than other Training API endpoints.
104
+ */
105
+ async createWorkout(workout) {
106
+ return this.post("/workoutportal/workout/v2", workout);
107
+ }
108
+ /**
109
+ * Retrieve a workout by ID.
110
+ *
111
+ * Garmin API: `GET /training-api/workout/v2/{workoutId}`
112
+ */
113
+ async getWorkout(workoutId) {
114
+ return this.get(`/training-api/workout/v2/${workoutId}`);
115
+ }
116
+ /**
117
+ * Update a workout by ID.
118
+ *
119
+ * Garmin API: `PUT /training-api/workout/v2/{workoutId}`
120
+ */
121
+ async updateWorkout(workoutId, workout) {
122
+ return this.put(`/training-api/workout/v2/${workoutId}`, workout);
123
+ }
124
+ /**
125
+ * Delete a workout by ID.
126
+ *
127
+ * Garmin API: `DELETE /training-api/workout/v2/{workoutId}`
128
+ */
129
+ async deleteWorkout(workoutId) {
130
+ await this.del(`/training-api/workout/v2/${workoutId}`);
131
+ }
132
+ /**
133
+ * Schedule a workout to a specific date on the user's calendar.
134
+ *
135
+ * Garmin API: `POST /training-api/schedule/`
136
+ */
137
+ async createSchedule(workoutId, date) {
138
+ return this.post("/training-api/schedule/", {
139
+ workoutId,
140
+ date,
141
+ });
142
+ }
143
+ /**
144
+ * Retrieve workout schedules for a date range.
145
+ *
146
+ * Garmin API: `GET /training-api/schedule?startDate=...&endDate=...`
147
+ */
148
+ async getSchedulesByDate(startDate, endDate) {
149
+ return this.get("/training-api/schedule", {
150
+ startDate,
151
+ endDate,
152
+ });
153
+ }
154
+ /**
155
+ * Delete a workout schedule by ID.
156
+ *
157
+ * Garmin API: `DELETE /training-api/schedule/{scheduleId}`
158
+ */
159
+ async deleteSchedule(scheduleId) {
160
+ await this.del(`/training-api/schedule/${scheduleId}`);
161
+ }
162
+ // ─── User Deregistration ──────────────────────────────────────────────
163
+ /**
164
+ * Delete the user's registration with Garmin.
165
+ *
166
+ * Must be called when the user disconnects or deletes their account
167
+ * to comply with Garmin's API requirements.
168
+ */
169
+ async deleteUserRegistration() {
170
+ const url = `${this.baseUrl}/wellness-api/rest/user/registration`;
171
+ const response = await fetch(url, {
172
+ method: "DELETE",
173
+ headers: {
174
+ Authorization: `Bearer ${this.accessToken}`,
175
+ },
176
+ });
177
+ if (!response.ok) {
178
+ const body = await response.text().catch(() => "");
179
+ throw new GarminApiError(`Garmin API error: ${response.status} ${response.statusText}`, response.status, body);
180
+ }
181
+ }
101
182
  // ─── Internal ─────────────────────────────────────────────────────────
102
183
  async get(path, queryParams) {
103
184
  const fullUrl = `${this.baseUrl}${path}`;
@@ -105,24 +186,10 @@ export class GarminClient {
105
186
  ? `?${new URLSearchParams(queryParams).toString()}`
106
187
  : "";
107
188
  const requestUrl = `${fullUrl}${qs}`;
108
- const nonce = generateNonce();
109
- const timestamp = getTimestamp();
110
- const oauthParams = {
111
- oauth_consumer_key: this.consumerKey,
112
- oauth_nonce: nonce,
113
- oauth_signature_method: "HMAC-SHA1",
114
- oauth_timestamp: timestamp,
115
- oauth_token: this.accessToken,
116
- oauth_version: "1.0",
117
- };
118
- // OAuth signature must include both OAuth params and query params
119
- const allParams = { ...oauthParams, ...(queryParams ?? {}) };
120
- const signature = await buildOAuthSignature("GET", fullUrl, allParams, this.consumerSecret, this.tokenSecret);
121
- oauthParams.oauth_signature = signature;
122
189
  const response = await fetch(requestUrl, {
123
190
  method: "GET",
124
191
  headers: {
125
- Authorization: buildOAuthHeader(oauthParams),
192
+ Authorization: `Bearer ${this.accessToken}`,
126
193
  Accept: "application/json",
127
194
  },
128
195
  });
@@ -132,6 +199,58 @@ export class GarminClient {
132
199
  }
133
200
  return (await response.json());
134
201
  }
202
+ async post(path, body) {
203
+ const url = `${this.baseUrl}${path}`;
204
+ const response = await fetch(url, {
205
+ method: "POST",
206
+ headers: {
207
+ Authorization: `Bearer ${this.accessToken}`,
208
+ "Content-Type": "application/json",
209
+ Accept: "application/json",
210
+ },
211
+ body: JSON.stringify(body),
212
+ });
213
+ if (!response.ok) {
214
+ const text = await response.text().catch(() => "");
215
+ const isTrainingApi = path.includes("/workoutportal/") || path.includes("/training-api/");
216
+ const hint = isTrainingApi && response.status === 401
217
+ ? " — Ensure the Garmin app is registered for the Training API program " +
218
+ "and the user has WORKOUT_IMPORT permission."
219
+ : "";
220
+ throw new GarminApiError(`Garmin API error: ${response.status} ${response.statusText}${hint}`, response.status, text);
221
+ }
222
+ return (await response.json());
223
+ }
224
+ async put(path, body) {
225
+ const url = `${this.baseUrl}${path}`;
226
+ const response = await fetch(url, {
227
+ method: "PUT",
228
+ headers: {
229
+ Authorization: `Bearer ${this.accessToken}`,
230
+ "Content-Type": "application/json",
231
+ Accept: "application/json",
232
+ },
233
+ body: JSON.stringify(body),
234
+ });
235
+ if (!response.ok) {
236
+ const text = await response.text().catch(() => "");
237
+ throw new GarminApiError(`Garmin API error: ${response.status} ${response.statusText}`, response.status, text);
238
+ }
239
+ return (await response.json());
240
+ }
241
+ async del(path) {
242
+ const url = `${this.baseUrl}${path}`;
243
+ const response = await fetch(url, {
244
+ method: "DELETE",
245
+ headers: {
246
+ Authorization: `Bearer ${this.accessToken}`,
247
+ },
248
+ });
249
+ if (!response.ok) {
250
+ const text = await response.text().catch(() => "");
251
+ throw new GarminApiError(`Garmin API error: ${response.status} ${response.statusText}`, response.status, text);
252
+ }
253
+ }
135
254
  }
136
255
  function timeRangeQuery(params) {
137
256
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/garmin/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/garmin/client.ts"],"names":[],"mappings":"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"}