@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.
- package/dist/client/index.d.ts +151 -53
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +162 -69
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/component.d.ts +130 -17
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/garmin.d.ts +61 -43
- package/dist/component/garmin.d.ts.map +1 -1
- package/dist/component/garmin.js +228 -122
- package/dist/component/garmin.js.map +1 -1
- package/dist/component/public.d.ts +363 -0
- package/dist/component/public.d.ts.map +1 -1
- package/dist/component/public.js +124 -0
- package/dist/component/public.js.map +1 -1
- package/dist/component/schema.d.ts +7 -9
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +9 -10
- package/dist/component/schema.js.map +1 -1
- package/dist/component/strava.d.ts +0 -1
- package/dist/component/strava.d.ts.map +1 -1
- package/dist/component/strava.js +0 -1
- package/dist/component/strava.js.map +1 -1
- package/dist/component/validators/enums.d.ts +2 -2
- package/dist/garmin/auth.d.ts +55 -46
- package/dist/garmin/auth.d.ts.map +1 -1
- package/dist/garmin/auth.js +82 -122
- package/dist/garmin/auth.js.map +1 -1
- package/dist/garmin/client.d.ts +64 -17
- package/dist/garmin/client.d.ts.map +1 -1
- package/dist/garmin/client.js +148 -29
- package/dist/garmin/client.js.map +1 -1
- package/dist/garmin/index.d.ts +3 -3
- package/dist/garmin/index.d.ts.map +1 -1
- package/dist/garmin/index.js +4 -4
- package/dist/garmin/index.js.map +1 -1
- package/dist/garmin/plannedWorkout.d.ts +12 -0
- package/dist/garmin/plannedWorkout.d.ts.map +1 -0
- package/dist/garmin/plannedWorkout.js +267 -0
- package/dist/garmin/plannedWorkout.js.map +1 -0
- package/dist/garmin/types.d.ts +78 -6
- package/dist/garmin/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +147 -0
- package/src/component/_generated/component.ts +142 -0
- package/src/component/garmin.ts +145 -0
- package/src/component/public.ts +135 -0
- package/src/garmin/client.ts +171 -0
- package/src/garmin/plannedWorkout.ts +333 -0
- package/src/garmin/types.ts +143 -0
package/dist/garmin/auth.js
CHANGED
|
@@ -1,155 +1,115 @@
|
|
|
1
|
-
// ─── Garmin OAuth
|
|
2
|
-
// Pure helper functions for the Garmin OAuth
|
|
3
|
-
// Uses the Web Crypto API for
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
// ───
|
|
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
|
|
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
|
|
11
|
-
const bytes = new Uint8Array(
|
|
12
|
+
export function generateCodeVerifier(length = 64) {
|
|
13
|
+
const bytes = new Uint8Array(length);
|
|
12
14
|
crypto.getRandomValues(bytes);
|
|
13
|
-
return Array.from(bytes, (b) => b.
|
|
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
|
-
*
|
|
23
|
-
* Unlike encodeURIComponent, this also encodes `!`, `*`, `'`, `(`, `)`.
|
|
18
|
+
* Generate a random state parameter for CSRF protection.
|
|
24
19
|
*/
|
|
25
|
-
export function
|
|
26
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
|
50
|
-
const
|
|
51
|
-
return
|
|
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
|
|
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
|
|
57
|
-
const
|
|
58
|
-
.
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
*
|
|
58
|
+
* Exchange an authorization code for access and refresh tokens.
|
|
64
59
|
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
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
|
|
63
|
+
* @returns The token response including `access_token`, `refresh_token`,
|
|
64
|
+
* `expires_in`, and `refresh_token_expires_in`.
|
|
71
65
|
*/
|
|
72
|
-
export async function
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
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
|
-
|
|
92
|
-
},
|
|
79
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
80
|
+
body: body.toString(),
|
|
93
81
|
});
|
|
94
82
|
if (!response.ok) {
|
|
95
|
-
const
|
|
96
|
-
throw new Error(`Garmin OAuth error (
|
|
83
|
+
const text = await response.text().catch(() => "");
|
|
84
|
+
throw new Error(`Garmin OAuth error (exchangeCode): ${response.status} ${response.statusText} — ${text}`);
|
|
97
85
|
}
|
|
98
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
140
|
-
},
|
|
106
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
107
|
+
body: body.toString(),
|
|
141
108
|
});
|
|
142
109
|
if (!response.ok) {
|
|
143
|
-
const
|
|
144
|
-
throw new Error(`Garmin OAuth error (
|
|
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
|
|
113
|
+
return (await response.json());
|
|
154
114
|
}
|
|
155
115
|
//# sourceMappingURL=auth.js.map
|
package/dist/garmin/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/garmin/auth.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/garmin/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/garmin/client.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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":"
|
|
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"}
|
package/dist/garmin/client.js
CHANGED
|
@@ -1,23 +1,18 @@
|
|
|
1
1
|
// ─── Garmin Health API Client ────────────────────────────────────────────────
|
|
2
2
|
// Lightweight, fetch-based client for the Garmin Health API.
|
|
3
|
-
//
|
|
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
|
|
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
|
-
*
|
|
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:
|
|
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":"AAAA,gFAAgF;AAChF,6DAA6D;AAC7D,
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/garmin/client.ts"],"names":[],"mappings":"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"}
|