@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.
Files changed (202) hide show
  1. package/dist/client/index.d.ts +96 -33
  2. package/dist/client/index.d.ts.map +1 -1
  3. package/dist/client/index.js +80 -35
  4. package/dist/client/index.js.map +1 -1
  5. package/dist/component/_generated/api.d.ts +18 -0
  6. package/dist/component/_generated/api.d.ts.map +1 -1
  7. package/dist/component/_generated/api.js.map +1 -1
  8. package/dist/component/_generated/component.d.ts +43 -9
  9. package/dist/component/_generated/component.d.ts.map +1 -1
  10. package/dist/component/garmin/auth.d.ts +0 -4
  11. package/dist/component/garmin/auth.d.ts.map +1 -1
  12. package/dist/component/garmin/auth.js +0 -8
  13. package/dist/component/garmin/auth.js.map +1 -1
  14. package/dist/component/garmin/private.d.ts +20 -3
  15. package/dist/component/garmin/private.d.ts.map +1 -1
  16. package/dist/component/garmin/private.js +17 -26
  17. package/dist/component/garmin/private.js.map +1 -1
  18. package/dist/component/garmin/public.d.ts +4 -4
  19. package/dist/component/garmin/public.d.ts.map +1 -1
  20. package/dist/component/garmin/public.js +6 -1
  21. package/dist/component/garmin/public.js.map +1 -1
  22. package/dist/component/garmin/webhooks.d.ts +4 -0
  23. package/dist/component/garmin/webhooks.d.ts.map +1 -1
  24. package/dist/component/garmin/webhooks.js +23 -18
  25. package/dist/component/garmin/webhooks.js.map +1 -1
  26. package/dist/component/schema.d.ts +2 -2
  27. package/dist/component/schema.d.ts.map +1 -1
  28. package/dist/component/schema.js +5 -3
  29. package/dist/component/schema.js.map +1 -1
  30. package/dist/{strava → component/strava}/auth.d.ts +15 -48
  31. package/dist/component/strava/auth.d.ts.map +1 -0
  32. package/dist/{strava → component/strava}/auth.js +4 -39
  33. package/dist/component/strava/auth.js.map +1 -0
  34. package/dist/component/strava/client.d.ts +8 -0
  35. package/dist/component/strava/client.d.ts.map +1 -0
  36. package/dist/component/strava/client.js +18 -0
  37. package/dist/component/strava/client.js.map +1 -0
  38. package/dist/component/strava/private.d.ts +19 -0
  39. package/dist/component/strava/private.d.ts.map +1 -1
  40. package/dist/component/strava/private.js +52 -2
  41. package/dist/component/strava/private.js.map +1 -1
  42. package/dist/component/strava/public.d.ts +87 -12
  43. package/dist/component/strava/public.d.ts.map +1 -1
  44. package/dist/component/strava/public.js +218 -92
  45. package/dist/component/strava/public.js.map +1 -1
  46. package/dist/component/strava/transform/activity.d.ts +19 -0
  47. package/dist/component/strava/transform/activity.d.ts.map +1 -0
  48. package/dist/{strava → component/strava/transform}/activity.js +21 -41
  49. package/dist/component/strava/transform/activity.js.map +1 -0
  50. package/dist/{strava → component/strava/transform}/athlete.d.ts +4 -10
  51. package/dist/component/strava/transform/athlete.d.ts.map +1 -0
  52. package/dist/{strava → component/strava/transform}/athlete.js +2 -8
  53. package/dist/component/strava/transform/athlete.js.map +1 -0
  54. package/dist/component/strava/transform/maps/sportType.d.ts +7 -0
  55. package/dist/component/strava/transform/maps/sportType.d.ts.map +1 -0
  56. package/dist/{strava/maps/sport-type.js → component/strava/transform/maps/sportType.js} +4 -2
  57. package/dist/component/strava/transform/maps/sportType.js.map +1 -0
  58. package/dist/component/strava/types/stravaApi/client/client.gen.d.ts +3 -0
  59. package/dist/component/strava/types/stravaApi/client/client.gen.d.ts.map +1 -0
  60. package/dist/component/strava/types/stravaApi/client/client.gen.js +236 -0
  61. package/dist/component/strava/types/stravaApi/client/client.gen.js.map +1 -0
  62. package/dist/component/strava/types/stravaApi/client/index.d.ts +9 -0
  63. package/dist/component/strava/types/stravaApi/client/index.d.ts.map +1 -0
  64. package/dist/component/strava/types/stravaApi/client/index.js +7 -0
  65. package/dist/component/strava/types/stravaApi/client/index.js.map +1 -0
  66. package/dist/component/strava/types/stravaApi/client/types.gen.d.ts +118 -0
  67. package/dist/component/strava/types/stravaApi/client/types.gen.d.ts.map +1 -0
  68. package/dist/component/strava/types/stravaApi/client/types.gen.js +3 -0
  69. package/dist/component/strava/types/stravaApi/client/types.gen.js.map +1 -0
  70. package/dist/component/strava/types/stravaApi/client/utils.gen.d.ts +34 -0
  71. package/dist/component/strava/types/stravaApi/client/utils.gen.d.ts.map +1 -0
  72. package/dist/component/strava/types/stravaApi/client/utils.gen.js +229 -0
  73. package/dist/component/strava/types/stravaApi/client/utils.gen.js.map +1 -0
  74. package/dist/component/strava/types/stravaApi/client.gen.d.ts +13 -0
  75. package/dist/component/strava/types/stravaApi/client.gen.d.ts.map +1 -0
  76. package/dist/component/strava/types/stravaApi/client.gen.js +4 -0
  77. package/dist/component/strava/types/stravaApi/client.gen.js.map +1 -0
  78. package/dist/component/strava/types/stravaApi/core/auth.gen.d.ts +19 -0
  79. package/dist/component/strava/types/stravaApi/core/auth.gen.d.ts.map +1 -0
  80. package/dist/component/strava/types/stravaApi/core/auth.gen.js +15 -0
  81. package/dist/component/strava/types/stravaApi/core/auth.gen.js.map +1 -0
  82. package/dist/component/strava/types/stravaApi/core/bodySerializer.gen.d.ts +26 -0
  83. package/dist/component/strava/types/stravaApi/core/bodySerializer.gen.d.ts.map +1 -0
  84. package/dist/component/strava/types/stravaApi/core/bodySerializer.gen.js +58 -0
  85. package/dist/component/strava/types/stravaApi/core/bodySerializer.gen.js.map +1 -0
  86. package/dist/component/strava/types/stravaApi/core/params.gen.d.ts +44 -0
  87. package/dist/component/strava/types/stravaApi/core/params.gen.d.ts.map +1 -0
  88. package/dist/component/strava/types/stravaApi/core/params.gen.js +101 -0
  89. package/dist/component/strava/types/stravaApi/core/params.gen.js.map +1 -0
  90. package/dist/component/strava/types/stravaApi/core/pathSerializer.gen.d.ts +34 -0
  91. package/dist/component/strava/types/stravaApi/core/pathSerializer.gen.d.ts.map +1 -0
  92. package/dist/component/strava/types/stravaApi/core/pathSerializer.gen.js +107 -0
  93. package/dist/component/strava/types/stravaApi/core/pathSerializer.gen.js.map +1 -0
  94. package/dist/component/strava/types/stravaApi/core/queryKeySerializer.gen.d.ts +19 -0
  95. package/dist/component/strava/types/stravaApi/core/queryKeySerializer.gen.d.ts.map +1 -0
  96. package/dist/component/strava/types/stravaApi/core/queryKeySerializer.gen.js +93 -0
  97. package/dist/component/strava/types/stravaApi/core/queryKeySerializer.gen.js.map +1 -0
  98. package/dist/component/strava/types/stravaApi/core/serverSentEvents.gen.d.ts +72 -0
  99. package/dist/component/strava/types/stravaApi/core/serverSentEvents.gen.d.ts.map +1 -0
  100. package/dist/component/strava/types/stravaApi/core/serverSentEvents.gen.js +134 -0
  101. package/dist/component/strava/types/stravaApi/core/serverSentEvents.gen.js.map +1 -0
  102. package/dist/component/strava/types/stravaApi/core/types.gen.d.ts +79 -0
  103. package/dist/component/strava/types/stravaApi/core/types.gen.d.ts.map +1 -0
  104. package/dist/component/strava/types/stravaApi/core/types.gen.js +3 -0
  105. package/dist/component/strava/types/stravaApi/core/types.gen.js.map +1 -0
  106. package/dist/component/strava/types/stravaApi/core/utils.gen.d.ts +20 -0
  107. package/dist/component/strava/types/stravaApi/core/utils.gen.d.ts.map +1 -0
  108. package/dist/component/strava/types/stravaApi/core/utils.gen.js +88 -0
  109. package/dist/component/strava/types/stravaApi/core/utils.gen.js.map +1 -0
  110. package/dist/component/strava/types/stravaApi/index.d.ts +3 -0
  111. package/dist/component/strava/types/stravaApi/index.d.ts.map +1 -0
  112. package/dist/component/strava/types/stravaApi/index.js +3 -0
  113. package/dist/component/strava/types/stravaApi/index.js.map +1 -0
  114. package/dist/component/strava/types/stravaApi/sdk.gen.d.ts +224 -0
  115. package/dist/component/strava/types/stravaApi/sdk.gen.d.ts.map +1 -0
  116. package/dist/component/strava/types/stravaApi/sdk.gen.js +361 -0
  117. package/dist/component/strava/types/stravaApi/sdk.gen.js.map +1 -0
  118. package/dist/component/strava/types/stravaApi/types.gen.d.ts +2209 -0
  119. package/dist/component/strava/types/stravaApi/types.gen.d.ts.map +1 -0
  120. package/dist/component/strava/types/stravaApi/types.gen.js +3 -0
  121. package/dist/component/strava/types/stravaApi/types.gen.js.map +1 -0
  122. package/dist/component/strava/types/stravaApi/zod.gen.d.ts +5332 -0
  123. package/dist/component/strava/types/stravaApi/zod.gen.d.ts.map +1 -0
  124. package/dist/component/strava/types/stravaApi/zod.gen.js +1009 -0
  125. package/dist/component/strava/types/stravaApi/zod.gen.js.map +1 -0
  126. package/dist/component/strava/utils.d.ts +15 -0
  127. package/dist/component/strava/utils.d.ts.map +1 -0
  128. package/dist/component/strava/utils.js +36 -0
  129. package/dist/component/strava/utils.js.map +1 -0
  130. package/dist/component/utils.d.ts +5 -0
  131. package/dist/component/utils.d.ts.map +1 -0
  132. package/dist/component/utils.js +11 -0
  133. package/dist/component/utils.js.map +1 -0
  134. package/package.json +131 -130
  135. package/src/client/index.ts +121 -52
  136. package/src/component/_generated/api.ts +18 -0
  137. package/src/component/_generated/component.ts +44 -11
  138. package/src/component/garmin/auth.ts +0 -9
  139. package/src/component/garmin/private.ts +0 -12
  140. package/src/component/garmin/public.ts +8 -1
  141. package/src/component/schema.ts +5 -3
  142. package/src/{strava → component/strava}/auth.ts +143 -185
  143. package/src/component/strava/client.ts +20 -0
  144. package/src/component/strava/private.ts +147 -89
  145. package/src/component/strava/public.ts +268 -110
  146. package/src/{strava → component/strava/transform}/activity.ts +256 -276
  147. package/src/{strava → component/strava/transform}/athlete.ts +41 -47
  148. package/src/{strava/maps/sport-type.ts → component/strava/transform/maps/sportType.ts} +100 -99
  149. package/src/component/strava/types/specs/strava-api.json +4796 -0
  150. package/src/component/strava/types/stravaApi/client/client.gen.ts +290 -0
  151. package/src/component/strava/types/stravaApi/client/index.ts +25 -0
  152. package/src/component/strava/types/stravaApi/client/types.gen.ts +214 -0
  153. package/src/component/strava/types/stravaApi/client/utils.gen.ts +316 -0
  154. package/src/component/strava/types/stravaApi/client.gen.ts +16 -0
  155. package/src/component/strava/types/stravaApi/core/auth.gen.ts +41 -0
  156. package/src/component/strava/types/stravaApi/core/bodySerializer.gen.ts +82 -0
  157. package/src/component/strava/types/stravaApi/core/params.gen.ts +169 -0
  158. package/src/component/strava/types/stravaApi/core/pathSerializer.gen.ts +171 -0
  159. package/src/component/strava/types/stravaApi/core/queryKeySerializer.gen.ts +117 -0
  160. package/src/component/strava/types/stravaApi/core/serverSentEvents.gen.ts +243 -0
  161. package/src/component/strava/types/stravaApi/core/types.gen.ts +104 -0
  162. package/src/component/strava/types/stravaApi/core/utils.gen.ts +140 -0
  163. package/src/component/strava/types/stravaApi/index.ts +4 -0
  164. package/src/component/strava/types/stravaApi/sdk.gen.ts +410 -0
  165. package/src/component/strava/types/stravaApi/types.gen.ts +2435 -0
  166. package/src/component/strava/types/stravaApi/zod.gen.ts +1132 -0
  167. package/src/component/strava/utils.ts +52 -0
  168. package/src/component/utils.ts +11 -0
  169. package/dist/strava/activity.d.ts +0 -121
  170. package/dist/strava/activity.d.ts.map +0 -1
  171. package/dist/strava/activity.js.map +0 -1
  172. package/dist/strava/athlete.d.ts.map +0 -1
  173. package/dist/strava/athlete.js.map +0 -1
  174. package/dist/strava/auth.d.ts.map +0 -1
  175. package/dist/strava/auth.js.map +0 -1
  176. package/dist/strava/client.d.ts +0 -93
  177. package/dist/strava/client.d.ts.map +0 -1
  178. package/dist/strava/client.js +0 -158
  179. package/dist/strava/client.js.map +0 -1
  180. package/dist/strava/index.d.ts +0 -13
  181. package/dist/strava/index.d.ts.map +0 -1
  182. package/dist/strava/index.js +0 -17
  183. package/dist/strava/index.js.map +0 -1
  184. package/dist/strava/maps/sport-type.d.ts +0 -7
  185. package/dist/strava/maps/sport-type.d.ts.map +0 -1
  186. package/dist/strava/maps/sport-type.js.map +0 -1
  187. package/dist/strava/sync.d.ts +0 -104
  188. package/dist/strava/sync.d.ts.map +0 -1
  189. package/dist/strava/sync.js +0 -87
  190. package/dist/strava/sync.js.map +0 -1
  191. package/dist/strava/types.d.ts +0 -266
  192. package/dist/strava/types.d.ts.map +0 -1
  193. package/dist/strava/types.js +0 -8
  194. package/dist/strava/types.js.map +0 -1
  195. package/src/strava/activity.test.ts +0 -415
  196. package/src/strava/athlete.test.ts +0 -139
  197. package/src/strava/auth.test.ts +0 -78
  198. package/src/strava/client.ts +0 -212
  199. package/src/strava/index.ts +0 -54
  200. package/src/strava/maps/sport-type.test.ts +0 -69
  201. package/src/strava/sync.ts +0 -168
  202. 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
