@nativesquare/soma 0.11.0 → 0.13.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 (60) hide show
  1. package/dist/client/garmin.d.ts +291 -0
  2. package/dist/client/garmin.d.ts.map +1 -0
  3. package/dist/client/garmin.js +493 -0
  4. package/dist/client/garmin.js.map +1 -0
  5. package/dist/client/index.d.ts +29 -394
  6. package/dist/client/index.d.ts.map +1 -1
  7. package/dist/client/index.js +30 -520
  8. package/dist/client/index.js.map +1 -1
  9. package/dist/client/strava.d.ts +97 -0
  10. package/dist/client/strava.d.ts.map +1 -0
  11. package/dist/client/strava.js +160 -0
  12. package/dist/client/strava.js.map +1 -0
  13. package/dist/client/types.d.ts +238 -0
  14. package/dist/client/types.d.ts.map +1 -1
  15. package/dist/component/_generated/component.d.ts +24 -12
  16. package/dist/component/_generated/component.d.ts.map +1 -1
  17. package/dist/component/garmin/private.d.ts +53 -68
  18. package/dist/component/garmin/private.d.ts.map +1 -1
  19. package/dist/component/garmin/private.js +87 -85
  20. package/dist/component/garmin/private.js.map +1 -1
  21. package/dist/component/garmin/public.d.ts +97 -53
  22. package/dist/component/garmin/public.d.ts.map +1 -1
  23. package/dist/component/garmin/public.js +75 -148
  24. package/dist/component/garmin/public.js.map +1 -1
  25. package/dist/component/garmin/webhooks.d.ts +22 -20
  26. package/dist/component/garmin/webhooks.d.ts.map +1 -1
  27. package/dist/component/garmin/webhooks.js +115 -76
  28. package/dist/component/garmin/webhooks.js.map +1 -1
  29. package/dist/component/public.d.ts +15 -15
  30. package/dist/component/schema.d.ts +25 -25
  31. package/dist/component/strava/public.d.ts +12 -8
  32. package/dist/component/strava/public.d.ts.map +1 -1
  33. package/dist/component/strava/public.js +7 -7
  34. package/dist/component/strava/public.js.map +1 -1
  35. package/dist/component/validators/activity.d.ts +4 -4
  36. package/dist/component/validators/body.d.ts +4 -4
  37. package/dist/component/validators/daily.d.ts +4 -4
  38. package/dist/component/validators/nutrition.d.ts +3 -3
  39. package/dist/component/validators/samples.d.ts +4 -4
  40. package/dist/component/validators/shared.d.ts +13 -4
  41. package/dist/component/validators/shared.d.ts.map +1 -1
  42. package/dist/component/validators/shared.js +7 -0
  43. package/dist/component/validators/shared.js.map +1 -1
  44. package/dist/component/validators/sleep.d.ts +5 -5
  45. package/dist/validators.d.ts +41 -40
  46. package/dist/validators.d.ts.map +1 -1
  47. package/dist/validators.js +1 -0
  48. package/dist/validators.js.map +1 -1
  49. package/package.json +1 -1
  50. package/src/client/garmin.ts +692 -0
  51. package/src/client/index.ts +68 -933
  52. package/src/client/strava.ts +199 -0
  53. package/src/client/types.ts +285 -0
  54. package/src/component/_generated/component.ts +19 -32
  55. package/src/component/garmin/private.ts +1872 -1870
  56. package/src/component/garmin/public.ts +1073 -1184
  57. package/src/component/garmin/webhooks.ts +898 -857
  58. package/src/component/strava/public.ts +393 -393
  59. package/src/component/validators/shared.ts +9 -0
  60. package/src/validators.ts +1 -0
