@torqlab/strava-api 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +167 -0
- package/dist/client/client.d.ts +34 -0
- package/dist/client/client.d.ts.map +1 -0
- package/dist/client/create-error/create-error.d.ts +13 -0
- package/dist/client/create-error/create-error.d.ts.map +1 -0
- package/dist/client/create-error/index.d.ts +2 -0
- package/dist/client/create-error/index.d.ts.map +1 -0
- package/dist/client/get-auth-headers/get-auth-headers.d.ts +22 -0
- package/dist/client/get-auth-headers/get-auth-headers.d.ts.map +1 -0
- package/dist/client/get-auth-headers/index.d.ts +2 -0
- package/dist/client/get-auth-headers/index.d.ts.map +1 -0
- package/dist/client/handle-fetch-error/handle-fetch-error.d.ts +19 -0
- package/dist/client/handle-fetch-error/handle-fetch-error.d.ts.map +1 -0
- package/dist/client/handle-fetch-error/index.d.ts +2 -0
- package/dist/client/handle-fetch-error/index.d.ts.map +1 -0
- package/dist/client/handle-rate-limit/constants.d.ts +3 -0
- package/dist/client/handle-rate-limit/constants.d.ts.map +1 -0
- package/dist/client/handle-rate-limit/handle-rate-limit.d.ts +32 -0
- package/dist/client/handle-rate-limit/handle-rate-limit.d.ts.map +1 -0
- package/dist/client/handle-rate-limit/index.d.ts +2 -0
- package/dist/client/handle-rate-limit/index.d.ts.map +1 -0
- package/dist/client/handle-retry/handle-retry.d.ts +30 -0
- package/dist/client/handle-retry/handle-retry.d.ts.map +1 -0
- package/dist/client/handle-retry/index.d.ts +2 -0
- package/dist/client/handle-retry/index.d.ts.map +1 -0
- package/dist/client/handle-retry/types.d.ts +7 -0
- package/dist/client/handle-retry/types.d.ts.map +1 -0
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/parse-error/index.d.ts +2 -0
- package/dist/client/parse-error/index.d.ts.map +1 -0
- package/dist/client/parse-error/parse-error.d.ts +14 -0
- package/dist/client/parse-error/parse-error.d.ts.map +1 -0
- package/dist/client/parse-error-code/index.d.ts +2 -0
- package/dist/client/parse-error-code/index.d.ts.map +1 -0
- package/dist/client/parse-error-code/parse-error-code.d.ts +12 -0
- package/dist/client/parse-error-code/parse-error-code.d.ts.map +1 -0
- package/dist/client/parse-response/index.d.ts +2 -0
- package/dist/client/parse-response/index.d.ts.map +1 -0
- package/dist/client/parse-response/parse-response.d.ts +30 -0
- package/dist/client/parse-response/parse-response.d.ts.map +1 -0
- package/dist/constants.d.ts +35 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/exchange-token/exchange-token.d.ts +36 -0
- package/dist/exchange-token/exchange-token.d.ts.map +1 -0
- package/dist/exchange-token/index.d.ts +2 -0
- package/dist/exchange-token/index.d.ts.map +1 -0
- package/dist/fetch-activities/fetch-activities.d.ts +39 -0
- package/dist/fetch-activities/fetch-activities.d.ts.map +1 -0
- package/dist/fetch-activities/index.d.ts +2 -0
- package/dist/fetch-activities/index.d.ts.map +1 -0
- package/dist/fetch-activity/fetch-activity.d.ts +49 -0
- package/dist/fetch-activity/fetch-activity.d.ts.map +1 -0
- package/dist/fetch-activity/index.d.ts +2 -0
- package/dist/fetch-activity/index.d.ts.map +1 -0
- package/dist/get-auth-url/get-auth-url.d.ts +30 -0
- package/dist/get-auth-url/get-auth-url.d.ts.map +1 -0
- package/dist/get-auth-url/index.d.ts +2 -0
- package/dist/get-auth-url/index.d.ts.map +1 -0
- package/dist/index.cjs +473 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +431 -0
- package/dist/refresh-token/index.d.ts +2 -0
- package/dist/refresh-token/index.d.ts.map +1 -0
- package/dist/refresh-token/refresh-token.d.ts +34 -0
- package/dist/refresh-token/refresh-token.d.ts.map +1 -0
- package/dist/refresh-token/types.d.ts +5 -0
- package/dist/refresh-token/types.d.ts.map +1 -0
- package/dist/types.d.ts +192 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/validators/index.d.ts +2 -0
- package/dist/validators/index.d.ts.map +1 -0
- package/dist/validators/validate-activity-id/index.d.ts +2 -0
- package/dist/validators/validate-activity-id/index.d.ts.map +1 -0
- package/dist/validators/validate-activity-id/validate-activity-id.d.ts +22 -0
- package/dist/validators/validate-activity-id/validate-activity-id.d.ts.map +1 -0
- package/package.json +69 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
// constants.ts
|
|
2
|
+
var STRAVA_API_INITIAL_BACKOFF_MS = 1000;
|
|
3
|
+
var STRAVA_API_MAX_BACKOFF_MS = 16000;
|
|
4
|
+
var STRAVA_API_MAX_RETRIES = 3;
|
|
5
|
+
var STRAVA_AUTH_DEFAULT_SCOPE = "activity:read";
|
|
6
|
+
var STRAVA_OAUTH_BASE_URL = "https://www.strava.com/oauth";
|
|
7
|
+
var STRAVA_API_BASE_URL = "https://www.strava.com/api/v3";
|
|
8
|
+
var STRAVA_API_ENDPOINTS = {
|
|
9
|
+
TOKEN: `${STRAVA_OAUTH_BASE_URL}/token`,
|
|
10
|
+
AUTH: `${STRAVA_OAUTH_BASE_URL}/authorize`,
|
|
11
|
+
ACTIVITIES: `${STRAVA_API_BASE_URL}/athlete/activities`,
|
|
12
|
+
ACTIVITY: `${STRAVA_API_BASE_URL}/activities`
|
|
13
|
+
};
|
|
14
|
+
var STRAVA_API_ERROR_CODES = {
|
|
15
|
+
UNAUTHORIZED: "UNAUTHORIZED",
|
|
16
|
+
FORBIDDEN: "FORBIDDEN",
|
|
17
|
+
RATE_LIMITED: "RATE_LIMITED",
|
|
18
|
+
SERVER_ERROR: "SERVER_ERROR",
|
|
19
|
+
MALFORMED_RESPONSE: "MALFORMED_RESPONSE",
|
|
20
|
+
INVALID_ID: "INVALID_ID",
|
|
21
|
+
NOT_FOUND: "NOT_FOUND",
|
|
22
|
+
NETWORK_ERROR: "NETWORK_ERROR",
|
|
23
|
+
VALIDATION_FAILED: "VALIDATION_FAILED",
|
|
24
|
+
INVALID_CONFIG: "INVALID_CONFIG",
|
|
25
|
+
INVALID_CODE: "INVALID_CODE"
|
|
26
|
+
};
|
|
27
|
+
var STRAVA_API_STATUS_CODES = {
|
|
28
|
+
UNAUTHORIZED: 401,
|
|
29
|
+
FORBIDDEN: 403,
|
|
30
|
+
RATE_LIMITED: 429,
|
|
31
|
+
SERVER_ERROR: 500
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// validators/validate-activity-id/validate-activity-id.ts
|
|
35
|
+
var validateActivityId = (activityId) => {
|
|
36
|
+
const trimmedActivityId = String(activityId).trim();
|
|
37
|
+
const numericActivityId = Number(trimmedActivityId);
|
|
38
|
+
const isValidNumericActivityId = !Number.isNaN(numericActivityId) && Number.isFinite(numericActivityId) && numericActivityId > 0;
|
|
39
|
+
if (!trimmedActivityId) {
|
|
40
|
+
const error = {
|
|
41
|
+
code: "INVALID_ID",
|
|
42
|
+
message: "Activity ID is required and cannot be empty",
|
|
43
|
+
retryable: false
|
|
44
|
+
};
|
|
45
|
+
throw new Error(JSON.stringify(error));
|
|
46
|
+
} else if (!isValidNumericActivityId) {
|
|
47
|
+
const error = {
|
|
48
|
+
code: "INVALID_ID",
|
|
49
|
+
message: "Activity ID must be a valid positive number",
|
|
50
|
+
retryable: false
|
|
51
|
+
};
|
|
52
|
+
throw new Error(JSON.stringify(error));
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var validate_activity_id_default = validateActivityId;
|
|
56
|
+
// client/get-auth-headers/get-auth-headers.ts
|
|
57
|
+
var getAuthHeaders = (config) => {
|
|
58
|
+
const headers = {
|
|
59
|
+
Authorization: `Bearer ${config.accessToken}`
|
|
60
|
+
};
|
|
61
|
+
return headers;
|
|
62
|
+
};
|
|
63
|
+
var get_auth_headers_default = getAuthHeaders;
|
|
64
|
+
// client/create-error/create-error.ts
|
|
65
|
+
var createError = (code, message, retryable = false, response) => {
|
|
66
|
+
if (response) {
|
|
67
|
+
const error = {
|
|
68
|
+
code,
|
|
69
|
+
message,
|
|
70
|
+
retryable
|
|
71
|
+
};
|
|
72
|
+
return new Error(JSON.stringify({ ...error, response }));
|
|
73
|
+
} else {
|
|
74
|
+
const error = {
|
|
75
|
+
code,
|
|
76
|
+
message,
|
|
77
|
+
retryable
|
|
78
|
+
};
|
|
79
|
+
return new Error(JSON.stringify(error));
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
var create_error_default = createError;
|
|
83
|
+
// client/handle-rate-limit/constants.ts
|
|
84
|
+
var DEFAULT_WAIT_MS = 60 * 1000;
|
|
85
|
+
var WINDOW_MS = 15 * 60 * 1000;
|
|
86
|
+
|
|
87
|
+
// client/handle-rate-limit/handle-rate-limit.ts
|
|
88
|
+
var handleRateLimit = async (response) => {
|
|
89
|
+
const retryAfterHeader = response.headers.get("Retry-After");
|
|
90
|
+
const retryAfterSeconds = retryAfterHeader ? Number.parseFloat(retryAfterHeader) : NaN;
|
|
91
|
+
const shouldWaitAndRetry = !Number.isNaN(retryAfterSeconds) && Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0;
|
|
92
|
+
if (shouldWaitAndRetry) {
|
|
93
|
+
const waitMs = Math.ceil(retryAfterSeconds * 1000);
|
|
94
|
+
await new Promise((resolve) => {
|
|
95
|
+
setTimeout(resolve, waitMs);
|
|
96
|
+
});
|
|
97
|
+
} else {
|
|
98
|
+
const rateLimitLimit = response.headers.get("X-RateLimit-Limit");
|
|
99
|
+
const rateLimitUsage = response.headers.get("X-RateLimit-Usage");
|
|
100
|
+
const limit = rateLimitLimit ? Number.parseInt(rateLimitLimit, 10) : NaN;
|
|
101
|
+
const usage = rateLimitUsage ? Number.parseInt(rateLimitUsage, 10) : NaN;
|
|
102
|
+
const shouldWaitInWindow = !Number.isNaN(limit) && !Number.isNaN(usage) && Number.isFinite(limit) && Number.isFinite(usage) && usage >= limit;
|
|
103
|
+
if (shouldWaitInWindow) {
|
|
104
|
+
await new Promise((resolve) => {
|
|
105
|
+
setTimeout(resolve, WINDOW_MS);
|
|
106
|
+
});
|
|
107
|
+
} else {
|
|
108
|
+
await new Promise((resolve) => {
|
|
109
|
+
setTimeout(resolve, DEFAULT_WAIT_MS);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
var handle_rate_limit_default = handleRateLimit;
|
|
115
|
+
// client/parse-error/parse-error.ts
|
|
116
|
+
var parseError = (error) => {
|
|
117
|
+
try {
|
|
118
|
+
return JSON.parse(error.message);
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
var parse_error_default = parseError;
|
|
124
|
+
|
|
125
|
+
// client/parse-error-code/parse-error-code.ts
|
|
126
|
+
var parseErrorCode = (error) => {
|
|
127
|
+
try {
|
|
128
|
+
const errorParsed = parse_error_default(error);
|
|
129
|
+
return errorParsed?.code || null;
|
|
130
|
+
} catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
var parse_error_code_default = parseErrorCode;
|
|
135
|
+
// refresh-token/refresh-token.ts
|
|
136
|
+
var createError2 = (code, message) => {
|
|
137
|
+
const error = {
|
|
138
|
+
retryable: false,
|
|
139
|
+
code,
|
|
140
|
+
message
|
|
141
|
+
};
|
|
142
|
+
return new Error(JSON.stringify(error));
|
|
143
|
+
};
|
|
144
|
+
var doRefreshToken = async (body) => {
|
|
145
|
+
try {
|
|
146
|
+
return await fetch(STRAVA_API_ENDPOINTS.TOKEN, {
|
|
147
|
+
method: "POST",
|
|
148
|
+
body: body.toString(),
|
|
149
|
+
headers: {
|
|
150
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
} catch {
|
|
154
|
+
throw createError2("NETWORK_ERROR", "Failed to connect to Strava OAuth endpoint");
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
var parseApiResponse = async (response) => {
|
|
158
|
+
try {
|
|
159
|
+
return await response.json();
|
|
160
|
+
} catch {
|
|
161
|
+
throw createError2("MALFORMED_RESPONSE", "Invalid response format from token refresh endpoint");
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
var refreshToken = async (config) => {
|
|
165
|
+
if (!config.refreshToken) {
|
|
166
|
+
throw createError2("UNAUTHORIZED", "Refresh token is not available");
|
|
167
|
+
} else if (!config.clientId || !config.clientSecret) {
|
|
168
|
+
throw createError2("UNAUTHORIZED", "Client ID and client secret are required for token refresh");
|
|
169
|
+
} else {
|
|
170
|
+
const body = new URLSearchParams({
|
|
171
|
+
client_id: config.clientId,
|
|
172
|
+
client_secret: config.clientSecret,
|
|
173
|
+
grant_type: "refresh_token",
|
|
174
|
+
refresh_token: config.refreshToken
|
|
175
|
+
});
|
|
176
|
+
const response = await doRefreshToken(body);
|
|
177
|
+
if (!response.ok) {
|
|
178
|
+
throw createError2("UNAUTHORIZED", "Token refresh failed");
|
|
179
|
+
} else {
|
|
180
|
+
const jsonData = await parseApiResponse(response.clone());
|
|
181
|
+
if (jsonData.access_token && jsonData.refresh_token) {
|
|
182
|
+
return {
|
|
183
|
+
access_token: jsonData.access_token,
|
|
184
|
+
refresh_token: jsonData.refresh_token
|
|
185
|
+
};
|
|
186
|
+
} else {
|
|
187
|
+
throw createError2("MALFORMED_RESPONSE", "Access token not found in refresh response");
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
var refresh_token_default = refreshToken;
|
|
193
|
+
// client/handle-fetch-error/handle-fetch-error.ts
|
|
194
|
+
var handleFetchError = async (fn, config, error) => {
|
|
195
|
+
const errorCode = parse_error_code_default(error);
|
|
196
|
+
const isRateLimitedError = errorCode === STRAVA_API_ERROR_CODES.RATE_LIMITED;
|
|
197
|
+
const isUnauthorizedError = errorCode === STRAVA_API_ERROR_CODES.UNAUTHORIZED;
|
|
198
|
+
if (isRateLimitedError) {
|
|
199
|
+
const errorWithResponse = error;
|
|
200
|
+
const rateLimitedResponse = errorWithResponse.response ?? new Response("Rate Limited", {
|
|
201
|
+
status: STRAVA_API_STATUS_CODES.RATE_LIMITED,
|
|
202
|
+
headers: { "Retry-After": "60" }
|
|
203
|
+
});
|
|
204
|
+
await handle_rate_limit_default(rateLimitedResponse);
|
|
205
|
+
throw error;
|
|
206
|
+
} else if (isUnauthorizedError) {
|
|
207
|
+
const canRefreshToken = Boolean(config.refreshToken) && Boolean(config.clientId) && Boolean(config.clientSecret);
|
|
208
|
+
if (canRefreshToken) {
|
|
209
|
+
try {
|
|
210
|
+
const { access_token } = await refresh_token_default(config);
|
|
211
|
+
const refreshedConfig = {
|
|
212
|
+
...config,
|
|
213
|
+
accessToken: access_token
|
|
214
|
+
};
|
|
215
|
+
return await fn(refreshedConfig);
|
|
216
|
+
} catch {
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
} else {
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
var handle_fetch_error_default = handleFetchError;
|
|
227
|
+
// client/parse-response/parse-response.ts
|
|
228
|
+
var parseApiResponse2 = async (response) => {
|
|
229
|
+
try {
|
|
230
|
+
const data = await response.json();
|
|
231
|
+
if (!data) {
|
|
232
|
+
throw create_error_default("MALFORMED_RESPONSE", "Expected non-empty response from Strava API", false);
|
|
233
|
+
} else {
|
|
234
|
+
return data;
|
|
235
|
+
}
|
|
236
|
+
} catch (error) {
|
|
237
|
+
const parsedError = parse_error_default(error);
|
|
238
|
+
if (parsedError) {
|
|
239
|
+
throw create_error_default("MALFORMED_RESPONSE", "Invalid response format from Strava API", false);
|
|
240
|
+
} else {
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
var parseResponse = async (response) => {
|
|
246
|
+
const isUnauthorized = response.status === STRAVA_API_STATUS_CODES.UNAUTHORIZED;
|
|
247
|
+
const isForbidden = response.status === STRAVA_API_STATUS_CODES.FORBIDDEN;
|
|
248
|
+
const isRateLimited = response.status === STRAVA_API_STATUS_CODES.RATE_LIMITED;
|
|
249
|
+
const isServerError = response.status >= STRAVA_API_STATUS_CODES.SERVER_ERROR;
|
|
250
|
+
if (isUnauthorized) {
|
|
251
|
+
throw create_error_default("UNAUTHORIZED", "Authentication failed. Token may be expired or invalid.", false);
|
|
252
|
+
} else if (isForbidden) {
|
|
253
|
+
throw create_error_default("FORBIDDEN", "Insufficient permissions to access Strava API", false);
|
|
254
|
+
} else if (isRateLimited) {
|
|
255
|
+
throw create_error_default("RATE_LIMITED", "Rate limit exceeded. Please try again later.", true, response);
|
|
256
|
+
} else if (isServerError) {
|
|
257
|
+
throw create_error_default("SERVER_ERROR", "Strava API server error", true);
|
|
258
|
+
} else if (!response.ok) {
|
|
259
|
+
throw create_error_default("SERVER_ERROR", `Unexpected API error: ${response.status}`, false);
|
|
260
|
+
} else {
|
|
261
|
+
return parseApiResponse2(response);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
var parse_response_default = parseResponse;
|
|
265
|
+
// client/client.ts
|
|
266
|
+
var doFetch = async (url, config) => {
|
|
267
|
+
try {
|
|
268
|
+
const response = await fetch(url, {
|
|
269
|
+
method: "GET",
|
|
270
|
+
headers: get_auth_headers_default(config)
|
|
271
|
+
});
|
|
272
|
+
return parse_response_default(response);
|
|
273
|
+
} catch {
|
|
274
|
+
throw create_error_default("NETWORK_ERROR", "Failed to connect to Strava API", true);
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
var client = async (url, config) => {
|
|
278
|
+
try {
|
|
279
|
+
return await doFetch(url, config);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
return await handle_fetch_error_default((newConfig) => doFetch(url, newConfig), config, error);
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
var client_default = client;
|
|
285
|
+
// client/handle-retry/handle-retry.ts
|
|
286
|
+
var parseError2 = (error) => {
|
|
287
|
+
try {
|
|
288
|
+
return JSON.parse(error.message);
|
|
289
|
+
} catch {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
var attemptWithBackoff = async (fn, attemptIndex, maxRetries, currentBackoffMs, _previousError) => {
|
|
294
|
+
try {
|
|
295
|
+
return await fn();
|
|
296
|
+
} catch (error) {
|
|
297
|
+
const currentError = error;
|
|
298
|
+
const activityError = parseError2(currentError);
|
|
299
|
+
if (activityError !== null && activityError.retryable === false) {
|
|
300
|
+
throw currentError;
|
|
301
|
+
} else {
|
|
302
|
+
const isLastAttempt = attemptIndex >= maxRetries;
|
|
303
|
+
if (isLastAttempt) {
|
|
304
|
+
throw currentError;
|
|
305
|
+
} else {
|
|
306
|
+
const delayMs = Math.min(currentBackoffMs, STRAVA_API_MAX_BACKOFF_MS);
|
|
307
|
+
await new Promise((resolve) => {
|
|
308
|
+
setTimeout(resolve, delayMs);
|
|
309
|
+
});
|
|
310
|
+
const nextBackoffMs = currentBackoffMs * 2;
|
|
311
|
+
const nextAttemptIndex = attemptIndex + 1;
|
|
312
|
+
return attemptWithBackoff(fn, nextAttemptIndex, maxRetries, nextBackoffMs, currentError);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
var handleRetry = async ({
|
|
318
|
+
fn,
|
|
319
|
+
maxRetries,
|
|
320
|
+
initialBackoffMs = STRAVA_API_INITIAL_BACKOFF_MS
|
|
321
|
+
}) => attemptWithBackoff(fn, 0, maxRetries, initialBackoffMs, null);
|
|
322
|
+
var handle_retry_default = handleRetry;
|
|
323
|
+
// fetch-activity/fetch-activity.ts
|
|
324
|
+
var fetchActivityWithValidation = async (config, activityId) => {
|
|
325
|
+
const activity = await client_default(`/activities/${activityId}`, config);
|
|
326
|
+
if (config.guardrails) {
|
|
327
|
+
const validationResult = activity ? config.guardrails.validate(activity) : { valid: false, errors: ["Activity is empty"] };
|
|
328
|
+
if (!validationResult.valid) {
|
|
329
|
+
const error = create_error_default("VALIDATION_FAILED", validationResult.errors?.join(", ") ?? "Activity validation failed", false);
|
|
330
|
+
throw new Error(JSON.stringify(error));
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return activity;
|
|
334
|
+
};
|
|
335
|
+
var fetchActivity = async (activityId, config) => {
|
|
336
|
+
validate_activity_id_default(activityId);
|
|
337
|
+
return handle_retry_default({
|
|
338
|
+
maxRetries: STRAVA_API_MAX_RETRIES,
|
|
339
|
+
initialBackoffMs: STRAVA_API_INITIAL_BACKOFF_MS,
|
|
340
|
+
fn: async () => fetchActivityWithValidation(config, activityId)
|
|
341
|
+
});
|
|
342
|
+
};
|
|
343
|
+
var fetch_activity_default = fetchActivity;
|
|
344
|
+
// fetch-activities/fetch-activities.ts
|
|
345
|
+
var fetchActivities = async (config) => handle_retry_default({
|
|
346
|
+
maxRetries: STRAVA_API_MAX_RETRIES,
|
|
347
|
+
initialBackoffMs: STRAVA_API_INITIAL_BACKOFF_MS,
|
|
348
|
+
fn: () => client_default(STRAVA_API_ENDPOINTS.ACTIVITIES, config)
|
|
349
|
+
});
|
|
350
|
+
var fetch_activities_default = fetchActivities;
|
|
351
|
+
// get-auth-url/get-auth-url.ts
|
|
352
|
+
var getAuthUrl = (config) => {
|
|
353
|
+
if (!config.clientId) {
|
|
354
|
+
throw create_error_default("INVALID_CONFIG", "Client ID is required");
|
|
355
|
+
} else if (!config.redirectUri) {
|
|
356
|
+
throw create_error_default("INVALID_CONFIG", "Redirect URI is required");
|
|
357
|
+
} else {
|
|
358
|
+
const scope = config.scope ?? STRAVA_AUTH_DEFAULT_SCOPE;
|
|
359
|
+
const params = new URLSearchParams({
|
|
360
|
+
client_id: config.clientId,
|
|
361
|
+
response_type: "code",
|
|
362
|
+
redirect_uri: config.redirectUri,
|
|
363
|
+
scope,
|
|
364
|
+
approval_prompt: "force"
|
|
365
|
+
});
|
|
366
|
+
return `${STRAVA_API_ENDPOINTS.AUTH}?${params.toString()}`;
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
var get_auth_url_default = getAuthUrl;
|
|
370
|
+
// exchange-token/exchange-token.ts
|
|
371
|
+
var fetchTokenResponse = async (body) => {
|
|
372
|
+
try {
|
|
373
|
+
return await fetch(STRAVA_API_ENDPOINTS.TOKEN, {
|
|
374
|
+
method: "POST",
|
|
375
|
+
body: body.toString(),
|
|
376
|
+
headers: {
|
|
377
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
} catch {
|
|
381
|
+
throw create_error_default("NETWORK_ERROR", "Failed to connect to Strava OAuth endpoint");
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
var parseTokenResponse = async (response) => {
|
|
385
|
+
try {
|
|
386
|
+
return await response.json();
|
|
387
|
+
} catch {
|
|
388
|
+
throw create_error_default("MALFORMED_RESPONSE", "Invalid response format from token endpoint");
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
var exchangeToken = async (authorizationCode, config) => {
|
|
392
|
+
if (!config.clientId || !config.clientSecret) {
|
|
393
|
+
throw create_error_default("INVALID_CONFIG", "Client ID and client secret are required");
|
|
394
|
+
} else if (!config.redirectUri) {
|
|
395
|
+
throw create_error_default("INVALID_CONFIG", "Redirect URI is required");
|
|
396
|
+
} else if (!authorizationCode) {
|
|
397
|
+
throw create_error_default("INVALID_CODE", "Authorization code is required");
|
|
398
|
+
} else {
|
|
399
|
+
const body = new URLSearchParams({
|
|
400
|
+
client_id: config.clientId,
|
|
401
|
+
client_secret: config.clientSecret,
|
|
402
|
+
code: authorizationCode,
|
|
403
|
+
grant_type: "authorization_code"
|
|
404
|
+
});
|
|
405
|
+
const response = await fetchTokenResponse(body);
|
|
406
|
+
if (response.ok) {
|
|
407
|
+
const tokenData = await parseTokenResponse(response.clone());
|
|
408
|
+
if (!tokenData.access_token) {
|
|
409
|
+
throw create_error_default("MALFORMED_RESPONSE", "Access token not found in response");
|
|
410
|
+
} else if (!tokenData.refresh_token) {
|
|
411
|
+
throw create_error_default("MALFORMED_RESPONSE", "Refresh token not found in response");
|
|
412
|
+
} else {
|
|
413
|
+
return tokenData;
|
|
414
|
+
}
|
|
415
|
+
} else {
|
|
416
|
+
if (response.status === 401 || response.status === 403) {
|
|
417
|
+
throw create_error_default("UNAUTHORIZED", "Invalid client credentials or authorization code");
|
|
418
|
+
} else {
|
|
419
|
+
throw create_error_default("UNAUTHORIZED", `Token exchange failed with status ${response.status}`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
var exchange_token_default = exchangeToken;
|
|
425
|
+
export {
|
|
426
|
+
refresh_token_default as refreshStravaAuthToken,
|
|
427
|
+
get_auth_url_default as getStravaAuthUrl,
|
|
428
|
+
fetch_activity_default as fetchStravaActivity,
|
|
429
|
+
fetch_activities_default as fetchStravaActivities,
|
|
430
|
+
exchange_token_default as exchangeStravaAuthToken
|
|
431
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../refresh-token/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { StravaApiConfig } from '../types';
|
|
2
|
+
import type { Output } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Refreshes an expired OAuth2 access token using refresh token.
|
|
5
|
+
*
|
|
6
|
+
* Calls the Strava OAuth token refresh endpoint to obtain a new access token
|
|
7
|
+
* when the current one has expired. Requires refresh token, client ID, and
|
|
8
|
+
* client secret to be present in the configuration.
|
|
9
|
+
*
|
|
10
|
+
* @param {StravaApiConfig} config - API configuration.
|
|
11
|
+
* @returns {Promise<string>} Promise resolving to the new access token.
|
|
12
|
+
* @throws {Error} Throws an error for various failure scenarios:
|
|
13
|
+
* - 'UNAUTHORIZED' (not retryable): Refresh token missing or invalid credentials
|
|
14
|
+
* - 'NETWORK_ERROR' (not retryable): Network connection failure
|
|
15
|
+
* - 'UNAUTHORIZED' (not retryable): Token refresh request failed
|
|
16
|
+
* - 'MALFORMED_RESPONSE' (not retryable): Invalid JSON response or missing access_token
|
|
17
|
+
* - And others...
|
|
18
|
+
*
|
|
19
|
+
* @see {@link https://developers.strava.com/docs/authentication/#refreshingexpiredaccesstokens | Strava Token Refresh}
|
|
20
|
+
* @see {@link https://www.oauth.com/oauth2-servers/access-tokens/refreshing-access-tokens/ | OAuth2 Token Refresh}
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* const newToken = await refreshToken({
|
|
25
|
+
* accessToken: 'expired-token',
|
|
26
|
+
* refreshToken: 'refresh-token-123',
|
|
27
|
+
* clientId: 'client-id',
|
|
28
|
+
* clientSecret: 'client-secret'
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
declare const refreshToken: (config: StravaApiConfig) => Promise<Output>;
|
|
33
|
+
export default refreshToken;
|
|
34
|
+
//# sourceMappingURL=refresh-token.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresh-token.d.ts","sourceRoot":"","sources":["../../refresh-token/refresh-token.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EAIhB,MAAM,UAAU,CAAC;AAElB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAuDtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,QAAA,MAAM,YAAY,GAAU,QAAQ,eAAe,KAAG,OAAO,CAAC,MAAM,CA8BnE,CAAC;AAEF,eAAe,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../refresh-token/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,MAAM;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;CACvB"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { STRAVA_API_ERROR_CODES } from './constants';
|
|
2
|
+
export type StravaApiErrorCode = keyof typeof STRAVA_API_ERROR_CODES;
|
|
3
|
+
export interface StravaApiError {
|
|
4
|
+
code: StravaApiErrorCode;
|
|
5
|
+
message: string;
|
|
6
|
+
retryable?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface StravaApiErrorWithResponse extends Error {
|
|
9
|
+
response?: Response;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Entity validation result from Guardrails.
|
|
13
|
+
*/
|
|
14
|
+
export interface StravaApiConfigGuardrailsValidationResult {
|
|
15
|
+
/** Whether the entity is valid. */
|
|
16
|
+
valid: boolean;
|
|
17
|
+
/** Validation errors if any. */
|
|
18
|
+
errors?: string[];
|
|
19
|
+
}
|
|
20
|
+
export interface StravaApiConfig {
|
|
21
|
+
/** OAuth2 access token for Strava API. */
|
|
22
|
+
accessToken: string;
|
|
23
|
+
/** OAuth2 refresh token for token refresh. */
|
|
24
|
+
refreshToken?: string;
|
|
25
|
+
/** OAuth2 client ID for token refresh. */
|
|
26
|
+
clientId?: string;
|
|
27
|
+
/** OAuth2 client secret for token refresh. */
|
|
28
|
+
clientSecret?: string;
|
|
29
|
+
/** Guardrails instance for validation (dependency injection). */
|
|
30
|
+
guardrails?: {
|
|
31
|
+
/**
|
|
32
|
+
* Validates an entity and returns validation result.
|
|
33
|
+
* @param {object} input - The entity to validate
|
|
34
|
+
* @returns {StravaApiConfigGuardrailsValidationResult} Validation result
|
|
35
|
+
*/
|
|
36
|
+
validate: (input: StravaActivity) => StravaApiConfigGuardrailsValidationResult;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export interface StravaApiTokenRefreshResponse {
|
|
40
|
+
access_token?: string;
|
|
41
|
+
refresh_token?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Strava Activity type.
|
|
45
|
+
* The type is dictated by the Strava API and used internally by the system.
|
|
46
|
+
* @see {@link https://developers.strava.com/docs/reference/#api-Activities-getActivityById | Strava Activity Response Format}
|
|
47
|
+
*/
|
|
48
|
+
export interface StravaActivity {
|
|
49
|
+
id: number;
|
|
50
|
+
resource_state?: number;
|
|
51
|
+
external_id?: string;
|
|
52
|
+
upload_id?: number;
|
|
53
|
+
athlete?: {
|
|
54
|
+
id: number;
|
|
55
|
+
resource_state?: number;
|
|
56
|
+
};
|
|
57
|
+
name?: string;
|
|
58
|
+
distance?: number;
|
|
59
|
+
moving_time?: number;
|
|
60
|
+
elapsed_time?: number;
|
|
61
|
+
total_elevation_gain?: number;
|
|
62
|
+
type: string;
|
|
63
|
+
sport_type: string;
|
|
64
|
+
start_date?: string;
|
|
65
|
+
start_date_local?: string;
|
|
66
|
+
timezone?: string;
|
|
67
|
+
utc_offset?: number;
|
|
68
|
+
start_latlng?: [number, number];
|
|
69
|
+
end_latlng?: [number, number];
|
|
70
|
+
achievement_count?: number;
|
|
71
|
+
kudos_count?: number;
|
|
72
|
+
comment_count?: number;
|
|
73
|
+
athlete_count?: number;
|
|
74
|
+
photo_count?: number;
|
|
75
|
+
map?: {
|
|
76
|
+
id?: string;
|
|
77
|
+
polyline?: string;
|
|
78
|
+
resource_state?: number;
|
|
79
|
+
summary_polyline?: string;
|
|
80
|
+
};
|
|
81
|
+
trainer?: boolean;
|
|
82
|
+
commute?: boolean;
|
|
83
|
+
manual?: boolean;
|
|
84
|
+
private?: boolean;
|
|
85
|
+
flagged?: boolean;
|
|
86
|
+
gear_id?: string;
|
|
87
|
+
from_accepted_tag?: boolean;
|
|
88
|
+
average_speed?: number;
|
|
89
|
+
max_speed?: number;
|
|
90
|
+
average_cadence?: number;
|
|
91
|
+
average_temp?: number;
|
|
92
|
+
average_watts?: number;
|
|
93
|
+
weighted_average_watts?: number;
|
|
94
|
+
kilojoules?: number;
|
|
95
|
+
device_watts?: boolean;
|
|
96
|
+
has_heartrate?: boolean;
|
|
97
|
+
max_watts?: number;
|
|
98
|
+
elev_high?: number;
|
|
99
|
+
elev_low?: number;
|
|
100
|
+
pr_count?: number;
|
|
101
|
+
total_photo_count?: number;
|
|
102
|
+
has_kudoed?: boolean;
|
|
103
|
+
workout_type?: number;
|
|
104
|
+
suffer_score?: number | null;
|
|
105
|
+
description?: string;
|
|
106
|
+
calories?: number;
|
|
107
|
+
segment_efforts?: Record<string, unknown>[];
|
|
108
|
+
splits_metric?: Record<string, unknown>[];
|
|
109
|
+
laps?: Record<string, unknown>[];
|
|
110
|
+
gear?: {
|
|
111
|
+
id?: string;
|
|
112
|
+
primary?: boolean;
|
|
113
|
+
name?: string;
|
|
114
|
+
resource_state?: number;
|
|
115
|
+
distance?: number;
|
|
116
|
+
};
|
|
117
|
+
partner_brand_tag?: string | null;
|
|
118
|
+
photos?: Record<string, unknown>;
|
|
119
|
+
highlighted_kudosers?: Record<string, unknown>[];
|
|
120
|
+
hide_from_home?: boolean;
|
|
121
|
+
device_name?: string;
|
|
122
|
+
embed_token?: string;
|
|
123
|
+
segment_leaderboard_opt_out?: boolean;
|
|
124
|
+
leaderboard_opt_out?: boolean;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* OAuth2 authorization configuration for Strava API.
|
|
128
|
+
*/
|
|
129
|
+
export interface StravaAuthConfig {
|
|
130
|
+
/** OAuth2 client ID from Strava application. */
|
|
131
|
+
clientId: string;
|
|
132
|
+
/** OAuth2 client secret from Strava application. */
|
|
133
|
+
clientSecret: string;
|
|
134
|
+
/** OAuth2 redirect URI (must match application settings). */
|
|
135
|
+
redirectUri: string;
|
|
136
|
+
/** OAuth2 scopes (e.g., 'activity:read' or 'activity:read_all'). */
|
|
137
|
+
scope?: string;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Token exchange response from Strava OAuth endpoint.
|
|
141
|
+
*/
|
|
142
|
+
export interface StravaAuthTokenResponse {
|
|
143
|
+
/** OAuth2 access token. */
|
|
144
|
+
access_token: string;
|
|
145
|
+
/** OAuth2 refresh token. */
|
|
146
|
+
refresh_token: string;
|
|
147
|
+
/** Token expiration timestamp (Unix time). */
|
|
148
|
+
expires_at: number;
|
|
149
|
+
/** Token expiration time in seconds. */
|
|
150
|
+
expires_in: number;
|
|
151
|
+
/** Token type (typically 'Bearer'). */
|
|
152
|
+
token_type: string;
|
|
153
|
+
/** Athlete information. */
|
|
154
|
+
athlete?: {
|
|
155
|
+
id: number;
|
|
156
|
+
username?: string;
|
|
157
|
+
resource_state?: number;
|
|
158
|
+
firstname?: string;
|
|
159
|
+
lastname?: string;
|
|
160
|
+
bio?: string;
|
|
161
|
+
city?: string;
|
|
162
|
+
state?: string;
|
|
163
|
+
country?: string;
|
|
164
|
+
sex?: string;
|
|
165
|
+
premium?: boolean;
|
|
166
|
+
summit?: boolean;
|
|
167
|
+
created_at?: string;
|
|
168
|
+
updated_at?: string;
|
|
169
|
+
badge_type_id?: number;
|
|
170
|
+
weight?: number;
|
|
171
|
+
profile_medium?: string;
|
|
172
|
+
profile?: string;
|
|
173
|
+
friend?: unknown;
|
|
174
|
+
follower?: unknown;
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Token refresh response from Strava OAuth endpoint.
|
|
179
|
+
*/
|
|
180
|
+
export interface StravaAuthTokenRefreshResponse {
|
|
181
|
+
/** New OAuth2 access token. */
|
|
182
|
+
access_token: string;
|
|
183
|
+
/** New OAuth2 refresh token (optional, may not be provided). */
|
|
184
|
+
refresh_token?: string;
|
|
185
|
+
/** Token expiration timestamp (Unix time). */
|
|
186
|
+
expires_at: number;
|
|
187
|
+
/** Token expiration time in seconds. */
|
|
188
|
+
expires_in: number;
|
|
189
|
+
/** Token type (typically 'Bearer'). */
|
|
190
|
+
token_type: string;
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":"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"}
|