@pinta365/strava 0.0.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/LICENSE +21 -0
- package/README.md +390 -0
- package/esm/_dnt.shims.d.ts +2 -0
- package/esm/_dnt.shims.js +57 -0
- package/esm/deps/jsr.io/@cross/runtime/1.2.1/mod.d.ts +126 -0
- package/esm/deps/jsr.io/@cross/runtime/1.2.1/mod.js +480 -0
- package/esm/mod.d.ts +27 -0
- package/esm/mod.js +27 -0
- package/esm/package.json +3 -0
- package/esm/src/auth/oauth.d.ts +68 -0
- package/esm/src/auth/oauth.js +203 -0
- package/esm/src/auth/scopes.d.ts +52 -0
- package/esm/src/auth/scopes.js +71 -0
- package/esm/src/auth/token-store.d.ts +57 -0
- package/esm/src/auth/token-store.js +142 -0
- package/esm/src/client.d.ts +98 -0
- package/esm/src/client.js +235 -0
- package/esm/src/errors.d.ts +52 -0
- package/esm/src/errors.js +102 -0
- package/esm/src/http/deduplication.d.ts +33 -0
- package/esm/src/http/deduplication.js +96 -0
- package/esm/src/http/rate-limiter.d.ts +47 -0
- package/esm/src/http/rate-limiter.js +168 -0
- package/esm/src/http/request.d.ts +24 -0
- package/esm/src/http/request.js +158 -0
- package/esm/src/http/retry.d.ts +9 -0
- package/esm/src/http/retry.js +61 -0
- package/esm/src/resources/activities.d.ts +149 -0
- package/esm/src/resources/activities.js +189 -0
- package/esm/src/resources/athletes.d.ts +37 -0
- package/esm/src/resources/athletes.js +85 -0
- package/esm/src/resources/clubs.d.ts +45 -0
- package/esm/src/resources/clubs.js +71 -0
- package/esm/src/resources/gears.d.ts +17 -0
- package/esm/src/resources/gears.js +27 -0
- package/esm/src/resources/routes.d.ts +33 -0
- package/esm/src/resources/routes.js +71 -0
- package/esm/src/resources/segment-efforts.d.ts +38 -0
- package/esm/src/resources/segment-efforts.js +53 -0
- package/esm/src/resources/segments.d.ts +42 -0
- package/esm/src/resources/segments.js +67 -0
- package/esm/src/resources/streams.d.ts +44 -0
- package/esm/src/resources/streams.js +75 -0
- package/esm/src/resources/uploads.d.ts +41 -0
- package/esm/src/resources/uploads.js +79 -0
- package/esm/src/types/api.d.ts +9 -0
- package/esm/src/types/api.js +7 -0
- package/esm/src/types/common.d.ts +65 -0
- package/esm/src/types/common.js +4 -0
- package/esm/src/types/generated.d.ts +731 -0
- package/esm/src/types/generated.js +7 -0
- package/esm/src/utils/pagination.d.ts +45 -0
- package/esm/src/utils/pagination.js +112 -0
- package/esm/src/utils/transformers.d.ts +30 -0
- package/esm/src/utils/transformers.js +189 -0
- package/esm/src/utils/validators.d.ts +53 -0
- package/esm/src/utils/validators.js +84 -0
- package/package.json +40 -0
- package/script/_dnt.shims.d.ts +2 -0
- package/script/_dnt.shims.js +60 -0
- package/script/deps/jsr.io/@cross/runtime/1.2.1/mod.d.ts +126 -0
- package/script/deps/jsr.io/@cross/runtime/1.2.1/mod.js +526 -0
- package/script/mod.d.ts +27 -0
- package/script/mod.js +73 -0
- package/script/package.json +3 -0
- package/script/src/auth/oauth.d.ts +68 -0
- package/script/src/auth/oauth.js +211 -0
- package/script/src/auth/scopes.d.ts +52 -0
- package/script/src/auth/scopes.js +79 -0
- package/script/src/auth/token-store.d.ts +57 -0
- package/script/src/auth/token-store.js +182 -0
- package/script/src/client.d.ts +98 -0
- package/script/src/client.js +239 -0
- package/script/src/errors.d.ts +52 -0
- package/script/src/errors.js +111 -0
- package/script/src/http/deduplication.d.ts +33 -0
- package/script/src/http/deduplication.js +100 -0
- package/script/src/http/rate-limiter.d.ts +47 -0
- package/script/src/http/rate-limiter.js +172 -0
- package/script/src/http/request.d.ts +24 -0
- package/script/src/http/request.js +161 -0
- package/script/src/http/retry.d.ts +9 -0
- package/script/src/http/retry.js +64 -0
- package/script/src/resources/activities.d.ts +149 -0
- package/script/src/resources/activities.js +193 -0
- package/script/src/resources/athletes.d.ts +37 -0
- package/script/src/resources/athletes.js +89 -0
- package/script/src/resources/clubs.d.ts +45 -0
- package/script/src/resources/clubs.js +75 -0
- package/script/src/resources/gears.d.ts +17 -0
- package/script/src/resources/gears.js +31 -0
- package/script/src/resources/routes.d.ts +33 -0
- package/script/src/resources/routes.js +75 -0
- package/script/src/resources/segment-efforts.d.ts +38 -0
- package/script/src/resources/segment-efforts.js +57 -0
- package/script/src/resources/segments.d.ts +42 -0
- package/script/src/resources/segments.js +71 -0
- package/script/src/resources/streams.d.ts +44 -0
- package/script/src/resources/streams.js +79 -0
- package/script/src/resources/uploads.d.ts +41 -0
- package/script/src/resources/uploads.js +83 -0
- package/script/src/types/api.d.ts +9 -0
- package/script/src/types/api.js +23 -0
- package/script/src/types/common.d.ts +65 -0
- package/script/src/types/common.js +5 -0
- package/script/src/types/generated.d.ts +731 -0
- package/script/src/types/generated.js +8 -0
- package/script/src/utils/pagination.d.ts +45 -0
- package/script/src/utils/pagination.js +118 -0
- package/script/src/utils/transformers.d.ts +30 -0
- package/script/src/utils/transformers.js +196 -0
- package/script/src/utils/validators.d.ts +53 -0
- package/script/src/utils/validators.js +92 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.0 authentication flow for Strava API
|
|
3
|
+
*/
|
|
4
|
+
import { StravaAuthError } from "../errors.js";
|
|
5
|
+
import { formatScopes, parseScopes } from "./scopes.js";
|
|
6
|
+
const OAUTH_BASE_URL = "https://www.strava.com/api/v3/oauth";
|
|
7
|
+
/**
|
|
8
|
+
* Generate authorization URL
|
|
9
|
+
*/
|
|
10
|
+
export function getAuthorizationUrl(options) {
|
|
11
|
+
const { clientId, redirectUri, scope, state, approvalPrompt = "auto" } = options;
|
|
12
|
+
const params = new URLSearchParams({
|
|
13
|
+
client_id: clientId,
|
|
14
|
+
redirect_uri: redirectUri,
|
|
15
|
+
response_type: "code",
|
|
16
|
+
scope: formatScopes(scope),
|
|
17
|
+
approval_prompt: approvalPrompt,
|
|
18
|
+
});
|
|
19
|
+
if (state) {
|
|
20
|
+
params.append("state", state);
|
|
21
|
+
}
|
|
22
|
+
return `${OAUTH_BASE_URL}/authorize?${params.toString()}`;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Exchange authorization code for tokens
|
|
26
|
+
* @param code - Authorization code from OAuth callback
|
|
27
|
+
* @param clientId - Strava client ID
|
|
28
|
+
* @param clientSecret - Strava client secret
|
|
29
|
+
* @param redirectUri - Redirect URI used in authorization (optional)
|
|
30
|
+
* @returns Token data
|
|
31
|
+
*/
|
|
32
|
+
export async function exchangeCode(code, clientId, clientSecret, redirectUri) {
|
|
33
|
+
const params = new URLSearchParams({
|
|
34
|
+
client_id: clientId,
|
|
35
|
+
client_secret: clientSecret,
|
|
36
|
+
code,
|
|
37
|
+
grant_type: "authorization_code",
|
|
38
|
+
});
|
|
39
|
+
if (redirectUri) {
|
|
40
|
+
params.append("redirect_uri", redirectUri);
|
|
41
|
+
}
|
|
42
|
+
const response = await fetch(`${OAUTH_BASE_URL}/token`, {
|
|
43
|
+
method: "POST",
|
|
44
|
+
headers: {
|
|
45
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
46
|
+
},
|
|
47
|
+
body: params.toString(),
|
|
48
|
+
});
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
51
|
+
throw new StravaAuthError(`Failed to exchange code: ${error.message || response.statusText}`, response.status, error);
|
|
52
|
+
}
|
|
53
|
+
const data = await response.json();
|
|
54
|
+
return {
|
|
55
|
+
accessToken: data.access_token,
|
|
56
|
+
refreshToken: data.refresh_token,
|
|
57
|
+
expiresAt: data.expires_at,
|
|
58
|
+
tokenType: data.token_type,
|
|
59
|
+
athleteId: data.athlete?.id,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Refresh access token using refresh token
|
|
64
|
+
*/
|
|
65
|
+
export async function refreshAccessToken(refreshToken, clientId, clientSecret) {
|
|
66
|
+
const params = new URLSearchParams({
|
|
67
|
+
client_id: clientId,
|
|
68
|
+
client_secret: clientSecret,
|
|
69
|
+
refresh_token: refreshToken,
|
|
70
|
+
grant_type: "refresh_token",
|
|
71
|
+
});
|
|
72
|
+
const response = await fetch(`${OAUTH_BASE_URL}/token`, {
|
|
73
|
+
method: "POST",
|
|
74
|
+
headers: {
|
|
75
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
76
|
+
},
|
|
77
|
+
body: params.toString(),
|
|
78
|
+
});
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
81
|
+
throw new StravaAuthError(`Failed to refresh token: ${error.message || response.statusText}`, response.status, error);
|
|
82
|
+
}
|
|
83
|
+
const data = await response.json();
|
|
84
|
+
return {
|
|
85
|
+
accessToken: data.access_token,
|
|
86
|
+
refreshToken: data.refresh_token,
|
|
87
|
+
expiresAt: data.expires_at,
|
|
88
|
+
tokenType: data.token_type,
|
|
89
|
+
athleteId: data.athlete?.id,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Check if token is expired or about to expire
|
|
94
|
+
*/
|
|
95
|
+
export function isTokenExpired(token, bufferSeconds = 300) {
|
|
96
|
+
const now = Math.floor(Date.now() / 1000);
|
|
97
|
+
return token.expiresAt <= (now + bufferSeconds);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* OAuth manager for handling authentication and token refresh
|
|
101
|
+
*/
|
|
102
|
+
export class OAuthManager {
|
|
103
|
+
constructor(clientId, clientSecret, tokenStore) {
|
|
104
|
+
Object.defineProperty(this, "tokenStore", {
|
|
105
|
+
enumerable: true,
|
|
106
|
+
configurable: true,
|
|
107
|
+
writable: true,
|
|
108
|
+
value: void 0
|
|
109
|
+
});
|
|
110
|
+
Object.defineProperty(this, "clientId", {
|
|
111
|
+
enumerable: true,
|
|
112
|
+
configurable: true,
|
|
113
|
+
writable: true,
|
|
114
|
+
value: void 0
|
|
115
|
+
});
|
|
116
|
+
Object.defineProperty(this, "clientSecret", {
|
|
117
|
+
enumerable: true,
|
|
118
|
+
configurable: true,
|
|
119
|
+
writable: true,
|
|
120
|
+
value: void 0
|
|
121
|
+
});
|
|
122
|
+
this.clientId = clientId;
|
|
123
|
+
this.clientSecret = clientSecret;
|
|
124
|
+
this.tokenStore = tokenStore;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get authorization URL
|
|
128
|
+
*/
|
|
129
|
+
getAuthorizationUrl(options) {
|
|
130
|
+
return getAuthorizationUrl({
|
|
131
|
+
...options,
|
|
132
|
+
clientId: this.clientId,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Exchange code and store tokens
|
|
137
|
+
*/
|
|
138
|
+
async authenticate(code, redirectUri) {
|
|
139
|
+
const token = await exchangeCode(code, this.clientId, this.clientSecret, redirectUri);
|
|
140
|
+
await this.tokenStore.set(token);
|
|
141
|
+
return token;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Get current token, refreshing if needed
|
|
145
|
+
*/
|
|
146
|
+
async getToken() {
|
|
147
|
+
const token = await this.tokenStore.get();
|
|
148
|
+
if (!token) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
// Refresh if expired or about to expire
|
|
152
|
+
if (isTokenExpired(token)) {
|
|
153
|
+
try {
|
|
154
|
+
const newToken = await refreshAccessToken(token.refreshToken, this.clientId, this.clientSecret);
|
|
155
|
+
const refreshedToken = {
|
|
156
|
+
...newToken,
|
|
157
|
+
scope: token.scope,
|
|
158
|
+
athleteId: token.athleteId || newToken.athleteId,
|
|
159
|
+
};
|
|
160
|
+
await this.tokenStore.set(refreshedToken);
|
|
161
|
+
return refreshedToken;
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
await this.tokenStore.clear();
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return token;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Manually refresh token
|
|
172
|
+
*/
|
|
173
|
+
async refreshToken() {
|
|
174
|
+
const token = await this.tokenStore.get();
|
|
175
|
+
if (!token) {
|
|
176
|
+
throw new StravaAuthError("No token available to refresh");
|
|
177
|
+
}
|
|
178
|
+
const newToken = await refreshAccessToken(token.refreshToken, this.clientId, this.clientSecret);
|
|
179
|
+
const refreshedToken = {
|
|
180
|
+
...newToken,
|
|
181
|
+
scope: token.scope,
|
|
182
|
+
athleteId: token.athleteId || newToken.athleteId,
|
|
183
|
+
};
|
|
184
|
+
await this.tokenStore.set(refreshedToken);
|
|
185
|
+
return refreshedToken;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Get current scopes
|
|
189
|
+
*/
|
|
190
|
+
async getScopes() {
|
|
191
|
+
const token = await this.tokenStore.get();
|
|
192
|
+
if (!token || !token.scope) {
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
return parseScopes(token.scope);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Clear stored tokens
|
|
199
|
+
*/
|
|
200
|
+
async clearTokens() {
|
|
201
|
+
await this.tokenStore.clear();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.0 scope definitions for Strava API
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Available OAuth scopes
|
|
6
|
+
*/
|
|
7
|
+
export declare enum StravaScope {
|
|
8
|
+
Read = "read",
|
|
9
|
+
ReadAll = "read_all",
|
|
10
|
+
ProfileReadAll = "profile:read_all",
|
|
11
|
+
ProfileWrite = "profile:write",
|
|
12
|
+
ActivityRead = "activity:read",
|
|
13
|
+
ActivityReadAll = "activity:read_all",
|
|
14
|
+
ActivityWrite = "activity:write"
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Scope descriptions
|
|
18
|
+
*/
|
|
19
|
+
export declare const SCOPE_DESCRIPTIONS: Record<StravaScope, string>;
|
|
20
|
+
/**
|
|
21
|
+
* Parse scope string into array
|
|
22
|
+
* @param scopeString - Comma-separated scope string
|
|
23
|
+
* @returns Array of scope enums
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseScopes(scopeString: string): StravaScope[];
|
|
26
|
+
/**
|
|
27
|
+
* Format scopes array into string
|
|
28
|
+
* @param scopes - Array of scope enums
|
|
29
|
+
* @returns Comma-separated scope string
|
|
30
|
+
*/
|
|
31
|
+
export declare function formatScopes(scopes: StravaScope[]): string;
|
|
32
|
+
/**
|
|
33
|
+
* Check if a scope is included in the provided scopes
|
|
34
|
+
* @param required - Required scope
|
|
35
|
+
* @param available - Available scopes
|
|
36
|
+
* @returns True if the required scope is available
|
|
37
|
+
*/
|
|
38
|
+
export declare function hasScope(required: StravaScope, available: StravaScope[]): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Check if all required scopes are available
|
|
41
|
+
* @param required - Required scopes
|
|
42
|
+
* @param available - Available scopes
|
|
43
|
+
* @returns True if all required scopes are available
|
|
44
|
+
*/
|
|
45
|
+
export declare function hasAllScopes(required: StravaScope[], available: StravaScope[]): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Validate scopes
|
|
48
|
+
* @param scopes - Scopes to validate
|
|
49
|
+
* @returns True if all scopes are valid
|
|
50
|
+
*/
|
|
51
|
+
export declare function validateScopes(scopes: StravaScope[]): boolean;
|
|
52
|
+
//# sourceMappingURL=scopes.d.ts.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.0 scope definitions for Strava API
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Available OAuth scopes
|
|
6
|
+
*/
|
|
7
|
+
export var StravaScope;
|
|
8
|
+
(function (StravaScope) {
|
|
9
|
+
StravaScope["Read"] = "read";
|
|
10
|
+
StravaScope["ReadAll"] = "read_all";
|
|
11
|
+
StravaScope["ProfileReadAll"] = "profile:read_all";
|
|
12
|
+
StravaScope["ProfileWrite"] = "profile:write";
|
|
13
|
+
StravaScope["ActivityRead"] = "activity:read";
|
|
14
|
+
StravaScope["ActivityReadAll"] = "activity:read_all";
|
|
15
|
+
StravaScope["ActivityWrite"] = "activity:write";
|
|
16
|
+
})(StravaScope || (StravaScope = {}));
|
|
17
|
+
/**
|
|
18
|
+
* Scope descriptions
|
|
19
|
+
*/
|
|
20
|
+
export const SCOPE_DESCRIPTIONS = {
|
|
21
|
+
[StravaScope.Read]: "Read public segments, public routes, public profile data, public posts, public events, club feeds, and leaderboards",
|
|
22
|
+
[StravaScope.ReadAll]: "Read private routes, private segments, and private events for the user",
|
|
23
|
+
[StravaScope.ProfileReadAll]: "Read all profile information even if the user has set their profile visibility to Followers or Only You",
|
|
24
|
+
[StravaScope.ProfileWrite]: "Update the user's weight and Functional Threshold Power (FTP), and access to star or unstar segments on their behalf",
|
|
25
|
+
[StravaScope.ActivityRead]: "Read the user's activity data for activities that are visible to Everyone and Followers, excluding privacy zone data",
|
|
26
|
+
[StravaScope.ActivityReadAll]: "The same access as activity:read, plus privacy zone data and access to read the user's activities with visibility set to Only You",
|
|
27
|
+
[StravaScope.ActivityWrite]: "Access to create manual activities and uploads, and access to edit any activities that are visible to the app, based on activity read access level",
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Parse scope string into array
|
|
31
|
+
* @param scopeString - Comma-separated scope string
|
|
32
|
+
* @returns Array of scope enums
|
|
33
|
+
*/
|
|
34
|
+
export function parseScopes(scopeString) {
|
|
35
|
+
return scopeString.split(",").map((s) => s.trim()).filter(Boolean);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Format scopes array into string
|
|
39
|
+
* @param scopes - Array of scope enums
|
|
40
|
+
* @returns Comma-separated scope string
|
|
41
|
+
*/
|
|
42
|
+
export function formatScopes(scopes) {
|
|
43
|
+
return scopes.join(",");
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Check if a scope is included in the provided scopes
|
|
47
|
+
* @param required - Required scope
|
|
48
|
+
* @param available - Available scopes
|
|
49
|
+
* @returns True if the required scope is available
|
|
50
|
+
*/
|
|
51
|
+
export function hasScope(required, available) {
|
|
52
|
+
return available.includes(required);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Check if all required scopes are available
|
|
56
|
+
* @param required - Required scopes
|
|
57
|
+
* @param available - Available scopes
|
|
58
|
+
* @returns True if all required scopes are available
|
|
59
|
+
*/
|
|
60
|
+
export function hasAllScopes(required, available) {
|
|
61
|
+
return required.every((scope) => available.includes(scope));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Validate scopes
|
|
65
|
+
* @param scopes - Scopes to validate
|
|
66
|
+
* @returns True if all scopes are valid
|
|
67
|
+
*/
|
|
68
|
+
export function validateScopes(scopes) {
|
|
69
|
+
const validScopes = Object.values(StravaScope);
|
|
70
|
+
return scopes.every((scope) => validScopes.includes(scope));
|
|
71
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token storage interface and implementations
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Token data structure
|
|
6
|
+
*/
|
|
7
|
+
export interface TokenData {
|
|
8
|
+
accessToken: string;
|
|
9
|
+
refreshToken: string;
|
|
10
|
+
expiresAt: number;
|
|
11
|
+
tokenType: string;
|
|
12
|
+
scope?: string;
|
|
13
|
+
athleteId?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Token storage interface
|
|
17
|
+
*/
|
|
18
|
+
export interface TokenStore {
|
|
19
|
+
get(): Promise<TokenData | null>;
|
|
20
|
+
set(token: TokenData): Promise<void>;
|
|
21
|
+
clear(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* In-memory token store (default)
|
|
25
|
+
*/
|
|
26
|
+
export declare class MemoryTokenStore implements TokenStore {
|
|
27
|
+
private token;
|
|
28
|
+
get(): Promise<TokenData | null>;
|
|
29
|
+
set(token: TokenData): Promise<void>;
|
|
30
|
+
clear(): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Browser localStorage token store
|
|
34
|
+
*/
|
|
35
|
+
export declare class LocalStorageTokenStore implements TokenStore {
|
|
36
|
+
private readonly key;
|
|
37
|
+
constructor(key?: string);
|
|
38
|
+
get(): Promise<TokenData | null>;
|
|
39
|
+
set(token: TokenData): Promise<void>;
|
|
40
|
+
clear(): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* File system token store (Node.js/Deno)
|
|
44
|
+
*/
|
|
45
|
+
export declare class FileSystemTokenStore implements TokenStore {
|
|
46
|
+
private readonly path;
|
|
47
|
+
constructor(path?: string);
|
|
48
|
+
get(): Promise<TokenData | null>;
|
|
49
|
+
set(token: TokenData): Promise<void>;
|
|
50
|
+
clear(): Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Get default token store based on runtime environment
|
|
54
|
+
* @returns TokenStore appropriate for the current runtime (browser, Node.js, Deno, Bun)
|
|
55
|
+
*/
|
|
56
|
+
export declare function getDefaultTokenStore(): TokenStore;
|
|
57
|
+
//# sourceMappingURL=token-store.d.ts.map
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token storage interface and implementations
|
|
3
|
+
*/
|
|
4
|
+
import { CurrentRuntime, Runtime } from "../../deps/jsr.io/@cross/runtime/1.2.1/mod.js";
|
|
5
|
+
/**
|
|
6
|
+
* In-memory token store (default)
|
|
7
|
+
*/
|
|
8
|
+
export class MemoryTokenStore {
|
|
9
|
+
constructor() {
|
|
10
|
+
Object.defineProperty(this, "token", {
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
writable: true,
|
|
14
|
+
value: null
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
get() {
|
|
18
|
+
return Promise.resolve(this.token);
|
|
19
|
+
}
|
|
20
|
+
set(token) {
|
|
21
|
+
this.token = token;
|
|
22
|
+
return Promise.resolve();
|
|
23
|
+
}
|
|
24
|
+
clear() {
|
|
25
|
+
this.token = null;
|
|
26
|
+
return Promise.resolve();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Browser localStorage token store
|
|
31
|
+
*/
|
|
32
|
+
export class LocalStorageTokenStore {
|
|
33
|
+
constructor(key = "strava_tokens") {
|
|
34
|
+
Object.defineProperty(this, "key", {
|
|
35
|
+
enumerable: true,
|
|
36
|
+
configurable: true,
|
|
37
|
+
writable: true,
|
|
38
|
+
value: void 0
|
|
39
|
+
});
|
|
40
|
+
this.key = key;
|
|
41
|
+
}
|
|
42
|
+
get() {
|
|
43
|
+
if (CurrentRuntime !== Runtime.Browser) {
|
|
44
|
+
throw new Error("LocalStorageTokenStore can only be used in browser environment");
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const stored = localStorage.getItem(this.key);
|
|
48
|
+
if (!stored)
|
|
49
|
+
return Promise.resolve(null);
|
|
50
|
+
return Promise.resolve(JSON.parse(stored));
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return Promise.resolve(null);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
set(token) {
|
|
57
|
+
if (CurrentRuntime !== Runtime.Browser) {
|
|
58
|
+
throw new Error("LocalStorageTokenStore can only be used in browser environment");
|
|
59
|
+
}
|
|
60
|
+
localStorage.setItem(this.key, JSON.stringify(token));
|
|
61
|
+
return Promise.resolve();
|
|
62
|
+
}
|
|
63
|
+
clear() {
|
|
64
|
+
if (CurrentRuntime !== Runtime.Browser) {
|
|
65
|
+
throw new Error("LocalStorageTokenStore can only be used in browser environment");
|
|
66
|
+
}
|
|
67
|
+
localStorage.removeItem(this.key);
|
|
68
|
+
return Promise.resolve();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* File system token store (Node.js/Deno)
|
|
73
|
+
*/
|
|
74
|
+
export class FileSystemTokenStore {
|
|
75
|
+
constructor(path = "./.strava-tokens.json") {
|
|
76
|
+
Object.defineProperty(this, "path", {
|
|
77
|
+
enumerable: true,
|
|
78
|
+
configurable: true,
|
|
79
|
+
writable: true,
|
|
80
|
+
value: void 0
|
|
81
|
+
});
|
|
82
|
+
this.path = path;
|
|
83
|
+
}
|
|
84
|
+
async get() {
|
|
85
|
+
try {
|
|
86
|
+
let content;
|
|
87
|
+
if (CurrentRuntime === Runtime.Node || CurrentRuntime === Runtime.Bun) {
|
|
88
|
+
const { readFile } = await import("node:fs/promises");
|
|
89
|
+
content = await readFile(this.path, "utf-8");
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
// Deno
|
|
93
|
+
content = await Deno.readTextFile(this.path);
|
|
94
|
+
}
|
|
95
|
+
return JSON.parse(content);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async set(token) {
|
|
102
|
+
const content = JSON.stringify(token, null, 2);
|
|
103
|
+
if (CurrentRuntime === Runtime.Node || CurrentRuntime === Runtime.Bun) {
|
|
104
|
+
const { writeFile } = await import("node:fs/promises");
|
|
105
|
+
await writeFile(this.path, content, "utf-8");
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
// Deno
|
|
109
|
+
await Deno.writeTextFile(this.path, content);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async clear() {
|
|
113
|
+
try {
|
|
114
|
+
if (CurrentRuntime === Runtime.Node || CurrentRuntime === Runtime.Bun) {
|
|
115
|
+
const { unlink } = await import("node:fs/promises");
|
|
116
|
+
await unlink(this.path);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
// Deno
|
|
120
|
+
await Deno.remove(this.path);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// File doesn't exist, ignore
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Get default token store based on runtime environment
|
|
130
|
+
* @returns TokenStore appropriate for the current runtime (browser, Node.js, Deno, Bun)
|
|
131
|
+
*/
|
|
132
|
+
export function getDefaultTokenStore() {
|
|
133
|
+
if (CurrentRuntime === Runtime.Browser) {
|
|
134
|
+
return new LocalStorageTokenStore();
|
|
135
|
+
}
|
|
136
|
+
else if (CurrentRuntime === Runtime.Node || CurrentRuntime === Runtime.Deno || CurrentRuntime === Runtime.Bun) {
|
|
137
|
+
return new FileSystemTokenStore();
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
return new MemoryTokenStore();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main Strava API client
|
|
3
|
+
*/
|
|
4
|
+
import { type TokenStore } from "./auth/token-store.js";
|
|
5
|
+
import type { RateLimitStrategy, RequestConfig, RetryConfig } from "./types/common.js";
|
|
6
|
+
/**
|
|
7
|
+
* Client configuration options
|
|
8
|
+
*/
|
|
9
|
+
export interface ClientOptions {
|
|
10
|
+
clientId: string;
|
|
11
|
+
clientSecret: string;
|
|
12
|
+
redirectUri?: string;
|
|
13
|
+
tokenStore?: TokenStore;
|
|
14
|
+
baseUrl?: string;
|
|
15
|
+
timeout?: number;
|
|
16
|
+
retries?: RetryConfig;
|
|
17
|
+
rateLimitStrategy?: RateLimitStrategy;
|
|
18
|
+
deduplicationWindow?: number;
|
|
19
|
+
normalizeKeys?: boolean;
|
|
20
|
+
transformDates?: boolean;
|
|
21
|
+
flattenResponses?: boolean;
|
|
22
|
+
addComputedFields?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Authentication credentials
|
|
26
|
+
*/
|
|
27
|
+
export interface AuthCredentials {
|
|
28
|
+
code: string;
|
|
29
|
+
redirectUri?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Main Strava API client class
|
|
33
|
+
*/
|
|
34
|
+
export declare class StravaClient {
|
|
35
|
+
private readonly options;
|
|
36
|
+
private readonly oauthManager;
|
|
37
|
+
private readonly rateLimiter;
|
|
38
|
+
private readonly deduplicator;
|
|
39
|
+
private _athletes?;
|
|
40
|
+
private _activities?;
|
|
41
|
+
private _segments?;
|
|
42
|
+
private _segmentEfforts?;
|
|
43
|
+
private _clubs?;
|
|
44
|
+
private _gears?;
|
|
45
|
+
private _routes?;
|
|
46
|
+
private _uploads?;
|
|
47
|
+
private _streams?;
|
|
48
|
+
constructor(options: ClientOptions);
|
|
49
|
+
/**
|
|
50
|
+
* Authenticate with authorization code
|
|
51
|
+
*/
|
|
52
|
+
authenticate(credentials: AuthCredentials): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Refresh access token
|
|
55
|
+
*/
|
|
56
|
+
refreshToken(): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Get authorization URL
|
|
59
|
+
*/
|
|
60
|
+
getAuthorizationUrl(options: {
|
|
61
|
+
redirectUri?: string;
|
|
62
|
+
scope: string[];
|
|
63
|
+
state?: string;
|
|
64
|
+
approvalPrompt?: "force" | "auto";
|
|
65
|
+
}): string;
|
|
66
|
+
/**
|
|
67
|
+
* Get current access token (for direct fetch calls)
|
|
68
|
+
*/
|
|
69
|
+
getAccessToken(): Promise<string>;
|
|
70
|
+
/**
|
|
71
|
+
* Low-level request method
|
|
72
|
+
*/
|
|
73
|
+
request<T>(config: RequestConfig): Promise<T>;
|
|
74
|
+
get athletes(): AthletesResource;
|
|
75
|
+
get activities(): ActivitiesResource;
|
|
76
|
+
get segments(): SegmentsResource;
|
|
77
|
+
get segmentEfforts(): SegmentEffortsResource;
|
|
78
|
+
get clubs(): ClubsResource;
|
|
79
|
+
get gears(): GearsResource;
|
|
80
|
+
get routes(): RoutesResource;
|
|
81
|
+
get uploads(): UploadsResource;
|
|
82
|
+
get streams(): StreamsResource;
|
|
83
|
+
/**
|
|
84
|
+
* Clean up resources (stop timers, clear caches)
|
|
85
|
+
* Call this when you're done with the client to allow the process to exit
|
|
86
|
+
*/
|
|
87
|
+
destroy(): void;
|
|
88
|
+
}
|
|
89
|
+
import { AthletesResource } from "./resources/athletes.js";
|
|
90
|
+
import { ActivitiesResource } from "./resources/activities.js";
|
|
91
|
+
import { SegmentsResource } from "./resources/segments.js";
|
|
92
|
+
import { SegmentEffortsResource } from "./resources/segment-efforts.js";
|
|
93
|
+
import { ClubsResource } from "./resources/clubs.js";
|
|
94
|
+
import { GearsResource } from "./resources/gears.js";
|
|
95
|
+
import { RoutesResource } from "./resources/routes.js";
|
|
96
|
+
import { UploadsResource } from "./resources/uploads.js";
|
|
97
|
+
import { StreamsResource } from "./resources/streams.js";
|
|
98
|
+
//# sourceMappingURL=client.d.ts.map
|