@@ -1,9 +1,9 @@
1
- import { httpActionGeneric, } from "convex/server";
2
- // ─── Default OAuth Callback Paths ────────────────────────────────────────────
3
- // Used by `registerRoutes` as defaults. Override per-provider in the opts.
4
- export const STRAVA_CALLBACK_PATH = "/api/strava/callback";
5
- export const GARMIN_OAUTH_CALLBACK_PATH = "/api/garmin/callback";
6
- export const GARMIN_WEBHOOK_BASE_PATH = "/api/garmin/webhook";
1
+ import { SomaGarmin } from "./garmin.js";
2
+ import { SomaStrava } from "./strava.js";
3
+ export { SomaGarmin } from "./garmin.js";
4
+ export { SomaStrava } from "./strava.js";
5
+ export { STRAVA_CALLBACK_PATH } from "./strava.js";
6
+ export { GARMIN_OAUTH_CALLBACK_PATH, GARMIN_WEBHOOK_BASE_PATH } from "./garmin.js";
7
7
  /**
8
8
  * Client class for the @nativesquare/soma Convex component.
9
9
  *
@@ -11,7 +11,7 @@ export const GARMIN_WEBHOOK_BASE_PATH = "/api/garmin/webhook";
11
11
  * and querying normalized health & fitness data.
12
12
  *
13
13
  * All capabilities are also accessible via direct component function calls:
14
- * `ctx.runMutation(components.soma.public.connect, { userId, provider: "GARMIN" })`
14
+ * `ctx.runQuery(components.soma.public.listActivities, { userId: "user_123" })`
15
15
  *
16
16
  * @example
17
17
  * ```ts
@@ -20,35 +20,38 @@ export const GARMIN_WEBHOOK_BASE_PATH = "/api/garmin/webhook";
20
20
  * import { components } from "./_generated/api";
21
21
  *
22
22
  * // Zero config if env vars are set in Convex dashboard:
23
- * // STRAVA_CLIENT_ID, STRAVA_CLIENT_SECRET, STRAVA_BASE_URL (optional)
23
+ * // STRAVA_CLIENT_ID, STRAVA_CLIENT_SECRET
24
+ * // GARMIN_CLIENT_ID, GARMIN_CLIENT_SECRET
24
25
  * const soma = new Soma(components.soma);
25
26
  *
26
- * // Or with explicit Strava config:
27
+ * // Or with explicit config:
27
28
  * // const soma = new Soma(components.soma, {
28
29
  * // strava: { clientId: "...", clientSecret: "..." },
30
+ * // garmin: { clientId: "...", clientSecret: "..." },
29
31
  * // });
30
32
  *
31
- * // Connect a user to a provider:
32
- * const connectionId = await soma.connect(ctx, {
33
- * userId: "user_123",
34
- * provider: "GARMIN",
35
- * });
36
- *
37
33
  * // Start Strava OAuth (redirects user, callback handled by registerRoutes):
38
- * const { authUrl } = await soma.getStravaAuthUrl(ctx, {
34
+ * const { authUrl } = await soma.strava.getAuthUrl(ctx, {
39
35
  * userId: "user_123",
40
36
  * redirectUri: "https://your-app.convex.site/api/strava/callback",
41
37
  * });
38
+ *
39
+ * // Pull all Garmin data:
40
+ * await soma.garmin.pullAll(ctx, { userId: "user_123" });
42
41
  * ```
43
42
  */
