@nativesquare/soma 0.13.0 → 0.14.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 (96) hide show
  1. package/dist/client/garmin.d.ts +4 -1
  2. package/dist/client/garmin.d.ts.map +1 -1
  3. package/dist/client/garmin.js +4 -1
  4. package/dist/client/garmin.js.map +1 -1
  5. package/dist/client/index.d.ts +2 -2
  6. package/dist/client/index.d.ts.map +1 -1
  7. package/dist/client/index.js +1 -1
  8. package/dist/client/index.js.map +1 -1
  9. package/dist/client/strava.d.ts +48 -34
  10. package/dist/client/strava.d.ts.map +1 -1
  11. package/dist/client/strava.js +141 -23
  12. package/dist/client/strava.js.map +1 -1
  13. package/dist/client/types.d.ts +108 -0
  14. package/dist/client/types.d.ts.map +1 -1
  15. package/dist/component/_generated/api.d.ts +2 -2
  16. package/dist/component/_generated/api.d.ts.map +1 -1
  17. package/dist/component/_generated/component.d.ts +19 -17
  18. package/dist/component/_generated/component.d.ts.map +1 -1
  19. package/dist/component/garmin/auth.d.ts +2 -1
  20. package/dist/component/garmin/auth.d.ts.map +1 -1
  21. package/dist/component/garmin/auth.js +6 -1
  22. package/dist/component/garmin/auth.js.map +1 -1
  23. package/dist/component/garmin/private.d.ts +17 -75
  24. package/dist/component/garmin/private.d.ts.map +1 -1
  25. package/dist/component/garmin/private.js +4 -167
  26. package/dist/component/garmin/private.js.map +1 -1
  27. package/dist/component/garmin/public.d.ts +18 -33
  28. package/dist/component/garmin/public.d.ts.map +1 -1
  29. package/dist/component/garmin/public.js +23 -22
  30. package/dist/component/garmin/public.js.map +1 -1
  31. package/dist/component/garmin/webhooks.d.ts +3 -6
  32. package/dist/component/garmin/webhooks.d.ts.map +1 -1
  33. package/dist/component/garmin/webhooks.js +17 -28
  34. package/dist/component/garmin/webhooks.js.map +1 -1
  35. package/dist/component/private.d.ts +59 -0
  36. package/dist/component/private.d.ts.map +1 -1
  37. package/dist/component/private.js +182 -1
  38. package/dist/component/private.js.map +1 -1
  39. package/dist/component/strava/auth.d.ts +2 -1
  40. package/dist/component/strava/auth.d.ts.map +1 -1
  41. package/dist/component/strava/auth.js +6 -1
  42. package/dist/component/strava/auth.js.map +1 -1
  43. package/dist/component/strava/public.d.ts +26 -50
  44. package/dist/component/strava/public.d.ts.map +1 -1
  45. package/dist/component/strava/public.js +88 -132
  46. package/dist/component/strava/public.js.map +1 -1
  47. package/dist/component/strava/webhooks.d.ts +17 -0
  48. package/dist/component/strava/webhooks.d.ts.map +1 -0
  49. package/dist/component/strava/webhooks.js +231 -0
  50. package/dist/component/strava/webhooks.js.map +1 -0
  51. package/dist/component/utils.d.ts +10 -0
  52. package/dist/component/utils.d.ts.map +1 -1
  53. package/dist/component/utils.js.map +1 -1
  54. package/dist/component/validators/athlete.d.ts +6 -0
  55. package/dist/component/validators/athlete.d.ts.map +1 -1
  56. package/dist/component/validators/athlete.js.map +1 -1
  57. package/dist/component/validators/nutrition.d.ts +6 -0
  58. package/dist/component/validators/nutrition.d.ts.map +1 -1
  59. package/dist/component/validators/nutrition.js.map +1 -1
  60. package/dist/component/validators/shared.d.ts +3 -0
  61. package/dist/component/validators/shared.d.ts.map +1 -1
  62. package/dist/component/validators/shared.js +1 -1
  63. package/dist/component/validators/shared.js.map +1 -1
  64. package/dist/component/validators/sleep.d.ts +6 -0
  65. package/dist/component/validators/sleep.d.ts.map +1 -1
  66. package/dist/component/validators/sleep.js.map +1 -1
  67. package/dist/validators.d.ts +7 -1
  68. package/dist/validators.d.ts.map +1 -1
  69. package/dist/validators.js +6 -6
  70. package/dist/validators.js.map +1 -1
  71. package/package.json +1 -1
  72. package/src/client/garmin.ts +4 -1
  73. package/src/client/index.ts +8 -1
  74. package/src/client/strava.ts +193 -27
  75. package/src/client/types.ts +125 -0
  76. package/src/component/_generated/api.ts +2 -2
  77. package/src/component/_generated/component.ts +25 -6
  78. package/src/component/garmin/auth.ts +9 -2
  79. package/src/component/garmin/private.ts +22 -243
  80. package/src/component/garmin/public.ts +56 -54
  81. package/src/component/garmin/webhooks.ts +38 -55
  82. package/src/component/private.ts +245 -1
  83. package/src/component/strava/auth.ts +9 -2
  84. package/src/component/strava/public.ts +105 -171
  85. package/src/component/strava/webhooks.ts +312 -0
  86. package/src/component/utils.ts +11 -0
  87. package/src/component/validators/athlete.ts +6 -0
  88. package/src/component/validators/nutrition.ts +6 -0
  89. package/src/component/validators/shared.ts +5 -2
  90. package/src/component/validators/sleep.ts +6 -0
  91. package/src/validators.ts +34 -7
  92. package/dist/component/strava/private.d.ts +0 -49
  93. package/dist/component/strava/private.d.ts.map +0 -1
  94. package/dist/component/strava/private.js +0 -121
  95. package/dist/component/strava/private.js.map +0 -1
  96. package/src/component/strava/private.ts +0 -147
