@pinta365/strava 0.0.1 → 0.0.3
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 -21
- package/README.md +29 -5
- package/esm/src/auth/oauth.d.ts +14 -0
- package/esm/src/auth/oauth.js +14 -0
- package/esm/src/auth/token-store.d.ts +50 -0
- package/esm/src/auth/token-store.js +39 -0
- package/esm/src/client.d.ts +58 -1
- package/esm/src/client.js +43 -1
- package/esm/src/utils/pagination.d.ts +4 -1
- package/esm/src/utils/pagination.js +4 -1
- package/package.json +1 -1
- package/script/src/auth/oauth.d.ts +14 -0
- package/script/src/auth/oauth.js +14 -0
- package/script/src/auth/token-store.d.ts +50 -0
- package/script/src/auth/token-store.js +39 -0
- package/script/src/client.d.ts +58 -1
- package/script/src/client.js +43 -1
- package/script/src/utils/pagination.d.ts +4 -1
- package/script/src/utils/pagination.js +4 -1
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Pinta <https://github.com/Pinta365>
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Pinta <https://github.com/Pinta365>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -16,18 +16,42 @@ A cross-runtime TypeScript client for the Strava API v3. Works on Deno, Node.js
|
|
|
16
16
|
|
|
17
17
|
### Deno
|
|
18
18
|
|
|
19
|
-
```
|
|
20
|
-
|
|
19
|
+
```bash
|
|
20
|
+
deno add jsr:@pinta365/strava
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
### Node.js
|
|
23
|
+
### Node.js
|
|
24
|
+
|
|
25
|
+
#### Using npm
|
|
24
26
|
|
|
25
27
|
```bash
|
|
26
28
|
npm install @pinta365/strava
|
|
27
29
|
```
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
#### Using JSR (recommended)
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx jsr add @pinta365/strava
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
#### Using pnpm
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pnpm i jsr:@pinta365/strava
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Bun
|
|
44
|
+
|
|
45
|
+
#### Using JSR (recommended)
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
bunx jsr add @pinta365/strava
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
#### Using npm
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
bun add @pinta365/strava
|
|
31
55
|
```
|
|
32
56
|
|
|
33
57
|
## Quick Start
|
package/esm/src/auth/oauth.d.ts
CHANGED
|
@@ -38,25 +38,39 @@ export declare class OAuthManager {
|
|
|
38
38
|
private tokenStore;
|
|
39
39
|
private clientId;
|
|
40
40
|
private clientSecret;
|
|
41
|
+
/**
|
|
42
|
+
* Create a new OAuth manager
|
|
43
|
+
* @param clientId - Strava client ID
|
|
44
|
+
* @param clientSecret - Strava client secret
|
|
45
|
+
* @param tokenStore - Token storage implementation
|
|
46
|
+
*/
|
|
41
47
|
constructor(clientId: string, clientSecret: string, tokenStore: TokenStore);
|
|
42
48
|
/**
|
|
43
49
|
* Get authorization URL
|
|
50
|
+
* @param options - Authorization URL options
|
|
51
|
+
* @returns Authorization URL to redirect user to
|
|
44
52
|
*/
|
|
45
53
|
getAuthorizationUrl(options: Omit<AuthorizationUrlOptions, "clientId">): string;
|
|
46
54
|
/**
|
|
47
55
|
* Exchange code and store tokens
|
|
56
|
+
* @param code - Authorization code from OAuth callback
|
|
57
|
+
* @param redirectUri - Redirect URI used in authorization (optional)
|
|
58
|
+
* @returns Token data
|
|
48
59
|
*/
|
|
49
60
|
authenticate(code: string, redirectUri?: string): Promise<TokenData>;
|
|
50
61
|
/**
|
|
51
62
|
* Get current token, refreshing if needed
|
|
63
|
+
* @returns Token data or null if not authenticated
|
|
52
64
|
*/
|
|
53
65
|
getToken(): Promise<TokenData | null>;
|
|
54
66
|
/**
|
|
55
67
|
* Manually refresh token
|
|
68
|
+
* @returns New token data
|
|
56
69
|
*/
|
|
57
70
|
refreshToken(): Promise<TokenData>;
|
|
58
71
|
/**
|
|
59
72
|
* Get current scopes
|
|
73
|
+
* @returns Array of current OAuth scopes
|
|
60
74
|
*/
|
|
61
75
|
getScopes(): Promise<StravaScope[]>;
|
|
62
76
|
/**
|
package/esm/src/auth/oauth.js
CHANGED
|
@@ -100,6 +100,12 @@ export function isTokenExpired(token, bufferSeconds = 300) {
|
|
|
100
100
|
* OAuth manager for handling authentication and token refresh
|
|
101
101
|
*/
|
|
102
102
|
export class OAuthManager {
|
|
103
|
+
/**
|
|
104
|
+
* Create a new OAuth manager
|
|
105
|
+
* @param clientId - Strava client ID
|
|
106
|
+
* @param clientSecret - Strava client secret
|
|
107
|
+
* @param tokenStore - Token storage implementation
|
|
108
|
+
*/
|
|
103
109
|
constructor(clientId, clientSecret, tokenStore) {
|
|
104
110
|
Object.defineProperty(this, "tokenStore", {
|
|
105
111
|
enumerable: true,
|
|
@@ -125,6 +131,8 @@ export class OAuthManager {
|
|
|
125
131
|
}
|
|
126
132
|
/**
|
|
127
133
|
* Get authorization URL
|
|
134
|
+
* @param options - Authorization URL options
|
|
135
|
+
* @returns Authorization URL to redirect user to
|
|
128
136
|
*/
|
|
129
137
|
getAuthorizationUrl(options) {
|
|
130
138
|
return getAuthorizationUrl({
|
|
@@ -134,6 +142,9 @@ export class OAuthManager {
|
|
|
134
142
|
}
|
|
135
143
|
/**
|
|
136
144
|
* Exchange code and store tokens
|
|
145
|
+
* @param code - Authorization code from OAuth callback
|
|
146
|
+
* @param redirectUri - Redirect URI used in authorization (optional)
|
|
147
|
+
* @returns Token data
|
|
137
148
|
*/
|
|
138
149
|
async authenticate(code, redirectUri) {
|
|
139
150
|
const token = await exchangeCode(code, this.clientId, this.clientSecret, redirectUri);
|
|
@@ -142,6 +153,7 @@ export class OAuthManager {
|
|
|
142
153
|
}
|
|
143
154
|
/**
|
|
144
155
|
* Get current token, refreshing if needed
|
|
156
|
+
* @returns Token data or null if not authenticated
|
|
145
157
|
*/
|
|
146
158
|
async getToken() {
|
|
147
159
|
const token = await this.tokenStore.get();
|
|
@@ -169,6 +181,7 @@ export class OAuthManager {
|
|
|
169
181
|
}
|
|
170
182
|
/**
|
|
171
183
|
* Manually refresh token
|
|
184
|
+
* @returns New token data
|
|
172
185
|
*/
|
|
173
186
|
async refreshToken() {
|
|
174
187
|
const token = await this.tokenStore.get();
|
|
@@ -186,6 +199,7 @@ export class OAuthManager {
|
|
|
186
199
|
}
|
|
187
200
|
/**
|
|
188
201
|
* Get current scopes
|
|
202
|
+
* @returns Array of current OAuth scopes
|
|
189
203
|
*/
|
|
190
204
|
async getScopes() {
|
|
191
205
|
const token = await this.tokenStore.get();
|
|
@@ -16,8 +16,19 @@ export interface TokenData {
|
|
|
16
16
|
* Token storage interface
|
|
17
17
|
*/
|
|
18
18
|
export interface TokenStore {
|
|
19
|
+
/**
|
|
20
|
+
* Get stored token data
|
|
21
|
+
* @returns Token data or null if not found
|
|
22
|
+
*/
|
|
19
23
|
get(): Promise<TokenData | null>;
|
|
24
|
+
/**
|
|
25
|
+
* Store token data
|
|
26
|
+
* @param token - Token data to store
|
|
27
|
+
*/
|
|
20
28
|
set(token: TokenData): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Clear stored token data
|
|
31
|
+
*/
|
|
21
32
|
clear(): Promise<void>;
|
|
22
33
|
}
|
|
23
34
|
/**
|
|
@@ -25,8 +36,19 @@ export interface TokenStore {
|
|
|
25
36
|
*/
|
|
26
37
|
export declare class MemoryTokenStore implements TokenStore {
|
|
27
38
|
private token;
|
|
39
|
+
/**
|
|
40
|
+
* Get stored token data
|
|
41
|
+
* @returns Token data or null if not found
|
|
42
|
+
*/
|
|
28
43
|
get(): Promise<TokenData | null>;
|
|
44
|
+
/**
|
|
45
|
+
* Store token data
|
|
46
|
+
* @param token - Token data to store
|
|
47
|
+
*/
|
|
29
48
|
set(token: TokenData): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Clear stored token data
|
|
51
|
+
*/
|
|
30
52
|
clear(): Promise<void>;
|
|
31
53
|
}
|
|
32
54
|
/**
|
|
@@ -34,9 +56,23 @@ export declare class MemoryTokenStore implements TokenStore {
|
|
|
34
56
|
*/
|
|
35
57
|
export declare class LocalStorageTokenStore implements TokenStore {
|
|
36
58
|
private readonly key;
|
|
59
|
+
/**
|
|
60
|
+
* @param key - localStorage key to use (default: "strava_tokens")
|
|
61
|
+
*/
|
|
37
62
|
constructor(key?: string);
|
|
63
|
+
/**
|
|
64
|
+
* Get stored token data
|
|
65
|
+
* @returns Token data or null if not found
|
|
66
|
+
*/
|
|
38
67
|
get(): Promise<TokenData | null>;
|
|
68
|
+
/**
|
|
69
|
+
* Store token data
|
|
70
|
+
* @param token - Token data to store
|
|
71
|
+
*/
|
|
39
72
|
set(token: TokenData): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Clear stored token data
|
|
75
|
+
*/
|
|
40
76
|
clear(): Promise<void>;
|
|
41
77
|
}
|
|
42
78
|
/**
|
|
@@ -44,9 +80,23 @@ export declare class LocalStorageTokenStore implements TokenStore {
|
|
|
44
80
|
*/
|
|
45
81
|
export declare class FileSystemTokenStore implements TokenStore {
|
|
46
82
|
private readonly path;
|
|
83
|
+
/**
|
|
84
|
+
* @param path - File path to store tokens (default: "./.strava-tokens.json")
|
|
85
|
+
*/
|
|
47
86
|
constructor(path?: string);
|
|
87
|
+
/**
|
|
88
|
+
* Get stored token data
|
|
89
|
+
* @returns Token data or null if not found
|
|
90
|
+
*/
|
|
48
91
|
get(): Promise<TokenData | null>;
|
|
92
|
+
/**
|
|
93
|
+
* Store token data
|
|
94
|
+
* @param token - Token data to store
|
|
95
|
+
*/
|
|
49
96
|
set(token: TokenData): Promise<void>;
|
|
97
|
+
/**
|
|
98
|
+
* Clear stored token data
|
|
99
|
+
*/
|
|
50
100
|
clear(): Promise<void>;
|
|
51
101
|
}
|
|
52
102
|
/**
|
|
@@ -14,13 +14,24 @@ export class MemoryTokenStore {
|
|
|
14
14
|
value: null
|
|
15
15
|
});
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Get stored token data
|
|
19
|
+
* @returns Token data or null if not found
|
|
20
|
+
*/
|
|
17
21
|
get() {
|
|
18
22
|
return Promise.resolve(this.token);
|
|
19
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Store token data
|
|
26
|
+
* @param token - Token data to store
|
|
27
|
+
*/
|
|
20
28
|
set(token) {
|
|
21
29
|
this.token = token;
|
|
22
30
|
return Promise.resolve();
|
|
23
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Clear stored token data
|
|
34
|
+
*/
|
|
24
35
|
clear() {
|
|
25
36
|
this.token = null;
|
|
26
37
|
return Promise.resolve();
|
|
@@ -30,6 +41,9 @@ export class MemoryTokenStore {
|
|
|
30
41
|
* Browser localStorage token store
|
|
31
42
|
*/
|
|
32
43
|
export class LocalStorageTokenStore {
|
|
44
|
+
/**
|
|
45
|
+
* @param key - localStorage key to use (default: "strava_tokens")
|
|
46
|
+
*/
|
|
33
47
|
constructor(key = "strava_tokens") {
|
|
34
48
|
Object.defineProperty(this, "key", {
|
|
35
49
|
enumerable: true,
|
|
@@ -39,6 +53,10 @@ export class LocalStorageTokenStore {
|
|
|
39
53
|
});
|
|
40
54
|
this.key = key;
|
|
41
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Get stored token data
|
|
58
|
+
* @returns Token data or null if not found
|
|
59
|
+
*/
|
|
42
60
|
get() {
|
|
43
61
|
if (CurrentRuntime !== Runtime.Browser) {
|
|
44
62
|
throw new Error("LocalStorageTokenStore can only be used in browser environment");
|
|
@@ -53,6 +71,10 @@ export class LocalStorageTokenStore {
|
|
|
53
71
|
return Promise.resolve(null);
|
|
54
72
|
}
|
|
55
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Store token data
|
|
76
|
+
* @param token - Token data to store
|
|
77
|
+
*/
|
|
56
78
|
set(token) {
|
|
57
79
|
if (CurrentRuntime !== Runtime.Browser) {
|
|
58
80
|
throw new Error("LocalStorageTokenStore can only be used in browser environment");
|
|
@@ -60,6 +82,9 @@ export class LocalStorageTokenStore {
|
|
|
60
82
|
localStorage.setItem(this.key, JSON.stringify(token));
|
|
61
83
|
return Promise.resolve();
|
|
62
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Clear stored token data
|
|
87
|
+
*/
|
|
63
88
|
clear() {
|
|
64
89
|
if (CurrentRuntime !== Runtime.Browser) {
|
|
65
90
|
throw new Error("LocalStorageTokenStore can only be used in browser environment");
|
|
@@ -72,6 +97,9 @@ export class LocalStorageTokenStore {
|
|
|
72
97
|
* File system token store (Node.js/Deno)
|
|
73
98
|
*/
|
|
74
99
|
export class FileSystemTokenStore {
|
|
100
|
+
/**
|
|
101
|
+
* @param path - File path to store tokens (default: "./.strava-tokens.json")
|
|
102
|
+
*/
|
|
75
103
|
constructor(path = "./.strava-tokens.json") {
|
|
76
104
|
Object.defineProperty(this, "path", {
|
|
77
105
|
enumerable: true,
|
|
@@ -81,6 +109,10 @@ export class FileSystemTokenStore {
|
|
|
81
109
|
});
|
|
82
110
|
this.path = path;
|
|
83
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Get stored token data
|
|
114
|
+
* @returns Token data or null if not found
|
|
115
|
+
*/
|
|
84
116
|
async get() {
|
|
85
117
|
try {
|
|
86
118
|
let content;
|
|
@@ -98,6 +130,10 @@ export class FileSystemTokenStore {
|
|
|
98
130
|
return null;
|
|
99
131
|
}
|
|
100
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Store token data
|
|
135
|
+
* @param token - Token data to store
|
|
136
|
+
*/
|
|
101
137
|
async set(token) {
|
|
102
138
|
const content = JSON.stringify(token, null, 2);
|
|
103
139
|
if (CurrentRuntime === Runtime.Node || CurrentRuntime === Runtime.Bun) {
|
|
@@ -109,6 +145,9 @@ export class FileSystemTokenStore {
|
|
|
109
145
|
await Deno.writeTextFile(this.path, content);
|
|
110
146
|
}
|
|
111
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Clear stored token data
|
|
150
|
+
*/
|
|
112
151
|
async clear() {
|
|
113
152
|
try {
|
|
114
153
|
if (CurrentRuntime === Runtime.Node || CurrentRuntime === Runtime.Bun) {
|
package/esm/src/client.d.ts
CHANGED
|
@@ -7,25 +7,40 @@ import type { RateLimitStrategy, RequestConfig, RetryConfig } from "./types/comm
|
|
|
7
7
|
* Client configuration options
|
|
8
8
|
*/
|
|
9
9
|
export interface ClientOptions {
|
|
10
|
+
/** Strava client ID */
|
|
10
11
|
clientId: string;
|
|
12
|
+
/** Strava client secret */
|
|
11
13
|
clientSecret: string;
|
|
14
|
+
/** OAuth redirect URI */
|
|
12
15
|
redirectUri?: string;
|
|
16
|
+
/** Token storage implementation (default: auto-detected based on runtime) */
|
|
13
17
|
tokenStore?: TokenStore;
|
|
18
|
+
/** Base URL for API requests (default: "https://www.strava.com/api/v3") */
|
|
14
19
|
baseUrl?: string;
|
|
20
|
+
/** Request timeout in milliseconds (default: 30000) */
|
|
15
21
|
timeout?: number;
|
|
22
|
+
/** Retry configuration */
|
|
16
23
|
retries?: RetryConfig;
|
|
24
|
+
/** Rate limit handling strategy (default: "queue") */
|
|
17
25
|
rateLimitStrategy?: RateLimitStrategy;
|
|
26
|
+
/** Request deduplication window in milliseconds (default: 5000) */
|
|
18
27
|
deduplicationWindow?: number;
|
|
28
|
+
/** Normalize snake_case keys to camelCase (default: true) */
|
|
19
29
|
normalizeKeys?: boolean;
|
|
30
|
+
/** Transform ISO date strings to Date objects (default: true) */
|
|
20
31
|
transformDates?: boolean;
|
|
32
|
+
/** Flatten nested response structures (default: true) */
|
|
21
33
|
flattenResponses?: boolean;
|
|
34
|
+
/** Add computed fields (pace, duration, etc.) (default: true) */
|
|
22
35
|
addComputedFields?: boolean;
|
|
23
36
|
}
|
|
24
37
|
/**
|
|
25
38
|
* Authentication credentials
|
|
26
39
|
*/
|
|
27
40
|
export interface AuthCredentials {
|
|
41
|
+
/** Authorization code from OAuth callback */
|
|
28
42
|
code: string;
|
|
43
|
+
/** Redirect URI used in authorization (optional, uses client default if not provided) */
|
|
29
44
|
redirectUri?: string;
|
|
30
45
|
}
|
|
31
46
|
/**
|
|
@@ -45,9 +60,14 @@ export declare class StravaClient {
|
|
|
45
60
|
private _routes?;
|
|
46
61
|
private _uploads?;
|
|
47
62
|
private _streams?;
|
|
63
|
+
/**
|
|
64
|
+
* Create a new Strava API client
|
|
65
|
+
* @param options - Client configuration options
|
|
66
|
+
*/
|
|
48
67
|
constructor(options: ClientOptions);
|
|
49
68
|
/**
|
|
50
69
|
* Authenticate with authorization code
|
|
70
|
+
* @param credentials - Authentication credentials
|
|
51
71
|
*/
|
|
52
72
|
authenticate(credentials: AuthCredentials): Promise<void>;
|
|
53
73
|
/**
|
|
@@ -55,7 +75,13 @@ export declare class StravaClient {
|
|
|
55
75
|
*/
|
|
56
76
|
refreshToken(): Promise<void>;
|
|
57
77
|
/**
|
|
58
|
-
* Get authorization URL
|
|
78
|
+
* Get authorization URL for OAuth flow
|
|
79
|
+
* @param options - Authorization URL options
|
|
80
|
+
* @param options.redirectUri - Redirect URI (optional, uses client default if not provided)
|
|
81
|
+
* @param options.scope - OAuth scopes to request
|
|
82
|
+
* @param options.state - Optional state parameter for CSRF protection
|
|
83
|
+
* @param options.approvalPrompt - Approval prompt behavior (default: "auto")
|
|
84
|
+
* @returns Authorization URL to redirect user to
|
|
59
85
|
*/
|
|
60
86
|
getAuthorizationUrl(options: {
|
|
61
87
|
redirectUri?: string;
|
|
@@ -65,20 +91,51 @@ export declare class StravaClient {
|
|
|
65
91
|
}): string;
|
|
66
92
|
/**
|
|
67
93
|
* Get current access token (for direct fetch calls)
|
|
94
|
+
* @returns Current access token
|
|
95
|
+
* @throws Error if not authenticated
|
|
68
96
|
*/
|
|
69
97
|
getAccessToken(): Promise<string>;
|
|
70
98
|
/**
|
|
71
99
|
* Low-level request method
|
|
100
|
+
* @param config - Request configuration
|
|
101
|
+
* @returns Response data
|
|
72
102
|
*/
|
|
73
103
|
request<T>(config: RequestConfig): Promise<T>;
|
|
104
|
+
/**
|
|
105
|
+
* Athletes resource
|
|
106
|
+
*/
|
|
74
107
|
get athletes(): AthletesResource;
|
|
108
|
+
/**
|
|
109
|
+
* Activities resource
|
|
110
|
+
*/
|
|
75
111
|
get activities(): ActivitiesResource;
|
|
112
|
+
/**
|
|
113
|
+
* Segments resource
|
|
114
|
+
*/
|
|
76
115
|
get segments(): SegmentsResource;
|
|
116
|
+
/**
|
|
117
|
+
* Segment efforts resource
|
|
118
|
+
*/
|
|
77
119
|
get segmentEfforts(): SegmentEffortsResource;
|
|
120
|
+
/**
|
|
121
|
+
* Clubs resource
|
|
122
|
+
*/
|
|
78
123
|
get clubs(): ClubsResource;
|
|
124
|
+
/**
|
|
125
|
+
* Gears resource
|
|
126
|
+
*/
|
|
79
127
|
get gears(): GearsResource;
|
|
128
|
+
/**
|
|
129
|
+
* Routes resource
|
|
130
|
+
*/
|
|
80
131
|
get routes(): RoutesResource;
|
|
132
|
+
/**
|
|
133
|
+
* Uploads resource
|
|
134
|
+
*/
|
|
81
135
|
get uploads(): UploadsResource;
|
|
136
|
+
/**
|
|
137
|
+
* Streams resource
|
|
138
|
+
*/
|
|
82
139
|
get streams(): StreamsResource;
|
|
83
140
|
/**
|
|
84
141
|
* Clean up resources (stop timers, clear caches)
|
package/esm/src/client.js
CHANGED
|
@@ -10,6 +10,10 @@ import { getDefaultTokenStore } from "./auth/token-store.js";
|
|
|
10
10
|
* Main Strava API client class
|
|
11
11
|
*/
|
|
12
12
|
export class StravaClient {
|
|
13
|
+
/**
|
|
14
|
+
* Create a new Strava API client
|
|
15
|
+
* @param options - Client configuration options
|
|
16
|
+
*/
|
|
13
17
|
constructor(options) {
|
|
14
18
|
Object.defineProperty(this, "options", {
|
|
15
19
|
enumerable: true,
|
|
@@ -110,6 +114,7 @@ export class StravaClient {
|
|
|
110
114
|
}
|
|
111
115
|
/**
|
|
112
116
|
* Authenticate with authorization code
|
|
117
|
+
* @param credentials - Authentication credentials
|
|
113
118
|
*/
|
|
114
119
|
async authenticate(credentials) {
|
|
115
120
|
await this.oauthManager.authenticate(credentials.code, credentials.redirectUri || this.options.redirectUri);
|
|
@@ -121,7 +126,13 @@ export class StravaClient {
|
|
|
121
126
|
await this.oauthManager.refreshToken();
|
|
122
127
|
}
|
|
123
128
|
/**
|
|
124
|
-
* Get authorization URL
|
|
129
|
+
* Get authorization URL for OAuth flow
|
|
130
|
+
* @param options - Authorization URL options
|
|
131
|
+
* @param options.redirectUri - Redirect URI (optional, uses client default if not provided)
|
|
132
|
+
* @param options.scope - OAuth scopes to request
|
|
133
|
+
* @param options.state - Optional state parameter for CSRF protection
|
|
134
|
+
* @param options.approvalPrompt - Approval prompt behavior (default: "auto")
|
|
135
|
+
* @returns Authorization URL to redirect user to
|
|
125
136
|
*/
|
|
126
137
|
getAuthorizationUrl(options) {
|
|
127
138
|
return this.oauthManager.getAuthorizationUrl({
|
|
@@ -133,6 +144,8 @@ export class StravaClient {
|
|
|
133
144
|
}
|
|
134
145
|
/**
|
|
135
146
|
* Get current access token (for direct fetch calls)
|
|
147
|
+
* @returns Current access token
|
|
148
|
+
* @throws Error if not authenticated
|
|
136
149
|
*/
|
|
137
150
|
async getAccessToken() {
|
|
138
151
|
const token = await this.oauthManager.getToken();
|
|
@@ -143,6 +156,8 @@ export class StravaClient {
|
|
|
143
156
|
}
|
|
144
157
|
/**
|
|
145
158
|
* Low-level request method
|
|
159
|
+
* @param config - Request configuration
|
|
160
|
+
* @returns Response data
|
|
146
161
|
*/
|
|
147
162
|
async request(config) {
|
|
148
163
|
const token = await this.oauthManager.getToken();
|
|
@@ -162,54 +177,81 @@ export class StravaClient {
|
|
|
162
177
|
addComputedFields: this.options.addComputedFields,
|
|
163
178
|
});
|
|
164
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Athletes resource
|
|
182
|
+
*/
|
|
165
183
|
get athletes() {
|
|
166
184
|
if (!this._athletes) {
|
|
167
185
|
this._athletes = new AthletesResource(this);
|
|
168
186
|
}
|
|
169
187
|
return this._athletes;
|
|
170
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Activities resource
|
|
191
|
+
*/
|
|
171
192
|
get activities() {
|
|
172
193
|
if (!this._activities) {
|
|
173
194
|
this._activities = new ActivitiesResource(this);
|
|
174
195
|
}
|
|
175
196
|
return this._activities;
|
|
176
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Segments resource
|
|
200
|
+
*/
|
|
177
201
|
get segments() {
|
|
178
202
|
if (!this._segments) {
|
|
179
203
|
this._segments = new SegmentsResource(this);
|
|
180
204
|
}
|
|
181
205
|
return this._segments;
|
|
182
206
|
}
|
|
207
|
+
/**
|
|
208
|
+
* Segment efforts resource
|
|
209
|
+
*/
|
|
183
210
|
get segmentEfforts() {
|
|
184
211
|
if (!this._segmentEfforts) {
|
|
185
212
|
this._segmentEfforts = new SegmentEffortsResource(this);
|
|
186
213
|
}
|
|
187
214
|
return this._segmentEfforts;
|
|
188
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Clubs resource
|
|
218
|
+
*/
|
|
189
219
|
get clubs() {
|
|
190
220
|
if (!this._clubs) {
|
|
191
221
|
this._clubs = new ClubsResource(this);
|
|
192
222
|
}
|
|
193
223
|
return this._clubs;
|
|
194
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Gears resource
|
|
227
|
+
*/
|
|
195
228
|
get gears() {
|
|
196
229
|
if (!this._gears) {
|
|
197
230
|
this._gears = new GearsResource(this);
|
|
198
231
|
}
|
|
199
232
|
return this._gears;
|
|
200
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Routes resource
|
|
236
|
+
*/
|
|
201
237
|
get routes() {
|
|
202
238
|
if (!this._routes) {
|
|
203
239
|
this._routes = new RoutesResource(this);
|
|
204
240
|
}
|
|
205
241
|
return this._routes;
|
|
206
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* Uploads resource
|
|
245
|
+
*/
|
|
207
246
|
get uploads() {
|
|
208
247
|
if (!this._uploads) {
|
|
209
248
|
this._uploads = new UploadsResource(this);
|
|
210
249
|
}
|
|
211
250
|
return this._uploads;
|
|
212
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* Streams resource
|
|
254
|
+
*/
|
|
213
255
|
get streams() {
|
|
214
256
|
if (!this._streams) {
|
|
215
257
|
this._streams = new StreamsResource(this);
|
|
@@ -14,18 +14,21 @@ export declare class PaginatedIterator<T> {
|
|
|
14
14
|
constructor(fetchPage: (page: number, perPage: number) => Promise<T[]>, perPage?: number);
|
|
15
15
|
/**
|
|
16
16
|
* Get next page
|
|
17
|
+
* @returns True if a page was fetched, false if no more pages
|
|
17
18
|
*/
|
|
18
19
|
next(): Promise<boolean>;
|
|
19
20
|
/**
|
|
20
21
|
* Get current page items
|
|
22
|
+
* @returns Array of items from the current page
|
|
21
23
|
*/
|
|
22
24
|
get current(): T[];
|
|
23
25
|
/**
|
|
24
26
|
* Check if there are more pages
|
|
27
|
+
* @returns True if there are more pages available
|
|
25
28
|
*/
|
|
26
29
|
get hasMorePages(): boolean;
|
|
27
30
|
/**
|
|
28
|
-
* Reset iterator
|
|
31
|
+
* Reset iterator to start from the first page
|
|
29
32
|
*/
|
|
30
33
|
reset(): void;
|
|
31
34
|
}
|
|
@@ -39,6 +39,7 @@ export class PaginatedIterator {
|
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
41
|
* Get next page
|
|
42
|
+
* @returns True if a page was fetched, false if no more pages
|
|
42
43
|
*/
|
|
43
44
|
async next() {
|
|
44
45
|
if (!this.hasMore) {
|
|
@@ -54,18 +55,20 @@ export class PaginatedIterator {
|
|
|
54
55
|
}
|
|
55
56
|
/**
|
|
56
57
|
* Get current page items
|
|
58
|
+
* @returns Array of items from the current page
|
|
57
59
|
*/
|
|
58
60
|
get current() {
|
|
59
61
|
return this.currentItems;
|
|
60
62
|
}
|
|
61
63
|
/**
|
|
62
64
|
* Check if there are more pages
|
|
65
|
+
* @returns True if there are more pages available
|
|
63
66
|
*/
|
|
64
67
|
get hasMorePages() {
|
|
65
68
|
return this.hasMore;
|
|
66
69
|
}
|
|
67
70
|
/**
|
|
68
|
-
* Reset iterator
|
|
71
|
+
* Reset iterator to start from the first page
|
|
69
72
|
*/
|
|
70
73
|
reset() {
|
|
71
74
|
this.currentPage = 1;
|
package/package.json
CHANGED
|
@@ -38,25 +38,39 @@ export declare class OAuthManager {
|
|
|
38
38
|
private tokenStore;
|
|
39
39
|
private clientId;
|
|
40
40
|
private clientSecret;
|
|
41
|
+
/**
|
|
42
|
+
* Create a new OAuth manager
|
|
43
|
+
* @param clientId - Strava client ID
|
|
44
|
+
* @param clientSecret - Strava client secret
|
|
45
|
+
* @param tokenStore - Token storage implementation
|
|
46
|
+
*/
|
|
41
47
|
constructor(clientId: string, clientSecret: string, tokenStore: TokenStore);
|
|
42
48
|
/**
|
|
43
49
|
* Get authorization URL
|
|
50
|
+
* @param options - Authorization URL options
|
|
51
|
+
* @returns Authorization URL to redirect user to
|
|
44
52
|
*/
|
|
45
53
|
getAuthorizationUrl(options: Omit<AuthorizationUrlOptions, "clientId">): string;
|
|
46
54
|
/**
|
|
47
55
|
* Exchange code and store tokens
|
|
56
|
+
* @param code - Authorization code from OAuth callback
|
|
57
|
+
* @param redirectUri - Redirect URI used in authorization (optional)
|
|
58
|
+
* @returns Token data
|
|
48
59
|
*/
|
|
49
60
|
authenticate(code: string, redirectUri?: string): Promise<TokenData>;
|
|
50
61
|
/**
|
|
51
62
|
* Get current token, refreshing if needed
|
|
63
|
+
* @returns Token data or null if not authenticated
|
|
52
64
|
*/
|
|
53
65
|
getToken(): Promise<TokenData | null>;
|
|
54
66
|
/**
|
|
55
67
|
* Manually refresh token
|
|
68
|
+
* @returns New token data
|
|
56
69
|
*/
|
|
57
70
|
refreshToken(): Promise<TokenData>;
|
|
58
71
|
/**
|
|
59
72
|
* Get current scopes
|
|
73
|
+
* @returns Array of current OAuth scopes
|
|
60
74
|
*/
|
|
61
75
|
getScopes(): Promise<StravaScope[]>;
|
|
62
76
|
/**
|
package/script/src/auth/oauth.js
CHANGED
|
@@ -107,6 +107,12 @@ function isTokenExpired(token, bufferSeconds = 300) {
|
|
|
107
107
|
* OAuth manager for handling authentication and token refresh
|
|
108
108
|
*/
|
|
109
109
|
class OAuthManager {
|
|
110
|
+
/**
|
|
111
|
+
* Create a new OAuth manager
|
|
112
|
+
* @param clientId - Strava client ID
|
|
113
|
+
* @param clientSecret - Strava client secret
|
|
114
|
+
* @param tokenStore - Token storage implementation
|
|
115
|
+
*/
|
|
110
116
|
constructor(clientId, clientSecret, tokenStore) {
|
|
111
117
|
Object.defineProperty(this, "tokenStore", {
|
|
112
118
|
enumerable: true,
|
|
@@ -132,6 +138,8 @@ class OAuthManager {
|
|
|
132
138
|
}
|
|
133
139
|
/**
|
|
134
140
|
* Get authorization URL
|
|
141
|
+
* @param options - Authorization URL options
|
|
142
|
+
* @returns Authorization URL to redirect user to
|
|
135
143
|
*/
|
|
136
144
|
getAuthorizationUrl(options) {
|
|
137
145
|
return getAuthorizationUrl({
|
|
@@ -141,6 +149,9 @@ class OAuthManager {
|
|
|
141
149
|
}
|
|
142
150
|
/**
|
|
143
151
|
* Exchange code and store tokens
|
|
152
|
+
* @param code - Authorization code from OAuth callback
|
|
153
|
+
* @param redirectUri - Redirect URI used in authorization (optional)
|
|
154
|
+
* @returns Token data
|
|
144
155
|
*/
|
|
145
156
|
async authenticate(code, redirectUri) {
|
|
146
157
|
const token = await exchangeCode(code, this.clientId, this.clientSecret, redirectUri);
|
|
@@ -149,6 +160,7 @@ class OAuthManager {
|
|
|
149
160
|
}
|
|
150
161
|
/**
|
|
151
162
|
* Get current token, refreshing if needed
|
|
163
|
+
* @returns Token data or null if not authenticated
|
|
152
164
|
*/
|
|
153
165
|
async getToken() {
|
|
154
166
|
const token = await this.tokenStore.get();
|
|
@@ -176,6 +188,7 @@ class OAuthManager {
|
|
|
176
188
|
}
|
|
177
189
|
/**
|
|
178
190
|
* Manually refresh token
|
|
191
|
+
* @returns New token data
|
|
179
192
|
*/
|
|
180
193
|
async refreshToken() {
|
|
181
194
|
const token = await this.tokenStore.get();
|
|
@@ -193,6 +206,7 @@ class OAuthManager {
|
|
|
193
206
|
}
|
|
194
207
|
/**
|
|
195
208
|
* Get current scopes
|
|
209
|
+
* @returns Array of current OAuth scopes
|
|
196
210
|
*/
|
|
197
211
|
async getScopes() {
|
|
198
212
|
const token = await this.tokenStore.get();
|
|
@@ -16,8 +16,19 @@ export interface TokenData {
|
|
|
16
16
|
* Token storage interface
|
|
17
17
|
*/
|
|
18
18
|
export interface TokenStore {
|
|
19
|
+
/**
|
|
20
|
+
* Get stored token data
|
|
21
|
+
* @returns Token data or null if not found
|
|
22
|
+
*/
|
|
19
23
|
get(): Promise<TokenData | null>;
|
|
24
|
+
/**
|
|
25
|
+
* Store token data
|
|
26
|
+
* @param token - Token data to store
|
|
27
|
+
*/
|
|
20
28
|
set(token: TokenData): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Clear stored token data
|
|
31
|
+
*/
|
|
21
32
|
clear(): Promise<void>;
|
|
22
33
|
}
|
|
23
34
|
/**
|
|
@@ -25,8 +36,19 @@ export interface TokenStore {
|
|
|
25
36
|
*/
|
|
26
37
|
export declare class MemoryTokenStore implements TokenStore {
|
|
27
38
|
private token;
|
|
39
|
+
/**
|
|
40
|
+
* Get stored token data
|
|
41
|
+
* @returns Token data or null if not found
|
|
42
|
+
*/
|
|
28
43
|
get(): Promise<TokenData | null>;
|
|
44
|
+
/**
|
|
45
|
+
* Store token data
|
|
46
|
+
* @param token - Token data to store
|
|
47
|
+
*/
|
|
29
48
|
set(token: TokenData): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Clear stored token data
|
|
51
|
+
*/
|
|
30
52
|
clear(): Promise<void>;
|
|
31
53
|
}
|
|
32
54
|
/**
|
|
@@ -34,9 +56,23 @@ export declare class MemoryTokenStore implements TokenStore {
|
|
|
34
56
|
*/
|
|
35
57
|
export declare class LocalStorageTokenStore implements TokenStore {
|
|
36
58
|
private readonly key;
|
|
59
|
+
/**
|
|
60
|
+
* @param key - localStorage key to use (default: "strava_tokens")
|
|
61
|
+
*/
|
|
37
62
|
constructor(key?: string);
|
|
63
|
+
/**
|
|
64
|
+
* Get stored token data
|
|
65
|
+
* @returns Token data or null if not found
|
|
66
|
+
*/
|
|
38
67
|
get(): Promise<TokenData | null>;
|
|
68
|
+
/**
|
|
69
|
+
* Store token data
|
|
70
|
+
* @param token - Token data to store
|
|
71
|
+
*/
|
|
39
72
|
set(token: TokenData): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Clear stored token data
|
|
75
|
+
*/
|
|
40
76
|
clear(): Promise<void>;
|
|
41
77
|
}
|
|
42
78
|
/**
|
|
@@ -44,9 +80,23 @@ export declare class LocalStorageTokenStore implements TokenStore {
|
|
|
44
80
|
*/
|
|
45
81
|
export declare class FileSystemTokenStore implements TokenStore {
|
|
46
82
|
private readonly path;
|
|
83
|
+
/**
|
|
84
|
+
* @param path - File path to store tokens (default: "./.strava-tokens.json")
|
|
85
|
+
*/
|
|
47
86
|
constructor(path?: string);
|
|
87
|
+
/**
|
|
88
|
+
* Get stored token data
|
|
89
|
+
* @returns Token data or null if not found
|
|
90
|
+
*/
|
|
48
91
|
get(): Promise<TokenData | null>;
|
|
92
|
+
/**
|
|
93
|
+
* Store token data
|
|
94
|
+
* @param token - Token data to store
|
|
95
|
+
*/
|
|
49
96
|
set(token: TokenData): Promise<void>;
|
|
97
|
+
/**
|
|
98
|
+
* Clear stored token data
|
|
99
|
+
*/
|
|
50
100
|
clear(): Promise<void>;
|
|
51
101
|
}
|
|
52
102
|
/**
|
|
@@ -51,13 +51,24 @@ class MemoryTokenStore {
|
|
|
51
51
|
value: null
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Get stored token data
|
|
56
|
+
* @returns Token data or null if not found
|
|
57
|
+
*/
|
|
54
58
|
get() {
|
|
55
59
|
return Promise.resolve(this.token);
|
|
56
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Store token data
|
|
63
|
+
* @param token - Token data to store
|
|
64
|
+
*/
|
|
57
65
|
set(token) {
|
|
58
66
|
this.token = token;
|
|
59
67
|
return Promise.resolve();
|
|
60
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Clear stored token data
|
|
71
|
+
*/
|
|
61
72
|
clear() {
|
|
62
73
|
this.token = null;
|
|
63
74
|
return Promise.resolve();
|
|
@@ -68,6 +79,9 @@ exports.MemoryTokenStore = MemoryTokenStore;
|
|
|
68
79
|
* Browser localStorage token store
|
|
69
80
|
*/
|
|
70
81
|
class LocalStorageTokenStore {
|
|
82
|
+
/**
|
|
83
|
+
* @param key - localStorage key to use (default: "strava_tokens")
|
|
84
|
+
*/
|
|
71
85
|
constructor(key = "strava_tokens") {
|
|
72
86
|
Object.defineProperty(this, "key", {
|
|
73
87
|
enumerable: true,
|
|
@@ -77,6 +91,10 @@ class LocalStorageTokenStore {
|
|
|
77
91
|
});
|
|
78
92
|
this.key = key;
|
|
79
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Get stored token data
|
|
96
|
+
* @returns Token data or null if not found
|
|
97
|
+
*/
|
|
80
98
|
get() {
|
|
81
99
|
if (mod_js_1.CurrentRuntime !== mod_js_1.Runtime.Browser) {
|
|
82
100
|
throw new Error("LocalStorageTokenStore can only be used in browser environment");
|
|
@@ -91,6 +109,10 @@ class LocalStorageTokenStore {
|
|
|
91
109
|
return Promise.resolve(null);
|
|
92
110
|
}
|
|
93
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Store token data
|
|
114
|
+
* @param token - Token data to store
|
|
115
|
+
*/
|
|
94
116
|
set(token) {
|
|
95
117
|
if (mod_js_1.CurrentRuntime !== mod_js_1.Runtime.Browser) {
|
|
96
118
|
throw new Error("LocalStorageTokenStore can only be used in browser environment");
|
|
@@ -98,6 +120,9 @@ class LocalStorageTokenStore {
|
|
|
98
120
|
localStorage.setItem(this.key, JSON.stringify(token));
|
|
99
121
|
return Promise.resolve();
|
|
100
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Clear stored token data
|
|
125
|
+
*/
|
|
101
126
|
clear() {
|
|
102
127
|
if (mod_js_1.CurrentRuntime !== mod_js_1.Runtime.Browser) {
|
|
103
128
|
throw new Error("LocalStorageTokenStore can only be used in browser environment");
|
|
@@ -111,6 +136,9 @@ exports.LocalStorageTokenStore = LocalStorageTokenStore;
|
|
|
111
136
|
* File system token store (Node.js/Deno)
|
|
112
137
|
*/
|
|
113
138
|
class FileSystemTokenStore {
|
|
139
|
+
/**
|
|
140
|
+
* @param path - File path to store tokens (default: "./.strava-tokens.json")
|
|
141
|
+
*/
|
|
114
142
|
constructor(path = "./.strava-tokens.json") {
|
|
115
143
|
Object.defineProperty(this, "path", {
|
|
116
144
|
enumerable: true,
|
|
@@ -120,6 +148,10 @@ class FileSystemTokenStore {
|
|
|
120
148
|
});
|
|
121
149
|
this.path = path;
|
|
122
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Get stored token data
|
|
153
|
+
* @returns Token data or null if not found
|
|
154
|
+
*/
|
|
123
155
|
async get() {
|
|
124
156
|
try {
|
|
125
157
|
let content;
|
|
@@ -137,6 +169,10 @@ class FileSystemTokenStore {
|
|
|
137
169
|
return null;
|
|
138
170
|
}
|
|
139
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Store token data
|
|
174
|
+
* @param token - Token data to store
|
|
175
|
+
*/
|
|
140
176
|
async set(token) {
|
|
141
177
|
const content = JSON.stringify(token, null, 2);
|
|
142
178
|
if (mod_js_1.CurrentRuntime === mod_js_1.Runtime.Node || mod_js_1.CurrentRuntime === mod_js_1.Runtime.Bun) {
|
|
@@ -148,6 +184,9 @@ class FileSystemTokenStore {
|
|
|
148
184
|
await Deno.writeTextFile(this.path, content);
|
|
149
185
|
}
|
|
150
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Clear stored token data
|
|
189
|
+
*/
|
|
151
190
|
async clear() {
|
|
152
191
|
try {
|
|
153
192
|
if (mod_js_1.CurrentRuntime === mod_js_1.Runtime.Node || mod_js_1.CurrentRuntime === mod_js_1.Runtime.Bun) {
|
package/script/src/client.d.ts
CHANGED
|
@@ -7,25 +7,40 @@ import type { RateLimitStrategy, RequestConfig, RetryConfig } from "./types/comm
|
|
|
7
7
|
* Client configuration options
|
|
8
8
|
*/
|
|
9
9
|
export interface ClientOptions {
|
|
10
|
+
/** Strava client ID */
|
|
10
11
|
clientId: string;
|
|
12
|
+
/** Strava client secret */
|
|
11
13
|
clientSecret: string;
|
|
14
|
+
/** OAuth redirect URI */
|
|
12
15
|
redirectUri?: string;
|
|
16
|
+
/** Token storage implementation (default: auto-detected based on runtime) */
|
|
13
17
|
tokenStore?: TokenStore;
|
|
18
|
+
/** Base URL for API requests (default: "https://www.strava.com/api/v3") */
|
|
14
19
|
baseUrl?: string;
|
|
20
|
+
/** Request timeout in milliseconds (default: 30000) */
|
|
15
21
|
timeout?: number;
|
|
22
|
+
/** Retry configuration */
|
|
16
23
|
retries?: RetryConfig;
|
|
24
|
+
/** Rate limit handling strategy (default: "queue") */
|
|
17
25
|
rateLimitStrategy?: RateLimitStrategy;
|
|
26
|
+
/** Request deduplication window in milliseconds (default: 5000) */
|
|
18
27
|
deduplicationWindow?: number;
|
|
28
|
+
/** Normalize snake_case keys to camelCase (default: true) */
|
|
19
29
|
normalizeKeys?: boolean;
|
|
30
|
+
/** Transform ISO date strings to Date objects (default: true) */
|
|
20
31
|
transformDates?: boolean;
|
|
32
|
+
/** Flatten nested response structures (default: true) */
|
|
21
33
|
flattenResponses?: boolean;
|
|
34
|
+
/** Add computed fields (pace, duration, etc.) (default: true) */
|
|
22
35
|
addComputedFields?: boolean;
|
|
23
36
|
}
|
|
24
37
|
/**
|
|
25
38
|
* Authentication credentials
|
|
26
39
|
*/
|
|
27
40
|
export interface AuthCredentials {
|
|
41
|
+
/** Authorization code from OAuth callback */
|
|
28
42
|
code: string;
|
|
43
|
+
/** Redirect URI used in authorization (optional, uses client default if not provided) */
|
|
29
44
|
redirectUri?: string;
|
|
30
45
|
}
|
|
31
46
|
/**
|
|
@@ -45,9 +60,14 @@ export declare class StravaClient {
|
|
|
45
60
|
private _routes?;
|
|
46
61
|
private _uploads?;
|
|
47
62
|
private _streams?;
|
|
63
|
+
/**
|
|
64
|
+
* Create a new Strava API client
|
|
65
|
+
* @param options - Client configuration options
|
|
66
|
+
*/
|
|
48
67
|
constructor(options: ClientOptions);
|
|
49
68
|
/**
|
|
50
69
|
* Authenticate with authorization code
|
|
70
|
+
* @param credentials - Authentication credentials
|
|
51
71
|
*/
|
|
52
72
|
authenticate(credentials: AuthCredentials): Promise<void>;
|
|
53
73
|
/**
|
|
@@ -55,7 +75,13 @@ export declare class StravaClient {
|
|
|
55
75
|
*/
|
|
56
76
|
refreshToken(): Promise<void>;
|
|
57
77
|
/**
|
|
58
|
-
* Get authorization URL
|
|
78
|
+
* Get authorization URL for OAuth flow
|
|
79
|
+
* @param options - Authorization URL options
|
|
80
|
+
* @param options.redirectUri - Redirect URI (optional, uses client default if not provided)
|
|
81
|
+
* @param options.scope - OAuth scopes to request
|
|
82
|
+
* @param options.state - Optional state parameter for CSRF protection
|
|
83
|
+
* @param options.approvalPrompt - Approval prompt behavior (default: "auto")
|
|
84
|
+
* @returns Authorization URL to redirect user to
|
|
59
85
|
*/
|
|
60
86
|
getAuthorizationUrl(options: {
|
|
61
87
|
redirectUri?: string;
|
|
@@ -65,20 +91,51 @@ export declare class StravaClient {
|
|
|
65
91
|
}): string;
|
|
66
92
|
/**
|
|
67
93
|
* Get current access token (for direct fetch calls)
|
|
94
|
+
* @returns Current access token
|
|
95
|
+
* @throws Error if not authenticated
|
|
68
96
|
*/
|
|
69
97
|
getAccessToken(): Promise<string>;
|
|
70
98
|
/**
|
|
71
99
|
* Low-level request method
|
|
100
|
+
* @param config - Request configuration
|
|
101
|
+
* @returns Response data
|
|
72
102
|
*/
|
|
73
103
|
request<T>(config: RequestConfig): Promise<T>;
|
|
104
|
+
/**
|
|
105
|
+
* Athletes resource
|
|
106
|
+
*/
|
|
74
107
|
get athletes(): AthletesResource;
|
|
108
|
+
/**
|
|
109
|
+
* Activities resource
|
|
110
|
+
*/
|
|
75
111
|
get activities(): ActivitiesResource;
|
|
112
|
+
/**
|
|
113
|
+
* Segments resource
|
|
114
|
+
*/
|
|
76
115
|
get segments(): SegmentsResource;
|
|
116
|
+
/**
|
|
117
|
+
* Segment efforts resource
|
|
118
|
+
*/
|
|
77
119
|
get segmentEfforts(): SegmentEffortsResource;
|
|
120
|
+
/**
|
|
121
|
+
* Clubs resource
|
|
122
|
+
*/
|
|
78
123
|
get clubs(): ClubsResource;
|
|
124
|
+
/**
|
|
125
|
+
* Gears resource
|
|
126
|
+
*/
|
|
79
127
|
get gears(): GearsResource;
|
|
128
|
+
/**
|
|
129
|
+
* Routes resource
|
|
130
|
+
*/
|
|
80
131
|
get routes(): RoutesResource;
|
|
132
|
+
/**
|
|
133
|
+
* Uploads resource
|
|
134
|
+
*/
|
|
81
135
|
get uploads(): UploadsResource;
|
|
136
|
+
/**
|
|
137
|
+
* Streams resource
|
|
138
|
+
*/
|
|
82
139
|
get streams(): StreamsResource;
|
|
83
140
|
/**
|
|
84
141
|
* Clean up resources (stop timers, clear caches)
|
package/script/src/client.js
CHANGED
|
@@ -13,6 +13,10 @@ const token_store_js_1 = require("./auth/token-store.js");
|
|
|
13
13
|
* Main Strava API client class
|
|
14
14
|
*/
|
|
15
15
|
class StravaClient {
|
|
16
|
+
/**
|
|
17
|
+
* Create a new Strava API client
|
|
18
|
+
* @param options - Client configuration options
|
|
19
|
+
*/
|
|
16
20
|
constructor(options) {
|
|
17
21
|
Object.defineProperty(this, "options", {
|
|
18
22
|
enumerable: true,
|
|
@@ -113,6 +117,7 @@ class StravaClient {
|
|
|
113
117
|
}
|
|
114
118
|
/**
|
|
115
119
|
* Authenticate with authorization code
|
|
120
|
+
* @param credentials - Authentication credentials
|
|
116
121
|
*/
|
|
117
122
|
async authenticate(credentials) {
|
|
118
123
|
await this.oauthManager.authenticate(credentials.code, credentials.redirectUri || this.options.redirectUri);
|
|
@@ -124,7 +129,13 @@ class StravaClient {
|
|
|
124
129
|
await this.oauthManager.refreshToken();
|
|
125
130
|
}
|
|
126
131
|
/**
|
|
127
|
-
* Get authorization URL
|
|
132
|
+
* Get authorization URL for OAuth flow
|
|
133
|
+
* @param options - Authorization URL options
|
|
134
|
+
* @param options.redirectUri - Redirect URI (optional, uses client default if not provided)
|
|
135
|
+
* @param options.scope - OAuth scopes to request
|
|
136
|
+
* @param options.state - Optional state parameter for CSRF protection
|
|
137
|
+
* @param options.approvalPrompt - Approval prompt behavior (default: "auto")
|
|
138
|
+
* @returns Authorization URL to redirect user to
|
|
128
139
|
*/
|
|
129
140
|
getAuthorizationUrl(options) {
|
|
130
141
|
return this.oauthManager.getAuthorizationUrl({
|
|
@@ -136,6 +147,8 @@ class StravaClient {
|
|
|
136
147
|
}
|
|
137
148
|
/**
|
|
138
149
|
* Get current access token (for direct fetch calls)
|
|
150
|
+
* @returns Current access token
|
|
151
|
+
* @throws Error if not authenticated
|
|
139
152
|
*/
|
|
140
153
|
async getAccessToken() {
|
|
141
154
|
const token = await this.oauthManager.getToken();
|
|
@@ -146,6 +159,8 @@ class StravaClient {
|
|
|
146
159
|
}
|
|
147
160
|
/**
|
|
148
161
|
* Low-level request method
|
|
162
|
+
* @param config - Request configuration
|
|
163
|
+
* @returns Response data
|
|
149
164
|
*/
|
|
150
165
|
async request(config) {
|
|
151
166
|
const token = await this.oauthManager.getToken();
|
|
@@ -165,54 +180,81 @@ class StravaClient {
|
|
|
165
180
|
addComputedFields: this.options.addComputedFields,
|
|
166
181
|
});
|
|
167
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* Athletes resource
|
|
185
|
+
*/
|
|
168
186
|
get athletes() {
|
|
169
187
|
if (!this._athletes) {
|
|
170
188
|
this._athletes = new athletes_js_1.AthletesResource(this);
|
|
171
189
|
}
|
|
172
190
|
return this._athletes;
|
|
173
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* Activities resource
|
|
194
|
+
*/
|
|
174
195
|
get activities() {
|
|
175
196
|
if (!this._activities) {
|
|
176
197
|
this._activities = new activities_js_1.ActivitiesResource(this);
|
|
177
198
|
}
|
|
178
199
|
return this._activities;
|
|
179
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Segments resource
|
|
203
|
+
*/
|
|
180
204
|
get segments() {
|
|
181
205
|
if (!this._segments) {
|
|
182
206
|
this._segments = new segments_js_1.SegmentsResource(this);
|
|
183
207
|
}
|
|
184
208
|
return this._segments;
|
|
185
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Segment efforts resource
|
|
212
|
+
*/
|
|
186
213
|
get segmentEfforts() {
|
|
187
214
|
if (!this._segmentEfforts) {
|
|
188
215
|
this._segmentEfforts = new segment_efforts_js_1.SegmentEffortsResource(this);
|
|
189
216
|
}
|
|
190
217
|
return this._segmentEfforts;
|
|
191
218
|
}
|
|
219
|
+
/**
|
|
220
|
+
* Clubs resource
|
|
221
|
+
*/
|
|
192
222
|
get clubs() {
|
|
193
223
|
if (!this._clubs) {
|
|
194
224
|
this._clubs = new clubs_js_1.ClubsResource(this);
|
|
195
225
|
}
|
|
196
226
|
return this._clubs;
|
|
197
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Gears resource
|
|
230
|
+
*/
|
|
198
231
|
get gears() {
|
|
199
232
|
if (!this._gears) {
|
|
200
233
|
this._gears = new gears_js_1.GearsResource(this);
|
|
201
234
|
}
|
|
202
235
|
return this._gears;
|
|
203
236
|
}
|
|
237
|
+
/**
|
|
238
|
+
* Routes resource
|
|
239
|
+
*/
|
|
204
240
|
get routes() {
|
|
205
241
|
if (!this._routes) {
|
|
206
242
|
this._routes = new routes_js_1.RoutesResource(this);
|
|
207
243
|
}
|
|
208
244
|
return this._routes;
|
|
209
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* Uploads resource
|
|
248
|
+
*/
|
|
210
249
|
get uploads() {
|
|
211
250
|
if (!this._uploads) {
|
|
212
251
|
this._uploads = new uploads_js_1.UploadsResource(this);
|
|
213
252
|
}
|
|
214
253
|
return this._uploads;
|
|
215
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* Streams resource
|
|
257
|
+
*/
|
|
216
258
|
get streams() {
|
|
217
259
|
if (!this._streams) {
|
|
218
260
|
this._streams = new streams_js_1.StreamsResource(this);
|
|
@@ -14,18 +14,21 @@ export declare class PaginatedIterator<T> {
|
|
|
14
14
|
constructor(fetchPage: (page: number, perPage: number) => Promise<T[]>, perPage?: number);
|
|
15
15
|
/**
|
|
16
16
|
* Get next page
|
|
17
|
+
* @returns True if a page was fetched, false if no more pages
|
|
17
18
|
*/
|
|
18
19
|
next(): Promise<boolean>;
|
|
19
20
|
/**
|
|
20
21
|
* Get current page items
|
|
22
|
+
* @returns Array of items from the current page
|
|
21
23
|
*/
|
|
22
24
|
get current(): T[];
|
|
23
25
|
/**
|
|
24
26
|
* Check if there are more pages
|
|
27
|
+
* @returns True if there are more pages available
|
|
25
28
|
*/
|
|
26
29
|
get hasMorePages(): boolean;
|
|
27
30
|
/**
|
|
28
|
-
* Reset iterator
|
|
31
|
+
* Reset iterator to start from the first page
|
|
29
32
|
*/
|
|
30
33
|
reset(): void;
|
|
31
34
|
}
|
|
@@ -44,6 +44,7 @@ class PaginatedIterator {
|
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
46
|
* Get next page
|
|
47
|
+
* @returns True if a page was fetched, false if no more pages
|
|
47
48
|
*/
|
|
48
49
|
async next() {
|
|
49
50
|
if (!this.hasMore) {
|
|
@@ -59,18 +60,20 @@ class PaginatedIterator {
|
|
|
59
60
|
}
|
|
60
61
|
/**
|
|
61
62
|
* Get current page items
|
|
63
|
+
* @returns Array of items from the current page
|
|
62
64
|
*/
|
|
63
65
|
get current() {
|
|
64
66
|
return this.currentItems;
|
|
65
67
|
}
|
|
66
68
|
/**
|
|
67
69
|
* Check if there are more pages
|
|
70
|
+
* @returns True if there are more pages available
|
|
68
71
|
*/
|
|
69
72
|
get hasMorePages() {
|
|
70
73
|
return this.hasMore;
|
|
71
74
|
}
|
|
72
75
|
/**
|
|
73
|
-
* Reset iterator
|
|
76
|
+
* Reset iterator to start from the first page
|
|
74
77
|
*/
|
|
75
78
|
reset() {
|
|
76
79
|
this.currentPage = 1;
|