@nativesquare/soma 0.9.3 → 0.10.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/dist/client/index.d.ts +96 -33
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +80 -35
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +18 -0
- package/dist/component/_generated/api.d.ts.map +1 -1
- package/dist/component/_generated/api.js.map +1 -1
- package/dist/component/_generated/component.d.ts +43 -9
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/garmin/auth.d.ts +0 -4
- package/dist/component/garmin/auth.d.ts.map +1 -1
- package/dist/component/garmin/auth.js +0 -8
- package/dist/component/garmin/auth.js.map +1 -1
- package/dist/component/garmin/private.d.ts +20 -3
- package/dist/component/garmin/private.d.ts.map +1 -1
- package/dist/component/garmin/private.js +17 -26
- package/dist/component/garmin/private.js.map +1 -1
- package/dist/component/garmin/public.d.ts +4 -4
- package/dist/component/garmin/public.d.ts.map +1 -1
- package/dist/component/garmin/public.js +6 -1
- package/dist/component/garmin/public.js.map +1 -1
- package/dist/component/garmin/webhooks.d.ts +4 -0
- package/dist/component/garmin/webhooks.d.ts.map +1 -1
- package/dist/component/garmin/webhooks.js +23 -18
- package/dist/component/garmin/webhooks.js.map +1 -1
- package/dist/component/schema.d.ts +2 -2
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +5 -3
- package/dist/component/schema.js.map +1 -1
- package/dist/{strava → component/strava}/auth.d.ts +15 -48
- package/dist/component/strava/auth.d.ts.map +1 -0
- package/dist/{strava → component/strava}/auth.js +4 -39
- package/dist/component/strava/auth.js.map +1 -0
- package/dist/component/strava/client.d.ts +8 -0
- package/dist/component/strava/client.d.ts.map +1 -0
- package/dist/component/strava/client.js +18 -0
- package/dist/component/strava/client.js.map +1 -0
- package/dist/component/strava/private.d.ts +19 -0
- package/dist/component/strava/private.d.ts.map +1 -1
- package/dist/component/strava/private.js +52 -2
- package/dist/component/strava/private.js.map +1 -1
- package/dist/component/strava/public.d.ts +87 -12
- package/dist/component/strava/public.d.ts.map +1 -1
- package/dist/component/strava/public.js +218 -92
- package/dist/component/strava/public.js.map +1 -1
- package/dist/component/strava/transform/activity.d.ts +19 -0
- package/dist/component/strava/transform/activity.d.ts.map +1 -0
- package/dist/{strava → component/strava/transform}/activity.js +21 -41
- package/dist/component/strava/transform/activity.js.map +1 -0
- package/dist/{strava → component/strava/transform}/athlete.d.ts +4 -10
- package/dist/component/strava/transform/athlete.d.ts.map +1 -0
- package/dist/{strava → component/strava/transform}/athlete.js +2 -8
- package/dist/component/strava/transform/athlete.js.map +1 -0
- package/dist/component/strava/transform/maps/sportType.d.ts +7 -0
- package/dist/component/strava/transform/maps/sportType.d.ts.map +1 -0
- package/dist/{strava/maps/sport-type.js → component/strava/transform/maps/sportType.js} +4 -2
- package/dist/component/strava/transform/maps/sportType.js.map +1 -0
- package/dist/component/strava/types/stravaApi/client/client.gen.d.ts +3 -0
- package/dist/component/strava/types/stravaApi/client/client.gen.d.ts.map +1 -0
- package/dist/component/strava/types/stravaApi/client/client.gen.js +236 -0
- package/dist/component/strava/types/stravaApi/client/client.gen.js.map +1 -0
- package/dist/component/strava/types/stravaApi/client/index.d.ts +9 -0
- package/dist/component/strava/types/stravaApi/client/index.d.ts.map +1 -0
- package/dist/component/strava/types/stravaApi/client/index.js +7 -0
- package/dist/component/strava/types/stravaApi/client/index.js.map +1 -0
- package/dist/component/strava/types/stravaApi/client/types.gen.d.ts +118 -0
- package/dist/component/strava/types/stravaApi/client/types.gen.d.ts.map +1 -0
- package/dist/component/strava/types/stravaApi/client/types.gen.js +3 -0
- package/dist/component/strava/types/stravaApi/client/types.gen.js.map +1 -0
- package/dist/component/strava/types/stravaApi/client/utils.gen.d.ts +34 -0
- package/dist/component/strava/types/stravaApi/client/utils.gen.d.ts.map +1 -0
- package/dist/component/strava/types/stravaApi/client/utils.gen.js +229 -0
- package/dist/component/strava/types/stravaApi/client/utils.gen.js.map +1 -0
- package/dist/component/strava/types/stravaApi/client.gen.d.ts +13 -0
- package/dist/component/strava/types/stravaApi/client.gen.d.ts.map +1 -0
- package/dist/component/strava/types/stravaApi/client.gen.js +4 -0
- package/dist/component/strava/types/stravaApi/client.gen.js.map +1 -0
- package/dist/component/strava/types/stravaApi/core/auth.gen.d.ts +19 -0
- package/dist/component/strava/types/stravaApi/core/auth.gen.d.ts.map +1 -0
- package/dist/component/strava/types/stravaApi/core/auth.gen.js +15 -0
- package/dist/component/strava/types/stravaApi/core/auth.gen.js.map +1 -0
- package/dist/component/strava/types/stravaApi/core/bodySerializer.gen.d.ts +26 -0
- package/dist/component/strava/types/stravaApi/core/bodySerializer.gen.d.ts.map +1 -0
- package/dist/component/strava/types/stravaApi/core/bodySerializer.gen.js +58 -0
- package/dist/component/strava/types/stravaApi/core/bodySerializer.gen.js.map +1 -0
- package/dist/component/strava/types/stravaApi/core/params.gen.d.ts +44 -0
- package/dist/component/strava/types/stravaApi/core/params.gen.d.ts.map +1 -0
- package/dist/component/strava/types/stravaApi/core/params.gen.js +101 -0
- package/dist/component/strava/types/stravaApi/core/params.gen.js.map +1 -0
- package/dist/component/strava/types/stravaApi/core/pathSerializer.gen.d.ts +34 -0
- package/dist/component/strava/types/stravaApi/core/pathSerializer.gen.d.ts.map +1 -0
- package/dist/component/strava/types/stravaApi/core/pathSerializer.gen.js +107 -0
- package/dist/component/strava/types/stravaApi/core/pathSerializer.gen.js.map +1 -0
- package/dist/component/strava/types/stravaApi/core/queryKeySerializer.gen.d.ts +19 -0
- package/dist/component/strava/types/stravaApi/core/queryKeySerializer.gen.d.ts.map +1 -0
- package/dist/component/strava/types/stravaApi/core/queryKeySerializer.gen.js +93 -0
- package/dist/component/strava/types/stravaApi/core/queryKeySerializer.gen.js.map +1 -0
- package/dist/component/strava/types/stravaApi/core/serverSentEvents.gen.d.ts +72 -0
- package/dist/component/strava/types/stravaApi/core/serverSentEvents.gen.d.ts.map +1 -0
- package/dist/component/strava/types/stravaApi/core/serverSentEvents.gen.js +134 -0
- package/dist/component/strava/types/stravaApi/core/serverSentEvents.gen.js.map +1 -0
- package/dist/component/strava/types/stravaApi/core/types.gen.d.ts +79 -0
- package/dist/component/strava/types/stravaApi/core/types.gen.d.ts.map +1 -0
- package/dist/component/strava/types/stravaApi/core/types.gen.js +3 -0
- package/dist/component/strava/types/stravaApi/core/types.gen.js.map +1 -0
- package/dist/component/strava/types/stravaApi/core/utils.gen.d.ts +20 -0
- package/dist/component/strava/types/stravaApi/core/utils.gen.d.ts.map +1 -0
- package/dist/component/strava/types/stravaApi/core/utils.gen.js +88 -0
- package/dist/component/strava/types/stravaApi/core/utils.gen.js.map +1 -0
- package/dist/component/strava/types/stravaApi/index.d.ts +3 -0
- package/dist/component/strava/types/stravaApi/index.d.ts.map +1 -0
- package/dist/component/strava/types/stravaApi/index.js +3 -0
- package/dist/component/strava/types/stravaApi/index.js.map +1 -0
- package/dist/component/strava/types/stravaApi/sdk.gen.d.ts +224 -0
- package/dist/component/strava/types/stravaApi/sdk.gen.d.ts.map +1 -0
- package/dist/component/strava/types/stravaApi/sdk.gen.js +361 -0
- package/dist/component/strava/types/stravaApi/sdk.gen.js.map +1 -0
- package/dist/component/strava/types/stravaApi/types.gen.d.ts +2209 -0
- package/dist/component/strava/types/stravaApi/types.gen.d.ts.map +1 -0
- package/dist/component/strava/types/stravaApi/types.gen.js +3 -0
- package/dist/component/strava/types/stravaApi/types.gen.js.map +1 -0
- package/dist/component/strava/types/stravaApi/zod.gen.d.ts +5332 -0
- package/dist/component/strava/types/stravaApi/zod.gen.d.ts.map +1 -0
- package/dist/component/strava/types/stravaApi/zod.gen.js +1009 -0
- package/dist/component/strava/types/stravaApi/zod.gen.js.map +1 -0
- package/dist/component/strava/utils.d.ts +15 -0
- package/dist/component/strava/utils.d.ts.map +1 -0
- package/dist/component/strava/utils.js +36 -0
- package/dist/component/strava/utils.js.map +1 -0
- package/dist/component/utils.d.ts +5 -0
- package/dist/component/utils.d.ts.map +1 -0
- package/dist/component/utils.js +11 -0
- package/dist/component/utils.js.map +1 -0
- package/package.json +131 -130
- package/src/client/index.ts +121 -52
- package/src/component/_generated/api.ts +18 -0
- package/src/component/_generated/component.ts +44 -11
- package/src/component/garmin/auth.ts +0 -9
- package/src/component/garmin/private.ts +0 -12
- package/src/component/garmin/public.ts +8 -1
- package/src/component/schema.ts +5 -3
- package/src/{strava → component/strava}/auth.ts +143 -185
- package/src/component/strava/client.ts +20 -0
- package/src/component/strava/private.ts +147 -89
- package/src/component/strava/public.ts +268 -110
- package/src/{strava → component/strava/transform}/activity.ts +256 -276
- package/src/{strava → component/strava/transform}/athlete.ts +41 -47
- package/src/{strava/maps/sport-type.ts → component/strava/transform/maps/sportType.ts} +100 -99
- package/src/component/strava/types/specs/strava-api.json +4796 -0
- package/src/component/strava/types/stravaApi/client/client.gen.ts +290 -0
- package/src/component/strava/types/stravaApi/client/index.ts +25 -0
- package/src/component/strava/types/stravaApi/client/types.gen.ts +214 -0
- package/src/component/strava/types/stravaApi/client/utils.gen.ts +316 -0
- package/src/component/strava/types/stravaApi/client.gen.ts +16 -0
- package/src/component/strava/types/stravaApi/core/auth.gen.ts +41 -0
- package/src/component/strava/types/stravaApi/core/bodySerializer.gen.ts +82 -0
- package/src/component/strava/types/stravaApi/core/params.gen.ts +169 -0
- package/src/component/strava/types/stravaApi/core/pathSerializer.gen.ts +171 -0
- package/src/component/strava/types/stravaApi/core/queryKeySerializer.gen.ts +117 -0
- package/src/component/strava/types/stravaApi/core/serverSentEvents.gen.ts +243 -0
- package/src/component/strava/types/stravaApi/core/types.gen.ts +104 -0
- package/src/component/strava/types/stravaApi/core/utils.gen.ts +140 -0
- package/src/component/strava/types/stravaApi/index.ts +4 -0
- package/src/component/strava/types/stravaApi/sdk.gen.ts +410 -0
- package/src/component/strava/types/stravaApi/types.gen.ts +2435 -0
- package/src/component/strava/types/stravaApi/zod.gen.ts +1132 -0
- package/src/component/strava/utils.ts +52 -0
- package/src/component/utils.ts +11 -0
- package/dist/strava/activity.d.ts +0 -121
- package/dist/strava/activity.d.ts.map +0 -1
- package/dist/strava/activity.js.map +0 -1
- package/dist/strava/athlete.d.ts.map +0 -1
- package/dist/strava/athlete.js.map +0 -1
- package/dist/strava/auth.d.ts.map +0 -1
- package/dist/strava/auth.js.map +0 -1
- package/dist/strava/client.d.ts +0 -93
- package/dist/strava/client.d.ts.map +0 -1
- package/dist/strava/client.js +0 -158
- package/dist/strava/client.js.map +0 -1
- package/dist/strava/index.d.ts +0 -13
- package/dist/strava/index.d.ts.map +0 -1
- package/dist/strava/index.js +0 -17
- package/dist/strava/index.js.map +0 -1
- package/dist/strava/maps/sport-type.d.ts +0 -7
- package/dist/strava/maps/sport-type.d.ts.map +0 -1
- package/dist/strava/maps/sport-type.js.map +0 -1
- package/dist/strava/sync.d.ts +0 -104
- package/dist/strava/sync.d.ts.map +0 -1
- package/dist/strava/sync.js +0 -87
- package/dist/strava/sync.js.map +0 -1
- package/dist/strava/types.d.ts +0 -266
- package/dist/strava/types.d.ts.map +0 -1
- package/dist/strava/types.js +0 -8
- package/dist/strava/types.js.map +0 -1
- package/src/strava/activity.test.ts +0 -415
- package/src/strava/athlete.test.ts +0 -139
- package/src/strava/auth.test.ts +0 -78
- package/src/strava/client.ts +0 -212
- package/src/strava/index.ts +0 -54
- package/src/strava/maps/sport-type.test.ts +0 -69
- package/src/strava/sync.ts +0 -168
- package/src/strava/types.ts +0 -361
|
@@ -1,185 +1,143 @@
|
|
|
1
|
-
// ─── Strava OAuth Helpers
|
|
2
|
-
// Pure helper functions for the Strava OAuth 2.0 Authorization Code flow.
|
|
3
|
-
// No external dependencies — uses the global `fetch`.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
* Strava access tokens expire after ~6 hours. Call this when the
|
|
145
|
-
* `expires_at` timestamp has passed to obtain a fresh access token.
|
|
146
|
-
*
|
|
147
|
-
* @returns A new token response with a fresh `access_token` and
|
|
148
|
-
* possibly a new `refresh_token`.
|
|
149
|
-
*
|
|
150
|
-
* @example
|
|
151
|
-
* ```ts
|
|
152
|
-
* const tokens = await refreshToken({
|
|
153
|
-
* clientId: process.env.STRAVA_CLIENT_ID!,
|
|
154
|
-
* clientSecret: process.env.STRAVA_CLIENT_SECRET!,
|
|
155
|
-
* refreshToken: storedRefreshToken,
|
|
156
|
-
* });
|
|
157
|
-
* // Update stored tokens
|
|
158
|
-
* ```
|
|
159
|
-
*/
|
|
160
|
-
export async function refreshToken(
|
|
161
|
-
opts: RefreshTokenOptions,
|
|
162
|
-
): Promise<OAuthTokenResponse> {
|
|
163
|
-
const base = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
164
|
-
const url = `${base}/oauth/token`;
|
|
165
|
-
|
|
166
|
-
const response = await fetch(url, {
|
|
167
|
-
method: "POST",
|
|
168
|
-
headers: { "Content-Type": "application/json" },
|
|
169
|
-
body: JSON.stringify({
|
|
170
|
-
client_id: opts.clientId,
|
|
171
|
-
client_secret: opts.clientSecret,
|
|
172
|
-
refresh_token: opts.refreshToken,
|
|
173
|
-
grant_type: "refresh_token",
|
|
174
|
-
}),
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
if (!response.ok) {
|
|
178
|
-
const body = await response.text().catch(() => "");
|
|
179
|
-
throw new Error(
|
|
180
|
-
`Strava OAuth error (refreshToken): ${response.status} ${response.statusText} — ${body}`,
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return (await response.json()) as OAuthTokenResponse;
|
|
185
|
-
}
|
|
1
|
+
// ─── Strava OAuth 2.0 Helpers ────────────────────────────────────────────────
|
|
2
|
+
// Pure helper functions for the Strava OAuth 2.0 Authorization Code flow.
|
|
3
|
+
// No external dependencies — uses the global `fetch`.
|
|
4
|
+
|
|
5
|
+
export interface StravaOAuthTokenResponse {
|
|
6
|
+
token_type: string;
|
|
7
|
+
expires_at: number; // Unix timestamp
|
|
8
|
+
expires_in: number; // seconds
|
|
9
|
+
refresh_token: string;
|
|
10
|
+
access_token: string;
|
|
11
|
+
athlete: {
|
|
12
|
+
id: number;
|
|
13
|
+
firstname: string;
|
|
14
|
+
lastname: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ─── Build Authorization URL ─────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
export interface BuildAuthUrlOptions {
|
|
21
|
+
/** Your Strava application's Client ID. */
|
|
22
|
+
clientId: string;
|
|
23
|
+
/** The URL Strava will redirect to after authorization. */
|
|
24
|
+
redirectUri: string;
|
|
25
|
+
/**
|
|
26
|
+
* Comma-separated Strava OAuth scopes.
|
|
27
|
+
* @default "read,activity:read_all,profile:read_all"
|
|
28
|
+
*/
|
|
29
|
+
scope?: string;
|
|
30
|
+
/** State parameter for CSRF protection (echoed back in the callback). */
|
|
31
|
+
state?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Build the Strava OAuth authorization URL.
|
|
36
|
+
*
|
|
37
|
+
* Redirect the user to this URL to begin the OAuth flow. After the user
|
|
38
|
+
* grants access, Strava will redirect back to `redirectUri` with a `code`
|
|
39
|
+
* query parameter.
|
|
40
|
+
*/
|
|
41
|
+
export function buildAuthUrl(opts: BuildAuthUrlOptions): string {
|
|
42
|
+
const params = new URLSearchParams({
|
|
43
|
+
client_id: opts.clientId,
|
|
44
|
+
redirect_uri: opts.redirectUri,
|
|
45
|
+
response_type: "code",
|
|
46
|
+
approval_prompt: "auto",
|
|
47
|
+
scope: opts.scope ?? "read,activity:read_all,profile:read_all",
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (opts.state) {
|
|
51
|
+
params.set("state", opts.state);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return `https://www.strava.com/oauth/authorize?${params.toString()}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ─── Exchange Authorization Code ─────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
export interface ExchangeCodeOptions {
|
|
60
|
+
/** Your Strava application's Client ID. */
|
|
61
|
+
clientId: string;
|
|
62
|
+
/** Your Strava application's Client Secret. */
|
|
63
|
+
clientSecret: string;
|
|
64
|
+
/** The authorization code from the OAuth callback. */
|
|
65
|
+
code: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Exchange an authorization code for access and refresh tokens.
|
|
70
|
+
*
|
|
71
|
+
* Call this from your OAuth callback endpoint after receiving the `code`
|
|
72
|
+
* query parameter from Strava.
|
|
73
|
+
*
|
|
74
|
+
* @returns The token response including `access_token`, `refresh_token`,
|
|
75
|
+
* `expires_at`, and the authenticated `athlete` profile.
|
|
76
|
+
*/
|
|
77
|
+
export async function exchangeCode(
|
|
78
|
+
opts: ExchangeCodeOptions,
|
|
79
|
+
): Promise<StravaOAuthTokenResponse> {
|
|
80
|
+
const response = await fetch("https://www.strava.com/oauth/token", {
|
|
81
|
+
method: "POST",
|
|
82
|
+
headers: { "Content-Type": "application/json" },
|
|
83
|
+
body: JSON.stringify({
|
|
84
|
+
client_id: opts.clientId,
|
|
85
|
+
client_secret: opts.clientSecret,
|
|
86
|
+
code: opts.code,
|
|
87
|
+
grant_type: "authorization_code",
|
|
88
|
+
}),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
const body = await response.text().catch(() => "");
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Strava OAuth error (exchangeCode): ${response.status} ${response.statusText} — ${body}`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return (await response.json()) as StravaOAuthTokenResponse;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ─── Refresh Token ───────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
export interface RefreshTokenOptions {
|
|
104
|
+
/** Your Strava application's Client ID. */
|
|
105
|
+
clientId: string;
|
|
106
|
+
/** Your Strava application's Client Secret. */
|
|
107
|
+
clientSecret: string;
|
|
108
|
+
/** The refresh token from a previous token exchange or refresh. */
|
|
109
|
+
refreshToken: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Refresh an expired access token using a refresh token.
|
|
114
|
+
*
|
|
115
|
+
* Strava access tokens expire after ~6 hours. Call this when the
|
|
116
|
+
* `expires_at` timestamp has passed to obtain a fresh access token.
|
|
117
|
+
*
|
|
118
|
+
* @returns A new token response with a fresh `access_token` and
|
|
119
|
+
* possibly a new `refresh_token`.
|
|
120
|
+
*/
|
|
121
|
+
export async function refreshToken(
|
|
122
|
+
opts: RefreshTokenOptions,
|
|
123
|
+
): Promise<StravaOAuthTokenResponse> {
|
|
124
|
+
const response = await fetch("https://www.strava.com/oauth/token", {
|
|
125
|
+
method: "POST",
|
|
126
|
+
headers: { "Content-Type": "application/json" },
|
|
127
|
+
body: JSON.stringify({
|
|
128
|
+
client_id: opts.clientId,
|
|
129
|
+
client_secret: opts.clientSecret,
|
|
130
|
+
refresh_token: opts.refreshToken,
|
|
131
|
+
grant_type: "refresh_token",
|
|
132
|
+
}),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (!response.ok) {
|
|
136
|
+
const body = await response.text().catch(() => "");
|
|
137
|
+
throw new Error(
|
|
138
|
+
`Strava OAuth error (refreshToken): ${response.status} ${response.statusText} — ${body}`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return (await response.json()) as StravaOAuthTokenResponse;
|
|
143
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// ─── Strava API Helper ──────────────────────────────────────────────────────
|
|
2
|
+
// Shared utility for calling the Strava API v3.
|
|
3
|
+
// Uses the auto-generated @hey-api/openapi-ts SDK.
|
|
4
|
+
|
|
5
|
+
import { createClient, createConfig } from "./types/stravaApi/client/index.js";
|
|
6
|
+
|
|
7
|
+
// ─── Strava API Client ─────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Create a configured Strava API client with Bearer auth.
|
|
11
|
+
*
|
|
12
|
+
* Each call creates a lightweight client instance — no connection pooling or
|
|
13
|
+
* shared state. Safe to create per-request.
|
|
14
|
+
*/
|
|
15
|
+
export function createStravaClient(accessToken: string) {
|
|
16
|
+
return createClient(createConfig({
|
|
17
|
+
baseUrl: "https://www.strava.com/api/v3",
|
|
18
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
@@ -1,89 +1,147 @@
|
|
|
1
|
-
// ─── Internal Token CRUD
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
import { v } from "convex/values";
|
|
5
|
-
import {
|
|
6
|
-
internalMutation,
|
|
7
|
-
internalQuery,
|
|
8
|
-
} from "../_generated/server";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export const
|
|
15
|
-
args: {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
1
|
+
// ─── Internal Token & Pending OAuth CRUD ─────────────────────────────────────
|
|
2
|
+
// Called only from strava/public.ts. Not exposed to the host app.
|
|
3
|
+
|
|
4
|
+
import { v } from "convex/values";
|
|
5
|
+
import {
|
|
6
|
+
internalMutation,
|
|
7
|
+
internalQuery,
|
|
8
|
+
} from "../_generated/server";
|
|
9
|
+
|
|
10
|
+
// ─── Internal Pending OAuth CRUD ─────────────────────────────────────────────
|
|
11
|
+
// Temporary storage for in-progress Strava OAuth flows.
|
|
12
|
+
// Bridges getStravaAuthUrl and completeStravaOAuth.
|
|
13
|
+
|
|
14
|
+
export const storePendingOAuth = internalMutation({
|
|
15
|
+
args: {
|
|
16
|
+
provider: v.string(),
|
|
17
|
+
state: v.string(),
|
|
18
|
+
userId: v.string(),
|
|
19
|
+
},
|
|
20
|
+
returns: v.null(),
|
|
21
|
+
handler: async (ctx, args) => {
|
|
22
|
+
await ctx.db.insert("pendingOAuth", {
|
|
23
|
+
...args,
|
|
24
|
+
createdAt: Date.now(),
|
|
25
|
+
});
|
|
26
|
+
return null;
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const getPendingOAuth = internalQuery({
|
|
31
|
+
args: { state: v.string() },
|
|
32
|
+
returns: v.union(
|
|
33
|
+
v.object({
|
|
34
|
+
_id: v.id("pendingOAuth"),
|
|
35
|
+
_creationTime: v.number(),
|
|
36
|
+
provider: v.string(),
|
|
37
|
+
state: v.string(),
|
|
38
|
+
userId: v.string(),
|
|
39
|
+
createdAt: v.number(),
|
|
40
|
+
}),
|
|
41
|
+
v.null(),
|
|
42
|
+
),
|
|
43
|
+
handler: async (ctx, args) => {
|
|
44
|
+
return await ctx.db
|
|
45
|
+
.query("pendingOAuth")
|
|
46
|
+
.withIndex("by_state", (q) => q.eq("state", args.state))
|
|
47
|
+
.first();
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export const deletePendingOAuth = internalMutation({
|
|
52
|
+
args: { state: v.string() },
|
|
53
|
+
returns: v.null(),
|
|
54
|
+
handler: async (ctx, args) => {
|
|
55
|
+
const pending = await ctx.db
|
|
56
|
+
.query("pendingOAuth")
|
|
57
|
+
.withIndex("by_state", (q) => q.eq("state", args.state))
|
|
58
|
+
.first();
|
|
59
|
+
if (pending) {
|
|
60
|
+
await ctx.db.delete(pending._id);
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// ─── Internal Token CRUD ─────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Store or update OAuth tokens for a connection.
|
|
70
|
+
* Upserts by connectionId — one token record per connection.
|
|
71
|
+
*/
|
|
72
|
+
export const storeTokens = internalMutation({
|
|
73
|
+
args: {
|
|
74
|
+
connectionId: v.id("connections"),
|
|
75
|
+
accessToken: v.string(),
|
|
76
|
+
refreshToken: v.string(),
|
|
77
|
+
expiresAt: v.number(),
|
|
78
|
+
},
|
|
79
|
+
returns: v.null(),
|
|
80
|
+
handler: async (ctx, args) => {
|
|
81
|
+
const existing = await ctx.db
|
|
82
|
+
.query("providerTokens")
|
|
83
|
+
.withIndex("by_connectionId", (q) =>
|
|
84
|
+
q.eq("connectionId", args.connectionId),
|
|
85
|
+
)
|
|
86
|
+
.first();
|
|
87
|
+
|
|
88
|
+
if (existing) {
|
|
89
|
+
await ctx.db.patch(existing._id, {
|
|
90
|
+
accessToken: args.accessToken,
|
|
91
|
+
refreshToken: args.refreshToken,
|
|
92
|
+
expiresAt: args.expiresAt,
|
|
93
|
+
});
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
await ctx.db.insert("providerTokens", args);
|
|
98
|
+
return null;
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get stored tokens for a connection.
|
|
104
|
+
*/
|
|
105
|
+
export const getTokens = internalQuery({
|
|
106
|
+
args: { connectionId: v.id("connections") },
|
|
107
|
+
returns: v.union(
|
|
108
|
+
v.object({
|
|
109
|
+
_id: v.id("providerTokens"),
|
|
110
|
+
_creationTime: v.number(),
|
|
111
|
+
connectionId: v.id("connections"),
|
|
112
|
+
accessToken: v.string(),
|
|
113
|
+
refreshToken: v.optional(v.string()),
|
|
114
|
+
expiresAt: v.optional(v.number()),
|
|
115
|
+
}),
|
|
116
|
+
v.null(),
|
|
117
|
+
),
|
|
118
|
+
handler: async (ctx, args) => {
|
|
119
|
+
return await ctx.db
|
|
120
|
+
.query("providerTokens")
|
|
121
|
+
.withIndex("by_connectionId", (q) =>
|
|
122
|
+
q.eq("connectionId", args.connectionId),
|
|
123
|
+
)
|
|
124
|
+
.first();
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Delete stored tokens for a connection.
|
|
130
|
+
*/
|
|
131
|
+
export const deleteTokens = internalMutation({
|
|
132
|
+
args: { connectionId: v.id("connections") },
|
|
133
|
+
returns: v.null(),
|
|
134
|
+
handler: async (ctx, args) => {
|
|
135
|
+
const existing = await ctx.db
|
|
136
|
+
.query("providerTokens")
|
|
137
|
+
.withIndex("by_connectionId", (q) =>
|
|
138
|
+
q.eq("connectionId", args.connectionId),
|
|
139
|
+
)
|
|
140
|
+
.first();
|
|
141
|
+
|
|
142
|
+
if (existing) {
|
|
143
|
+
await ctx.db.delete(existing._id);
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
},
|
|
147
|
+
});
|