@@ -1,14 +1,22 @@
1
1
  import type { SomaComponent } from "./index.js";
2
- import type { ActionCtx, SomaStravaConfig, RegisterRoutesOptions } from "./types.js";
2
+ import type {
3
+ ActionCtx,
4
+ SomaStravaConfig,
5
+ RegisterRoutesOptions,
6
+ StravaWebhookActionResult,
7
+ StravaWebhookEvent,
8
+ StravaWebhookEventName,
9
+ } from "./types.js";
3
10
  import { httpActionGeneric, type HttpRouter } from "convex/server";
4
11
 
5
- export const STRAVA_CALLBACK_PATH = "/api/strava/callback";
12
+ export const STRAVA_OAUTH_CALLBACK_PATH = "/api/strava/callback";
13
+ export const STRAVA_WEBHOOK_BASE_PATH = "/api/strava/webhook";
6
14
 
7
15
  export class SomaStrava {
8
16
  constructor(
9
17
  private component: SomaComponent,
10
18
  private requireConfig: () => SomaStravaConfig,
11
- ) {}
19
+ ) { }
12
20
 
13
21
  /**
14
22
  * Generate a Strava OAuth authorization URL.
@@ -19,7 +27,9 @@ export class SomaStrava {
19
27
  *
20
28
  * @param ctx - Action context from the host app
21
29
  * @param opts.userId - The host app's user identifier
22
- * @param opts.redirectUri - The URL Strava will redirect to after authorization
30
+ * @param opts.redirectUri - The URL Strava will redirect to after authorization.
31
+ * This should match the `registerRoutes` callback path
32
+ * (default: `${CONVEX_SITE_URL}/api/strava/callback`).
23
33
  * @param opts.scope - Comma-separated Strava OAuth scopes (default: "read,activity:read_all,profile:read_all")
24
34
  * @returns `{ authUrl, state }`
25
35
  *
@@ -46,32 +56,30 @@ export class SomaStrava {
46
56
  }
47
57
 
48
58
  /**
49
- * Sync activities from Strava for an already-connected user.
59
+ * Disconnect a user from Strava.
50
60
  *
51
- * Automatically refreshes the access token if expired. Fetches the
52
- * athlete profile and activities, transforms them, and ingests into Soma.
61
+ * Revokes the token at Strava (best-effort), deletes stored tokens,
62
+ * and sets the connection to inactive.
53
63
  *
54
64
  * @param ctx - Action context from the host app
55
65
  * @param args.userId - The host app's user identifier
56
- * @param args.after - Only sync activities after this Unix epoch timestamp (for incremental sync)
57
- * @returns `{ synced, errors }`
58
66
  *
59
67
  * @example
60
68
  * ```ts
61
- * export const syncStrava = action({
69
+ * export const disconnectStrava = action({
62
70
  * args: { userId: v.string() },
63
71
  * handler: async (ctx, { userId }) => {
64
- * return await soma.strava.sync(ctx, { userId });
72
+ * await soma.strava.disconnect(ctx, { userId });
65
73
  * },
66
74
  * });
67
75
  * ```
68
76
  */