44
43
  export class Soma {
45
44
  component;
46
45
  stravaConfig;
47
46
  garminConfig;
47
+ garmin;
48
+ strava;
48
49
  constructor(component, options) {
49
50
  this.component = component;
50
51
  this.stravaConfig = options?.strava ?? this.readStravaEnv();
51
52
  this.garminConfig = options?.garmin ?? this.readGarminEnv();
53
+ this.garmin = new SomaGarmin(this.component, () => this.requireGarminConfig());
54
+ this.strava = new SomaStrava(this.component, () => this.requireStravaConfig());
52
55
  }
53
56
  /**
54
57
  * Read Strava config from environment variables.
@@ -97,57 +100,6 @@ export class Soma {
97
100
  }
98
101
  return this.garminConfig;
99
102
  }
100
- // ─── Connect / Disconnect ───────────────────────────────────────────────────
101
- /**
102
- * Connect a user to a wearable provider.
103
- *
104
- * Creates the connection if it doesn't exist, or re-activates it if it was
105
- * previously disconnected. Idempotent — calling twice is a no-op.
106
- *
107
- * Use this when the host app has completed the provider's auth flow and
108
- * wants to register the connection in Soma.
109
- *
110
- * @param ctx - Mutation context from the host app
111
- * @param args.userId - The host app's user identifier (Clerk ID, etc.)
112
- * @param args.provider - The wearable provider name ("GARMIN", "FITBIT", "OURA", etc.)
113
- * @returns The connection document ID
114
- *
115
- * @example
116
- * ```ts
117
- * // "Connect to Garmin" button handler
118
- * const connectionId = await soma.connect(ctx, {
119
- * userId: "user_123",
120
- * provider: "GARMIN",
121
- * });
122
- * ```
123
- */
124
- async connect(ctx, args) {
125
- return await ctx.runMutation(this.component.public.connect, args);
126
- }
127
- /**
128
- * Disconnect a user from a wearable provider.
129
- *
130
- * Sets the connection to inactive. Does not delete any synced data,
131
- * so re-connecting later preserves historical records.
132
- *
133
- * @param ctx - Mutation context from the host app
134
- * @param args.userId - The host app's user identifier
135
- * @param args.provider - The wearable provider name
136
- *
137
- * @throws Error if no connection exists for the given user–provider pair
138
- *
139
- * @example
140
- * ```ts
141
- * // "Disconnect Garmin" button handler
142
- * await soma.disconnect(ctx, {
143
- * userId: "user_123",
144
- * provider: "GARMIN",
145
- * });
146
- * ```
147
- */
148
- async disconnect(ctx, args) {
149
- return await ctx.runMutation(this.component.public.disconnect, args);
150
- }
151
103
  // ─── Connection Queries ─────────────────────────────────────────────────────
152
104
  /**
153
105
  * Get a connection by its document ID.
@@ -571,253 +523,6 @@ export class Soma {
571
523
  async getPlannedWorkout(ctx, args) {
572
524
  return await ctx.runQuery(this.component.public.getPlannedWorkout, args);
573
525
  }
574
- // ─── Strava Integration ──────────────────────────────────────────────────────
575
- // High-level methods that handle OAuth, token storage, and data syncing
576
- // for Strava. Requires Strava credentials to be configured either via
577
- // environment variables or the constructor.
578
- /**
579
- * Generate a Strava OAuth authorization URL.
580
- *
581
- * The state parameter is stored inside the component automatically,
582
- * and the callback handler registered by `registerRoutes` will
583
- * complete the flow without further host-app intervention.
584
- *
585
- * @param ctx - Action context from the host app
586
- * @param opts.userId - The host app's user identifier
587
- * @param opts.redirectUri - The URL Strava will redirect to after authorization
588
- * @param opts.scope - Comma-separated Strava OAuth scopes (default: "read,activity:read_all,profile:read_all")
589
- * @returns `{ authUrl, state }`
590
- *
591
- * @example
592
- * ```ts
593
- * const { authUrl } = await soma.getStravaAuthUrl(ctx, {
594
- * userId: "user_123",
595
- * redirectUri: "https://your-app.convex.site/api/strava/callback",
596
- * });
597
- * // Redirect user to authUrl — the callback is handled automatically
598
- * ```
599
- */
600
- async getStravaAuthUrl(ctx, opts) {
601
- const config = this.requireStravaConfig();
602
- return await ctx.runAction(this.component.strava.public.getStravaAuthUrl, {
603
- clientId: config.clientId,
604
- redirectUri: opts.redirectUri,
605
- scope: opts.scope,
606
- userId: opts.userId,
607
- });
608
- }
609
- /**
610
- * Sync activities from Strava for an already-connected user.
611
- *
612
- * Automatically refreshes the access token if expired. Fetches the
613
- * athlete profile and activities, transforms them, and ingests into Soma.
614
- *
615
- * @param ctx - Action context from the host app
616
- * @param args.userId - The host app's user identifier
617
- * @param args.after - Only sync activities after this Unix epoch timestamp (for incremental sync)
618
- * @returns `{ synced, errors }`
619
- *
620
- * @example
621
- * ```ts
622
- * export const syncStrava = action({
623
- * args: { userId: v.string() },
624
- * handler: async (ctx, { userId }) => {
625
- * return await soma.syncStrava(ctx, { userId });
626
- * },
627
- * });
628
- * ```
629
- */
630
- async syncStrava(ctx, args) {
631
- const config = this.requireStravaConfig();
632
- return await ctx.runAction(this.component.strava.public.syncStrava, {
633
- ...args,
634
- clientId: config.clientId,
635
- clientSecret: config.clientSecret,
636
- });
637
- }
638
- /**
639
- * Disconnect a user from Strava.
640
- *
641
- * Revokes the token at Strava (best-effort), deletes stored tokens,
642
- * and sets the connection to inactive.
643
- *
644
- * @param ctx - Action context from the host app
645
- * @param args.userId - The host app's user identifier
646
- *
647
- * @example
648
- * ```ts
649
- * export const disconnectStrava = action({
650
- * args: { userId: v.string() },
651
- * handler: async (ctx, { userId }) => {
652
- * await soma.disconnectStrava(ctx, { userId });
653
- * },
654
- * });
655
- * ```
656
- */
657
- async disconnectStrava(ctx, args) {
658
- const config = this.requireStravaConfig();
659
- return await ctx.runAction(this.component.strava.public.disconnectStrava, {
660
- ...args,
661
- clientId: config.clientId,
662
- clientSecret: config.clientSecret,
663
- });
664
- }
665
- // ─── Garmin ──────────────────────────────────────────────────────
666
- async getGarminAuthUrl(ctx, opts) {
667
- const config = this.requireGarminConfig();
668
- return await ctx.runAction(this.component.garmin.public.getGarminAuthUrl, {
669
- clientId: config.clientId,
670
- redirectUri: opts.redirectUri,
671
- userId: opts.userId,
672
- });
673
- }
674
- async disconnectGarmin(ctx, args) {
675
- return await ctx.runAction(this.component.garmin.public.disconnectGarmin, args);
676
- }
677
- async pullGarminActivities(ctx, args) {
678
- const config = this.requireGarminConfig();
679
- return await ctx.runAction(this.component.garmin.public.pullActivities, {
680
- ...args,
681
- clientId: config.clientId,
682
- clientSecret: config.clientSecret,
683
- });
684
- }
685
- async pullGarminDailies(ctx, args) {
686
- const config = this.requireGarminConfig();
687
- return await ctx.runAction(this.component.garmin.public.pullDailies, {
688
- ...args,
689
- clientId: config.clientId,
690
- clientSecret: config.clientSecret,
691
- });
692
- }
693
- async pullGarminSleep(ctx, args) {
694
- const config = this.requireGarminConfig();
695
- return await ctx.runAction(this.component.garmin.public.pullSleep, {
696
- ...args,
697
- clientId: config.clientId,
698
- clientSecret: config.clientSecret,
699
- });
700
- }
701
- async pullGarminBody(ctx, args) {
702
- const config = this.requireGarminConfig();
703
- return await ctx.runAction(this.component.garmin.public.pullBody, {
704
- ...args,
705
- clientId: config.clientId,
706
- clientSecret: config.clientSecret,
707
- });
708
- }
709
- async pullGarminMenstruation(ctx, args) {
710
- const config = this.requireGarminConfig();
711
- return await ctx.runAction(this.component.garmin.public.pullMenstruation, {
712
- ...args,
713
- clientId: config.clientId,
714
- clientSecret: config.clientSecret,
715
- });
716
- }
717
- async pullGarminBloodPressures(ctx, args) {
718
- const config = this.requireGarminConfig();
719
- return await ctx.runAction(this.component.garmin.public.pullBloodPressures, {
720
- ...args,
721
- clientId: config.clientId,
722
- clientSecret: config.clientSecret,
723
- });
724
- }
725
- async pullGarminSkinTemperature(ctx, args) {
726
- const config = this.requireGarminConfig();
727
- return await ctx.runAction(this.component.garmin.public.pullSkinTemperature, {
728
- ...args,
729
- clientId: config.clientId,
730
- clientSecret: config.clientSecret,
731
- });
732
- }
733
- async pullGarminUserMetrics(ctx, args) {
734
- const config = this.requireGarminConfig();
735
- return await ctx.runAction(this.component.garmin.public.pullUserMetrics, {
736
- ...args,
737
- clientId: config.clientId,
738
- clientSecret: config.clientSecret,
739
- });
740
- }
741
- async pullGarminHRV(ctx, args) {
742
- const config = this.requireGarminConfig();
743
- return await ctx.runAction(this.component.garmin.public.pullHRV, {
744
- ...args,
745
- clientId: config.clientId,
746
- clientSecret: config.clientSecret,
747
- });
748
- }
749
- async pullGarminStressDetails(ctx, args) {
750
- const config = this.requireGarminConfig();
751
- return await ctx.runAction(this.component.garmin.public.pullStressDetails, {
752
- ...args,
753
- clientId: config.clientId,
754
- clientSecret: config.clientSecret,
755
- });
756
- }
757
- async pullGarminPulseOx(ctx, args) {
758
- const config = this.requireGarminConfig();
759
- return await ctx.runAction(this.component.garmin.public.pullPulseOx, {
760
- ...args,
761
- clientId: config.clientId,
762
- clientSecret: config.clientSecret,
763
- });
764
- }
765
- async pullGarminRespiration(ctx, args) {
766
- const config = this.requireGarminConfig();
767
- return await ctx.runAction(this.component.garmin.public.pullRespiration, {
768
- ...args,
769
- clientId: config.clientId,
770
- clientSecret: config.clientSecret,
771
- });
772
- }
773
- async pullGarminAll(ctx, args) {
774
- const config = this.requireGarminConfig();
775
- return await ctx.runAction(this.component.garmin.public.pullAll, {
776
- ...args,
777
- clientId: config.clientId,
778
- clientSecret: config.clientSecret,
779
- });
780
- }
781
- async pushPlannedWorkoutToGarmin(ctx, args) {
782
- const config = this.requireGarminConfig();
783
- return await ctx.runAction(this.component.garmin.public.pushPlannedWorkout, {
784
- ...args,
785
- clientId: config.clientId,
786
- clientSecret: config.clientSecret,
787
- });
788
- }
789
- async pushWorkoutToGarmin(ctx, args) {
790
- const config = this.requireGarminConfig();
791
- return await ctx.runAction(this.component.garmin.public.pushWorkout, {
792
- ...args,
793
- clientId: config.clientId,
794
- clientSecret: config.clientSecret,
795
- });
796
- }
797
- async pushScheduleToGarmin(ctx, args) {
798
- const config = this.requireGarminConfig();
799
- return await ctx.runAction(this.component.garmin.public.pushSchedule, {
800
- ...args,
801
- clientId: config.clientId,
802
- clientSecret: config.clientSecret,
803
- });
804
- }
805
- async deleteWorkoutFromGarmin(ctx, args) {
806
- const config = this.requireGarminConfig();
807
- return await ctx.runAction(this.component.garmin.public.deleteWorkout, {
808
- ...args,
809
- clientId: config.clientId,
810
- clientSecret: config.clientSecret,
811
- });
812
- }
813
- async deleteScheduleFromGarmin(ctx, args) {
814
- const config = this.requireGarminConfig();
815
- return await ctx.runAction(this.component.garmin.public.deleteSchedule, {
816
- ...args,
817
- clientId: config.clientId,
818
- clientSecret: config.clientSecret,
819
- });
820
- }
821
526
  }