- import type { OAuthTokenResponse } from "./types.js";
6
-
7
- const DEFAULT_BASE_URL = "https://www.strava.com";
8
-
9
- // ─── Build Authorization URL ─────────────────────────────────────────────────
10
-
11
- export interface BuildAuthUrlOptions {
12
- /** Your Strava application's Client ID. */
13
- clientId: string;
14
- /** The URL Strava will redirect to after authorization. */
15
- redirectUri: string;
16
- /**
17
- * Comma-separated Strava OAuth scopes.
18
- * @default "read,activity:read_all,profile:read_all"
19
- */
20
- scope?: string;
21
- /** Optional state parameter for CSRF protection. */
22
- state?: string;
23
- /**
24
- * Base URL of the Strava site.
25
- * @default "https://www.strava.com"
26
- */
27
- baseUrl?: string;
28
- }
29
-
30
- /**
31
- * Build the Strava OAuth authorization URL.
32
- *
33
- * Redirect the user to this URL to begin the OAuth flow. After the user
34
- * grants access, Strava will redirect back to `redirectUri` with a `code`
35
- * query parameter.
36
- *
37
- * @example
38
- * ```ts
39
- * const url = buildAuthUrl({
40
- * clientId: process.env.STRAVA_CLIENT_ID!,
41
- * redirectUri: "https://your-app.com/api/strava/callback",
42
- * });
43
- * // Redirect user to `url`
44
- * ```
45
- */
46
- export function buildAuthUrl(opts: BuildAuthUrlOptions): string {
47
- const base = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
48
- const params = new URLSearchParams({
49
- client_id: opts.clientId,
50
- redirect_uri: opts.redirectUri,
51
- response_type: "code",
52
- approval_prompt: "auto",
53
- scope: opts.scope ?? "read,activity:read_all,profile:read_all",
54
- });
55
-
56
- if (opts.state) {
57
- params.set("state", opts.state);
58
- }
59
-
60
- return `${base}/oauth/authorize?${params.toString()}`;
61
- }
62
-
63
- // ─── Exchange Authorization Code ─────────────────────────────────────────────
64
-
65
- export interface ExchangeCodeOptions {
66
- /** Your Strava application's Client ID. */
67
- clientId: string;
68
- /** Your Strava application's Client Secret. */
69
- clientSecret: string;
70
- /** The authorization code from the OAuth callback. */
71
- code: string;
72
- /**
73
- * Base URL of the Strava site.
74
- * @default "https://www.strava.com"
75
- */
76
- baseUrl?: string;
77
- }
78
-
79
- /**
80
- * Exchange an authorization code for access and refresh tokens.
81
- *
82
- * Call this from your OAuth callback endpoint after receiving the `code`
83
- * query parameter from Strava.
84
- *
85
- * @returns The token response including `access_token`, `refresh_token`,
86
- * `expires_at`, and the authenticated `athlete` profile.
87
- *
88
- * @example
89
- * ```ts
90
- * const tokens = await exchangeCode({
91
- * clientId: process.env.STRAVA_CLIENT_ID!,
92
- * clientSecret: process.env.STRAVA_CLIENT_SECRET!,
93
- * code: request.query.code,
94
- * });
95
- * // Store tokens.access_token, tokens.refresh_token, tokens.expires_at
96
- * ```
97
- */
98
- export async function exchangeCode(
99
- opts: ExchangeCodeOptions,
100
- ): Promise<OAuthTokenResponse> {
101
- const base = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
102
- const url = `${base}/oauth/token`;
103
-
104
- const response = await fetch(url, {
105
- method: "POST",
106
- headers: { "Content-Type": "application/json" },
107
- body: JSON.stringify({
108
- client_id: opts.clientId,
109
- client_secret: opts.clientSecret,
110
- code: opts.code,
111
- grant_type: "authorization_code",
112
- }),
113
- });
114
-
115
- if (!response.ok) {
116
- const body = await response.text().catch(() => "");
117
- throw new Error(
118
- `Strava OAuth error (exchangeCode): ${response.status} ${response.statusText} ${body}`,
119
- );
120
- }
121
-
122
- return (await response.json()) as OAuthTokenResponse;
123
- }
124
-
125
- // ─── Refresh Token ───────────────────────────────────────────────────────────
126
-
127
- export interface RefreshTokenOptions {
128
- /** Your Strava application's Client ID. */
129
- clientId: string;
130
- /** Your Strava application's Client Secret. */
131
- clientSecret: string;
132
- /** The refresh token from a previous token exchange or refresh. */
133
- refreshToken: string;
134
- /**
135
- * Base URL of the Strava site.
136
- * @default "https://www.strava.com"
137
- */
138
- baseUrl?: string;
139
- }
140
-
141
- /**
142
- * Refresh an expired access token using a refresh token.
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
- // These are only callable from within the component (actions in this file).
3
-
4
- import { v } from "convex/values";
5
- import {
6
- internalMutation,
7
- internalQuery,
8
- } from "../_generated/server";
9
-
10
- /**
11
- * Store or update OAuth tokens for a connection.
12
- * Upserts by connectionId — one token record per connection.
13
- */
14
- export const storeTokens = internalMutation({
15
- args: {
16
- connectionId: v.id("connections"),
17
- accessToken: v.string(),
18
- refreshToken: v.string(),
19
- expiresAt: v.number(),
20
- },
21
- returns: v.null(),
22
- handler: async (ctx, args) => {
23
- const existing = await ctx.db
24
- .query("providerTokens")
25
- .withIndex("by_connectionId", (q) =>
26
- q.eq("connectionId", args.connectionId),
27
- )
28
- .first();
29
-
30
- if (existing) {
31
- await ctx.db.patch(existing._id, {
32
- accessToken: args.accessToken,
33
- refreshToken: args.refreshToken,
34
- expiresAt: args.expiresAt,
35
- });
36
- return null;
37
- }
38
-
39
- await ctx.db.insert("providerTokens", args);
40
- return null;
41
- },
42
- });
43
-
44
- /**
45
- * Get stored tokens for a connection.
46
- */
47
- export const getTokens = internalQuery({
48
- args: { connectionId: v.id("connections") },
49
- returns: v.union(
50
- v.object({
51
- _id: v.id("providerTokens"),
52
- _creationTime: v.number(),
53
- connectionId: v.id("connections"),
54
- accessToken: v.string(),
55
- refreshToken: v.optional(v.string()),
56
- expiresAt: v.optional(v.number()),
57
- }),
58
- v.null(),
59
- ),
60
- handler: async (ctx, args) => {
61
- return await ctx.db
62
- .query("providerTokens")
63
- .withIndex("by_connectionId", (q) =>
64
- q.eq("connectionId", args.connectionId),
65
- )
66
- .first();
67
- },
68
- });
69
-
70
- /**
71
- * Delete stored tokens for a connection.
72
- */
73
- export const deleteTokens = internalMutation({
74
- args: { connectionId: v.id("connections") },
75
- returns: v.null(),
76
- handler: async (ctx, args) => {
77
- const existing = await ctx.db
78
- .query("providerTokens")
79
- .withIndex("by_connectionId", (q) =>
80
- q.eq("connectionId", args.connectionId),
81
- )
82
- .first();
83
-
84
- if (existing) {
85
- await ctx.db.delete(existing._id);
86
- }
87
- return null;
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
+ });