69
- async sync(
77
+ async disconnect(
70
78
  ctx: ActionCtx,
71
- args: { userId: string; after?: number },
79
+ args: { userId: string },
72
80
  ) {
73
81
  const config = this.requireConfig();
74
- return await ctx.runAction(this.component.strava.public.syncStrava, {
82
+ return await ctx.runAction(this.component.strava.public.disconnectStrava, {
75
83
  ...args,
76
84
  clientId: config.clientId,
77
85
  clientSecret: config.clientSecret,
@@ -79,30 +87,78 @@ export class SomaStrava {
79
87
  }
80
88
 
81
89
  /**
82
- * Disconnect a user from Strava.
90
+ * Pull the authenticated athlete profile from Strava.
83
91
  *
84
- * Revokes the token at Strava (best-effort), deletes stored tokens,
85
- * and sets the connection to inactive.
92
+ * Automatically refreshes the access token if expired.
86
93
  *
87
94
  * @param ctx - Action context from the host app
88
95
  * @param args.userId - The host app's user identifier
96
+ */
97
+ async pullAthlete(
98
+ ctx: ActionCtx,
99
+ args: { userId: string },
100
+ ) {
101
+ const config = this.requireConfig();
102
+ return await ctx.runAction(this.component.strava.public.pullAthlete, {
103
+ ...args,
104
+ clientId: config.clientId,
105
+ clientSecret: config.clientSecret,
106
+ });
107
+ }
108
+
109
+ /**
110
+ * Pull activities from Strava.
111
+ *
112
+ * Fetches activities, transforms them into the normalized Soma schema,
113
+ * and ingests them with automatic deduplication.
114
+ *
115
+ * @param ctx - Action context from the host app
116
+ * @param args.userId - The host app's user identifier
117
+ * @param args.after - Only pull activities after this Unix epoch timestamp
118
+ * @param args.before - Only pull activities before this Unix epoch timestamp
119
+ */
120
+ async pullActivities(
121
+ ctx: ActionCtx,
122
+ args: {
123
+ userId: string;
124
+ after?: number;
125
+ before?: number;
126
+ },
127
+ ) {
128
+ const config = this.requireConfig();
129
+ return await ctx.runAction(this.component.strava.public.pullActivities, {
130
+ ...args,
131
+ clientId: config.clientId,
132
+ clientSecret: config.clientSecret,
133
+ });
134
+ }
135
+
136
+ /**
137
+ * Pull all supported data types from Strava in a single call.
138
+ *
139
+ * Equivalent to calling `pullAthlete` and `pullActivities`. Automatically
140
+ * refreshes the access token if expired.
141
+ *
142
+ * @param ctx - Action context from the host app
143
+ * @param args.userId - The host app's user identifier
144
+ * @param args.after - Only pull activities after this Unix epoch timestamp
145
+ * @param args.before - Only pull activities before this Unix epoch timestamp
89
146
  *
90
147
  * @example
91
148
  * ```ts
92
- * export const disconnectStrava = action({
93
- * args: { userId: v.string() },
94
- * handler: async (ctx, { userId }) => {
95
- * await soma.strava.disconnect(ctx, { userId });
96
- * },
97
- * });
149
+ * await soma.strava.pullAll(ctx, { userId: "user_123" });
98
150
  * ```
99
151
  */
100
- async disconnect(
152
+ async pullAll(
101
153
  ctx: ActionCtx,
102
- args: { userId: string },
154
+ args: {
155
+ userId: string;
156
+ after?: number;
157
+ before?: number;
158
+ },
103
159
  ) {
104
160
  const config = this.requireConfig();
105
- return await ctx.runAction(this.component.strava.public.disconnectStrava, {
161
+ return await ctx.runAction(this.component.strava.public.pullAll, {
106
162
  ...args,
107
163
  clientId: config.clientId,
108
164
  clientSecret: config.clientSecret,
@@ -115,7 +171,7 @@ export class SomaStrava {
115
171
  opts?: RegisterRoutesOptions["strava"],
116
172
  ) {
117
173
  const oauth = opts?.oauth ?? {};
118
- const path = oauth.path ?? STRAVA_CALLBACK_PATH;
174
+ const path = oauth.path ?? STRAVA_OAUTH_CALLBACK_PATH;
119
175
 
120
176
  http.route({
121
177
  path,
@@ -195,5 +251,115 @@ export class SomaStrava {
195
251
  });
196
252
  }),
197
253
  });
254
+
255
+ // ── Strava Webhook Routes ──────────────────────────────────────
256
+ const webhookCfg = typeof opts?.webhook === "object" ? opts.webhook : undefined;
257
+ if (webhookCfg?.events) {
258
+ const webhookPath = webhookCfg.path ?? STRAVA_WEBHOOK_BASE_PATH;
259
+ const verifyToken = webhookCfg.verifyToken ?? process.env.STRAVA_WEBHOOK_VERIFY_TOKEN;
260
+ const autoIngest = webhookCfg.autoIngest ?? true;
261
+
262
+ // GET: Strava subscription verification challenge
263
+ http.route({
264
+ path: webhookPath,
265
+ method: "GET",
266
+ handler: httpActionGeneric(async (_ctx, request) => {
267
+ const url = new URL(request.url);
268
+ const mode = url.searchParams.get("hub.mode");
269
+ const challenge = url.searchParams.get("hub.challenge");
270
+ const token = url.searchParams.get("hub.verify_token");
271
+
272
+ if (mode === "subscribe" && token === verifyToken && challenge) {
273
+ return new Response(
274
+ JSON.stringify({ "hub.challenge": challenge }),
275
+ { status: 200, headers: { "Content-Type": "application/json" } },
276
+ );
277
+ }
278
+
279
+ return new Response("Forbidden", { status: 403 });
280
+ }),
281
+ });
282
+
283
+ // POST: Strava webhook event
284
+ http.route({
285
+ path: webhookPath,
286
+ method: "POST",
287
+ handler: httpActionGeneric(async (ctx, request) => {
288
+ let payload: unknown;
289
+ try {
290
+ payload = await request.json();
291
+ } catch {
292
+ return new Response("Invalid JSON body", { status: 400 });
293
+ }
294
+
295
+ // Determine event name and check if it's registered
296
+ const p = payload as { object_type?: string; aspect_type?: string };
297
+ const eventName = `${p.object_type}-${p.aspect_type}` as StravaWebhookEventName;
298
+
299
+ if (!webhookCfg.events?.[eventName]) {
300
+ // Event type not registered — silently ignore
301
+ return new Response("OK", { status: 200 });
302
+ }
303
+
304
+ const webhookClientId = opts?.clientId ?? process.env.STRAVA_CLIENT_ID;
305
+ const webhookClientSecret = opts?.clientSecret ?? process.env.STRAVA_CLIENT_SECRET;
306
+
307
+ if (!webhookClientId || !webhookClientSecret) {
308
+ console.error("[soma] Strava webhook: missing client credentials");
309
+ return new Response("OK", { status: 200 });
310
+ }
311
+
312
+ let result: StravaWebhookActionResult | undefined;
313
+ try {
314
+ result = await ctx.runAction(
315
+ component.strava.webhooks.handleStravaWebhook,
316
+ { payload, clientId: webhookClientId, clientSecret: webhookClientSecret, autoIngest },
317
+ );
318
+ } catch (error) {
319
+ console.error(
320
+ "[soma] Strava webhook error:",
321
+ error instanceof Error ? error.message : error,
322
+ );
323
+ }
324
+
325
+ if (result) {
326
+ const event: StravaWebhookEvent = {
327
+ eventName,
328
+ errors: result.errors,
329
+ rawPayload: payload,
330
+ items: result.items,
331
+ };
332
+
333
+ // Fire per-event handler
334
+ const specificHandler = webhookCfg.events?.[eventName];
335
+ if (typeof specificHandler === "function") {
336
+ try {
337
+ await specificHandler(ctx, event);
338
+ } catch (callbackError) {
339
+ console.error(
340
+ `[soma] strava webhook events["${eventName}"] callback error:`,
341
+ callbackError instanceof Error ? callbackError.message : callbackError,
342
+ );
343
+ }
344
+ }
345
+
346
+ // Fire catch-all handler
347
+ if (webhookCfg.onEvent) {
348
+ try {
349
+ await webhookCfg.onEvent(ctx, event);
350
+ } catch (callbackError) {
351
+ console.error(
352
+ "[soma] strava webhook onEvent callback error:",
353
+ callbackError instanceof Error ? callbackError.message : callbackError,
354
+ );
355
+ }
356
+ }
357
+ }
358
+
359
+ // Always return 200 to prevent Strava retries
360
+ return new Response("OK", { status: 200 });
361
+ }),
362
+ });
363
+ }
198
364
  }
199
365
  }
@@ -208,6 +208,79 @@ export type GarminWebhookHandler = (
208
208
  event: GarminWebhookEvent,
209
209
  ) => Promise<void>;
210
210
 
211
+ // ─── Strava Webhook Types ───────────────────────────────────────────────────
212
+
213
+ /** Strava webhook event type names (object_type + aspect_type combined). */
214
+ export type StravaWebhookEventName =
215
+ | "activity-create"
216
+ | "activity-update"
217
+ | "activity-delete"
218
+ | "athlete-update"
219
+ | "athlete-deauthorize";
220
+
221
+ /** Args accepted by the Strava webhook handler action inside the component. */
222
+ export type StravaWebhookActionArgs = {
223
+ payload: {
224
+ object_type: string;
225
+ object_id: number;
226
+ aspect_type: string;
227
+ owner_id: number;
228
+ subscription_id: number;
229
+ event_time: number;
230
+ updates?: Record<string, unknown>;
231
+ };
232
+ clientId: string;
233
+ clientSecret: string;
234
+ autoIngest?: boolean;
235
+ };
236
+
237
+ /** Result returned by the Strava webhook handler action inside the component. */
238
+ export interface StravaWebhookActionResult {
239
+ errors: SomaError[];
240
+ items: StravaWebhookItem[];
241
+ }
242
+
243
+ /** A single transformed item from a Strava webhook, with user/connection mapping. */
244
+ export interface StravaWebhookItem {
245
+ /** The Soma connection ID that this data belongs to. */
246
+ connectionId: string;
247
+ /** The host app's user ID. */
248
+ userId: string;
249
+ /** The transformed data in Soma's normalized format, or null for delete/deauthorize events. */
250
+ data: Record<string, unknown> | null;
251
+ }
252
+
253
+ /**
254
+ * Event data passed to Strava webhook handlers after processing.
255
+ *
256
+ * The host app receives the full set of transformed items plus the raw Strava
257
+ * notification payload, regardless of the `autoIngest` setting.
258
+ */
259
+ export interface StravaWebhookEvent {
260
+ /** The combined event name (e.g. `"activity-create"`, `"athlete-deauthorize"`). */
261
+ eventName: StravaWebhookEventName;
262
+ /** Errors encountered during connection resolution, data fetching, or transformation. */
263
+ errors: SomaError[];
264
+ /** The raw Strava webhook notification payload. */
265
+ rawPayload: unknown;
266
+ /**
267
+ * Transformed items in Soma's normalized format.
268
+ *
269
+ * For `activity-create` / `activity-update` / `athlete-update`, contains the
270
+ * fetched and transformed data. For `activity-delete` / `athlete-deauthorize`,
271
+ * items have `data: null`.
272
+ *
273
+ * When `autoIngest` is `true`, these are the same items written to the database.
274
+ */
275
+ items: StravaWebhookItem[];
276
+ }
277
+
278
+ /** Handler for a specific Strava webhook event or the catch-all `onEvent`. */
279
+ export type StravaWebhookHandler = (
280
+ ctx: GenericActionCtx<GenericDataModel>,
281
+ event: StravaWebhookEvent,
282
+ ) => Promise<void>;
283
+
211
284
  // ─── Route Registration Options ─────────────────────────────────────────────
212
285
 
213
286
  /**
@@ -275,6 +348,51 @@ export interface GarminWebhookOptions {
275
348
  events?: Partial<Record<GarminWebhookEventName, GarminWebhookHandler | true>>;
276
349
  }
277
350
 
351
+ export interface StravaWebhookOptions {
352
+ /** HTTP path for the webhook endpoint. @default "/api/strava/webhook" */
353
+ path?: string;
354
+ /**
355
+ * Strava webhook subscription verify token.
356
+ * Must match the token used when creating the subscription via Strava's API.
357
+ * Falls back to `STRAVA_WEBHOOK_VERIFY_TOKEN` env var.
358
+ */
359
+ verifyToken?: string;
360
+ /**
361
+ * Whether to automatically ingest transformed data into the Soma database.
362
+ *
363
+ * When `true` (default), fetched data is transformed and written to the
364
+ * database automatically. When `false`, the webhook still fetches and
365
+ * transforms the data, but skips the database write — useful when you want
366
+ * to handle ingestion yourself via the `onEvent` / per-event callbacks.
367
+ *
368
+ * @default true
369
+ */
370
+ autoIngest?: boolean;
371
+ /** Called after every webhook event is processed, regardless of event type. */
372
+ onEvent?: StravaWebhookHandler;
373
+ /**
374
+ * Per-event-type webhook handlers.
375
+ *
376
+ * Unlike Garmin (which registers per-type HTTP routes), Strava sends all
377
+ * events to a single endpoint. **Only event types listed here are processed.**
378
+ * Unlisted event types are silently ignored (200 returned, no processing).
379
+ *
380
+ * Pass a handler function for custom logic after processing,
381
+ * or `true` to enable default processing for that event type.
382
+ *
383
+ * @example
384
+ * ```ts
385
+ * events: {
386
+ * "activity-create": async (ctx, event) => { // custom side-effect },
387
+ * "activity-update": true, // default processing only
388
+ * "athlete-update": true,
389
+ * "athlete-deauthorize": true,
390
+ * }
391
+ * ```
392
+ */
393
+ events?: Partial<Record<StravaWebhookEventName, StravaWebhookHandler | true>>;
394
+ }
395
+
278
396
  export interface RegisterRoutesOptions {
279
397
  strava?: {
280
398
  /** Override STRAVA_CLIENT_ID env var. */
@@ -283,6 +401,13 @@ export interface RegisterRoutesOptions {
283
401
  clientSecret?: string;
284
402
  /** OAuth callback configuration. */
285
403
  oauth?: StravaOAuthOptions;
404
+ /**
405
+ * Webhook configuration.
406
+ *
407
+ * Disabled by default. Only event types listed in `events` are processed.
408
+ * Omit entirely or set to `false` to skip webhook routes.
409
+ */
410
+ webhook?: StravaWebhookOptions | false;
286
411
  };
287
412
  garmin?: {
288
413
  /** Override GARMIN_CLIENT_ID env var. */
@@ -59,7 +59,6 @@ import type * as private_ from "../private.js";
59
59
  import type * as public_ from "../public.js";
60
60
  import type * as strava_auth from "../strava/auth.js";
61
61
  import type * as strava_client from "../strava/client.js";
62
- import type * as strava_private from "../strava/private.js";
63
62
  import type * as strava_public from "../strava/public.js";
64
63
  import type * as strava_transform_activity from "../strava/transform/activity.js";
65
64
  import type * as strava_transform_athlete from "../strava/transform/athlete.js";
@@ -67,6 +66,7 @@ import type * as strava_transform_maps_sportType from "../strava/transform/maps/
67
66
  import type * as strava_types_stravaApi_client_index from "../strava/types/stravaApi/client/index.js";
68
67
  import type * as strava_types_stravaApi_index from "../strava/types/stravaApi/index.js";
69
68
  import type * as strava_utils from "../strava/utils.js";
69
+ import type * as strava_webhooks from "../strava/webhooks.js";
70
70
  import type * as utils from "../utils.js";
71
71
  import type * as validators_activity from "../validators/activity.js";
72
72
  import type * as validators_athlete from "../validators/athlete.js";
@@ -141,7 +141,6 @@ const fullApi: ApiFromModules<{
141
141
  public: typeof public_;
142
142
  "strava/auth": typeof strava_auth;
143
143
  "strava/client": typeof strava_client;
144
- "strava/private": typeof strava_private;
145
144
  "strava/public": typeof strava_public;
146
145
  "strava/transform/activity": typeof strava_transform_activity;
147
146
  "strava/transform/athlete": typeof strava_transform_athlete;
@@ -149,6 +148,7 @@ const fullApi: ApiFromModules<{
149
148
  "strava/types/stravaApi/client/index": typeof strava_types_stravaApi_client_index;
150
149
  "strava/types/stravaApi/index": typeof strava_types_stravaApi_index;
151
150
  "strava/utils": typeof strava_utils;
151
+ "strava/webhooks": typeof strava_webhooks;
152
152
  utils: typeof utils;
153
153
  "validators/activity": typeof validators_activity;
154
154
  "validators/athlete": typeof validators_athlete;
@@ -1844,32 +1844,51 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
1844
1844
  any,
1845
1845
  Name
1846
1846
  >;
1847
- syncAllTypes: FunctionReference<
1847
+ pullActivities: FunctionReference<
1848
1848
  "action",
1849
1849
  "internal",
1850
1850
  {
1851
- accessToken: string;
1852
1851
  after?: number;
1853
1852
  before?: number;
1854
- connectionId: string;
1853
+ clientId: string;
1854
+ clientSecret: string;
1855
1855
  userId: string;
1856
1856
  },
1857
1857
  any,
1858
1858
  Name
1859
1859
  >;
1860
- syncStrava: FunctionReference<
1860
+ pullAll: FunctionReference<
1861
1861
  "action",
1862
1862
  "internal",
1863
1863
  {
1864
1864
  after?: number;
1865
+ before?: number;
1865
1866
  clientId: string;
1866
1867
  clientSecret: string;
1867
1868
  userId: string;
1868
1869
  },
1870
+ any,
1871
+ Name
1872
+ >;
1873
+ pullAthlete: FunctionReference<
1874
+ "action",
1875
+ "internal",
1876
+ { clientId: string; clientSecret: string; userId: string },
1877
+ any,
1878
+ Name
1879
+ >;
1880
+ };
1881
+ webhooks: {
1882
+ handleStravaWebhook: FunctionReference<
1883
+ "action",
1884
+ "internal",
1869
1885
  {
1870
- data: { synced: { activities: number; athletes: number } };
1871
- errors: Array<{ id: string; message: string; type: string }>;
1886
+ autoIngest?: boolean;
1887
+ clientId: string;
1888
+ clientSecret: string;
1889
+ payload: any;
1872
1890
  },
1891
+ any,
1873
1892
  Name
1874
1893
  >;
1875
1894
  };
@@ -2,6 +2,8 @@
2
2
  // Pure helper functions for the Garmin OAuth 2.0 PKCE flow.
3
3
  // Uses the Web Crypto API for SHA-256 challenge generation and global `fetch`.
4
4
 
5
+ import type { OAuthRefreshResult } from "../utils.js";
6
+
5
7
  export interface GarminOAuth2TokenResponse {
6
8
  access_token: string;
7
9
  refresh_token: string;
@@ -158,7 +160,7 @@ export interface RefreshTokenOptions {
158
160
  */
159
161
  export async function refreshToken(
160
162
  opts: RefreshTokenOptions,
161
- ): Promise<GarminOAuth2TokenResponse> {
163
+ ): Promise<OAuthRefreshResult> {
162
164
  const body = new URLSearchParams({
163
165
  grant_type: "refresh_token",
164
166
  client_id: opts.clientId,
@@ -179,5 +181,10 @@ export async function refreshToken(
179
181
  );
180
182
  }
181
183
 
182
- return (await response.json()) as GarminOAuth2TokenResponse;
184
+ const raw = (await response.json()) as GarminOAuth2TokenResponse;
185
+ return {
186
+ access_token: raw.access_token,
187
+ refresh_token: raw.refresh_token,
188
+ expiresAt: Math.floor(Date.now() / 1000) + raw.expires_in,
189
+ };
183
190
  }