@nativesquare/soma 0.16.5 → 0.16.7
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/garmin.d.ts.map +1 -1
- package/dist/client/garmin.js +2 -0
- package/dist/client/garmin.js.map +1 -1
- package/dist/client/strava.d.ts.map +1 -1
- package/dist/client/strava.js +3 -1
- package/dist/client/strava.js.map +1 -1
- package/dist/client/types.d.ts +3 -3
- package/dist/client/types.d.ts.map +1 -1
- package/dist/component/_generated/api.d.ts +2 -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 +4 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/garmin/schemas/deregistration.d.ts +8 -0
- package/dist/component/garmin/schemas/deregistration.d.ts.map +1 -0
- package/dist/component/garmin/schemas/deregistration.js +12 -0
- package/dist/component/garmin/schemas/deregistration.js.map +1 -0
- package/dist/component/garmin/webhooks.d.ts +14 -0
- package/dist/component/garmin/webhooks.d.ts.map +1 -1
- package/dist/component/garmin/webhooks.js +67 -0
- package/dist/component/garmin/webhooks.js.map +1 -1
- package/dist/component/strava/webhooks.d.ts.map +1 -1
- package/dist/component/strava/webhooks.js +13 -0
- package/dist/component/strava/webhooks.js.map +1 -1
- package/dist/component/validators/shared.d.ts +1 -1
- package/dist/component/validators/shared.d.ts.map +1 -1
- package/dist/component/validators/shared.js.map +1 -1
- package/package.json +1 -1
- package/src/client/garmin.ts +3 -1
- package/src/client/strava.ts +16 -2
- package/src/client/types.ts +4 -3
- package/src/component/_generated/api.ts +2 -0
- package/src/component/_generated/component.ts +7 -0
- package/src/component/garmin/schemas/deregistration.ts +14 -0
- package/src/component/garmin/webhooks.ts +90 -0
- package/src/component/strava/webhooks.ts +42 -0
- package/src/component/validators/shared.ts +1 -0
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
import { v } from "convex/values";
|
|
7
7
|
import { action, type ActionCtx } from "../_generated/server";
|
|
8
8
|
import { api, internal } from "../_generated/api";
|
|
9
|
+
import type { Doc } from "../_generated/dataModel";
|
|
9
10
|
import type { SomaError, WebhookResult } from "../validators/shared.js";
|
|
11
|
+
import { garminDeregistrationPayloadSchema } from "./schemas/deregistration.js";
|
|
10
12
|
import {
|
|
11
13
|
garminSkinTemperaturePingPayloadSchema,
|
|
12
14
|
garminSkinTemperaturePushPayloadSchema,
|
|
@@ -873,4 +875,92 @@ export const handleGarminWebhookRespiration = action({
|
|
|
873
875
|
);
|
|
874
876
|
return { errors: [], items: [] };
|
|
875
877
|
},
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
// Deregistration uses WebhookResult with nullable data (terminal event with no
|
|
881
|
+
// payload to ingest), mirroring Strava's athlete-deauthorize convention.
|
|
882
|
+
type GarminDeregistrationResult = WebhookResult<Record<string, unknown> | null>;
|
|
883
|
+
|
|
884
|
+
/**
|
|
885
|
+
* Handle a Garmin deregistration notification.
|
|
886
|
+
*
|
|
887
|
+
* Garmin POSTs this when a user revokes access at connect.garmin.com. We mirror
|
|
888
|
+
* Strava's `athlete-deauthorize`: clear stored tokens and mark the connection
|
|
889
|
+
* inactive. Proceeds even when the connection is already inactive (e.g. a user
|
|
890
|
+
* who deregisters after a prior in-app disconnect).
|
|
891
|
+
*/
|
|
892
|
+
export const handleGarminWebhookDeregistration = action({
|
|
893
|
+
args: { payload: v.any(), autoIngest: v.optional(v.boolean()) },
|
|
894
|
+
handler: async (ctx, args): Promise<GarminDeregistrationResult> => {
|
|
895
|
+
const shouldIngest = args.autoIngest !== false;
|
|
896
|
+
|
|
897
|
+
const parsed = garminDeregistrationPayloadSchema.safeParse(args.payload);
|
|
898
|
+
if (!parsed.success) {
|
|
899
|
+
console.warn(
|
|
900
|
+
`[garmin:webhook:deregistration] Payload did not match schema`,
|
|
901
|
+
);
|
|
902
|
+
return { errors: [], items: [] };
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
const errors: SomaError[] = [];
|
|
906
|
+
const items: GarminDeregistrationResult["items"] = [];
|
|
907
|
+
|
|
908
|
+
for (const entry of parsed.data.deregistrations) {
|
|
909
|
+
const garminUserId = entry.userId;
|
|
910
|
+
|
|
911
|
+
const connection: Doc<"connections"> | null = await ctx.runQuery(
|
|
912
|
+
internal.private.getConnectionByProviderUserId,
|
|
913
|
+
{ providerUserId: garminUserId, provider: "GARMIN" },
|
|
914
|
+
);
|
|
915
|
+
|
|
916
|
+
if (!connection) {
|
|
917
|
+
errors.push({
|
|
918
|
+
type: "deregistration",
|
|
919
|
+
id: garminUserId,
|
|
920
|
+
message:
|
|
921
|
+
`No Soma connection found for Garmin userId "${garminUserId}". ` +
|
|
922
|
+
"The user may have been disconnected already, or never finished OAuth.",
|
|
923
|
+
});
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
if (shouldIngest) {
|
|
928
|
+
try {
|
|
929
|
+
await ctx.runMutation(internal.private.deleteTokens, {
|
|
930
|
+
connectionId: connection._id,
|
|
931
|
+
});
|
|
932
|
+
await ctx.runMutation(api.public.disconnect, {
|
|
933
|
+
userId: connection.userId,
|
|
934
|
+
provider: "GARMIN",
|
|
935
|
+
});
|
|
936
|
+
console.log(
|
|
937
|
+
`[garmin:webhook:deregistration] completed garminUserId=${garminUserId} userId=${connection.userId}`,
|
|
938
|
+
);
|
|
939
|
+
} catch (err) {
|
|
940
|
+
console.error(
|
|
941
|
+
`[garmin:webhook:deregistration] errored garminUserId=${garminUserId} userId=${connection.userId} error=${err instanceof Error ? err.message : String(err)}`,
|
|
942
|
+
);
|
|
943
|
+
errors.push({
|
|
944
|
+
type: "deregistration",
|
|
945
|
+
id: garminUserId,
|
|
946
|
+
message: err instanceof Error ? err.message : String(err),
|
|
947
|
+
});
|
|
948
|
+
items.push({
|
|
949
|
+
connectionId: connection._id,
|
|
950
|
+
userId: connection.userId,
|
|
951
|
+
data: null,
|
|
952
|
+
});
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
items.push({
|
|
958
|
+
connectionId: connection._id,
|
|
959
|
+
userId: connection.userId,
|
|
960
|
+
data: null,
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
return { errors, items };
|
|
965
|
+
},
|
|
876
966
|
});
|
|
@@ -60,6 +60,9 @@ export const handleStravaWebhook = action({
|
|
|
60
60
|
const eventName = `${payload.object_type}-${payload.aspect_type}`;
|
|
61
61
|
|
|
62
62
|
if (!VALID_EVENT_NAMES.has(eventName)) {
|
|
63
|
+
console.warn(
|
|
64
|
+
`[strava:webhook] unknown eventName=${eventName} ownerId=${payload.owner_id} objectId=${payload.object_id}`,
|
|
65
|
+
);
|
|
63
66
|
return { errors: [], items: [] };
|
|
64
67
|
}
|
|
65
68
|
|
|
@@ -70,6 +73,9 @@ export const handleStravaWebhook = action({
|
|
|
70
73
|
);
|
|
71
74
|
|
|
72
75
|
if (!connection) {
|
|
76
|
+
console.warn(
|
|
77
|
+
`[strava:webhook] no connection found eventName=${eventName} ownerId=${payload.owner_id}`,
|
|
78
|
+
);
|
|
73
79
|
return {
|
|
74
80
|
errors: [{
|
|
75
81
|
type: payload.object_type as SomaErrorType,
|
|
@@ -83,6 +89,9 @@ export const handleStravaWebhook = action({
|
|
|
83
89
|
|
|
84
90
|
// Allow deauthorize to proceed even if connection is inactive
|
|
85
91
|
if (!connection.active && eventName !== "athlete-deauthorize") {
|
|
92
|
+
console.warn(
|
|
93
|
+
`[strava:webhook] inactive connection eventName=${eventName} ownerId=${payload.owner_id} userId=${connection.userId}`,
|
|
94
|
+
);
|
|
86
95
|
return {
|
|
87
96
|
errors: [{
|
|
88
97
|
type: payload.object_type as SomaErrorType,
|
|
@@ -93,6 +102,10 @@ export const handleStravaWebhook = action({
|
|
|
93
102
|
};
|
|
94
103
|
}
|
|
95
104
|
|
|
105
|
+
console.log(
|
|
106
|
+
`[strava:webhook] dispatching eventName=${eventName} ownerId=${payload.owner_id} userId=${connection.userId} objectId=${payload.object_id}`,
|
|
107
|
+
);
|
|
108
|
+
|
|
96
109
|
const connectionId = connection._id;
|
|
97
110
|
const userId = connection.userId;
|
|
98
111
|
|
|
@@ -105,6 +118,9 @@ export const handleStravaWebhook = action({
|
|
|
105
118
|
}
|
|
106
119
|
|
|
107
120
|
if (eventName === "activity-delete") {
|
|
121
|
+
console.log(
|
|
122
|
+
`[strava:webhook:activity-delete] received userId=${userId} objectId=${payload.object_id}`,
|
|
123
|
+
);
|
|
108
124
|
return {
|
|
109
125
|
errors: [],
|
|
110
126
|
items: [{ connectionId, userId, data: null }],
|
|
@@ -150,6 +166,9 @@ async function handleActivityCreateOrUpdate(
|
|
|
150
166
|
);
|
|
151
167
|
accessToken = resolved.accessToken;
|
|
152
168
|
} catch (err) {
|
|
169
|
+
console.error(
|
|
170
|
+
`[strava:webhook:activity] token resolution failed userId=${args.userId} objectId=${args.objectId} error=${err instanceof Error ? err.message : String(err)}`,
|
|
171
|
+
);
|
|
153
172
|
return {
|
|
154
173
|
errors: [{
|
|
155
174
|
type: "activity",
|
|
@@ -194,11 +213,18 @@ async function handleActivityCreateOrUpdate(
|
|
|
194
213
|
} as never);
|
|
195
214
|
}
|
|
196
215
|
|
|
216
|
+
console.log(
|
|
217
|
+
`[strava:webhook:activity] completed userId=${args.userId} objectId=${args.objectId}`,
|
|
218
|
+
);
|
|
219
|
+
|
|
197
220
|
return {
|
|
198
221
|
errors,
|
|
199
222
|
items: [{ connectionId: args.connectionId, userId: args.userId, data }],
|
|
200
223
|
};
|
|
201
224
|
} catch (err) {
|
|
225
|
+
console.error(
|
|
226
|
+
`[strava:webhook:activity] errored userId=${args.userId} objectId=${args.objectId} error=${err instanceof Error ? err.message : String(err)}`,
|
|
227
|
+
);
|
|
202
228
|
errors.push({
|
|
203
229
|
type: "activity",
|
|
204
230
|
id: String(args.objectId),
|
|
@@ -226,6 +252,9 @@ async function handleAthleteUpdate(
|
|
|
226
252
|
);
|
|
227
253
|
accessToken = resolved.accessToken;
|
|
228
254
|
} catch (err) {
|
|
255
|
+
console.error(
|
|
256
|
+
`[strava:webhook:athlete-update] token resolution failed userId=${args.userId} error=${err instanceof Error ? err.message : String(err)}`,
|
|
257
|
+
);
|
|
229
258
|
return {
|
|
230
259
|
errors: [{
|
|
231
260
|
type: "athlete",
|
|
@@ -258,11 +287,18 @@ async function handleAthleteUpdate(
|
|
|
258
287
|
} as never);
|
|
259
288
|
}
|
|
260
289
|
|
|
290
|
+
console.log(
|
|
291
|
+
`[strava:webhook:athlete-update] completed userId=${args.userId}`,
|
|
292
|
+
);
|
|
293
|
+
|
|
261
294
|
return {
|
|
262
295
|
errors: [],
|
|
263
296
|
items: [{ connectionId: args.connectionId, userId: args.userId, data }],
|
|
264
297
|
};
|
|
265
298
|
} catch (err) {
|
|
299
|
+
console.error(
|
|
300
|
+
`[strava:webhook:athlete-update] errored userId=${args.userId} error=${err instanceof Error ? err.message : String(err)}`,
|
|
301
|
+
);
|
|
266
302
|
return {
|
|
267
303
|
errors: [{
|
|
268
304
|
type: "athlete",
|
|
@@ -291,7 +327,13 @@ async function handleAthleteDeauthorize(
|
|
|
291
327
|
userId: args.userId,
|
|
292
328
|
provider: "STRAVA",
|
|
293
329
|
});
|
|
330
|
+
console.log(
|
|
331
|
+
`[strava:webhook:deauthorize] completed userId=${args.userId}`,
|
|
332
|
+
);
|
|
294
333
|
} catch (err) {
|
|
334
|
+
console.error(
|
|
335
|
+
`[strava:webhook:deauthorize] errored userId=${args.userId} error=${err instanceof Error ? err.message : String(err)}`,
|
|
336
|
+
);
|
|
295
337
|
return {
|
|
296
338
|
errors: [{
|
|
297
339
|
type: "athlete",
|