822
527
  /**
823
528
  * Register OAuth callback HTTP routes for Soma providers.
@@ -876,14 +581,18 @@ export class Soma {
876
581
  * },
877
582
  * },
878
583
  * webhook: {
879
- * onEvent: async (ctx, event) => {
880
- * // Catch-all: runs for every webhook
881
- * console.log(`Garmin ${event.dataType}: ${event.processed} processed`);
882
- * },
584
+ * // Only listed data types get an HTTP route registered.
585
+ * // Pass a handler for custom logic, or `true` for default processing.
883
586
  * events: {
884
587
  * "activities": async (ctx, event) => {
885
- * // Runs only for activity webhooks
588
+ * // Custom side-effect after activity ingestion
886
589
  * },
590
+ * "sleeps": true, // register route, default processing only
591
+ * "dailies": true,
592
+ * },
593
+ * // Optional catch-all, runs for every registered event
594
+ * onEvent: async (ctx, event) => {
595
+ * console.log(`Garmin ${event.dataType}: ${event.items.length} items`);
887
596
  * },
888
597
  * },
889
598
  * },
@@ -893,209 +602,10 @@ export class Soma {
893
602
  export function registerRoutes(http, component, opts) {
894
603
  const registerAll = opts === undefined;
895
604
  if (registerAll || opts?.strava) {
896
- const stravaOpts = opts?.strava ?? {};
897
- const oauth = stravaOpts.oauth ?? {};
898
- const path = oauth.path ?? STRAVA_CALLBACK_PATH;
899
- http.route({
900
- path,
901
- method: "GET",
902
- handler: httpActionGeneric(async (ctx, request) => {
903
- const url = new URL(request.url);
904
- const code = url.searchParams.get("code");
905
- const state = url.searchParams.get("state");
906
- if (!code) {
907
- return new Response("Missing authorization code", { status: 400 });
908
- }
909
- if (!state) {
910
- return new Response("Missing state parameter. Ensure the state was included " +
911
- "when building the Strava auth URL via getStravaAuthUrl.", { status: 400 });
912
- }
913
- const clientId = oauth.clientId ?? process.env.STRAVA_CLIENT_ID;
914
- const clientSecret = oauth.clientSecret ?? process.env.STRAVA_CLIENT_SECRET;
915
- if (!clientId || !clientSecret) {
916
- return new Response("Strava credentials not configured. Set STRAVA_CLIENT_ID and " +
917
- "STRAVA_CLIENT_SECRET environment variables, or pass them to registerRoutes.", { status: 500 });
918
- }
919
- let result;
920
- try {
921
- result = await ctx.runAction(component.strava.public.completeStravaOAuth, {
922
- code,
923
- state,
924
- clientId,
925
- clientSecret,
926
- });
927
- }
928
- catch (error) {
929
- const message = error instanceof Error ? error.message : "Unknown error";
930
- return new Response(`Strava OAuth callback failed: ${message}`, {
931
- status: 500,
932
- });
933
- }
934
- if (oauth.onComplete) {
935
- try {
936
- await oauth.onComplete(ctx, {
937
- provider: "STRAVA",
938
- userId: result.userId,
939
- connectionId: result.connectionId,
940
- });
941
- }
942
- catch (callbackError) {
943
- console.error("[soma] strava onComplete callback error:", callbackError instanceof Error ? callbackError.message : callbackError);
944
- }
945
- }
946
- if (oauth.redirectTo) {
947
- return new Response(null, {
948
- status: 302,
949
- headers: { Location: oauth.redirectTo },
950
- });
951
- }
952
- return new Response("Successfully connected to Strava!", {
953
- status: 200,
954
- });
955
- }),
956
- });
605
+ SomaStrava.registerRoutes(http, component, opts?.strava);
957
606
  }
958
607
  if (registerAll || opts?.garmin) {
959
- const garminOpts = opts?.garmin ?? {};
960
- const oauth = garminOpts.oauth ?? {};
961
- const path = oauth.path ?? GARMIN_OAUTH_CALLBACK_PATH;
962
- http.route({
963
- path,
964
- method: "GET",
965
- handler: httpActionGeneric(async (ctx, request) => {
966
- const url = new URL(request.url);
967
- const code = url.searchParams.get("code");
968
- const state = url.searchParams.get("state");
969
- if (!code) {
970
- return new Response("Missing authorization code", {
971
- status: 400,
972
- });
973
- }
974
- if (!state) {
975
- return new Response("Missing state parameter. Ensure the state was included " +
976
- "when building the Garmin auth URL.", { status: 400 });
977
- }
978
- const clientId = oauth.clientId ?? process.env.GARMIN_CLIENT_ID;
979
- const clientSecret = oauth.clientSecret ?? process.env.GARMIN_CLIENT_SECRET;
980
- if (!clientId || !clientSecret) {
981
- return new Response("Garmin credentials not configured. Set GARMIN_CLIENT_ID and " +
982
- "GARMIN_CLIENT_SECRET environment variables, or pass them to registerRoutes.", { status: 500 });
983
- }
984
- let result;
985
- try {
986
- result = await ctx.runAction(component.garmin.public.completeGarminOAuth, {
987
- code,
988
- state,
989
- clientId,
990
- clientSecret,
991
- });
992
- }
993
- catch (error) {
994
- const message = error instanceof Error ? error.message : "Unknown error";
995
- return new Response(`Garmin OAuth callback failed: ${message}`, {
996
- status: 500,
997
- });
998
- }
999
- if (oauth.onComplete) {
1000
- try {
1001
- await oauth.onComplete(ctx, {
1002
- provider: "GARMIN",
1003
- userId: result.userId,
1004
- connectionId: result.connectionId,
1005
- });
1006
- }
1007
- catch (callbackError) {
1008
- console.error("[soma] garmin oauth.onComplete callback error:", callbackError instanceof Error ? callbackError.message : callbackError);
1009
- }
1010
- }
1011
- if (oauth.redirectTo) {
1012
- return new Response(null, {
1013
- status: 302,
1014
- headers: { Location: oauth.redirectTo },
1015
- });
1016
- }
1017
- return new Response("Successfully connected to Garmin!", {
1018
- status: 200,
1019
- });
1020
- }),
1021
- });
1022
- // ── Garmin Webhook Routes ──────────────────────────────────
1023
- if (garminOpts.webhook !== false) {
1024
- const webhookCfg = typeof garminOpts.webhook === "object" ? garminOpts.webhook : {};
1025
- const webhookBase = webhookCfg.basePath ?? GARMIN_WEBHOOK_BASE_PATH;
1026
- const webhookRoutes = [
1027
- // ACTIVITY category
1028
- { suffix: "/activities", action: component.garmin.webhooks.handleGarminWebhookActivities },
1029
- { suffix: "/activity-details", action: component.garmin.webhooks.handleGarminWebhookActivityDetails },
1030
- { suffix: "/manually-updated-activities", action: component.garmin.webhooks.handleGarminWebhookManuallyUpdatedActivities },
1031
- { suffix: "/move-iq", action: component.garmin.webhooks.handleGarminWebhookMoveIQ },
1032
- // HEALTH category
1033
- { suffix: "/blood-pressures", action: component.garmin.webhooks.handleGarminWebhookBloodPressures },
1034
- { suffix: "/body-compositions", action: component.garmin.webhooks.handleGarminWebhookBodyCompositions },
1035
- { suffix: "/dailies", action: component.garmin.webhooks.handleGarminWebhookDailies },
1036
- { suffix: "/epochs", action: component.garmin.webhooks.handleGarminWebhookEpochs },
1037
- { suffix: "/health-snapshot", action: component.garmin.webhooks.handleGarminWebhookHealthSnapshot },
1038
- { suffix: "/sleeps", action: component.garmin.webhooks.handleGarminWebhookSleeps },
1039
- { suffix: "/hrv", action: component.garmin.webhooks.handleGarminWebhookHRVSummary },
1040
- { suffix: "/stress", action: component.garmin.webhooks.handleGarminWebhookStress },
1041
- { suffix: "/pulse-ox", action: component.garmin.webhooks.handleGarminWebhookPulseOx },
1042
- { suffix: "/respiration", action: component.garmin.webhooks.handleGarminWebhookRespiration },
1043
- { suffix: "/skin-temp", action: component.garmin.webhooks.handleGarminWebhookSkinTemp },
1044
- { suffix: "/user-metrics", action: component.garmin.webhooks.handleGarminWebhookUserMetrics },
1045
- // WOMEN_HEALTH category
1046
- { suffix: "/menstrual-cycle-tracking", action: component.garmin.webhooks.handleGarminWebhookMenstrualCycleTracking },
1047
- ];
1048
- for (const route of webhookRoutes) {
1049
- http.route({
1050
- path: `${webhookBase}${route.suffix}`,
1051
- method: "POST",
1052
- handler: httpActionGeneric(async (ctx, request) => {
1053
- let payload;
1054
- try {
1055
- payload = await request.json();
1056
- }
1057
- catch {
1058
- return new Response("Invalid JSON body", { status: 400 });
1059
- }
1060
- let result;
1061
- try {
1062
- result = await ctx.runAction(route.action, { payload });
1063
- }
1064
- catch (error) {
1065
- // Log but return 200 to prevent Garmin from retrying
1066
- console.error(`Garmin webhook error (${route.suffix}):`, error instanceof Error ? error.message : error);
1067
- }
1068
- if (result) {
1069
- const dataType = route.suffix.slice(1);
1070
- const event = {
1071
- dataType,
1072
- processed: result.processed,
1073
- errors: result.errors,
1074
- affectedUsers: result.affectedUsers,
1075
- };
1076
- const specificHandler = webhookCfg.events?.[dataType];
1077
- if (specificHandler) {
1078
- try {
1079
- await specificHandler(ctx, event);
1080
- }
1081
- catch (callbackError) {
1082
- console.error(`[soma] garmin webhook events["${dataType}"] callback error:`, callbackError instanceof Error ? callbackError.message : callbackError);
1083
- }
1084
- }
1085
- if (webhookCfg.onEvent) {
1086
- try {
1087
- await webhookCfg.onEvent(ctx, event);
1088
- }
1089
- catch (callbackError) {
1090
- console.error(`[soma] garmin webhook onEvent callback error:`, callbackError instanceof Error ? callbackError.message : callbackError);
1091
- }
1092
- }
1093
- }
1094
- return new Response("OK", { status: 200 });
1095
- }),
1096
- });
1097
- }
1098
- }
608
+ SomaGarmin.registerRoutes(http, component, opts?.garmin);
1099
609
  }
1100
610
  }
1101
611
  //# sourceMappingURL=index.js.map