@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,7 +1,57 @@
|
|
|
1
|
-
// ─── Internal Token CRUD
|
|
2
|
-
//
|
|
1
|
+
// ─── Internal Token & Pending OAuth CRUD ─────────────────────────────────────
|
|
2
|
+
// Called only from strava/public.ts. Not exposed to the host app.
|
|
3
3
|
import { v } from "convex/values";
|
|
4
4
|
import { internalMutation, internalQuery, } from "../_generated/server";
|
|
5
|
+
// ─── Internal Pending OAuth CRUD ─────────────────────────────────────────────
|
|
6
|
+
// Temporary storage for in-progress Strava OAuth flows.
|
|
7
|
+
// Bridges getStravaAuthUrl and completeStravaOAuth.
|
|
8
|
+
export const storePendingOAuth = internalMutation({
|
|
9
|
+
args: {
|
|
10
|
+
provider: v.string(),
|
|
11
|
+
state: v.string(),
|
|
12
|
+
userId: v.string(),
|
|
13
|
+
},
|
|
14
|
+
returns: v.null(),
|
|
15
|
+
handler: async (ctx, args) => {
|
|
16
|
+
await ctx.db.insert("pendingOAuth", {
|
|
17
|
+
...args,
|
|
18
|
+
createdAt: Date.now(),
|
|
19
|
+
});
|
|
20
|
+
return null;
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
export const getPendingOAuth = internalQuery({
|
|
24
|
+
args: { state: v.string() },
|
|
25
|
+
returns: v.union(v.object({
|
|
26
|
+
_id: v.id("pendingOAuth"),
|
|
27
|
+
_creationTime: v.number(),
|
|
28
|
+
provider: v.string(),
|
|
29
|
+
state: v.string(),
|
|
30
|
+
userId: v.string(),
|
|
31
|
+
createdAt: v.number(),
|
|
32
|
+
}), v.null()),
|
|
33
|
+
handler: async (ctx, args) => {
|
|
34
|
+
return await ctx.db
|
|
35
|
+
.query("pendingOAuth")
|
|
36
|
+
.withIndex("by_state", (q) => q.eq("state", args.state))
|
|
37
|
+
.first();
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
export const deletePendingOAuth = internalMutation({
|
|
41
|
+
args: { state: v.string() },
|
|
42
|
+
returns: v.null(),
|
|
43
|
+
handler: async (ctx, args) => {
|
|
44
|
+
const pending = await ctx.db
|
|
45
|
+
.query("pendingOAuth")
|
|
46
|
+
.withIndex("by_state", (q) => q.eq("state", args.state))
|
|
47
|
+
.first();
|
|
48
|
+
if (pending) {
|
|
49
|
+
await ctx.db.delete(pending._id);
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
// ─── Internal Token CRUD ─────────────────────────────────────────────────────
|
|
5
55
|
/**
|
|
6
56
|
* Store or update OAuth tokens for a connection.
|
|
7
57
|
* Upserts by connectionId — one token record per connection.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"private.js","sourceRoot":"","sources":["../../../src/component/strava/private.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,
|
|
1
|
+
{"version":3,"file":"private.js","sourceRoot":"","sources":["../../../src/component/strava/private.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,27 +1,78 @@
|
|
|
1
1
|
import type { Id } from "../_generated/dataModel";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Generate a Strava OAuth authorization URL.
|
|
4
|
+
*
|
|
5
|
+
* If `userId` is provided, the state parameter is stored in the component's
|
|
6
|
+
* `pendingOAuth` table so that `completeStravaOAuth` can look it up
|
|
7
|
+
* automatically when the callback fires. This is the recommended flow
|
|
8
|
+
* when using `registerRoutes`.
|
|
9
|
+
*
|
|
10
|
+
* If `userId` is omitted, the host app must store the returned `state`
|
|
11
|
+
* itself and pass the userId to `connectStrava` manually.
|
|
12
|
+
*/
|
|
13
|
+
export declare const getStravaAuthUrl: import("convex/server").RegisteredAction<"public", {
|
|
14
|
+
userId?: string | undefined;
|
|
15
|
+
scope?: string | undefined;
|
|
16
|
+
clientId: string;
|
|
17
|
+
redirectUri: string;
|
|
18
|
+
}, Promise<{
|
|
19
|
+
authUrl: string;
|
|
20
|
+
state: string;
|
|
21
|
+
}>>;
|
|
22
|
+
/**
|
|
23
|
+
* Full Strava OAuth callback handler (manual flow).
|
|
4
24
|
*
|
|
5
25
|
* Exchanges the authorization code for tokens, creates/reactivates the
|
|
6
26
|
* Soma connection, stores tokens securely, syncs the athlete profile,
|
|
7
27
|
* and syncs all activities.
|
|
8
28
|
*
|
|
29
|
+
* Used when the host app handles the callback itself and passes the
|
|
30
|
+
* userId directly (analogous to `connectGarmin`).
|
|
31
|
+
*
|
|
9
32
|
* Returns `{ connectionId, synced, errors }`.
|
|
10
33
|
*/
|
|
11
34
|
export declare const connectStrava: import("convex/server").RegisteredAction<"public", {
|
|
12
|
-
baseUrl?: string | undefined;
|
|
13
|
-
includeStreams?: boolean | undefined;
|
|
14
35
|
userId: string;
|
|
15
36
|
clientId: string;
|
|
16
37
|
clientSecret: string;
|
|
17
38
|
code: string;
|
|
18
39
|
}, Promise<{
|
|
19
40
|
connectionId: Id<"connections">;
|
|
20
|
-
synced:
|
|
21
|
-
|
|
22
|
-
|
|
41
|
+
synced: {
|
|
42
|
+
athletes: number;
|
|
43
|
+
activities: number;
|
|
44
|
+
};
|
|
45
|
+
errors: Array<{
|
|
46
|
+
type: string;
|
|
47
|
+
id: string;
|
|
23
48
|
error: string;
|
|
24
|
-
}
|
|
49
|
+
}>;
|
|
50
|
+
}>>;
|
|
51
|
+
/**
|
|
52
|
+
* Complete a Strava OAuth flow using stored pending state.
|
|
53
|
+
*
|
|
54
|
+
* Used by `registerRoutes` — the callback handler calls this with the
|
|
55
|
+
* `code` and `state` from the redirect. The action looks up the pending
|
|
56
|
+
* state (userId) stored during `getStravaAuthUrl`, exchanges for tokens,
|
|
57
|
+
* creates the connection, syncs data, and cleans up the pending entry.
|
|
58
|
+
*/
|
|
59
|
+
export declare const completeStravaOAuth: import("convex/server").RegisteredAction<"public", {
|
|
60
|
+
state: string;
|
|
61
|
+
clientId: string;
|
|
62
|
+
clientSecret: string;
|
|
63
|
+
code: string;
|
|
64
|
+
}, Promise<{
|
|
65
|
+
connectionId: Id<"connections">;
|
|
66
|
+
userId: string;
|
|
67
|
+
synced: {
|
|
68
|
+
athletes: number;
|
|
69
|
+
activities: number;
|
|
70
|
+
};
|
|
71
|
+
errors: Array<{
|
|
72
|
+
type: string;
|
|
73
|
+
id: string;
|
|
74
|
+
error: string;
|
|
75
|
+
}>;
|
|
25
76
|
}>>;
|
|
26
77
|
/**
|
|
27
78
|
* Incremental Strava sync for an already-connected user.
|
|
@@ -32,16 +83,41 @@ export declare const connectStrava: import("convex/server").RegisteredAction<"pu
|
|
|
32
83
|
* Returns `{ synced, errors }`.
|
|
33
84
|
*/
|
|
34
85
|
export declare const syncStrava: import("convex/server").RegisteredAction<"public", {
|
|
35
|
-
baseUrl?: string | undefined;
|
|
36
|
-
includeStreams?: boolean | undefined;
|
|
37
86
|
after?: number | undefined;
|
|
38
87
|
userId: string;
|
|
39
88
|
clientId: string;
|
|
40
89
|
clientSecret: string;
|
|
41
90
|
}, Promise<{
|
|
42
|
-
synced:
|
|
91
|
+
synced: {
|
|
92
|
+
athletes: number;
|
|
93
|
+
activities: number;
|
|
94
|
+
};
|
|
95
|
+
errors: Array<{
|
|
96
|
+
type: string;
|
|
97
|
+
id: string;
|
|
98
|
+
error: string;
|
|
99
|
+
}>;
|
|
100
|
+
}>>;
|
|
101
|
+
/**
|
|
102
|
+
* Fetch and ingest all Strava data types for a connected user.
|
|
103
|
+
*
|
|
104
|
+
* Called by the public actions (connectStrava, completeStravaOAuth, syncStrava)
|
|
105
|
+
* after obtaining a valid access token.
|
|
106
|
+
*/
|
|
107
|
+
export declare const syncAllTypes: import("convex/server").RegisteredAction<"public", {
|
|
108
|
+
after?: number | undefined;
|
|
109
|
+
before?: number | undefined;
|
|
110
|
+
connectionId: import("convex/values").GenericId<"connections">;
|
|
111
|
+
userId: string;
|
|
112
|
+
accessToken: string;
|
|
113
|
+
}, Promise<{
|
|
114
|
+
synced: {
|
|
115
|
+
athletes: number;
|
|
116
|
+
activities: number;
|
|
117
|
+
};
|
|
43
118
|
errors: {
|
|
44
|
-
|
|
119
|
+
type: string;
|
|
120
|
+
id: string;
|
|
45
121
|
error: string;
|
|
46
122
|
}[];
|
|
47
123
|
}>>;
|
|
@@ -52,7 +128,6 @@ export declare const syncStrava: import("convex/server").RegisteredAction<"publi
|
|
|
52
128
|
* and sets the connection to inactive.
|
|
53
129
|
*/
|
|
54
130
|
export declare const disconnectStrava: import("convex/server").RegisteredAction<"public", {
|
|
55
|
-
baseUrl?: string | undefined;
|
|
56
131
|
userId: string;
|
|
57
132
|
clientId: string;
|
|
58
133
|
clientSecret: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../../src/component/strava/public.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAO,EAAE,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../../src/component/strava/public.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAO,EAAE,EAAE,MAAM,yBAAyB,CAAC;AAmBvD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;GA2B3B,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,aAAa;;;;;;kBAeR,EAAE,CAAC,aAAa,CAAC;YACvB;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;YACxC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;GA+C5D,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB;;;;;;kBAgBd,EAAE,CAAC,aAAa,CAAC;YACvB,MAAM;YACN;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;YACxC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;GAsE5D,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU;;;;;;YAcX;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;YACxC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;GA+E5D,CAAC;AAIH;;;;;GAKG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;cAaO,MAAM;YAAM,MAAM;eAAS,MAAM;;GAmE/D,CAAC;AAIH;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB;;;;iBAoD3B,CAAC"}
|
|
@@ -5,33 +5,73 @@
|
|
|
5
5
|
import { v } from "convex/values";
|
|
6
6
|
import { action } from "../_generated/server";
|
|
7
7
|
import { api, internal } from "../_generated/api";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
8
|
+
import { createStravaClient } from "./client.js";
|
|
9
|
+
import { listAllActivities } from "./utils.js";
|
|
10
|
+
import { getLoggedInAthlete, getActivityById, getActivityStreams, } from "./types/stravaApi/sdk.gen.js";
|
|
11
|
+
import { generateState } from "../utils.js";
|
|
12
|
+
import { buildAuthUrl, exchangeCode, refreshToken as refreshStravaToken, } from "./auth.js";
|
|
13
|
+
import { transformActivity } from "./transform/activity.js";
|
|
14
|
+
import { transformAthlete } from "./transform/athlete.js";
|
|
12
15
|
// ─── Public Actions ──────────────────────────────────────────────────────────
|
|
13
16
|
/**
|
|
14
|
-
*
|
|
17
|
+
* Generate a Strava OAuth authorization URL.
|
|
18
|
+
*
|
|
19
|
+
* If `userId` is provided, the state parameter is stored in the component's
|
|
20
|
+
* `pendingOAuth` table so that `completeStravaOAuth` can look it up
|
|
21
|
+
* automatically when the callback fires. This is the recommended flow
|
|
22
|
+
* when using `registerRoutes`.
|
|
23
|
+
*
|
|
24
|
+
* If `userId` is omitted, the host app must store the returned `state`
|
|
25
|
+
* itself and pass the userId to `connectStrava` manually.
|
|
26
|
+
*/
|
|
27
|
+
export const getStravaAuthUrl = action({
|
|
28
|
+
args: {
|
|
29
|
+
clientId: v.string(),
|
|
30
|
+
redirectUri: v.string(),
|
|
31
|
+
scope: v.optional(v.string()),
|
|
32
|
+
userId: v.optional(v.string()),
|
|
33
|
+
},
|
|
34
|
+
handler: async (ctx, args) => {
|
|
35
|
+
const state = generateState();
|
|
36
|
+
const authUrl = buildAuthUrl({
|
|
37
|
+
clientId: args.clientId,
|
|
38
|
+
redirectUri: args.redirectUri,
|
|
39
|
+
scope: args.scope,
|
|
40
|
+
state,
|
|
41
|
+
});
|
|
42
|
+
if (args.userId) {
|
|
43
|
+
await ctx.runMutation(internal.strava.private.storePendingOAuth, {
|
|
44
|
+
provider: "STRAVA",
|
|
45
|
+
state,
|
|
46
|
+
userId: args.userId,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return { authUrl, state };
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
/**
|
|
53
|
+
* Full Strava OAuth callback handler (manual flow).
|
|
15
54
|
*
|
|
16
55
|
* Exchanges the authorization code for tokens, creates/reactivates the
|
|
17
56
|
* Soma connection, stores tokens securely, syncs the athlete profile,
|
|
18
57
|
* and syncs all activities.
|
|
19
58
|
*
|
|
59
|
+
* Used when the host app handles the callback itself and passes the
|
|
60
|
+
* userId directly (analogous to `connectGarmin`).
|
|
61
|
+
*
|
|
20
62
|
* Returns `{ connectionId, synced, errors }`.
|
|
21
63
|
*/
|
|
22
64
|
export const connectStrava = action({
|
|
23
65
|
args: {
|
|
24
66
|
userId: v.string(),
|
|
25
|
-
code: v.string(),
|
|
26
67
|
clientId: v.string(),
|
|
27
68
|
clientSecret: v.string(),
|
|
28
|
-
|
|
29
|
-
includeStreams: v.optional(v.boolean()),
|
|
69
|
+
code: v.string(),
|
|
30
70
|
},
|
|
31
71
|
returns: v.object({
|
|
32
72
|
connectionId: v.string(),
|
|
33
|
-
synced: v.number(),
|
|
34
|
-
errors: v.array(v.object({
|
|
73
|
+
synced: v.object({ athletes: v.number(), activities: v.number() }),
|
|
74
|
+
errors: v.array(v.object({ type: v.string(), id: v.string(), error: v.string() })),
|
|
35
75
|
}),
|
|
36
76
|
handler: async (ctx, args) => {
|
|
37
77
|
// 1. Exchange authorization code for tokens
|
|
@@ -39,7 +79,6 @@ export const connectStrava = action({
|
|
|
39
79
|
clientId: args.clientId,
|
|
40
80
|
clientSecret: args.clientSecret,
|
|
41
81
|
code: args.code,
|
|
42
|
-
baseUrl: args.baseUrl,
|
|
43
82
|
});
|
|
44
83
|
// 2. Create/reactivate the Soma connection
|
|
45
84
|
const connectionId = await ctx.runMutation(api.public.connect, {
|
|
@@ -47,55 +86,91 @@ export const connectStrava = action({
|
|
|
47
86
|
provider: "STRAVA",
|
|
48
87
|
});
|
|
49
88
|
// 3. Store OAuth tokens in providerTokens table
|
|
50
|
-
|
|
89
|
+
await ctx.runMutation(internal.strava.private.storeTokens, {
|
|
51
90
|
connectionId,
|
|
52
91
|
accessToken: tokens.access_token,
|
|
53
92
|
refreshToken: tokens.refresh_token,
|
|
54
93
|
expiresAt: tokens.expires_at,
|
|
55
94
|
});
|
|
56
|
-
// 4. Sync
|
|
57
|
-
const
|
|
95
|
+
// 4. Sync all data types
|
|
96
|
+
const result = await ctx.runAction(api.strava.public.syncAllTypes, {
|
|
58
97
|
accessToken: tokens.access_token,
|
|
59
|
-
baseUrl: args.baseUrl,
|
|
60
|
-
});
|
|
61
|
-
const athlete = await client.getAthlete();
|
|
62
|
-
const athleteData = transformAthlete(athlete);
|
|
63
|
-
const _athleteId = await ctx.runMutation(api.public.ingestAthlete, {
|
|
64
98
|
connectionId,
|
|
65
99
|
userId: args.userId,
|
|
66
|
-
...athleteData,
|
|
67
100
|
});
|
|
68
|
-
// 5.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
101
|
+
// 5. Update lastDataUpdate timestamp
|
|
102
|
+
await ctx.runMutation(api.public.updateConnection, {
|
|
103
|
+
connectionId,
|
|
104
|
+
lastDataUpdate: new Date().toISOString(),
|
|
105
|
+
});
|
|
106
|
+
return { connectionId, synced: result.synced, errors: result.errors };
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
/**
|
|
110
|
+
* Complete a Strava OAuth flow using stored pending state.
|
|
111
|
+
*
|
|
112
|
+
* Used by `registerRoutes` — the callback handler calls this with the
|
|
113
|
+
* `code` and `state` from the redirect. The action looks up the pending
|
|
114
|
+
* state (userId) stored during `getStravaAuthUrl`, exchanges for tokens,
|
|
115
|
+
* creates the connection, syncs data, and cleans up the pending entry.
|
|
116
|
+
*/
|
|
117
|
+
export const completeStravaOAuth = action({
|
|
118
|
+
args: {
|
|
119
|
+
code: v.string(),
|
|
120
|
+
state: v.string(),
|
|
121
|
+
clientId: v.string(),
|
|
122
|
+
clientSecret: v.string(),
|
|
123
|
+
},
|
|
124
|
+
returns: v.object({
|
|
125
|
+
connectionId: v.string(),
|
|
126
|
+
userId: v.string(),
|
|
127
|
+
synced: v.object({ athletes: v.number(), activities: v.number() }),
|
|
128
|
+
errors: v.array(v.object({ type: v.string(), id: v.string(), error: v.string() })),
|
|
129
|
+
}),
|
|
130
|
+
handler: async (ctx, args) => {
|
|
131
|
+
// 1. Look up pending state
|
|
132
|
+
const pending = await ctx.runQuery(internal.strava.private.getPendingOAuth, { state: args.state });
|
|
133
|
+
if (!pending) {
|
|
134
|
+
throw new Error("No pending Strava OAuth state found for this state parameter. " +
|
|
135
|
+
"The authorization may have expired or was already used.");
|
|
92
136
|
}
|
|
93
|
-
//
|
|
94
|
-
const
|
|
137
|
+
// 2. Exchange authorization code for tokens
|
|
138
|
+
const tokens = await exchangeCode({
|
|
139
|
+
clientId: args.clientId,
|
|
140
|
+
clientSecret: args.clientSecret,
|
|
141
|
+
code: args.code,
|
|
142
|
+
});
|
|
143
|
+
// 3. Clean up pending entry
|
|
144
|
+
await ctx.runMutation(internal.strava.private.deletePendingOAuth, { state: args.state });
|
|
145
|
+
// 4. Create/reactivate the Soma connection
|
|
146
|
+
const connectionId = await ctx.runMutation(api.public.connect, {
|
|
147
|
+
userId: pending.userId,
|
|
148
|
+
provider: "STRAVA",
|
|
149
|
+
});
|
|
150
|
+
// 5. Store OAuth tokens
|
|
151
|
+
await ctx.runMutation(internal.strava.private.storeTokens, {
|
|
152
|
+
connectionId,
|
|
153
|
+
accessToken: tokens.access_token,
|
|
154
|
+
refreshToken: tokens.refresh_token,
|
|
155
|
+
expiresAt: tokens.expires_at,
|
|
156
|
+
});
|
|
157
|
+
// 6. Sync all data types
|
|
158
|
+
const result = await ctx.runAction(api.strava.public.syncAllTypes, {
|
|
159
|
+
accessToken: tokens.access_token,
|
|
160
|
+
connectionId,
|
|
161
|
+
userId: pending.userId,
|
|
162
|
+
});
|
|
163
|
+
// 7. Update lastDataUpdate timestamp
|
|
164
|
+
await ctx.runMutation(api.public.updateConnection, {
|
|
95
165
|
connectionId,
|
|
96
166
|
lastDataUpdate: new Date().toISOString(),
|
|
97
167
|
});
|
|
98
|
-
return {
|
|
168
|
+
return {
|
|
169
|
+
connectionId,
|
|
170
|
+
userId: pending.userId,
|
|
171
|
+
synced: result.synced,
|
|
172
|
+
errors: result.errors,
|
|
173
|
+
};
|
|
99
174
|
},
|
|
100
175
|
});
|
|
101
176
|
/**
|
|
@@ -111,13 +186,11 @@ export const syncStrava = action({
|
|
|
111
186
|
userId: v.string(),
|
|
112
187
|
clientId: v.string(),
|
|
113
188
|
clientSecret: v.string(),
|
|
114
|
-
baseUrl: v.optional(v.string()),
|
|
115
|
-
includeStreams: v.optional(v.boolean()),
|
|
116
189
|
after: v.optional(v.number()),
|
|
117
190
|
},
|
|
118
191
|
returns: v.object({
|
|
119
|
-
synced: v.number(),
|
|
120
|
-
errors: v.array(v.object({
|
|
192
|
+
synced: v.object({ athletes: v.number(), activities: v.number() }),
|
|
193
|
+
errors: v.array(v.object({ type: v.string(), id: v.string(), error: v.string() })),
|
|
121
194
|
}),
|
|
122
195
|
handler: async (ctx, args) => {
|
|
123
196
|
// 1. Look up connection
|
|
@@ -148,64 +221,119 @@ export const syncStrava = action({
|
|
|
148
221
|
clientId: args.clientId,
|
|
149
222
|
clientSecret: args.clientSecret,
|
|
150
223
|
refreshToken: tokenDoc.refreshToken,
|
|
151
|
-
baseUrl: args.baseUrl,
|
|
152
224
|
});
|
|
153
225
|
accessToken = refreshed.access_token;
|
|
154
|
-
|
|
226
|
+
await ctx.runMutation(internal.strava.private.storeTokens, {
|
|
155
227
|
connectionId,
|
|
156
228
|
accessToken: refreshed.access_token,
|
|
157
229
|
refreshToken: refreshed.refresh_token,
|
|
158
230
|
expiresAt: refreshed.expires_at,
|
|
159
231
|
});
|
|
160
232
|
}
|
|
161
|
-
// 4.
|
|
162
|
-
const
|
|
233
|
+
// 4. Sync all data types
|
|
234
|
+
const result = await ctx.runAction(api.strava.public.syncAllTypes, {
|
|
163
235
|
accessToken,
|
|
164
|
-
baseUrl: args.baseUrl,
|
|
165
|
-
});
|
|
166
|
-
// Sync athlete profile
|
|
167
|
-
const athlete = await client.getAthlete();
|
|
168
|
-
const athleteData = transformAthlete(athlete);
|
|
169
|
-
const _athleteId = await ctx.runMutation(api.public.ingestAthlete, {
|
|
170
236
|
connectionId,
|
|
171
237
|
userId: args.userId,
|
|
172
|
-
...athleteData,
|
|
173
|
-
});
|
|
174
|
-
// Sync activities (optionally incremental via `after`)
|
|
175
|
-
const summaries = await client.listAllActivities({
|
|
176
238
|
after: args.after,
|
|
177
239
|
});
|
|
178
|
-
let synced = 0;
|
|
179
|
-
const errors = [];
|
|
180
|
-
for (const summary of summaries) {
|
|
181
|
-
try {
|
|
182
|
-
const detailed = await client.getActivity(summary.id);
|
|
183
|
-
const streams = args.includeStreams
|
|
184
|
-
? await client.getActivityStreams(summary.id)
|
|
185
|
-
: undefined;
|
|
186
|
-
const data = transformActivity(detailed, { streams });
|
|
187
|
-
const _activityId = await ctx.runMutation(api.public.ingestActivity, {
|
|
188
|
-
connectionId,
|
|
189
|
-
userId: args.userId,
|
|
190
|
-
...data,
|
|
191
|
-
});
|
|
192
|
-
synced++;
|
|
193
|
-
}
|
|
194
|
-
catch (err) {
|
|
195
|
-
errors.push({
|
|
196
|
-
activityId: summary.id,
|
|
197
|
-
error: err instanceof Error ? err.message : String(err),
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
240
|
// 5. Update lastDataUpdate timestamp
|
|
202
|
-
|
|
241
|
+
await ctx.runMutation(api.public.updateConnection, {
|
|
203
242
|
connectionId,
|
|
204
243
|
lastDataUpdate: new Date().toISOString(),
|
|
205
244
|
});
|
|
245
|
+
return { synced: result.synced, errors: result.errors };
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
// ─── Sync Engine ────────────────────────────────────────────────────────────
|
|
249
|
+
/**
|
|
250
|
+
* Fetch and ingest all Strava data types for a connected user.
|
|
251
|
+
*
|
|
252
|
+
* Called by the public actions (connectStrava, completeStravaOAuth, syncStrava)
|
|
253
|
+
* after obtaining a valid access token.
|
|
254
|
+
*/
|
|
255
|
+
export const syncAllTypes = action({
|
|
256
|
+
args: {
|
|
257
|
+
accessToken: v.string(),
|
|
258
|
+
connectionId: v.id("connections"),
|
|
259
|
+
userId: v.string(),
|
|
260
|
+
after: v.optional(v.number()),
|
|
261
|
+
before: v.optional(v.number()),
|
|
262
|
+
},
|
|
263
|
+
handler: async (ctx, args) => {
|
|
264
|
+
const { accessToken, connectionId, userId } = args;
|
|
265
|
+
const client = createStravaClient(accessToken);
|
|
266
|
+
const synced = { athletes: 0, activities: 0 };
|
|
267
|
+
const errors = [];
|
|
268
|
+
// ── Athlete ──────────────────────────────────────────────────────────
|
|
269
|
+
try {
|
|
270
|
+
const { data: athlete, error } = await getLoggedInAthlete({ client });
|
|
271
|
+
if (error || !athlete)
|
|
272
|
+
throw new Error(error ? JSON.stringify(error) : "No athlete data");
|
|
273
|
+
const data = transformAthlete(athlete);
|
|
274
|
+
await ctx.runMutation(api.public.ingestAthlete, {
|
|
275
|
+
connectionId,
|
|
276
|
+
userId,
|
|
277
|
+
...data,
|
|
278
|
+
});
|
|
279
|
+
synced.athletes++;
|
|
280
|
+
}
|
|
281
|
+
catch (err) {
|
|
282
|
+
errors.push({
|
|
283
|
+
type: "athlete",
|
|
284
|
+
id: "fetch",
|
|
285
|
+
error: err instanceof Error ? err.message : String(err),
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
// ── Activities ───────────────────────────────────────────────────────
|
|
289
|
+
try {
|
|
290
|
+
const summaries = await listAllActivities(client, {
|
|
291
|
+
after: args.after,
|
|
292
|
+
before: args.before,
|
|
293
|
+
});
|
|
294
|
+
for (const summary of summaries) {
|
|
295
|
+
if (summary.id == null)
|
|
296
|
+
continue;
|
|
297
|
+
try {
|
|
298
|
+
const { data: detailed, error: detailError } = await getActivityById({ client, path: { id: summary.id } });
|
|
299
|
+
if (detailError || !detailed)
|
|
300
|
+
throw new Error(detailError ? JSON.stringify(detailError) : "No activity data");
|
|
301
|
+
const { data: streams } = await getActivityStreams({
|
|
302
|
+
client,
|
|
303
|
+
path: { id: summary.id },
|
|
304
|
+
query: {
|
|
305
|
+
keys: ["time", "heartrate", "watts", "cadence", "latlng", "altitude", "velocity_smooth", "grade_smooth", "distance", "temp"],
|
|
306
|
+
key_by_type: true,
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
const data = transformActivity(detailed, { streams: streams ?? undefined });
|
|
310
|
+
await ctx.runMutation(api.public.ingestActivity, {
|
|
311
|
+
connectionId,
|
|
312
|
+
userId,
|
|
313
|
+
...data,
|
|
314
|
+
});
|
|
315
|
+
synced.activities++;
|
|
316
|
+
}
|
|
317
|
+
catch (err) {
|
|
318
|
+
errors.push({
|
|
319
|
+
type: "activity",
|
|
320
|
+
id: String(summary.id),
|
|
321
|
+
error: err instanceof Error ? err.message : String(err),
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
catch (err) {
|
|
327
|
+
errors.push({
|
|
328
|
+
type: "activity",
|
|
329
|
+
id: "fetch",
|
|
330
|
+
error: err instanceof Error ? err.message : String(err),
|
|
331
|
+
});
|
|
332
|
+
}
|
|
206
333
|
return { synced, errors };
|
|
207
334
|
},
|
|
208
335
|
});
|
|
336
|
+
// ─── Disconnect ─────────────────────────────────────────────────────────────
|
|
209
337
|
/**
|
|
210
338
|
* Disconnect a user from Strava.
|
|
211
339
|
*
|
|
@@ -217,7 +345,6 @@ export const disconnectStrava = action({
|
|
|
217
345
|
userId: v.string(),
|
|
218
346
|
clientId: v.string(),
|
|
219
347
|
clientSecret: v.string(),
|
|
220
|
-
baseUrl: v.optional(v.string()),
|
|
221
348
|
},
|
|
222
349
|
returns: v.null(),
|
|
223
350
|
handler: async (ctx, args) => {
|
|
@@ -231,8 +358,7 @@ export const disconnectStrava = action({
|
|
|
231
358
|
const tokenDoc = await ctx.runQuery(internal.strava.private.getTokens, { connectionId });
|
|
232
359
|
if (tokenDoc) {
|
|
233
360
|
try {
|
|
234
|
-
|
|
235
|
-
await fetch(`${base}/oauth/deauthorize`, {
|
|
361
|
+
await fetch("https://www.strava.com/oauth/deauthorize", {
|
|
236
362
|
method: "POST",
|
|
237
363
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
238
364
|
body: `access_token=${tokenDoc.accessToken}`,
|