@estifanos-sh/convex-auth 0.0.2 → 0.0.3
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/README.md +5 -5
- package/dist/_generated/component.d.ts +130 -61
- package/dist/_generated/component.d.ts.map +1 -1
- package/dist/_generated/dataModel.d.ts +28 -0
- package/dist/_generated/dataModel.d.ts.map +1 -0
- package/dist/bin.js +1 -2
- package/dist/browser/index.d.ts +2 -2
- package/dist/browser/index.d.ts.map +1 -1
- package/dist/browser/index.js.map +1 -1
- package/dist/client/core/types.d.ts +40 -79
- package/dist/client/core/types.d.ts.map +1 -1
- package/dist/client/core/types.js.map +1 -1
- package/dist/client/errors.d.ts +15 -0
- package/dist/client/errors.d.ts.map +1 -0
- package/dist/client/errors.js +8 -1
- package/dist/client/errors.js.map +1 -1
- package/dist/client/index.d.ts +6 -3
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +3 -2
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/component.d.ts +130 -61
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/_generated/dataModel.d.ts +1 -1
- package/dist/component/_generated/server.d.ts +1 -1
- package/dist/component/account.d.ts +43 -37
- package/dist/component/account.d.ts.map +1 -1
- package/dist/component/account.js +14 -16
- package/dist/component/account.js.map +1 -1
- package/dist/component/connection/audit.d.ts +29 -29
- package/dist/component/connection/audit.d.ts.map +1 -1
- package/dist/component/connection/audit.js +2 -1
- package/dist/component/connection/audit.js.map +1 -1
- package/dist/component/connection/cache.d.ts +6 -6
- package/dist/component/connection/domain/verification.d.ts +19 -19
- package/dist/component/connection/domain.d.ts +20 -20
- package/dist/component/connection/saml/assertion.d.ts +4 -4
- package/dist/component/connection/saml/request.d.ts +9 -9
- package/dist/component/connection/saml/request.d.ts.map +1 -1
- package/dist/component/connection/scim/config.d.ts +13 -13
- package/dist/component/connection/scim/identity.d.ts +102 -59
- package/dist/component/connection/scim/identity.d.ts.map +1 -1
- package/dist/component/connection/scim/identity.js +357 -56
- package/dist/component/connection/scim/identity.js.map +1 -1
- package/dist/component/connection/secret.d.ts +15 -15
- package/dist/component/connection/secret.d.ts.map +1 -1
- package/dist/component/connection/webhook/delivery.d.ts +69 -53
- package/dist/component/connection/webhook/delivery.d.ts.map +1 -1
- package/dist/component/connection/webhook/delivery.js +127 -170
- package/dist/component/connection/webhook/delivery.js.map +1 -1
- package/dist/component/connection/webhook/endpoint.d.ts +21 -21
- package/dist/component/connection/webhook/events.d.ts +26 -0
- package/dist/component/connection/webhook/events.d.ts.map +1 -0
- package/dist/component/connection/webhook/events.js +39 -0
- package/dist/component/connection/webhook/events.js.map +1 -0
- package/dist/component/connection.d.ts +38 -38
- package/dist/component/connection.d.ts.map +1 -1
- package/dist/component/context.d.ts +115 -0
- package/dist/component/context.d.ts.map +1 -0
- package/dist/component/context.js +50 -0
- package/dist/component/context.js.map +1 -0
- package/dist/component/convex.config.d.ts +2 -2
- package/dist/component/crons.d.ts +2 -2
- package/dist/component/event.d.ts +134 -138
- package/dist/component/event.d.ts.map +1 -1
- package/dist/component/event.js +88 -117
- package/dist/component/event.js.map +1 -1
- package/dist/component/eventstream.d.ts +30 -0
- package/dist/component/eventstream.d.ts.map +1 -0
- package/dist/component/eventstream.js +51 -0
- package/dist/component/eventstream.js.map +1 -0
- package/dist/component/factor/device.d.ts +25 -24
- package/dist/component/factor/device.d.ts.map +1 -1
- package/dist/component/factor/device.js +5 -2
- package/dist/component/factor/device.js.map +1 -1
- package/dist/component/factor/passkey.d.ts +84 -75
- package/dist/component/factor/passkey.d.ts.map +1 -1
- package/dist/component/factor/passkey.js +6 -6
- package/dist/component/factor/passkey.js.map +1 -1
- package/dist/component/factor/totp.d.ts +54 -49
- package/dist/component/factor/totp.d.ts.map +1 -1
- package/dist/component/factor/totp.js +3 -3
- package/dist/component/factor/totp.js.map +1 -1
- package/dist/component/group/active.d.ts +326 -22
- package/dist/component/group/active.d.ts.map +1 -1
- package/dist/component/group/active.js +21 -11
- package/dist/component/group/active.js.map +1 -1
- package/dist/component/group/invite.d.ts +28 -28
- package/dist/component/group/invite.d.ts.map +1 -1
- package/dist/component/group/member.d.ts +34 -34
- package/dist/component/group.d.ts +47 -62
- package/dist/component/group.d.ts.map +1 -1
- package/dist/component/http.d.ts +2 -2
- package/dist/component/limits.d.ts +4 -4
- package/dist/component/maintenance.d.ts +2 -2
- package/dist/component/model.d.ts +1362 -1027
- package/dist/component/model.d.ts.map +1 -1
- package/dist/component/model.js +23 -6
- package/dist/component/model.js.map +1 -1
- package/dist/component/modules.d.ts +8 -2
- package/dist/component/modules.d.ts.map +1 -1
- package/dist/component/modules.js +3 -0
- package/dist/component/modules.js.map +1 -1
- package/dist/component/oauth/client.d.ts +20 -20
- package/dist/component/oauth/code.d.ts +14 -14
- package/dist/component/oauth/refresh.d.ts +19 -19
- package/dist/component/schema.d.ts +1100 -611
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +17 -4
- package/dist/component/schema.js.map +1 -1
- package/dist/component/session.d.ts +72 -32
- package/dist/component/session.d.ts.map +1 -1
- package/dist/component/session.js +120 -32
- package/dist/component/session.js.map +1 -1
- package/dist/component/token/continuation.d.ts +40 -18
- package/dist/component/token/continuation.d.ts.map +1 -1
- package/dist/component/token/continuation.js +57 -11
- package/dist/component/token/continuation.js.map +1 -1
- package/dist/component/token/pkce.d.ts +22 -22
- package/dist/component/token/pkce.d.ts.map +1 -1
- package/dist/component/token/refresh.d.ts +39 -35
- package/dist/component/token/refresh.d.ts.map +1 -1
- package/dist/component/token/refresh.js +11 -6
- package/dist/component/token/refresh.js.map +1 -1
- package/dist/component/token/verification.d.ts +36 -32
- package/dist/component/token/verification.d.ts.map +1 -1
- package/dist/component/token/verification.js +3 -3
- package/dist/component/token/verification.js.map +1 -1
- package/dist/component/user/email.d.ts +21 -21
- package/dist/component/user/key.d.ts +35 -35
- package/dist/component/user/key.d.ts.map +1 -1
- package/dist/component/user.d.ts +51 -45
- package/dist/component/user.d.ts.map +1 -1
- package/dist/core/index.d.ts +54 -90
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -4
- package/dist/core/index.js.map +1 -1
- package/dist/expo/index.d.ts +3 -3
- package/dist/expo/index.d.ts.map +1 -1
- package/dist/expo/index.js.map +1 -1
- package/dist/model.js.map +1 -1
- package/dist/providers/anonymous.d.ts +11 -5
- package/dist/providers/anonymous.d.ts.map +1 -1
- package/dist/providers/anonymous.js +4 -1
- package/dist/providers/anonymous.js.map +1 -1
- package/dist/providers/apple.d.ts +1 -1
- package/dist/providers/credentials.d.ts +21 -13
- package/dist/providers/credentials.d.ts.map +1 -1
- package/dist/providers/credentials.js +2 -0
- package/dist/providers/credentials.js.map +1 -1
- package/dist/providers/custom.d.ts +3 -3
- package/dist/providers/custom.d.ts.map +1 -1
- package/dist/providers/custom.js.map +1 -1
- package/dist/providers/email.d.ts +3 -3
- package/dist/providers/email.d.ts.map +1 -1
- package/dist/providers/email.js.map +1 -1
- package/dist/providers/github.d.ts +1 -1
- package/dist/providers/google.d.ts +1 -1
- package/dist/providers/microsoft.d.ts +1 -1
- package/dist/providers/password.d.ts +103 -8
- package/dist/providers/password.d.ts.map +1 -1
- package/dist/providers/password.js +71 -26
- package/dist/providers/password.js.map +1 -1
- package/dist/providers/phone.d.ts +4 -3
- package/dist/providers/phone.d.ts.map +1 -1
- package/dist/providers/phone.js.map +1 -1
- package/dist/react/index.d.ts +40 -41
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +27 -52
- package/dist/react/index.js.map +1 -1
- package/dist/server/auth.d.ts +157 -117
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +41 -22
- package/dist/server/auth.js.map +1 -1
- package/dist/server/component/factor/db.js +4 -4
- package/dist/server/component/factor/db.js.map +1 -1
- package/dist/server/config.d.ts +2 -1
- package/dist/server/connection/domain.d.ts +4 -66
- package/dist/server/connection/domain.d.ts.map +1 -1
- package/dist/server/connection/domain.js +22 -4
- package/dist/server/connection/domain.js.map +1 -1
- package/dist/server/connection/group/service.js +6 -4
- package/dist/server/connection/group/service.js.map +1 -1
- package/dist/server/connection/http.js +94 -261
- package/dist/server/connection/http.js.map +1 -1
- package/dist/server/connection/oidc.js +8 -17
- package/dist/server/connection/oidc.js.map +1 -1
- package/dist/server/connection/policy/domain.js +4 -1
- package/dist/server/connection/policy/domain.js.map +1 -1
- package/dist/server/connection/policy.js +14 -13
- package/dist/server/connection/policy.js.map +1 -1
- package/dist/server/connection/provision.js +5 -19
- package/dist/server/connection/provision.js.map +1 -1
- package/dist/server/connection/webhook.js +4 -36
- package/dist/server/connection/webhook.js.map +1 -1
- package/dist/server/context.d.ts +2 -2
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/context.js +35 -24
- package/dist/server/context.js.map +1 -1
- package/dist/server/contract.d.ts.map +1 -1
- package/dist/server/contract.js +6 -14
- package/dist/server/contract.js.map +1 -1
- package/dist/server/core.js +6 -0
- package/dist/server/core.js.map +1 -1
- package/dist/server/cors.d.ts +2 -2
- package/dist/server/cors.d.ts.map +1 -1
- package/dist/server/crypto.js.map +1 -1
- package/dist/server/db.js +2 -1
- package/dist/server/db.js.map +1 -1
- package/dist/server/domains/context.js +34 -0
- package/dist/server/domains/context.js.map +1 -0
- package/dist/server/domains/factor.d.ts +3 -2
- package/dist/server/domains/factor.d.ts.map +1 -1
- package/dist/server/domains/factor.js.map +1 -1
- package/dist/server/domains/group.js.map +1 -1
- package/dist/server/domains/invite.js.map +1 -1
- package/dist/server/domains/key.js.map +1 -1
- package/dist/server/domains/member.js.map +1 -1
- package/dist/server/domains/session.js.map +1 -1
- package/dist/server/domains/user.js.map +1 -1
- package/dist/server/env.js +2 -22
- package/dist/server/env.js.map +1 -1
- package/dist/server/events.d.ts +0 -1
- package/dist/server/events.d.ts.map +1 -1
- package/dist/server/events.js +6 -6
- package/dist/server/events.js.map +1 -1
- package/dist/server/http.d.ts +1 -0
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +4 -4
- package/dist/server/http.js.map +1 -1
- package/dist/server/identity/claims.js +1 -1
- package/dist/server/identity/convex.d.ts +2 -0
- package/dist/server/identity/convex.d.ts.map +1 -1
- package/dist/server/index.d.ts +2 -3
- package/dist/server/index.js +1 -2
- package/dist/server/limits.js +21 -1
- package/dist/server/limits.js.map +1 -1
- package/dist/server/mutations/credentials/signin.js +6 -3
- package/dist/server/mutations/credentials/signin.js.map +1 -1
- package/dist/server/mutations/invalidate.js +5 -10
- package/dist/server/mutations/invalidate.js.map +1 -1
- package/dist/server/mutations/oauth.js +1 -1
- package/dist/server/mutations/oauth.js.map +1 -1
- package/dist/server/mutations/retrieve.js +4 -1
- package/dist/server/mutations/retrieve.js.map +1 -1
- package/dist/server/mutations/signin.js +2 -2
- package/dist/server/mutations/signin.js.map +1 -1
- package/dist/server/mutations/verify.js +4 -3
- package/dist/server/mutations/verify.js.map +1 -1
- package/dist/server/oauth/factory.js.map +1 -1
- package/dist/server/runtime.d.ts +107 -127
- package/dist/server/runtime.d.ts.map +1 -1
- package/dist/server/runtime.js +43 -28
- package/dist/server/runtime.js.map +1 -1
- package/dist/server/session/lifecycle.d.ts.map +1 -1
- package/dist/server/session/lifecycle.js +18 -3
- package/dist/server/session/lifecycle.js.map +1 -1
- package/dist/server/signin/flow.js +1 -1
- package/dist/server/signin/flow.js.map +1 -1
- package/dist/server/tokens.js +1 -0
- package/dist/server/tokens.js.map +1 -1
- package/dist/server/totp.js +3 -3
- package/dist/server/totp.js.map +1 -1
- package/dist/server/types.d.ts +63 -43
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/user/account.js +0 -1
- package/dist/server/user/account.js.map +1 -1
- package/dist/server/validators.d.ts +874 -917
- package/dist/server/validators.d.ts.map +1 -1
- package/dist/server/validators.js +33 -32
- package/dist/server/validators.js.map +1 -1
- package/dist/server/webauthn.js +4 -4
- package/dist/server/webauthn.js.map +1 -1
- package/dist/shared/brand.d.ts +3 -7
- package/dist/shared/brand.d.ts.map +1 -1
- package/dist/shared/params.d.ts +78 -0
- package/dist/shared/params.d.ts.map +1 -0
- package/dist/shared/results.d.ts +7 -1
- package/dist/shared/results.d.ts.map +1 -1
- package/dist/svelte/context.svelte.d.ts +23 -12
- package/dist/svelte/context.svelte.d.ts.map +1 -1
- package/dist/svelte/context.svelte.js +20 -13
- package/dist/svelte/index.d.ts +1 -4
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +1 -4
- package/dist/test.d.ts +1105 -611
- package/dist/test.d.ts.map +1 -1
- package/package.json +2 -1
- package/dist/server/env.d.ts +0 -28
- package/dist/server/env.d.ts.map +0 -1
- package/dist/svelte/AuthLoading.svelte +0 -13
- package/dist/svelte/AuthLoading.svelte.d.ts +0 -8
- package/dist/svelte/AuthLoading.svelte.d.ts.map +0 -1
- package/dist/svelte/SignedIn.svelte +0 -14
- package/dist/svelte/SignedIn.svelte.d.ts +0 -8
- package/dist/svelte/SignedIn.svelte.d.ts.map +0 -1
- package/dist/svelte/SignedOut.svelte +0 -13
- package/dist/svelte/SignedOut.svelte.d.ts +0 -8
- package/dist/svelte/SignedOut.svelte.d.ts.map +0 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { vAuthEventKind, vGroupWebhookDeliveryDoc, vGroupWebhookDeliveryPublicDoc, vPaginated, vWebhookDeliveryStatus } from "../../model.js";
|
|
1
|
+
import { vAuthEventKind, vGroupWebhookDeliveryDoc, vGroupWebhookDeliveryPublicDoc, vPaginated } from "../../model.js";
|
|
3
2
|
import { unsafeFetchUrlReason } from "../../../shared/fetch/guard.js";
|
|
4
3
|
import schema_default from "../../schema.js";
|
|
5
4
|
import { api, components, internal } from "../../_generated/api.js";
|
|
6
5
|
import { internalAction, internalMutation, internalQuery, mutation, query } from "../../_generated/server.js";
|
|
7
6
|
import "../../functions.js";
|
|
7
|
+
import { appendDeliveryEvent } from "./events.js";
|
|
8
8
|
import { v } from "convex/values";
|
|
9
9
|
import { paginationOptsValidator } from "convex/server";
|
|
10
10
|
import { paginator } from "convex-helpers/server/pagination";
|
|
@@ -40,44 +40,6 @@ const workpool = new Workpool(components.webhookWorkpool, {
|
|
|
40
40
|
function isRetriableStatus(status) {
|
|
41
41
|
return status >= 500 || status === 408 || status === 429;
|
|
42
42
|
}
|
|
43
|
-
async function appendDeliveryEvent(ctx, args) {
|
|
44
|
-
const attemptPart = typeof args.data?.attemptCount === "number" ? `:${args.data.attemptCount}` : "";
|
|
45
|
-
const event = {
|
|
46
|
-
eventId: `${args.kind}:${args.deliveryId}${attemptPart}`,
|
|
47
|
-
kind: args.kind,
|
|
48
|
-
category: "webhook",
|
|
49
|
-
occurredAt: args.occurredAt,
|
|
50
|
-
actor: {
|
|
51
|
-
type: "webhook",
|
|
52
|
-
id: args.endpointId
|
|
53
|
-
},
|
|
54
|
-
subject: {
|
|
55
|
-
type: "webhook_delivery",
|
|
56
|
-
id: args.deliveryId
|
|
57
|
-
},
|
|
58
|
-
targets: [{
|
|
59
|
-
kind: "connection",
|
|
60
|
-
id: args.connectionId
|
|
61
|
-
}],
|
|
62
|
-
outcome: args.outcome,
|
|
63
|
-
data: {
|
|
64
|
-
sourceEventId: args.sourceEventId,
|
|
65
|
-
sourceEventType: args.sourceEventType,
|
|
66
|
-
endpointId: args.endpointId,
|
|
67
|
-
deliveryId: args.deliveryId,
|
|
68
|
-
...args.data
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
try {
|
|
72
|
-
await ctx.runMutation(api.event.append, {
|
|
73
|
-
event,
|
|
74
|
-
targets: event.targets,
|
|
75
|
-
idempotencyKey: event.eventId
|
|
76
|
-
});
|
|
77
|
-
} catch (error) {
|
|
78
|
-
logMessage("connection.webhook.delivery", "WARN", [`audit event ${args.kind} emit failed (best-effort)`, error]);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
43
|
function publicDelivery(delivery) {
|
|
82
44
|
return {
|
|
83
45
|
_id: delivery._id,
|
|
@@ -133,12 +95,85 @@ const dueForDispatch = query({
|
|
|
133
95
|
return await ctx.db.query("GroupWebhookDelivery").withIndex("status_next_attempt_at", (idx) => idx.eq("status", "pending").lte("nextAttemptAt", now)).take(take);
|
|
134
96
|
}
|
|
135
97
|
});
|
|
98
|
+
/** Mark a pending delivery as in progress and record its attempt atomically. */
|
|
99
|
+
const begin = internalMutation({
|
|
100
|
+
args: {
|
|
101
|
+
id: v.id("GroupWebhookDelivery"),
|
|
102
|
+
occurredAt: v.number()
|
|
103
|
+
},
|
|
104
|
+
returns: v.union(vGroupWebhookDeliveryDoc, v.null()),
|
|
105
|
+
handler: async (ctx, { id: deliveryId, occurredAt }) => {
|
|
106
|
+
const delivery = await ctx.db.get("GroupWebhookDelivery", deliveryId);
|
|
107
|
+
if (delivery === null || delivery.status !== "pending") return null;
|
|
108
|
+
const attemptCount = delivery.attemptCount + 1;
|
|
109
|
+
await ctx.db.patch("GroupWebhookDelivery", deliveryId, {
|
|
110
|
+
status: "processing",
|
|
111
|
+
attemptCount,
|
|
112
|
+
lastAttemptAt: occurredAt
|
|
113
|
+
});
|
|
114
|
+
await appendDeliveryEvent(ctx, {
|
|
115
|
+
deliveryId,
|
|
116
|
+
connectionId: delivery.connectionId,
|
|
117
|
+
endpointId: delivery.endpointId,
|
|
118
|
+
sourceEventId: delivery.eventId,
|
|
119
|
+
sourceEventType: delivery.kind,
|
|
120
|
+
kind: "webhook.delivery.attempted",
|
|
121
|
+
outcome: "success",
|
|
122
|
+
occurredAt,
|
|
123
|
+
data: { attemptCount }
|
|
124
|
+
});
|
|
125
|
+
return {
|
|
126
|
+
...delivery,
|
|
127
|
+
status: "processing",
|
|
128
|
+
attemptCount,
|
|
129
|
+
lastAttemptAt: occurredAt
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
/** Settle an in-progress delivery and record its outcome atomically. */
|
|
134
|
+
const settle = internalMutation({
|
|
135
|
+
args: {
|
|
136
|
+
id: v.id("GroupWebhookDelivery"),
|
|
137
|
+
occurredAt: v.number(),
|
|
138
|
+
outcome: v.union(v.literal("success"), v.literal("failure")),
|
|
139
|
+
retry: v.boolean(),
|
|
140
|
+
responseStatus: v.optional(v.number()),
|
|
141
|
+
error: v.optional(v.string())
|
|
142
|
+
},
|
|
143
|
+
returns: v.null(),
|
|
144
|
+
handler: async (ctx, args) => {
|
|
145
|
+
const delivery = await ctx.db.get("GroupWebhookDelivery", args.id);
|
|
146
|
+
if (delivery === null || delivery.status !== "processing") return null;
|
|
147
|
+
const succeeded = args.outcome === "success";
|
|
148
|
+
await ctx.db.patch("GroupWebhookDelivery", args.id, {
|
|
149
|
+
status: succeeded ? "delivered" : args.retry ? "pending" : "failed",
|
|
150
|
+
nextAttemptAt: args.retry ? args.occurredAt : delivery.nextAttemptAt,
|
|
151
|
+
lastAttemptAt: args.occurredAt,
|
|
152
|
+
lastResponseStatus: args.responseStatus,
|
|
153
|
+
lastError: args.error
|
|
154
|
+
});
|
|
155
|
+
await appendDeliveryEvent(ctx, {
|
|
156
|
+
deliveryId: args.id,
|
|
157
|
+
connectionId: delivery.connectionId,
|
|
158
|
+
endpointId: delivery.endpointId,
|
|
159
|
+
sourceEventId: delivery.eventId,
|
|
160
|
+
sourceEventType: delivery.kind,
|
|
161
|
+
kind: succeeded ? "webhook.delivery.succeeded" : "webhook.delivery.failed",
|
|
162
|
+
outcome: args.outcome,
|
|
163
|
+
occurredAt: args.occurredAt,
|
|
164
|
+
data: {
|
|
165
|
+
attemptCount: delivery.attemptCount,
|
|
166
|
+
status: args.responseStatus,
|
|
167
|
+
error: args.error
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
});
|
|
136
173
|
/**
|
|
137
174
|
* Queue a delivery for an endpoint. Idempotent on `(eventId, endpointId)` —
|
|
138
|
-
* returns the existing id if already queued.
|
|
139
|
-
*
|
|
140
|
-
* the workpool (running at `nextAttemptAt`, with `onDispatchComplete` as the
|
|
141
|
-
* completion hook).
|
|
175
|
+
* returns the existing id if already queued. The row, its audit event, and the
|
|
176
|
+
* durable workpool entry commit together; an audit failure aborts all three.
|
|
142
177
|
*/
|
|
143
178
|
const create = mutation({
|
|
144
179
|
args: {
|
|
@@ -192,7 +227,21 @@ const onDispatchComplete = internalMutation({
|
|
|
192
227
|
handler: async (ctx, { context, result }) => {
|
|
193
228
|
if (result.kind === "success") return null;
|
|
194
229
|
const delivery = await ctx.db.get("GroupWebhookDelivery", context.deliveryId);
|
|
195
|
-
if (delivery === null || delivery.status
|
|
230
|
+
if (delivery === null || delivery.status !== "processing") return null;
|
|
231
|
+
await appendDeliveryEvent(ctx, {
|
|
232
|
+
deliveryId: context.deliveryId,
|
|
233
|
+
connectionId: delivery.connectionId,
|
|
234
|
+
endpointId: delivery.endpointId,
|
|
235
|
+
sourceEventId: delivery.eventId,
|
|
236
|
+
sourceEventType: delivery.kind,
|
|
237
|
+
kind: "webhook.delivery.failed",
|
|
238
|
+
outcome: "failure",
|
|
239
|
+
occurredAt: Date.now(),
|
|
240
|
+
data: {
|
|
241
|
+
attemptCount: delivery.attemptCount,
|
|
242
|
+
error: result.kind === "failed" ? result.error : "delivery canceled"
|
|
243
|
+
}
|
|
244
|
+
});
|
|
196
245
|
await ctx.db.patch("GroupWebhookDelivery", context.deliveryId, {
|
|
197
246
|
status: "failed",
|
|
198
247
|
lastError: result.kind === "failed" ? result.error : "delivery canceled"
|
|
@@ -200,25 +249,6 @@ const onDispatchComplete = internalMutation({
|
|
|
200
249
|
return null;
|
|
201
250
|
}
|
|
202
251
|
});
|
|
203
|
-
/** Patch fields on a delivery (attempt bookkeeping). */
|
|
204
|
-
const update = mutation({
|
|
205
|
-
args: {
|
|
206
|
-
id: v.id("GroupWebhookDelivery"),
|
|
207
|
-
patch: v.object({
|
|
208
|
-
status: v.optional(vWebhookDeliveryStatus),
|
|
209
|
-
attemptCount: v.optional(v.number()),
|
|
210
|
-
nextAttemptAt: v.optional(v.number()),
|
|
211
|
-
lastAttemptAt: v.optional(v.number()),
|
|
212
|
-
lastResponseStatus: v.optional(v.number()),
|
|
213
|
-
lastError: v.optional(v.string())
|
|
214
|
-
})
|
|
215
|
-
},
|
|
216
|
-
returns: v.null(),
|
|
217
|
-
handler: async (ctx, { id: deliveryId, patch }) => {
|
|
218
|
-
await ctx.db.patch("GroupWebhookDelivery", deliveryId, patch);
|
|
219
|
-
return null;
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
252
|
/**
|
|
223
253
|
* POST a delivery's signed payload to its endpoint and settle the row. Skips
|
|
224
254
|
* (failing the delivery) when the endpoint is missing or disabled. Marks
|
|
@@ -231,49 +261,23 @@ const dispatch = internalAction({
|
|
|
231
261
|
args: { id: v.id("GroupWebhookDelivery") },
|
|
232
262
|
returns: v.null(),
|
|
233
263
|
handler: async (ctx, { id: deliveryId }) => {
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
264
|
+
const startedAt = Date.now();
|
|
265
|
+
const delivery = await ctx.runMutation(internal.connection.webhook.delivery.begin, {
|
|
266
|
+
id: deliveryId,
|
|
267
|
+
occurredAt: startedAt
|
|
268
|
+
});
|
|
269
|
+
if (delivery === null) return null;
|
|
237
270
|
const endpoint = await ctx.runQuery(api.connection.webhook.endpoint.get, { id: delivery.endpointId });
|
|
238
271
|
if (!endpoint || endpoint.status !== "active") {
|
|
239
|
-
|
|
240
|
-
await appendDeliveryEvent(ctx, {
|
|
241
|
-
deliveryId,
|
|
242
|
-
connectionId: delivery.connectionId,
|
|
243
|
-
endpointId: delivery.endpointId,
|
|
244
|
-
sourceEventId: delivery.eventId,
|
|
245
|
-
sourceEventType: delivery.kind,
|
|
246
|
-
kind: "webhook.delivery.failed",
|
|
247
|
-
outcome: "failure",
|
|
248
|
-
occurredAt: failedAt,
|
|
249
|
-
data: {
|
|
250
|
-
attemptCount: delivery.attemptCount + 1,
|
|
251
|
-
error: "endpoint missing or disabled"
|
|
252
|
-
}
|
|
253
|
-
});
|
|
254
|
-
await ctx.runMutation(api.connection.webhook.delivery.update, {
|
|
272
|
+
await ctx.runMutation(internal.connection.webhook.delivery.settle, {
|
|
255
273
|
id: deliveryId,
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
attemptCount: delivery.attemptCount + 1
|
|
261
|
-
}
|
|
274
|
+
occurredAt: startedAt,
|
|
275
|
+
outcome: "failure",
|
|
276
|
+
retry: false,
|
|
277
|
+
error: "endpoint missing or disabled"
|
|
262
278
|
});
|
|
263
279
|
return null;
|
|
264
280
|
}
|
|
265
|
-
const startedAt = Date.now();
|
|
266
|
-
await appendDeliveryEvent(ctx, {
|
|
267
|
-
deliveryId,
|
|
268
|
-
connectionId: delivery.connectionId,
|
|
269
|
-
endpointId: delivery.endpointId,
|
|
270
|
-
sourceEventId: delivery.eventId,
|
|
271
|
-
sourceEventType: delivery.kind,
|
|
272
|
-
kind: "webhook.delivery.attempted",
|
|
273
|
-
outcome: "success",
|
|
274
|
-
occurredAt: startedAt,
|
|
275
|
-
data: { attemptCount: delivery.attemptCount + 1 }
|
|
276
|
-
});
|
|
277
281
|
const body = JSON.stringify({
|
|
278
282
|
kind: delivery.kind,
|
|
279
283
|
payload: delivery.payload
|
|
@@ -296,89 +300,42 @@ const dispatch = internalAction({
|
|
|
296
300
|
signal: AbortSignal.timeout(15e3)
|
|
297
301
|
});
|
|
298
302
|
} catch (err) {
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
connectionId: delivery.connectionId,
|
|
303
|
-
endpointId: delivery.endpointId,
|
|
304
|
-
sourceEventId: delivery.eventId,
|
|
305
|
-
sourceEventType: delivery.kind,
|
|
306
|
-
kind: "webhook.delivery.failed",
|
|
307
|
-
outcome: "failure",
|
|
308
|
-
occurredAt: startedAt,
|
|
309
|
-
data: {
|
|
310
|
-
attemptCount: delivery.attemptCount + 1,
|
|
311
|
-
error: err instanceof Error ? err.message : String(err)
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
await ctx.runMutation(api.connection.webhook.delivery.update, {
|
|
303
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
304
|
+
const retry = delivery.attemptCount < MAX_ATTEMPTS;
|
|
305
|
+
await ctx.runMutation(internal.connection.webhook.delivery.settle, {
|
|
315
306
|
id: deliveryId,
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
attemptCount: delivery.attemptCount + 1
|
|
321
|
-
}
|
|
307
|
+
occurredAt: startedAt,
|
|
308
|
+
outcome: "failure",
|
|
309
|
+
retry,
|
|
310
|
+
error
|
|
322
311
|
});
|
|
323
|
-
if (
|
|
312
|
+
if (retry) throw err;
|
|
324
313
|
return null;
|
|
325
314
|
}
|
|
326
315
|
if (!response.ok) {
|
|
327
|
-
const
|
|
328
|
-
await
|
|
329
|
-
deliveryId,
|
|
330
|
-
connectionId: delivery.connectionId,
|
|
331
|
-
endpointId: delivery.endpointId,
|
|
332
|
-
sourceEventId: delivery.eventId,
|
|
333
|
-
sourceEventType: delivery.kind,
|
|
334
|
-
kind: "webhook.delivery.failed",
|
|
335
|
-
outcome: "failure",
|
|
336
|
-
occurredAt: startedAt,
|
|
337
|
-
data: {
|
|
338
|
-
attemptCount: delivery.attemptCount + 1,
|
|
339
|
-
status: response.status
|
|
340
|
-
}
|
|
341
|
-
});
|
|
342
|
-
await ctx.runMutation(api.connection.webhook.delivery.update, {
|
|
316
|
+
const retry = isRetriableStatus(response.status) && delivery.attemptCount < MAX_ATTEMPTS;
|
|
317
|
+
await ctx.runMutation(internal.connection.webhook.delivery.settle, {
|
|
343
318
|
id: deliveryId,
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
attemptCount: delivery.attemptCount + 1
|
|
350
|
-
}
|
|
319
|
+
occurredAt: startedAt,
|
|
320
|
+
outcome: "failure",
|
|
321
|
+
retry,
|
|
322
|
+
responseStatus: response.status,
|
|
323
|
+
error: `HTTP ${response.status}`
|
|
351
324
|
});
|
|
352
|
-
if (
|
|
325
|
+
if (retry) throw new Error(`Webhook delivery failed: HTTP ${response.status}`);
|
|
353
326
|
return null;
|
|
354
327
|
}
|
|
355
|
-
await
|
|
356
|
-
deliveryId,
|
|
357
|
-
connectionId: delivery.connectionId,
|
|
358
|
-
endpointId: delivery.endpointId,
|
|
359
|
-
sourceEventId: delivery.eventId,
|
|
360
|
-
sourceEventType: delivery.kind,
|
|
361
|
-
kind: "webhook.delivery.succeeded",
|
|
362
|
-
outcome: "success",
|
|
363
|
-
occurredAt: startedAt,
|
|
364
|
-
data: {
|
|
365
|
-
attemptCount: delivery.attemptCount + 1,
|
|
366
|
-
status: response.status
|
|
367
|
-
}
|
|
368
|
-
});
|
|
369
|
-
await ctx.runMutation(api.connection.webhook.delivery.update, {
|
|
328
|
+
await ctx.runMutation(internal.connection.webhook.delivery.settle, {
|
|
370
329
|
id: deliveryId,
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
attemptCount: delivery.attemptCount + 1
|
|
376
|
-
}
|
|
330
|
+
occurredAt: startedAt,
|
|
331
|
+
outcome: "success",
|
|
332
|
+
retry: false,
|
|
333
|
+
responseStatus: response.status
|
|
377
334
|
});
|
|
378
335
|
return null;
|
|
379
336
|
}
|
|
380
337
|
});
|
|
381
338
|
|
|
382
339
|
//#endregion
|
|
383
|
-
export { create, dispatch, dueForDispatch, get, list, onDispatchComplete,
|
|
340
|
+
export { begin, create, dispatch, dueForDispatch, get, list, onDispatchComplete, settle };
|
|
384
341
|
//# sourceMappingURL=delivery.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delivery.js","names":["schema"],"sources":["../../../../src/component/connection/webhook/delivery.ts"],"sourcesContent":["/**\n * `component.connection.webhook.delivery.*` — queued webhook delivery attempts\n * (sub-resource of webhook).\n *\n * `list` is overloaded (`{ connectionId }` history or\n * `{ now }` ready-for-dispatch).\n *\n * @module\n */\n\nimport { Workpool, vOnCompleteArgs } from \"@convex-dev/workpool\";\nimport { paginationOptsValidator } from \"convex/server\";\nimport { v, type Infer } from \"convex/values\";\nimport { paginator } from \"convex-helpers/server/pagination\";\n\nimport { api, components, internal } from \"../../_generated/api\";\nimport type { Id } from \"../../_generated/dataModel\";\nimport { internalAction, internalMutation, internalQuery, mutation, query } from \"../../functions\";\nimport { unsafeFetchUrlReason } from \"../../../shared/fetch/guard\";\nimport { logMessage } from \"../../../shared/log\";\nimport {\n vAuthEventKind,\n vGroupWebhookDeliveryDoc,\n vGroupWebhookDeliveryPublicDoc,\n vPaginated,\n vWebhookDeliveryStatus,\n} from \"../../model\";\nimport schema from \"../../schema\";\n\nconst MAX_ATTEMPTS = 5;\n\nconst workpool = new Workpool(components.webhookWorkpool, {\n maxParallelism: 5,\n defaultRetryBehavior: { maxAttempts: MAX_ATTEMPTS, initialBackoffMs: 1_000, base: 2 },\n retryActionsByDefault: true,\n});\n\n/**\n * Whether an HTTP response status warrants a retry. Only transient failures\n * retry — `5xx`, `408 Request Timeout`, and `429 Too Many Requests`. Every\n * other non-2xx (the rest of `4xx`, plus unfollowed `3xx`) is a permanent\n * rejection that re-sending the identical payload cannot fix, so it fails\n * immediately instead of burning the full retry budget.\n */\nfunction isRetriableStatus(status: number): boolean {\n return status >= 500 || status === 408 || status === 429;\n}\n\nasync function appendDeliveryEvent(\n ctx: any,\n args: {\n deliveryId: string;\n connectionId: string;\n endpointId: string;\n sourceEventId: string;\n sourceEventType: Infer<typeof vAuthEventKind>;\n kind:\n | \"webhook.delivery.created\"\n | \"webhook.delivery.attempted\"\n | \"webhook.delivery.succeeded\"\n | \"webhook.delivery.failed\";\n outcome: \"success\" | \"failure\";\n occurredAt: number;\n data?: Record<string, unknown>;\n },\n) {\n const attemptPart =\n typeof args.data?.attemptCount === \"number\" ? `:${args.data.attemptCount}` : \"\";\n const event = {\n eventId: `${args.kind}:${args.deliveryId}${attemptPart}`,\n kind: args.kind,\n category: \"webhook\" as const,\n occurredAt: args.occurredAt,\n actor: { type: \"webhook\" as const, id: args.endpointId },\n subject: { type: \"webhook_delivery\" as const, id: args.deliveryId },\n targets: [{ kind: \"connection\" as const, id: args.connectionId }],\n outcome: args.outcome,\n data: {\n sourceEventId: args.sourceEventId,\n sourceEventType: args.sourceEventType,\n endpointId: args.endpointId,\n deliveryId: args.deliveryId,\n ...args.data,\n },\n };\n try {\n await ctx.runMutation(api.event.append, {\n event,\n targets: event.targets,\n idempotencyKey: event.eventId,\n });\n } catch (error) {\n logMessage(\"connection.webhook.delivery\", \"WARN\", [\n `audit event ${args.kind} emit failed (best-effort)`,\n error,\n ]);\n }\n}\n\nfunction publicDelivery(\n delivery: Infer<typeof vGroupWebhookDeliveryDoc>,\n): Infer<typeof vGroupWebhookDeliveryPublicDoc> {\n return {\n _id: delivery._id,\n _creationTime: delivery._creationTime,\n connectionId: delivery.connectionId,\n endpointId: delivery.endpointId,\n eventId: delivery.eventId,\n kind: delivery.kind,\n status: delivery.status,\n attemptCount: delivery.attemptCount,\n nextAttemptAt: delivery.nextAttemptAt,\n lastAttemptAt: delivery.lastAttemptAt,\n lastResponseStatus: delivery.lastResponseStatus,\n lastError: delivery.lastError,\n signedAt: delivery.signedAt,\n };\n}\n\n/** Read a delivery by id (internal — returns the full, unredacted doc). */\nexport const get = internalQuery({\n args: { id: v.id(\"GroupWebhookDelivery\") },\n returns: v.union(vGroupWebhookDeliveryDoc, v.null()),\n handler: async (ctx, { id: deliveryId }) => {\n return await ctx.db.get(\"GroupWebhookDelivery\", deliveryId);\n },\n});\n\n/** List a connection's deliveries, newest first and paginated. Rows are redacted to public fields. */\nexport const list = query({\n args: {\n connectionId: v.id(\"GroupConnection\"),\n paginationOpts: paginationOptsValidator,\n },\n returns: vPaginated(vGroupWebhookDeliveryPublicDoc),\n handler: async (ctx, { connectionId, paginationOpts }) => {\n const result = await paginator(ctx.db, schema)\n .query(\"GroupWebhookDelivery\")\n .withIndex(\"group_connection_id\", (idx) => idx.eq(\"connectionId\", connectionId))\n .order(\"desc\")\n .paginate(paginationOpts);\n return {\n ...result,\n page: result.page.map(publicDelivery),\n };\n },\n});\n\n/**\n * List pending deliveries whose `nextAttemptAt` is at or before `now`, ready to\n * be dispatched (`limit` clamped to 1..100, default 50).\n */\nexport const dueForDispatch = query({\n args: {\n now: v.number(),\n limit: v.optional(v.number()),\n },\n returns: v.array(vGroupWebhookDeliveryDoc),\n handler: async (ctx, { now, limit }) => {\n const take = Math.min(Math.max(limit ?? 50, 1), 100);\n return await ctx.db\n .query(\"GroupWebhookDelivery\")\n .withIndex(\"status_next_attempt_at\", (idx) =>\n idx.eq(\"status\", \"pending\").lte(\"nextAttemptAt\", now),\n )\n .take(take);\n },\n});\n\n/**\n * Queue a delivery for an endpoint. Idempotent on `(eventId, endpointId)` —\n * returns the existing id if already queued. On insert, emits a\n * `webhook.delivery.created` audit event and enqueues the `dispatch` action on\n * the workpool (running at `nextAttemptAt`, with `onDispatchComplete` as the\n * completion hook).\n */\nexport const create = mutation({\n args: {\n connectionId: v.id(\"GroupConnection\"),\n endpointId: v.id(\"GroupWebhookEndpoint\"),\n eventId: v.string(),\n kind: vAuthEventKind,\n payload: v.any(),\n nextAttemptAt: v.number(),\n signature: v.string(),\n signedAt: v.number(),\n },\n returns: v.id(\"GroupWebhookDelivery\"),\n handler: async (ctx, args) => {\n const existing = await ctx.db\n .query(\"GroupWebhookDelivery\")\n .withIndex(\"event_id_endpoint_id\", (idx) =>\n idx.eq(\"eventId\", args.eventId).eq(\"endpointId\", args.endpointId),\n )\n .unique();\n if (existing !== null) return existing._id;\n const deliveryId = await ctx.db.insert(\"GroupWebhookDelivery\", {\n ...args,\n status: \"pending\",\n attemptCount: 0,\n });\n await appendDeliveryEvent(ctx, {\n deliveryId,\n connectionId: args.connectionId,\n endpointId: args.endpointId,\n sourceEventId: args.eventId,\n sourceEventType: args.kind,\n kind: \"webhook.delivery.created\",\n outcome: \"success\",\n occurredAt: args.signedAt,\n });\n await workpool.enqueueAction(\n ctx,\n internal.connection.webhook.delivery.dispatch,\n { id: deliveryId },\n {\n runAt: args.nextAttemptAt,\n onComplete: internal.connection.webhook.delivery.onDispatchComplete,\n context: { deliveryId },\n },\n );\n return deliveryId;\n },\n});\n\n/**\n * Workpool completion hook for a delivery's dispatch chain.\n *\n * Guarantees a terminal row state even when `dispatch` itself never reached its\n * own terminal branch — e.g. a bookkeeping mutation threw on every attempt, so\n * the workpool exhausted its retries while the row was left non-terminal. On any\n * non-success outcome, settle a still-open delivery as `failed`.\n */\nexport const onDispatchComplete = internalMutation({\n args: vOnCompleteArgs(v.object({ deliveryId: v.id(\"GroupWebhookDelivery\") })),\n returns: v.null(),\n handler: async (ctx, { context, result }) => {\n if (result.kind === \"success\") return null;\n const delivery = await ctx.db.get(\"GroupWebhookDelivery\", context.deliveryId);\n if (delivery === null || delivery.status === \"delivered\" || delivery.status === \"failed\") {\n return null;\n }\n await ctx.db.patch(\"GroupWebhookDelivery\", context.deliveryId, {\n status: \"failed\",\n lastError: result.kind === \"failed\" ? result.error : \"delivery canceled\",\n });\n return null;\n },\n});\n\n/** Patch fields on a delivery (attempt bookkeeping). */\nexport const update = mutation({\n args: {\n id: v.id(\"GroupWebhookDelivery\"),\n patch: v.object({\n status: v.optional(vWebhookDeliveryStatus),\n attemptCount: v.optional(v.number()),\n nextAttemptAt: v.optional(v.number()),\n lastAttemptAt: v.optional(v.number()),\n lastResponseStatus: v.optional(v.number()),\n lastError: v.optional(v.string()),\n }),\n },\n returns: v.null(),\n handler: async (ctx, { id: deliveryId, patch }) => {\n await ctx.db.patch(\"GroupWebhookDelivery\", deliveryId, patch);\n return null;\n },\n});\n\n/**\n * POST a delivery's signed payload to its endpoint and settle the row. Skips\n * (failing the delivery) when the endpoint is missing or disabled. Marks\n * `delivered` on a 2xx; on failure, retries via the workpool until the attempt\n * budget is spent — only transient HTTP statuses (see `isRetriableStatus`) and\n * fetch errors are retried, everything else fails immediately. Emits\n * `attempted`/`succeeded`/`failed` audit events along the way.\n */\nexport const dispatch = internalAction({\n args: { id: v.id(\"GroupWebhookDelivery\") },\n returns: v.null(),\n handler: async (ctx, { id: deliveryId }) => {\n const delivery = (await ctx.runQuery(internal.connection.webhook.delivery.get, {\n id: deliveryId,\n })) as {\n _id: string;\n connectionId: string;\n endpointId: string;\n eventId: string;\n kind: Infer<typeof vAuthEventKind>;\n payload: unknown;\n status: Infer<typeof vWebhookDeliveryStatus>;\n attemptCount: number;\n signature: string;\n signedAt: number;\n } | null;\n if (!delivery) return null;\n\n // At-least-once delivery. The workpool can re-invoke `dispatch` for a\n // delivery whose previous attempt already settled — a retry scheduled\n // before a sibling attempt committed its terminal status, a re-enqueue\n // after a crash, or an `onDispatchComplete` that already failed the row.\n // Re-read the row and bail before POSTing again once it is terminal, so a\n // delivered/failed delivery is never re-sent. A crash in the narrow window\n // between the POST and the status write still re-delivers, so receivers get\n // at-least-once (not exactly-once) semantics and MUST dedup on the stable\n // `X-Auth-Delivery-Id` header sent below.\n if (delivery.status === \"delivered\" || delivery.status === \"failed\") {\n return null;\n }\n\n const endpoint = (await ctx.runQuery(api.connection.webhook.endpoint.get, {\n id: delivery.endpointId as Id<\"GroupWebhookEndpoint\">,\n })) as { url: string; status: string } | null;\n if (!endpoint || endpoint.status !== \"active\") {\n const failedAt = Date.now();\n await appendDeliveryEvent(ctx, {\n deliveryId,\n connectionId: delivery.connectionId,\n endpointId: delivery.endpointId,\n sourceEventId: delivery.eventId,\n sourceEventType: delivery.kind,\n kind: \"webhook.delivery.failed\",\n outcome: \"failure\",\n occurredAt: failedAt,\n data: {\n attemptCount: delivery.attemptCount + 1,\n error: \"endpoint missing or disabled\",\n },\n });\n await ctx.runMutation(api.connection.webhook.delivery.update, {\n id: deliveryId,\n patch: {\n status: \"failed\",\n lastError: \"endpoint missing or disabled\",\n lastAttemptAt: failedAt,\n attemptCount: delivery.attemptCount + 1,\n },\n });\n return null;\n }\n\n const startedAt = Date.now();\n await appendDeliveryEvent(ctx, {\n deliveryId,\n connectionId: delivery.connectionId,\n endpointId: delivery.endpointId,\n sourceEventId: delivery.eventId,\n sourceEventType: delivery.kind,\n kind: \"webhook.delivery.attempted\",\n outcome: \"success\",\n occurredAt: startedAt,\n data: { attemptCount: delivery.attemptCount + 1 },\n });\n const body = JSON.stringify({\n kind: delivery.kind,\n payload: delivery.payload,\n });\n let response: Response;\n try {\n const unsafeUrlReason = unsafeFetchUrlReason(endpoint.url);\n if (unsafeUrlReason !== null) {\n throw new Error(`Webhook ${unsafeUrlReason}`);\n }\n response = await fetch(endpoint.url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-Auth-Event-Type\": delivery.kind,\n \"X-Auth-Delivery-Id\": delivery._id,\n \"X-Auth-Timestamp\": String(delivery.signedAt),\n \"X-Auth-Signature\": `sha256=${delivery.signature}`,\n },\n body,\n redirect: \"manual\",\n signal: AbortSignal.timeout(15_000),\n });\n } catch (err) {\n const exhausted = delivery.attemptCount + 1 >= MAX_ATTEMPTS;\n await appendDeliveryEvent(ctx, {\n deliveryId,\n connectionId: delivery.connectionId,\n endpointId: delivery.endpointId,\n sourceEventId: delivery.eventId,\n sourceEventType: delivery.kind,\n kind: \"webhook.delivery.failed\",\n outcome: \"failure\",\n occurredAt: startedAt,\n data: {\n attemptCount: delivery.attemptCount + 1,\n error: err instanceof Error ? err.message : String(err),\n },\n });\n await ctx.runMutation(api.connection.webhook.delivery.update, {\n id: deliveryId,\n patch: {\n status: exhausted ? \"failed\" : \"processing\",\n lastError: err instanceof Error ? err.message : String(err),\n lastAttemptAt: startedAt,\n attemptCount: delivery.attemptCount + 1,\n },\n });\n if (!exhausted) throw err;\n return null;\n }\n\n if (!response.ok) {\n const terminal =\n !isRetriableStatus(response.status) || delivery.attemptCount + 1 >= MAX_ATTEMPTS;\n await appendDeliveryEvent(ctx, {\n deliveryId,\n connectionId: delivery.connectionId,\n endpointId: delivery.endpointId,\n sourceEventId: delivery.eventId,\n sourceEventType: delivery.kind,\n kind: \"webhook.delivery.failed\",\n outcome: \"failure\",\n occurredAt: startedAt,\n data: {\n attemptCount: delivery.attemptCount + 1,\n status: response.status,\n },\n });\n await ctx.runMutation(api.connection.webhook.delivery.update, {\n id: deliveryId,\n patch: {\n status: terminal ? \"failed\" : \"processing\",\n lastResponseStatus: response.status,\n lastError: `HTTP ${response.status}`,\n lastAttemptAt: startedAt,\n attemptCount: delivery.attemptCount + 1,\n },\n });\n if (!terminal) throw new Error(`Webhook delivery failed: HTTP ${response.status}`);\n return null;\n }\n\n await appendDeliveryEvent(ctx, {\n deliveryId,\n connectionId: delivery.connectionId,\n endpointId: delivery.endpointId,\n sourceEventId: delivery.eventId,\n sourceEventType: delivery.kind,\n kind: \"webhook.delivery.succeeded\",\n outcome: \"success\",\n occurredAt: startedAt,\n data: {\n attemptCount: delivery.attemptCount + 1,\n status: response.status,\n },\n });\n await ctx.runMutation(api.connection.webhook.delivery.update, {\n id: deliveryId,\n patch: {\n status: \"delivered\",\n lastResponseStatus: response.status,\n lastAttemptAt: startedAt,\n attemptCount: delivery.attemptCount + 1,\n },\n });\n return null;\n },\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"delivery.js","names":["schema"],"sources":["../../../../src/component/connection/webhook/delivery.ts"],"sourcesContent":["/**\n * `component.connection.webhook.delivery.*` — queued webhook delivery attempts\n * (sub-resource of webhook).\n *\n * `list` is overloaded (`{ connectionId }` history or\n * `{ now }` ready-for-dispatch).\n *\n * @module\n */\n\nimport { Workpool, vOnCompleteArgs } from \"@convex-dev/workpool\";\nimport { paginationOptsValidator } from \"convex/server\";\nimport { v, type Infer } from \"convex/values\";\nimport { paginator } from \"convex-helpers/server/pagination\";\n\nimport { api, components, internal } from \"../../_generated/api\";\nimport { internalAction, internalMutation, internalQuery, mutation, query } from \"../../functions\";\nimport { unsafeFetchUrlReason } from \"../../../shared/fetch/guard\";\nimport {\n vAuthEventKind,\n vGroupWebhookDeliveryDoc,\n vGroupWebhookDeliveryPublicDoc,\n vPaginated,\n} from \"../../model\";\nimport schema from \"../../schema\";\nimport { appendDeliveryEvent } from \"./events\";\n\nconst MAX_ATTEMPTS = 5;\n\nconst workpool = new Workpool(components.webhookWorkpool, {\n maxParallelism: 5,\n defaultRetryBehavior: { maxAttempts: MAX_ATTEMPTS, initialBackoffMs: 1_000, base: 2 },\n retryActionsByDefault: true,\n});\n\n/**\n * Whether an HTTP response status warrants a retry. Only transient failures\n * retry — `5xx`, `408 Request Timeout`, and `429 Too Many Requests`. Every\n * other non-2xx (the rest of `4xx`, plus unfollowed `3xx`) is a permanent\n * rejection that re-sending the identical payload cannot fix, so it fails\n * immediately instead of burning the full retry budget.\n */\nfunction isRetriableStatus(status: number): boolean {\n return status >= 500 || status === 408 || status === 429;\n}\n\nfunction publicDelivery(\n delivery: Infer<typeof vGroupWebhookDeliveryDoc>,\n): Infer<typeof vGroupWebhookDeliveryPublicDoc> {\n return {\n _id: delivery._id,\n _creationTime: delivery._creationTime,\n connectionId: delivery.connectionId,\n endpointId: delivery.endpointId,\n eventId: delivery.eventId,\n kind: delivery.kind,\n status: delivery.status,\n attemptCount: delivery.attemptCount,\n nextAttemptAt: delivery.nextAttemptAt,\n lastAttemptAt: delivery.lastAttemptAt,\n lastResponseStatus: delivery.lastResponseStatus,\n lastError: delivery.lastError,\n signedAt: delivery.signedAt,\n };\n}\n\n/** Read a delivery by id (internal — returns the full, unredacted doc). */\nexport const get = internalQuery({\n args: { id: v.id(\"GroupWebhookDelivery\") },\n returns: v.union(vGroupWebhookDeliveryDoc, v.null()),\n handler: async (ctx, { id: deliveryId }) => {\n return await ctx.db.get(\"GroupWebhookDelivery\", deliveryId);\n },\n});\n\n/** List a connection's deliveries, newest first and paginated. Rows are redacted to public fields. */\nexport const list = query({\n args: {\n connectionId: v.id(\"GroupConnection\"),\n paginationOpts: paginationOptsValidator,\n },\n returns: vPaginated(vGroupWebhookDeliveryPublicDoc),\n handler: async (ctx, { connectionId, paginationOpts }) => {\n const result = await paginator(ctx.db, schema)\n .query(\"GroupWebhookDelivery\")\n .withIndex(\"group_connection_id\", (idx) => idx.eq(\"connectionId\", connectionId))\n .order(\"desc\")\n .paginate(paginationOpts);\n return {\n ...result,\n page: result.page.map(publicDelivery),\n };\n },\n});\n\n/**\n * List pending deliveries whose `nextAttemptAt` is at or before `now`, ready to\n * be dispatched (`limit` clamped to 1..100, default 50).\n */\nexport const dueForDispatch = query({\n args: {\n now: v.number(),\n limit: v.optional(v.number()),\n },\n returns: v.array(vGroupWebhookDeliveryDoc),\n handler: async (ctx, { now, limit }) => {\n const take = Math.min(Math.max(limit ?? 50, 1), 100);\n return await ctx.db\n .query(\"GroupWebhookDelivery\")\n .withIndex(\"status_next_attempt_at\", (idx) =>\n idx.eq(\"status\", \"pending\").lte(\"nextAttemptAt\", now),\n )\n .take(take);\n },\n});\n\n/** Mark a pending delivery as in progress and record its attempt atomically. */\nexport const begin = internalMutation({\n args: { id: v.id(\"GroupWebhookDelivery\"), occurredAt: v.number() },\n returns: v.union(vGroupWebhookDeliveryDoc, v.null()),\n handler: async (ctx, { id: deliveryId, occurredAt }) => {\n const delivery = await ctx.db.get(\"GroupWebhookDelivery\", deliveryId);\n if (delivery === null || delivery.status !== \"pending\") return null;\n\n const attemptCount = delivery.attemptCount + 1;\n await ctx.db.patch(\"GroupWebhookDelivery\", deliveryId, {\n status: \"processing\",\n attemptCount,\n lastAttemptAt: occurredAt,\n });\n await appendDeliveryEvent(ctx, {\n deliveryId,\n connectionId: delivery.connectionId,\n endpointId: delivery.endpointId,\n sourceEventId: delivery.eventId,\n sourceEventType: delivery.kind,\n kind: \"webhook.delivery.attempted\",\n outcome: \"success\",\n occurredAt,\n data: { attemptCount },\n });\n return {\n ...delivery,\n status: \"processing\" as const,\n attemptCount,\n lastAttemptAt: occurredAt,\n };\n },\n});\n\n/** Settle an in-progress delivery and record its outcome atomically. */\nexport const settle = internalMutation({\n args: {\n id: v.id(\"GroupWebhookDelivery\"),\n occurredAt: v.number(),\n outcome: v.union(v.literal(\"success\"), v.literal(\"failure\")),\n retry: v.boolean(),\n responseStatus: v.optional(v.number()),\n error: v.optional(v.string()),\n },\n returns: v.null(),\n handler: async (ctx, args) => {\n const delivery = await ctx.db.get(\"GroupWebhookDelivery\", args.id);\n if (delivery === null || delivery.status !== \"processing\") return null;\n\n const succeeded = args.outcome === \"success\";\n await ctx.db.patch(\"GroupWebhookDelivery\", args.id, {\n status: succeeded ? \"delivered\" : args.retry ? \"pending\" : \"failed\",\n nextAttemptAt: args.retry ? args.occurredAt : delivery.nextAttemptAt,\n lastAttemptAt: args.occurredAt,\n lastResponseStatus: args.responseStatus,\n lastError: args.error,\n });\n await appendDeliveryEvent(ctx, {\n deliveryId: args.id,\n connectionId: delivery.connectionId,\n endpointId: delivery.endpointId,\n sourceEventId: delivery.eventId,\n sourceEventType: delivery.kind,\n kind: succeeded ? \"webhook.delivery.succeeded\" : \"webhook.delivery.failed\",\n outcome: args.outcome,\n occurredAt: args.occurredAt,\n data: {\n attemptCount: delivery.attemptCount,\n status: args.responseStatus,\n error: args.error,\n },\n });\n return null;\n },\n});\n\n/**\n * Queue a delivery for an endpoint. Idempotent on `(eventId, endpointId)` —\n * returns the existing id if already queued. The row, its audit event, and the\n * durable workpool entry commit together; an audit failure aborts all three.\n */\nexport const create = mutation({\n args: {\n connectionId: v.id(\"GroupConnection\"),\n endpointId: v.id(\"GroupWebhookEndpoint\"),\n eventId: v.string(),\n kind: vAuthEventKind,\n payload: v.any(),\n nextAttemptAt: v.number(),\n signature: v.string(),\n signedAt: v.number(),\n },\n returns: v.id(\"GroupWebhookDelivery\"),\n handler: async (ctx, args) => {\n const existing = await ctx.db\n .query(\"GroupWebhookDelivery\")\n .withIndex(\"event_id_endpoint_id\", (idx) =>\n idx.eq(\"eventId\", args.eventId).eq(\"endpointId\", args.endpointId),\n )\n .unique();\n if (existing !== null) return existing._id;\n const deliveryId = await ctx.db.insert(\"GroupWebhookDelivery\", {\n ...args,\n status: \"pending\",\n attemptCount: 0,\n });\n await appendDeliveryEvent(ctx, {\n deliveryId,\n connectionId: args.connectionId,\n endpointId: args.endpointId,\n sourceEventId: args.eventId,\n sourceEventType: args.kind,\n kind: \"webhook.delivery.created\",\n outcome: \"success\",\n occurredAt: args.signedAt,\n });\n await workpool.enqueueAction(\n ctx,\n internal.connection.webhook.delivery.dispatch,\n { id: deliveryId },\n {\n runAt: args.nextAttemptAt,\n onComplete: internal.connection.webhook.delivery.onDispatchComplete,\n context: { deliveryId },\n },\n );\n return deliveryId;\n },\n});\n\n/**\n * Workpool completion hook for a delivery's dispatch chain.\n *\n * Guarantees a terminal row state even when `dispatch` itself never reached its\n * own terminal branch — e.g. a bookkeeping mutation threw on every attempt, so\n * the workpool exhausted its retries while the row was left non-terminal. On any\n * non-success outcome, settle a still-open delivery as `failed`.\n */\nexport const onDispatchComplete = internalMutation({\n args: vOnCompleteArgs(v.object({ deliveryId: v.id(\"GroupWebhookDelivery\") })),\n returns: v.null(),\n handler: async (ctx, { context, result }) => {\n if (result.kind === \"success\") return null;\n const delivery = await ctx.db.get(\"GroupWebhookDelivery\", context.deliveryId);\n if (delivery === null || delivery.status !== \"processing\") {\n return null;\n }\n await appendDeliveryEvent(ctx, {\n deliveryId: context.deliveryId,\n connectionId: delivery.connectionId,\n endpointId: delivery.endpointId,\n sourceEventId: delivery.eventId,\n sourceEventType: delivery.kind,\n kind: \"webhook.delivery.failed\",\n outcome: \"failure\",\n occurredAt: Date.now(),\n data: {\n attemptCount: delivery.attemptCount,\n error: result.kind === \"failed\" ? result.error : \"delivery canceled\",\n },\n });\n await ctx.db.patch(\"GroupWebhookDelivery\", context.deliveryId, {\n status: \"failed\",\n lastError: result.kind === \"failed\" ? result.error : \"delivery canceled\",\n });\n return null;\n },\n});\n\n/**\n * POST a delivery's signed payload to its endpoint and settle the row. Skips\n * (failing the delivery) when the endpoint is missing or disabled. Marks\n * `delivered` on a 2xx; on failure, retries via the workpool until the attempt\n * budget is spent — only transient HTTP statuses (see `isRetriableStatus`) and\n * fetch errors are retried, everything else fails immediately. Emits\n * `attempted`/`succeeded`/`failed` audit events along the way.\n */\nexport const dispatch = internalAction({\n args: { id: v.id(\"GroupWebhookDelivery\") },\n returns: v.null(),\n handler: async (ctx, { id: deliveryId }) => {\n const startedAt = Date.now();\n const delivery = await ctx.runMutation(internal.connection.webhook.delivery.begin, {\n id: deliveryId,\n occurredAt: startedAt,\n });\n if (delivery === null) return null;\n\n const endpoint = (await ctx.runQuery(api.connection.webhook.endpoint.get, {\n id: delivery.endpointId,\n })) as { url: string; status: string } | null;\n if (!endpoint || endpoint.status !== \"active\") {\n await ctx.runMutation(internal.connection.webhook.delivery.settle, {\n id: deliveryId,\n occurredAt: startedAt,\n outcome: \"failure\",\n retry: false,\n error: \"endpoint missing or disabled\",\n });\n return null;\n }\n\n const body = JSON.stringify({\n kind: delivery.kind,\n payload: delivery.payload,\n });\n let response: Response;\n try {\n const unsafeUrlReason = unsafeFetchUrlReason(endpoint.url);\n if (unsafeUrlReason !== null) {\n throw new Error(`Webhook ${unsafeUrlReason}`);\n }\n response = await fetch(endpoint.url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-Auth-Event-Type\": delivery.kind,\n \"X-Auth-Delivery-Id\": delivery._id,\n \"X-Auth-Timestamp\": String(delivery.signedAt),\n \"X-Auth-Signature\": `sha256=${delivery.signature}`,\n },\n body,\n redirect: \"manual\",\n signal: AbortSignal.timeout(15_000),\n });\n } catch (err) {\n const error = err instanceof Error ? err.message : String(err);\n const retry = delivery.attemptCount < MAX_ATTEMPTS;\n await ctx.runMutation(internal.connection.webhook.delivery.settle, {\n id: deliveryId,\n occurredAt: startedAt,\n outcome: \"failure\",\n retry,\n error,\n });\n if (retry) throw err;\n return null;\n }\n\n if (!response.ok) {\n const retry = isRetriableStatus(response.status) && delivery.attemptCount < MAX_ATTEMPTS;\n await ctx.runMutation(internal.connection.webhook.delivery.settle, {\n id: deliveryId,\n occurredAt: startedAt,\n outcome: \"failure\",\n retry,\n responseStatus: response.status,\n error: `HTTP ${response.status}`,\n });\n if (retry) throw new Error(`Webhook delivery failed: HTTP ${response.status}`);\n return null;\n }\n\n await ctx.runMutation(internal.connection.webhook.delivery.settle, {\n id: deliveryId,\n occurredAt: startedAt,\n outcome: \"success\",\n retry: false,\n responseStatus: response.status,\n });\n return null;\n },\n});\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import * as
|
|
1
|
+
import * as convex_values3280 from "convex/values";
|
|
2
|
+
import * as convex_server220 from "convex/server";
|
|
3
3
|
|
|
4
4
|
//#region src/component/connection/webhook/endpoint.d.ts
|
|
5
5
|
declare namespace endpoint_d_exports {
|
|
@@ -12,55 +12,55 @@ declare namespace endpoint_d_exports {
|
|
|
12
12
|
* @module
|
|
13
13
|
*/
|
|
14
14
|
/** Read a webhook endpoint by id. */
|
|
15
|
-
declare const get:
|
|
16
|
-
id:
|
|
15
|
+
declare const get: convex_server220.RegisteredQuery<"public", {
|
|
16
|
+
id: convex_values3280.GenericId<"GroupWebhookEndpoint">;
|
|
17
17
|
}, Promise<{
|
|
18
|
-
_id:
|
|
18
|
+
_id: convex_values3280.GenericId<"GroupWebhookEndpoint">;
|
|
19
19
|
_creationTime: number;
|
|
20
20
|
extend?: any;
|
|
21
|
-
createdByUserId?:
|
|
21
|
+
createdByUserId?: convex_values3280.GenericId<"User"> | undefined;
|
|
22
22
|
lastSuccessAt?: number | undefined;
|
|
23
23
|
lastFailureAt?: number | undefined;
|
|
24
|
-
connectionId:
|
|
24
|
+
connectionId: convex_values3280.GenericId<"GroupConnection">;
|
|
25
|
+
groupId: convex_values3280.GenericId<"Group">;
|
|
25
26
|
status: "active" | "disabled";
|
|
26
|
-
groupId: convex_values2855.GenericId<"Group">;
|
|
27
27
|
url: string;
|
|
28
28
|
secretCiphertext: string;
|
|
29
29
|
subscriptions: ("user.created" | "user.updated" | "session.signed_in" | "session.signed_out" | "session.invalidated" | "session.refresh_exchanged" | "session.refresh_reuse_detected" | "account.linked" | "account.unlinked" | "password.changed" | "passkey.added" | "passkey.removed" | "totp.enrolled" | "totp.removed" | "email.verified" | "phone.verified" | "api_key.created" | "api_key.revoked" | "oauth.client.created" | "oauth.client.revoked" | "oauth.code.created" | "oauth.token.created" | "oauth.token.exchanged" | "oauth.refresh.reuse_detected" | "oauth.refresh.revoked" | "connection.created" | "connection.updated" | "connection.removed" | "connection.login.succeeded" | "connection.login.failed" | "connection.domain.verification_requested" | "connection.domain.verified" | "connection.policy.updated" | "connection.saml.set" | "connection.saml.refreshed" | "connection.oidc.set" | "connection.scim.set" | "connection.scim.read" | "connection.scim.user.provisioned" | "connection.scim.user.updated" | "connection.scim.user.deactivated" | "connection.scim.user.reactivated" | "connection.scim.group.provisioned" | "connection.scim.group.updated" | "connection.scim.group.deactivated" | "connection.scim.group.reactivated" | "webhook.endpoint.created" | "webhook.endpoint.disabled" | "webhook.delivery.created" | "webhook.delivery.attempted" | "webhook.delivery.succeeded" | "webhook.delivery.failed")[];
|
|
30
30
|
failureCount: number;
|
|
31
31
|
} | null>>;
|
|
32
32
|
/** List a connection's webhook endpoints. */
|
|
33
|
-
declare const list:
|
|
34
|
-
connectionId:
|
|
33
|
+
declare const list: convex_server220.RegisteredQuery<"public", {
|
|
34
|
+
connectionId: convex_values3280.GenericId<"GroupConnection">;
|
|
35
35
|
}, Promise<{
|
|
36
|
-
_id:
|
|
36
|
+
_id: convex_values3280.GenericId<"GroupWebhookEndpoint">;
|
|
37
37
|
_creationTime: number;
|
|
38
38
|
extend?: any;
|
|
39
|
-
createdByUserId?:
|
|
39
|
+
createdByUserId?: convex_values3280.GenericId<"User"> | undefined;
|
|
40
40
|
lastSuccessAt?: number | undefined;
|
|
41
41
|
lastFailureAt?: number | undefined;
|
|
42
|
-
connectionId:
|
|
42
|
+
connectionId: convex_values3280.GenericId<"GroupConnection">;
|
|
43
|
+
groupId: convex_values3280.GenericId<"Group">;
|
|
43
44
|
status: "active" | "disabled";
|
|
44
|
-
groupId: convex_values2855.GenericId<"Group">;
|
|
45
45
|
url: string;
|
|
46
46
|
secretCiphertext: string;
|
|
47
47
|
subscriptions: ("user.created" | "user.updated" | "session.signed_in" | "session.signed_out" | "session.invalidated" | "session.refresh_exchanged" | "session.refresh_reuse_detected" | "account.linked" | "account.unlinked" | "password.changed" | "passkey.added" | "passkey.removed" | "totp.enrolled" | "totp.removed" | "email.verified" | "phone.verified" | "api_key.created" | "api_key.revoked" | "oauth.client.created" | "oauth.client.revoked" | "oauth.code.created" | "oauth.token.created" | "oauth.token.exchanged" | "oauth.refresh.reuse_detected" | "oauth.refresh.revoked" | "connection.created" | "connection.updated" | "connection.removed" | "connection.login.succeeded" | "connection.login.failed" | "connection.domain.verification_requested" | "connection.domain.verified" | "connection.policy.updated" | "connection.saml.set" | "connection.saml.refreshed" | "connection.oidc.set" | "connection.scim.set" | "connection.scim.read" | "connection.scim.user.provisioned" | "connection.scim.user.updated" | "connection.scim.user.deactivated" | "connection.scim.user.reactivated" | "connection.scim.group.provisioned" | "connection.scim.group.updated" | "connection.scim.group.deactivated" | "connection.scim.group.reactivated" | "webhook.endpoint.created" | "webhook.endpoint.disabled" | "webhook.delivery.created" | "webhook.delivery.attempted" | "webhook.delivery.succeeded" | "webhook.delivery.failed")[];
|
|
48
48
|
failureCount: number;
|
|
49
49
|
}[]>>;
|
|
50
50
|
/** Insert a new webhook endpoint (defaults `status` to `"active"`, `failureCount` to 0). */
|
|
51
|
-
declare const create:
|
|
51
|
+
declare const create: convex_server220.RegisteredMutation<"public", {
|
|
52
52
|
extend?: any;
|
|
53
53
|
status?: "active" | "disabled" | undefined;
|
|
54
|
-
createdByUserId?:
|
|
55
|
-
connectionId:
|
|
56
|
-
groupId:
|
|
54
|
+
createdByUserId?: convex_values3280.GenericId<"User"> | undefined;
|
|
55
|
+
connectionId: convex_values3280.GenericId<"GroupConnection">;
|
|
56
|
+
groupId: convex_values3280.GenericId<"Group">;
|
|
57
57
|
url: string;
|
|
58
58
|
secretCiphertext: string;
|
|
59
59
|
subscriptions: ("user.created" | "user.updated" | "session.signed_in" | "session.signed_out" | "session.invalidated" | "session.refresh_exchanged" | "session.refresh_reuse_detected" | "account.linked" | "account.unlinked" | "password.changed" | "passkey.added" | "passkey.removed" | "totp.enrolled" | "totp.removed" | "email.verified" | "phone.verified" | "api_key.created" | "api_key.revoked" | "oauth.client.created" | "oauth.client.revoked" | "oauth.code.created" | "oauth.token.created" | "oauth.token.exchanged" | "oauth.refresh.reuse_detected" | "oauth.refresh.revoked" | "connection.created" | "connection.updated" | "connection.removed" | "connection.login.succeeded" | "connection.login.failed" | "connection.domain.verification_requested" | "connection.domain.verified" | "connection.policy.updated" | "connection.saml.set" | "connection.saml.refreshed" | "connection.oidc.set" | "connection.scim.set" | "connection.scim.read" | "connection.scim.user.provisioned" | "connection.scim.user.updated" | "connection.scim.user.deactivated" | "connection.scim.user.reactivated" | "connection.scim.group.provisioned" | "connection.scim.group.updated" | "connection.scim.group.deactivated" | "connection.scim.group.reactivated" | "webhook.endpoint.created" | "webhook.endpoint.disabled" | "webhook.delivery.created" | "webhook.delivery.attempted" | "webhook.delivery.succeeded" | "webhook.delivery.failed")[];
|
|
60
|
-
}, Promise<
|
|
60
|
+
}, Promise<convex_values3280.GenericId<"GroupWebhookEndpoint">>>;
|
|
61
61
|
/** Patch fields on a webhook endpoint. */
|
|
62
|
-
declare const update:
|
|
63
|
-
id:
|
|
62
|
+
declare const update: convex_server220.RegisteredMutation<"public", {
|
|
63
|
+
id: convex_values3280.GenericId<"GroupWebhookEndpoint">;
|
|
64
64
|
patch: {
|
|
65
65
|
extend?: any;
|
|
66
66
|
status?: "active" | "disabled" | undefined;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { vAuthEventKind } from "../../model.js";
|
|
2
|
+
import { MutationCtx } from "../../_generated/server.js";
|
|
3
|
+
import { Infer } from "convex/values";
|
|
4
|
+
|
|
5
|
+
//#region src/component/connection/webhook/events.d.ts
|
|
6
|
+
declare namespace events_d_exports {
|
|
7
|
+
export { appendDeliveryEvent };
|
|
8
|
+
}
|
|
9
|
+
declare function appendDeliveryEvent(ctx: MutationCtx, args: {
|
|
10
|
+
deliveryId: string;
|
|
11
|
+
connectionId: string;
|
|
12
|
+
endpointId: string;
|
|
13
|
+
sourceEventId: string;
|
|
14
|
+
sourceEventType: Infer<typeof vAuthEventKind>;
|
|
15
|
+
kind: "webhook.delivery.created" | "webhook.delivery.attempted" | "webhook.delivery.succeeded" | "webhook.delivery.failed";
|
|
16
|
+
outcome: "success" | "failure";
|
|
17
|
+
occurredAt: number;
|
|
18
|
+
data?: {
|
|
19
|
+
attemptCount?: number;
|
|
20
|
+
status?: number;
|
|
21
|
+
error?: string;
|
|
22
|
+
};
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { events_d_exports };
|
|
26
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","names":[],"sources":["../../../../src/component/connection/webhook/events.ts"],"mappings":";;;;;;;;iBAQsB,mBAAA,CACpB,GAAA,EAAK,WAAA,EACL,IAAA;EACE,UAAA;EACA,YAAA;EACA,UAAA;EACA,aAAA;EACA,eAAA,EAAiB,KAAA,QAAa,cAAA;EAC9B,IAAA;EAKA,OAAA;EACA,UAAA;EACA,IAAA;IACE,YAAA;IACA,MAAA;IACA,KAAA;EAAA;AAAA,IAEH,OAAA"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { appendAuthEventProjection } from "../../event.js";
|
|
2
|
+
|
|
3
|
+
//#region src/component/connection/webhook/events.ts
|
|
4
|
+
async function appendDeliveryEvent(ctx, args) {
|
|
5
|
+
const attemptPart = typeof args.data?.attemptCount === "number" ? `:${args.data.attemptCount}` : "";
|
|
6
|
+
const event = {
|
|
7
|
+
eventId: `${args.kind}:${args.deliveryId}${attemptPart}`,
|
|
8
|
+
kind: args.kind,
|
|
9
|
+
occurredAt: args.occurredAt,
|
|
10
|
+
actor: {
|
|
11
|
+
type: "webhook",
|
|
12
|
+
id: args.endpointId
|
|
13
|
+
},
|
|
14
|
+
subject: {
|
|
15
|
+
type: "webhook_delivery",
|
|
16
|
+
id: args.deliveryId
|
|
17
|
+
},
|
|
18
|
+
targets: [{
|
|
19
|
+
kind: "connection",
|
|
20
|
+
id: args.connectionId
|
|
21
|
+
}],
|
|
22
|
+
outcome: args.outcome,
|
|
23
|
+
data: {
|
|
24
|
+
sourceEventId: args.sourceEventId,
|
|
25
|
+
sourceEventType: args.sourceEventType,
|
|
26
|
+
endpointId: args.endpointId,
|
|
27
|
+
deliveryId: args.deliveryId,
|
|
28
|
+
...args.data
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
await appendAuthEventProjection(ctx, {
|
|
32
|
+
event,
|
|
33
|
+
targets: event.targets
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
export { appendDeliveryEvent };
|
|
39
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","names":[],"sources":["../../../../src/component/connection/webhook/events.ts"],"sourcesContent":["/** Internal webhook delivery audit helpers. */\n\nimport type { Infer } from \"convex/values\";\n\nimport type { MutationCtx } from \"../../_generated/server\";\nimport { appendAuthEventProjection } from \"../../event\";\nimport { vAuthEventKind } from \"../../model\";\n\nexport async function appendDeliveryEvent(\n ctx: MutationCtx,\n args: {\n deliveryId: string;\n connectionId: string;\n endpointId: string;\n sourceEventId: string;\n sourceEventType: Infer<typeof vAuthEventKind>;\n kind:\n | \"webhook.delivery.created\"\n | \"webhook.delivery.attempted\"\n | \"webhook.delivery.succeeded\"\n | \"webhook.delivery.failed\";\n outcome: \"success\" | \"failure\";\n occurredAt: number;\n data?: {\n attemptCount?: number;\n status?: number;\n error?: string;\n };\n },\n) {\n const attemptPart =\n typeof args.data?.attemptCount === \"number\" ? `:${args.data.attemptCount}` : \"\";\n const event = {\n eventId: `${args.kind}:${args.deliveryId}${attemptPart}`,\n kind: args.kind,\n occurredAt: args.occurredAt,\n actor: { type: \"webhook\" as const, id: args.endpointId },\n subject: { type: \"webhook_delivery\" as const, id: args.deliveryId },\n targets: [{ kind: \"connection\" as const, id: args.connectionId }],\n outcome: args.outcome,\n data: {\n sourceEventId: args.sourceEventId,\n sourceEventType: args.sourceEventType,\n endpointId: args.endpointId,\n deliveryId: args.deliveryId,\n ...args.data,\n },\n };\n await appendAuthEventProjection(ctx, { event, targets: event.targets });\n}\n"],"mappings":";;;AAQA,eAAsB,oBACpB,KACA,MAmBA;CACA,MAAM,cACJ,OAAO,KAAK,MAAM,iBAAiB,WAAW,IAAI,KAAK,KAAK,iBAAiB;CAC/E,MAAM,QAAQ;EACZ,SAAS,GAAG,KAAK,KAAK,GAAG,KAAK,aAAa;EAC3C,MAAM,KAAK;EACX,YAAY,KAAK;EACjB,OAAO;GAAE,MAAM;GAAoB,IAAI,KAAK;GAAY;EACxD,SAAS;GAAE,MAAM;GAA6B,IAAI,KAAK;GAAY;EACnE,SAAS,CAAC;GAAE,MAAM;GAAuB,IAAI,KAAK;GAAc,CAAC;EACjE,SAAS,KAAK;EACd,MAAM;GACJ,eAAe,KAAK;GACpB,iBAAiB,KAAK;GACtB,YAAY,KAAK;GACjB,YAAY,KAAK;GACjB,GAAG,KAAK;GACT;EACF;AACD,OAAM,0BAA0B,KAAK;EAAE;EAAO,SAAS,MAAM;EAAS,CAAC"}
|