@01.software/sdk 0.49.0 → 0.56.1
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/CHANGELOG.md +257 -1
- package/MIGRATION.md +56 -0
- package/README.md +269 -56
- package/dist/client.cjs +608 -190
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +6 -6
- package/dist/client.d.ts +6 -6
- package/dist/client.js +608 -190
- package/dist/client.js.map +1 -1
- package/dist/{collection-client-DnDt6EQz.d.ts → collection-client-Bmr53saa.d.ts} +6 -4
- package/dist/{collection-client-XfGLPuTf.d.cts → collection-client-DxuBDso_.d.cts} +6 -4
- package/dist/{const-CBba4lDo.d.cts → const-BKUqg96Q.d.ts} +5 -5
- package/dist/{const-DCNV5Kq7.d.ts → const-Cv1DCsp6.d.cts} +5 -5
- package/dist/embedded-admin/server.cjs +306 -0
- package/dist/embedded-admin/server.cjs.map +1 -0
- package/dist/embedded-admin/server.d.cts +33 -0
- package/dist/embedded-admin/server.d.ts +33 -0
- package/dist/embedded-admin/server.js +288 -0
- package/dist/embedded-admin/server.js.map +1 -0
- package/dist/embedded-admin.cjs +400 -0
- package/dist/embedded-admin.cjs.map +1 -0
- package/dist/embedded-admin.d.cts +61 -0
- package/dist/embedded-admin.d.ts +61 -0
- package/dist/embedded-admin.js +377 -0
- package/dist/embedded-admin.js.map +1 -0
- package/dist/errors.cjs +1 -0
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.cts +3 -3
- package/dist/errors.d.ts +3 -3
- package/dist/errors.js +1 -0
- package/dist/errors.js.map +1 -1
- package/dist/{index-DUuIqUS8.d.ts → index-CGsT4AQ8.d.cts} +89 -10
- package/dist/{index-CbKyoEzy.d.cts → index-DFHLLqYu.d.ts} +89 -10
- package/dist/index.cjs +752 -436
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -69
- package/dist/index.d.ts +12 -69
- package/dist/index.js +752 -436
- package/dist/index.js.map +1 -1
- package/dist/{payload-types-MKbx5MuJ.d.cts → payload-types-Bk-duc5k.d.cts} +545 -141
- package/dist/{payload-types-MKbx5MuJ.d.ts → payload-types-Bk-duc5k.d.ts} +545 -141
- package/dist/query.cjs +108 -71
- package/dist/query.cjs.map +1 -1
- package/dist/query.d.cts +5 -5
- package/dist/query.d.ts +5 -5
- package/dist/query.js +108 -71
- package/dist/query.js.map +1 -1
- package/dist/realtime.d.cts +2 -2
- package/dist/realtime.d.ts +2 -2
- package/dist/server.cjs +502 -175
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +9 -8
- package/dist/server.d.ts +9 -8
- package/dist/server.js +502 -175
- package/dist/server.js.map +1 -1
- package/dist/storefront-cache.cjs +1 -0
- package/dist/storefront-cache.cjs.map +1 -1
- package/dist/storefront-cache.js +1 -0
- package/dist/storefront-cache.js.map +1 -1
- package/dist/{types-1UeOk1Uy.d.cts → types-CEK8uTvU.d.ts} +287 -30
- package/dist/{types-BKr_2avj.d.cts → types-CNdpj2fI.d.cts} +2 -2
- package/dist/{types-L_nh-ED7.d.ts → types-D4mjGFj4.d.ts} +2 -2
- package/dist/{types-Du4s5anw.d.ts → types-Dhk5NX7Z.d.cts} +287 -30
- package/dist/ui/form.d.cts +1 -1
- package/dist/ui/form.d.ts +1 -1
- package/dist/ui/video.d.cts +1 -1
- package/dist/ui/video.d.ts +1 -1
- package/dist/webhook.cjs +135 -80
- package/dist/webhook.cjs.map +1 -1
- package/dist/webhook.d.cts +21 -5
- package/dist/webhook.d.ts +21 -5
- package/dist/webhook.js +135 -80
- package/dist/webhook.js.map +1 -1
- package/package.json +28 -6
package/dist/webhook.js
CHANGED
|
@@ -1,10 +1,106 @@
|
|
|
1
|
+
// src/core/webhook/validation.ts
|
|
2
|
+
function isValidWebhookEventData(data) {
|
|
3
|
+
if (typeof data !== "object" || data === null) return false;
|
|
4
|
+
const event = data;
|
|
5
|
+
return typeof event.collection === "string" && typeof event.operation === "string" && event.operation.length > 0 && typeof event.data === "object" && event.data !== null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// src/core/webhook/receiver.ts
|
|
9
|
+
async function verifySignature(rawBody, secret, signature, timestamp, deliveryId) {
|
|
10
|
+
const encoder = new TextEncoder();
|
|
11
|
+
const signedPrefix = encoder.encode(`${timestamp}.${deliveryId}.`);
|
|
12
|
+
const signedPayload = new Uint8Array(signedPrefix.length + rawBody.length);
|
|
13
|
+
signedPayload.set(signedPrefix);
|
|
14
|
+
signedPayload.set(rawBody, signedPrefix.length);
|
|
15
|
+
const key = await crypto.subtle.importKey(
|
|
16
|
+
"raw",
|
|
17
|
+
encoder.encode(secret),
|
|
18
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
19
|
+
false,
|
|
20
|
+
["verify"]
|
|
21
|
+
);
|
|
22
|
+
if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const sigBytes = new Uint8Array(
|
|
26
|
+
(signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16))
|
|
27
|
+
);
|
|
28
|
+
return crypto.subtle.verify("HMAC", key, sigBytes, signedPayload);
|
|
29
|
+
}
|
|
30
|
+
function timestampIsFresh(timestamp, toleranceSeconds) {
|
|
31
|
+
if (!/^\d+$/.test(timestamp)) return false;
|
|
32
|
+
const timestampMs = Number(timestamp);
|
|
33
|
+
if (!Number.isFinite(timestampMs)) return false;
|
|
34
|
+
const skewMs = Math.abs(Date.now() - timestampMs);
|
|
35
|
+
return skewMs <= toleranceSeconds * 1e3;
|
|
36
|
+
}
|
|
37
|
+
var MAX_TOLERANCE_SECONDS = 86400;
|
|
38
|
+
function toleranceIsValid(toleranceSeconds) {
|
|
39
|
+
return typeof toleranceSeconds === "number" && Number.isFinite(toleranceSeconds) && toleranceSeconds >= 0 && toleranceSeconds <= MAX_TOLERANCE_SECONDS;
|
|
40
|
+
}
|
|
41
|
+
async function processWebhookRequest(request, handler, verify) {
|
|
42
|
+
try {
|
|
43
|
+
const rawBody = new Uint8Array(await request.arrayBuffer());
|
|
44
|
+
const verificationFailure = await verify?.(rawBody);
|
|
45
|
+
if (verificationFailure) return verificationFailure;
|
|
46
|
+
const body = JSON.parse(new TextDecoder().decode(rawBody));
|
|
47
|
+
if (!isValidWebhookEventData(body)) {
|
|
48
|
+
return new Response(
|
|
49
|
+
JSON.stringify({ error: "Invalid webhook event format" }),
|
|
50
|
+
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
await handler(body);
|
|
54
|
+
return new Response(
|
|
55
|
+
JSON.stringify({ success: true, message: "Webhook processed" }),
|
|
56
|
+
{ status: 200, headers: { "Content-Type": "application/json" } }
|
|
57
|
+
);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error("Webhook processing error:", error);
|
|
60
|
+
return new Response(JSON.stringify({ error: "Internal server error" }), {
|
|
61
|
+
status: 500,
|
|
62
|
+
headers: { "Content-Type": "application/json" }
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function handleSignedWebhook(request, handler, options) {
|
|
67
|
+
const secret = options?.secret;
|
|
68
|
+
const configuredTolerance = options?.toleranceSeconds;
|
|
69
|
+
const toleranceSeconds = configuredTolerance ?? 300;
|
|
70
|
+
if (typeof secret !== "string" || secret.trim().length === 0) {
|
|
71
|
+
return new Response(
|
|
72
|
+
JSON.stringify({ error: "Webhook signing secret is not configured" }),
|
|
73
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
if (!toleranceIsValid(toleranceSeconds)) {
|
|
77
|
+
return new Response(
|
|
78
|
+
JSON.stringify({ error: "Webhook signature tolerance is not valid" }),
|
|
79
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
return processWebhookRequest(request, handler, async (rawBody) => {
|
|
83
|
+
const signature = request.headers.get("x-webhook-signature") || "";
|
|
84
|
+
const timestamp = request.headers.get("x-webhook-timestamp") || "";
|
|
85
|
+
const deliveryId = request.headers.get("x-webhook-delivery-id") || "";
|
|
86
|
+
const valid = Boolean(timestamp && deliveryId) && timestampIsFresh(timestamp, toleranceSeconds) && await verifySignature(rawBody, secret, signature, timestamp, deliveryId);
|
|
87
|
+
if (!valid) {
|
|
88
|
+
return new Response(
|
|
89
|
+
JSON.stringify({ error: "Invalid webhook signature" }),
|
|
90
|
+
{ status: 401, headers: { "Content-Type": "application/json" } }
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
async function handleUnsignedWebhook(request, handler) {
|
|
96
|
+
return processWebhookRequest(request, handler);
|
|
97
|
+
}
|
|
98
|
+
|
|
1
99
|
// src/core/webhook/index.ts
|
|
2
100
|
var ORDER_CHANGED_EVENT_TYPE = "collection.orderChanged";
|
|
3
101
|
var COMMERCE_NOTIFICATION_EVENT_TYPE = "commerce.notification";
|
|
4
102
|
function isValidWebhookEvent(data) {
|
|
5
|
-
|
|
6
|
-
const obj = data;
|
|
7
|
-
return typeof obj.collection === "string" && typeof obj.operation === "string" && obj.operation.length > 0 && typeof obj.data === "object" && obj.data !== null;
|
|
103
|
+
return isValidWebhookEventData(data);
|
|
8
104
|
}
|
|
9
105
|
var COMMERCE_NOTIFICATION_OPERATION = "notification";
|
|
10
106
|
var COMMERCE_NOTIFICATION_EVENTS = [
|
|
@@ -16,7 +112,13 @@ var COMMERCE_NOTIFICATION_EVENTS = [
|
|
|
16
112
|
"returnApproved",
|
|
17
113
|
"returnRejected",
|
|
18
114
|
"returnCanceled",
|
|
19
|
-
"returnCompleted"
|
|
115
|
+
"returnCompleted",
|
|
116
|
+
"paymentReconciliationRequired",
|
|
117
|
+
"paymentRefundRequiresAttention",
|
|
118
|
+
"membershipActivated",
|
|
119
|
+
"membershipPastDue",
|
|
120
|
+
"membershipExpired",
|
|
121
|
+
"membershipCancelled"
|
|
20
122
|
];
|
|
21
123
|
function isStringOrNumber(value) {
|
|
22
124
|
return typeof value === "string" || typeof value === "number";
|
|
@@ -46,18 +148,29 @@ function isCommerceNotificationEventName(value) {
|
|
|
46
148
|
return typeof value === "string" && COMMERCE_NOTIFICATION_EVENTS.includes(value);
|
|
47
149
|
}
|
|
48
150
|
function isCommerceNotificationSourceCollection(value) {
|
|
49
|
-
return value === "orders" || value === "fulfillments" || value === "returns";
|
|
151
|
+
return value === "orders" || value === "fulfillments" || value === "returns" || value === "payment-sessions" || value === "payment-refund-attempts" || value === "customer-memberships";
|
|
50
152
|
}
|
|
51
153
|
function isCommerceNotificationData(value) {
|
|
52
154
|
if (!isRecord(value)) return false;
|
|
53
155
|
if (value.source !== void 0 && (!isRecord(value.source) || !isCommerceNotificationSourceCollection(value.source.collection) || !isStringOrNumber(value.source.id))) {
|
|
54
156
|
return false;
|
|
55
157
|
}
|
|
56
|
-
return (value.orderId === void 0 || isStringOrNumber(value.orderId)) && (value.orderNumber === void 0 || typeof value.orderNumber === "string") && (value.status === void 0 || typeof value.status === "string") && (value.totalAmount === void 0 || typeof value.totalAmount === "number") && (value.currency === void 0 || typeof value.currency === "string") && (value.fulfillmentId === void 0 || isStringOrNumber(value.fulfillmentId)) && (value.fulfillmentStatus === void 0 || typeof value.fulfillmentStatus === "string") && (value.shippedAt === void 0 || typeof value.shippedAt === "string") && (value.returnId === void 0 || isStringOrNumber(value.returnId)) && (value.returnStatus === void 0 || typeof value.returnStatus === "string") && (value.refundAmount === void 0 || typeof value.refundAmount === "number") && (value.completedAt === void 0 || typeof value.completedAt === "string");
|
|
158
|
+
return (value.orderId === void 0 || isStringOrNumber(value.orderId)) && (value.orderNumber === void 0 || typeof value.orderNumber === "string") && (value.status === void 0 || typeof value.status === "string") && (value.totalAmount === void 0 || typeof value.totalAmount === "number") && (value.currency === void 0 || typeof value.currency === "string") && (value.fulfillmentId === void 0 || isStringOrNumber(value.fulfillmentId)) && (value.fulfillmentStatus === void 0 || typeof value.fulfillmentStatus === "string") && (value.shippedAt === void 0 || typeof value.shippedAt === "string") && (value.returnId === void 0 || isStringOrNumber(value.returnId)) && (value.returnStatus === void 0 || typeof value.returnStatus === "string") && (value.refundAmount === void 0 || typeof value.refundAmount === "number") && (value.completedAt === void 0 || typeof value.completedAt === "string") && (value.paymentSessionId === void 0 || typeof value.paymentSessionId === "string") && (value.checkoutId === void 0 || typeof value.checkoutId === "string") && (value.refundAttemptId === void 0 || typeof value.refundAttemptId === "string") && (value.reason === void 0 || typeof value.reason === "string") && (value.observedAt === void 0 || typeof value.observedAt === "string") && (value.providerObject === void 0 || isRecord(value.providerObject) && typeof value.providerObject.provider === "string" && (value.providerObject.kind === "payment" || value.providerObject.kind === "refund") && typeof value.providerObject.id === "string") && (value.money === void 0 || isRecord(value.money) && Number.isSafeInteger(value.money.amountMinor) && Number(value.money.amountMinor) > 0 && typeof value.money.currency === "string");
|
|
57
159
|
}
|
|
58
160
|
function isCommerceNotification(value) {
|
|
59
161
|
if (!isRecord(value)) return false;
|
|
60
|
-
return isCommerceNotificationEventName(value.event) && typeof value.intentId === "string" && value.intentId.length > 0 && typeof value.dedupeKey === "string" && value.dedupeKey.length > 0 && (value.orderId === void 0 || typeof value.orderId === "string") && (value.fulfillmentId === void 0 || typeof value.fulfillmentId === "string") && (value.returnId === void 0 || typeof value.returnId === "string");
|
|
162
|
+
return isCommerceNotificationEventName(value.event) && typeof value.intentId === "string" && value.intentId.length > 0 && typeof value.dedupeKey === "string" && value.dedupeKey.length > 0 && (value.orderId === void 0 || typeof value.orderId === "string") && (value.fulfillmentId === void 0 || typeof value.fulfillmentId === "string") && (value.returnId === void 0 || typeof value.returnId === "string") && (value.paymentSessionId === void 0 || typeof value.paymentSessionId === "string") && (value.refundAttemptId === void 0 || typeof value.refundAttemptId === "string");
|
|
163
|
+
}
|
|
164
|
+
function isPaymentIncidentNotificationData(value, event) {
|
|
165
|
+
const reconciliation = event === "paymentReconciliationRequired";
|
|
166
|
+
const expectedCollection = reconciliation ? "payment-sessions" : "payment-refund-attempts";
|
|
167
|
+
const expectedStatus = reconciliation ? "reconciliation_required" : "requires_attention";
|
|
168
|
+
const expectedReason = reconciliation ? "orderless_capture" : "local_commit_ambiguous";
|
|
169
|
+
return value.source?.collection === expectedCollection && typeof value.source.id === "string" && value.source.id.length > 0 && typeof value.paymentSessionId === "string" && value.paymentSessionId.length > 0 && isRecord(value.providerObject) && typeof value.providerObject.provider === "string" && value.providerObject.provider.length > 0 && (value.providerObject.kind === "payment" || value.providerObject.kind === "refund") && typeof value.providerObject.id === "string" && value.providerObject.id.length > 0 && isRecord(value.money) && Number.isSafeInteger(value.money.amountMinor) && Number(value.money.amountMinor) > 0 && typeof value.money.currency === "string" && value.money.currency.length > 0 && value.status === expectedStatus && value.reason === expectedReason && typeof value.observedAt === "string" && value.observedAt.length > 0 && !Number.isNaN(Date.parse(value.observedAt)) && (value.checkoutId === void 0 || typeof value.checkoutId === "string" && value.checkoutId.length > 0) && (value.orderId === void 0 || typeof value.orderId === "string" && value.orderId.length > 0) && (reconciliation ? value.source.id === value.paymentSessionId && value.providerObject.kind === "payment" && value.refundAttemptId === void 0 : typeof value.refundAttemptId === "string" && value.refundAttemptId.length > 0 && value.source.id === value.refundAttemptId);
|
|
170
|
+
}
|
|
171
|
+
function isMembershipNotificationData(value, event) {
|
|
172
|
+
const expectedStatus = event === "membershipActivated" ? "active" : event === "membershipPastDue" ? "past_due" : event === "membershipExpired" ? "expired" : "cancelled";
|
|
173
|
+
return typeof value.membershipId === "string" && value.membershipId.length > 0 && typeof value.planId === "string" && value.planId.length > 0 && typeof value.customerId === "string" && value.customerId.length > 0 && typeof value.fromStatus === "string" && value.fromStatus.length > 0 && value.fromStatus !== value.toStatus && value.toStatus === expectedStatus && typeof value.occurredAt === "string" && !Number.isNaN(Date.parse(value.occurredAt));
|
|
61
174
|
}
|
|
62
175
|
function isWebhookCommerceNotificationChange(value) {
|
|
63
176
|
if (!isRecord(value)) return false;
|
|
@@ -98,6 +211,18 @@ function isCommerceNotificationWebhookEvent(event) {
|
|
|
98
211
|
if (!matchesOptionalId(change.sourceId, sourceId)) return false;
|
|
99
212
|
}
|
|
100
213
|
const changeSourceId = isWebhookCommerceNotificationChange(change) ? change.sourceId : void 0;
|
|
214
|
+
if (notification.event === "paymentReconciliationRequired" || notification.event === "paymentRefundRequiresAttention") {
|
|
215
|
+
if (!isPaymentIncidentNotificationData(data, notification.event)) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
const incidentData = data;
|
|
219
|
+
const expectedCollection = notification.event === "paymentReconciliationRequired" ? "payment-sessions" : "payment-refund-attempts";
|
|
220
|
+
const notificationSourceId = notification.event === "paymentReconciliationRequired" ? notification.paymentSessionId : notification.refundAttemptId;
|
|
221
|
+
return sourceCollection === expectedCollection && typeof notification.paymentSessionId === "string" && notification.paymentSessionId === incidentData.paymentSessionId && typeof notificationSourceId === "string" && notificationSourceId === incidentData.source.id && matchesOptionalId(changeSourceId, incidentData.source.id);
|
|
222
|
+
}
|
|
223
|
+
if (notification.event === "membershipActivated" || notification.event === "membershipPastDue" || notification.event === "membershipExpired" || notification.event === "membershipCancelled") {
|
|
224
|
+
return sourceCollection === "customer-memberships" && isMembershipNotificationData(data, notification.event) && typeof notification.membershipId === "string" && notification.membershipId === data.membershipId && matchesOptionalId(changeSourceId, data.membershipId);
|
|
225
|
+
}
|
|
101
226
|
if (notification.event === "orderPaid" || notification.event === "orderCanceled" || notification.event === "orderDelivered") {
|
|
102
227
|
return sourceCollection === "orders" && typeof notification.orderId === "string" && notification.orderId.length > 0 && matchesOptionalId(data.orderId, sourceId) && matchesOptionalId(notification.orderId, sourceId) && matchesOptionalId(data.orderId, notification.orderId) && matchesOptionalId(changeSourceId, data.orderId ?? notification.orderId);
|
|
103
228
|
}
|
|
@@ -166,80 +291,8 @@ function createCustomerAuthWebhookHandler(handlers) {
|
|
|
166
291
|
await handlers.unhandled?.(event);
|
|
167
292
|
};
|
|
168
293
|
}
|
|
169
|
-
async function verifySignature(payload, secret, signature, timestamp, deliveryId) {
|
|
170
|
-
const encoder = new TextEncoder();
|
|
171
|
-
const key = await crypto.subtle.importKey(
|
|
172
|
-
"raw",
|
|
173
|
-
encoder.encode(secret),
|
|
174
|
-
{ name: "HMAC", hash: "SHA-256" },
|
|
175
|
-
false,
|
|
176
|
-
["verify"]
|
|
177
|
-
);
|
|
178
|
-
if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {
|
|
179
|
-
return false;
|
|
180
|
-
}
|
|
181
|
-
const sigBytes = new Uint8Array(
|
|
182
|
-
(signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16))
|
|
183
|
-
);
|
|
184
|
-
return crypto.subtle.verify(
|
|
185
|
-
"HMAC",
|
|
186
|
-
key,
|
|
187
|
-
sigBytes,
|
|
188
|
-
encoder.encode(`${timestamp}.${deliveryId}.${payload}`)
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
function timestampIsFresh(timestamp, toleranceSeconds) {
|
|
192
|
-
if (!/^\d+$/.test(timestamp)) return false;
|
|
193
|
-
const timestampMs = Number(timestamp);
|
|
194
|
-
if (!Number.isFinite(timestampMs)) return false;
|
|
195
|
-
const skewMs = Math.abs(Date.now() - timestampMs);
|
|
196
|
-
return skewMs <= toleranceSeconds * 1e3;
|
|
197
|
-
}
|
|
198
294
|
async function handleWebhook(request, handler, options) {
|
|
199
|
-
|
|
200
|
-
const rawBody = await request.text();
|
|
201
|
-
if (options?.secret) {
|
|
202
|
-
const signature = request.headers.get("x-webhook-signature") || "";
|
|
203
|
-
const timestamp = request.headers.get("x-webhook-timestamp") || "";
|
|
204
|
-
const deliveryId = request.headers.get("x-webhook-delivery-id") || "";
|
|
205
|
-
const toleranceSeconds = options.toleranceSeconds ?? 300;
|
|
206
|
-
const valid = Boolean(timestamp && deliveryId) && timestampIsFresh(timestamp, toleranceSeconds) && await verifySignature(
|
|
207
|
-
rawBody,
|
|
208
|
-
options.secret,
|
|
209
|
-
signature,
|
|
210
|
-
timestamp,
|
|
211
|
-
deliveryId
|
|
212
|
-
);
|
|
213
|
-
if (!valid) {
|
|
214
|
-
return new Response(
|
|
215
|
-
JSON.stringify({ error: "Invalid webhook signature" }),
|
|
216
|
-
{ status: 401, headers: { "Content-Type": "application/json" } }
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
} else {
|
|
220
|
-
console.warn(
|
|
221
|
-
"[@01.software/sdk] Webhook signature verification is disabled. Set { secret } in handleWebhook() options to enable HMAC-SHA256 verification."
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
const body = JSON.parse(rawBody);
|
|
225
|
-
if (!isValidWebhookEvent(body)) {
|
|
226
|
-
return new Response(
|
|
227
|
-
JSON.stringify({ error: "Invalid webhook event format" }),
|
|
228
|
-
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
await handler(body);
|
|
232
|
-
return new Response(
|
|
233
|
-
JSON.stringify({ success: true, message: "Webhook processed" }),
|
|
234
|
-
{ status: 200, headers: { "Content-Type": "application/json" } }
|
|
235
|
-
);
|
|
236
|
-
} catch (error) {
|
|
237
|
-
console.error("Webhook processing error:", error);
|
|
238
|
-
return new Response(JSON.stringify({ error: "Internal server error" }), {
|
|
239
|
-
status: 500,
|
|
240
|
-
headers: { "Content-Type": "application/json" }
|
|
241
|
-
});
|
|
242
|
-
}
|
|
295
|
+
return handleSignedWebhook(request, handler, options);
|
|
243
296
|
}
|
|
244
297
|
function createTypedWebhookHandler(collection, handler) {
|
|
245
298
|
return async (event) => {
|
|
@@ -263,6 +316,8 @@ export {
|
|
|
263
316
|
createTypedWebhookHandler,
|
|
264
317
|
defineCommerceEmailConfig,
|
|
265
318
|
getCommerceNotificationIdempotencyKey,
|
|
319
|
+
handleSignedWebhook,
|
|
320
|
+
handleUnsignedWebhook,
|
|
266
321
|
handleWebhook,
|
|
267
322
|
isCommerceNotificationWebhookEvent,
|
|
268
323
|
isCustomerPasswordResetWebhookEvent,
|
package/dist/webhook.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/webhook/index.ts"],"sourcesContent":["import type { Collection } from '../client/types'\nimport type { CollectionType } from '../collection/types'\n\nexport type WebhookOperation = string\n\n/**\n * Semantic event type for manual Admin Panel ordering changes.\n *\n * Order-change webhooks remain `operation: \"update\"` events for compatibility.\n * Do not filter on or emit `operation: \"reorder\"`; use this event type with\n * `isOrderChangedWebhookEvent()` to distinguish ordering from normal content\n * updates.\n */\nexport const ORDER_CHANGED_EVENT_TYPE = 'collection.orderChanged' as const\n\n/** Semantic event type for v1 commerce notification webhooks. */\nexport const COMMERCE_NOTIFICATION_EVENT_TYPE = 'commerce.notification' as const\n\n/** Collection webhook operations emitted for tenant collection writes. */\nexport type CollectionWebhookOperation = 'create' | 'update'\n\n/** Describes the ordered area affected by an order-change webhook. */\nexport type WebhookOrderScope =\n | { kind: 'collection'; collection: string }\n | { kind: 'join'; collection: string; field: string; id: string | number }\n\n/** Identifies the public moved document for the ordered scope. */\nexport type WebhookOrderMoved = {\n collection: string\n id: string | number\n relatedCollection?: string\n relatedId?: string | number\n}\n\n/** Metadata attached to `collection.orderChanged` webhook events. */\nexport type WebhookOrderChange = {\n type: 'order'\n source: 'payload-orderable'\n /**\n * Diagnostic Payload order field name.\n *\n * This may contain hidden implementation field names. Application routing\n * should use `change.scope` and `change.moved` instead.\n */\n orderableFieldName?: string\n previousOrder?: string | null\n nextOrder?: string | null\n scope: WebhookOrderScope\n moved: WebhookOrderMoved\n}\n\n/** Known semantic webhook change metadata. */\nexport type WebhookChange = WebhookOrderChange | WebhookCommerceNotificationChange\n\nexport interface WebhookEvent<\n T extends Collection | string = Collection,\n TData = T extends Collection ? CollectionType<T> : Record<string, unknown>,\n> {\n collection: T\n operation: WebhookOperation\n data: TData\n eventType?: string\n change?: unknown\n timestamp?: string\n deliveryId?: string\n}\n\nexport type WebhookHandler<\n T extends Collection | string = Collection,\n TData = T extends Collection ? CollectionType<T> : Record<string, unknown>,\n> = (event: WebhookEvent<T, TData>) => Promise<void> | void\n\nexport interface WebhookOptions {\n secret?: string\n /** Max accepted skew for signed webhook delivery timestamps. Default: 300s. */\n toleranceSeconds?: number\n}\n\nexport function isValidWebhookEvent(data: unknown): data is WebhookEvent {\n if (typeof data !== 'object' || data === null) return false\n const obj = data as Record<string, unknown>\n return (\n typeof obj.collection === 'string' &&\n typeof obj.operation === 'string' &&\n obj.operation.length > 0 &&\n typeof obj.data === 'object' &&\n obj.data !== null\n )\n}\n\n/** Webhook event emitted when manual Admin Panel ordering changes. */\nexport type OrderChangedWebhookEvent<\n T extends Collection | string = Collection,\n TData = T extends Collection ? CollectionType<T> : Record<string, unknown>,\n> = WebhookEvent<T, TData> & {\n operation: 'update'\n eventType: typeof ORDER_CHANGED_EVENT_TYPE\n change: WebhookOrderChange\n}\n\nexport const COMMERCE_NOTIFICATION_OPERATION = 'notification' as const\n\nexport const COMMERCE_NOTIFICATION_EVENTS = [\n 'orderPaid',\n 'orderCanceled',\n 'fulfillmentShipped',\n 'orderDelivered',\n 'returnRequested',\n 'returnApproved',\n 'returnRejected',\n 'returnCanceled',\n 'returnCompleted',\n] as const\n\nexport type CommerceNotificationEventName =\n (typeof COMMERCE_NOTIFICATION_EVENTS)[number]\n\nexport type CommerceNotificationSourceCollection =\n | 'orders'\n | 'fulfillments'\n | 'returns'\n\nexport type CommerceNotificationSource = {\n collection: CommerceNotificationSourceCollection\n id: string | number\n}\n\nexport type CommerceNotificationData = {\n source?: CommerceNotificationSource\n orderId?: string | number\n orderNumber?: string\n status?: string\n totalAmount?: number\n currency?: string\n fulfillmentId?: string | number\n fulfillmentStatus?: string\n shippedAt?: string\n returnId?: string | number\n returnStatus?: string\n refundAmount?: number\n completedAt?: string\n}\n\nexport type CommerceNotification = {\n event: CommerceNotificationEventName\n intentId: string\n dedupeKey: string\n orderId?: string\n fulfillmentId?: string\n returnId?: string\n}\n\nexport type WebhookCommerceNotificationChange = {\n type: 'notification'\n source: 'commerce-notifications'\n event: CommerceNotificationEventName\n sourceCollection: CommerceNotificationSourceCollection\n sourceId: string | number\n}\n\n/** Webhook event emitted for v1 commerce notification deliveries. */\nexport type CommerceNotificationWebhookEvent = WebhookEvent<\n CommerceNotificationSourceCollection,\n CommerceNotificationData\n> & {\n operation: typeof COMMERCE_NOTIFICATION_OPERATION\n eventType: typeof COMMERCE_NOTIFICATION_EVENT_TYPE\n change?: WebhookCommerceNotificationChange\n notification: CommerceNotification\n}\n\nexport type CommerceEmailChannel = 'webhook' | 'provider'\n\nexport type CommerceEmailEventConfig = {\n enabled: boolean\n channel: CommerceEmailChannel\n template?: string\n}\n\nexport type CommerceEmailConfig = {\n version: 1\n commerceNotifications: Partial<\n Record<CommerceNotificationEventName, CommerceEmailEventConfig>\n >\n}\n\nexport type CommerceNotificationHandlerContext = {\n event: CommerceNotificationWebhookEvent\n idempotencyKey: string\n}\n\ntype MaybePromise<T> = T | Promise<T>\n\nexport type CommerceNotificationEventHandlers = Partial<\n Record<\n CommerceNotificationEventName,\n (context: CommerceNotificationHandlerContext) => MaybePromise<void>\n >\n> & {\n unhandled?: (event: WebhookEvent<string, unknown>) => MaybePromise<void>\n}\n\nexport type CommerceEmailHandlerContext = CommerceNotificationHandlerContext\n\nexport type CommerceEmailEventHandlers = CommerceNotificationEventHandlers\n\nfunction isStringOrNumber(value: unknown): value is string | number {\n return typeof value === 'string' || typeof value === 'number'\n}\n\nfunction isWebhookOrderScope(value: unknown): value is WebhookOrderScope {\n if (!isRecord(value)) return false\n\n if (value.kind === 'collection') {\n return typeof value.collection === 'string'\n }\n\n if (value.kind === 'join') {\n return (\n typeof value.collection === 'string' &&\n typeof value.field === 'string' &&\n isStringOrNumber(value.id)\n )\n }\n\n return false\n}\n\nfunction isWebhookOrderMoved(value: unknown): value is WebhookOrderMoved {\n if (!isRecord(value)) return false\n\n return (\n typeof value.collection === 'string' &&\n isStringOrNumber(value.id) &&\n (value.relatedCollection === undefined ||\n typeof value.relatedCollection === 'string') &&\n (value.relatedId === undefined || isStringOrNumber(value.relatedId))\n )\n}\n\nfunction hasOptionalOrderValue(\n value: Record<string, unknown>,\n key: string,\n): boolean {\n return (\n value[key] === undefined ||\n value[key] === null ||\n typeof value[key] === 'string'\n )\n}\n\nfunction isWebhookOrderChange(value: unknown): value is WebhookOrderChange {\n if (!isRecord(value)) return false\n\n return (\n value.type === 'order' &&\n value.source === 'payload-orderable' &&\n (value.orderableFieldName === undefined ||\n typeof value.orderableFieldName === 'string') &&\n hasOptionalOrderValue(value, 'previousOrder') &&\n hasOptionalOrderValue(value, 'nextOrder') &&\n isWebhookOrderScope(value.scope) &&\n isWebhookOrderMoved(value.moved)\n )\n}\n\nfunction isCommerceNotificationEventName(\n value: unknown,\n): value is CommerceNotificationEventName {\n return (\n typeof value === 'string' &&\n (COMMERCE_NOTIFICATION_EVENTS as readonly string[]).includes(value)\n )\n}\n\nfunction isCommerceNotificationSourceCollection(\n value: unknown,\n): value is CommerceNotificationSourceCollection {\n return value === 'orders' || value === 'fulfillments' || value === 'returns'\n}\n\nfunction isCommerceNotificationData(\n value: unknown,\n): value is CommerceNotificationData {\n if (!isRecord(value)) return false\n if (\n value.source !== undefined &&\n (!isRecord(value.source) ||\n !isCommerceNotificationSourceCollection(value.source.collection) ||\n !isStringOrNumber(value.source.id))\n ) {\n return false\n }\n\n return (\n (value.orderId === undefined || isStringOrNumber(value.orderId)) &&\n (value.orderNumber === undefined || typeof value.orderNumber === 'string') &&\n (value.status === undefined || typeof value.status === 'string') &&\n (value.totalAmount === undefined || typeof value.totalAmount === 'number') &&\n (value.currency === undefined || typeof value.currency === 'string') &&\n (value.fulfillmentId === undefined ||\n isStringOrNumber(value.fulfillmentId)) &&\n (value.fulfillmentStatus === undefined ||\n typeof value.fulfillmentStatus === 'string') &&\n (value.shippedAt === undefined || typeof value.shippedAt === 'string') &&\n (value.returnId === undefined || isStringOrNumber(value.returnId)) &&\n (value.returnStatus === undefined ||\n typeof value.returnStatus === 'string') &&\n (value.refundAmount === undefined ||\n typeof value.refundAmount === 'number') &&\n (value.completedAt === undefined || typeof value.completedAt === 'string')\n )\n}\n\nfunction isCommerceNotification(\n value: unknown,\n): value is CommerceNotification {\n if (!isRecord(value)) return false\n return (\n isCommerceNotificationEventName(value.event) &&\n typeof value.intentId === 'string' &&\n value.intentId.length > 0 &&\n typeof value.dedupeKey === 'string' &&\n value.dedupeKey.length > 0 &&\n (value.orderId === undefined || typeof value.orderId === 'string') &&\n (value.fulfillmentId === undefined ||\n typeof value.fulfillmentId === 'string') &&\n (value.returnId === undefined || typeof value.returnId === 'string')\n )\n}\n\nfunction isWebhookCommerceNotificationChange(\n value: unknown,\n): value is WebhookCommerceNotificationChange {\n if (!isRecord(value)) return false\n return (\n value.type === 'notification' &&\n value.source === 'commerce-notifications' &&\n isCommerceNotificationEventName(value.event) &&\n isCommerceNotificationSourceCollection(value.sourceCollection) &&\n isStringOrNumber(value.sourceId)\n )\n}\n\nfunction matchesOptionalId(\n actual: string | number | undefined,\n expected: string | number | undefined,\n): boolean {\n return actual === undefined || expected === undefined || actual === expected\n}\n\n/**\n * Returns true for semantic order-change webhooks emitted as update events.\n *\n * Use this guard with `ORDER_CHANGED_EVENT_TYPE` instead of filtering on\n * `operation: \"reorder\"`. Tenant order-change webhooks intentionally keep\n * `operation: \"update\"` for compatibility with existing webhook handlers.\n */\nexport function isOrderChangedWebhookEvent(\n event: unknown,\n): event is OrderChangedWebhookEvent<string, unknown> {\n if (\n !isValidWebhookEvent(event) ||\n event.operation !== 'update' ||\n event.eventType !== ORDER_CHANGED_EVENT_TYPE ||\n !isWebhookOrderChange(event.change)\n ) {\n return false\n }\n\n if (event.collection !== event.change.scope.collection) {\n return false\n }\n\n if (\n event.change.scope.kind === 'collection' &&\n event.change.moved.collection !== event.change.scope.collection\n ) {\n return false\n }\n\n return true\n}\n\n/**\n * Returns true for semantic commerce notification webhooks.\n *\n * Use `notification.intentId` and `notification.dedupeKey` for semantic\n * idempotency. `deliveryId`, when present, identifies a delivery attempt for\n * observability and is not the semantic notification identity.\n */\nexport function isCommerceNotificationWebhookEvent(\n event: unknown,\n): event is CommerceNotificationWebhookEvent {\n if (\n !isValidWebhookEvent(event) ||\n event.operation !== COMMERCE_NOTIFICATION_OPERATION ||\n event.eventType !== COMMERCE_NOTIFICATION_EVENT_TYPE ||\n !isCommerceNotificationData(event.data) ||\n !isCommerceNotification((event as { notification?: unknown }).notification)\n ) {\n return false\n }\n\n const notification = (event as unknown as CommerceNotificationWebhookEvent)\n .notification\n const data = event.data\n const change = event.change\n const sourceCollection = data.source?.collection ?? event.collection\n const sourceId = data.source?.id\n\n if (!isCommerceNotificationSourceCollection(sourceCollection)) return false\n if (data.source !== undefined && event.collection !== data.source.collection) {\n return false\n }\n\n if (change !== undefined) {\n if (!isWebhookCommerceNotificationChange(change)) return false\n if (change.sourceCollection !== sourceCollection) return false\n if (change.event !== notification.event) return false\n if (!matchesOptionalId(change.sourceId, sourceId)) return false\n }\n const changeSourceId = isWebhookCommerceNotificationChange(change)\n ? change.sourceId\n : undefined\n\n if (\n notification.event === 'orderPaid' ||\n notification.event === 'orderCanceled' ||\n notification.event === 'orderDelivered'\n ) {\n return (\n sourceCollection === 'orders' &&\n typeof notification.orderId === 'string' &&\n notification.orderId.length > 0 &&\n matchesOptionalId(data.orderId, sourceId) &&\n matchesOptionalId(notification.orderId, sourceId) &&\n matchesOptionalId(data.orderId, notification.orderId) &&\n matchesOptionalId(changeSourceId, data.orderId ?? notification.orderId)\n )\n }\n\n if (notification.event === 'fulfillmentShipped') {\n return (\n sourceCollection === 'fulfillments' &&\n matchesOptionalId(data.fulfillmentId, sourceId) &&\n typeof notification.fulfillmentId === 'string' &&\n notification.fulfillmentId.length > 0 &&\n matchesOptionalId(notification.fulfillmentId, sourceId) &&\n matchesOptionalId(data.fulfillmentId, notification.fulfillmentId) &&\n matchesOptionalId(changeSourceId, notification.fulfillmentId)\n )\n }\n\n if (\n notification.event === 'returnRequested' ||\n notification.event === 'returnApproved' ||\n notification.event === 'returnRejected' ||\n notification.event === 'returnCanceled' ||\n notification.event === 'returnCompleted'\n ) {\n return (\n sourceCollection === 'returns' &&\n matchesOptionalId(data.returnId, sourceId) &&\n typeof notification.returnId === 'string' &&\n notification.returnId.length > 0 &&\n matchesOptionalId(notification.returnId, sourceId) &&\n matchesOptionalId(data.returnId, notification.returnId) &&\n matchesOptionalId(changeSourceId, notification.returnId)\n )\n }\n\n return false\n}\n\nexport function getCommerceNotificationIdempotencyKey(\n event: Pick<CommerceNotificationWebhookEvent, 'notification'>,\n): string {\n return `${event.notification.intentId}:${event.notification.dedupeKey}`\n}\n\nexport function defineCommerceEmailConfig<TConfig extends CommerceEmailConfig>(\n config: TConfig,\n): TConfig {\n return config\n}\n\nexport function createCommerceNotificationWebhookHandler(\n handlers: CommerceNotificationEventHandlers,\n): WebhookHandler<string, unknown> {\n return async (event) => {\n if (!isCommerceNotificationWebhookEvent(event)) {\n await handlers.unhandled?.(event)\n return\n }\n\n const handler = handlers[event.notification.event]\n if (!handler) {\n await handlers.unhandled?.(event)\n return\n }\n\n await handler({\n event,\n idempotencyKey: getCommerceNotificationIdempotencyKey(event),\n })\n }\n}\n\nexport function createCommerceEmailWebhookHandler(\n handlers: CommerceEmailEventHandlers,\n): WebhookHandler<string, unknown> {\n return createCommerceNotificationWebhookHandler(handlers)\n}\n\n/** Narrows a webhook event to a specific collection. */\nexport function isWebhookCollection<TCollection extends string>(\n event: unknown,\n collection: TCollection,\n): event is WebhookEvent<TCollection, unknown> {\n return isValidWebhookEvent(event) && event.collection === collection\n}\n\n/** Narrows a webhook event to a specific operation. */\nexport function isWebhookOperation<TOperation extends string>(\n event: unknown,\n operation: TOperation,\n): event is WebhookEvent<string, unknown> & { operation: TOperation } {\n return isValidWebhookEvent(event) && event.operation === operation\n}\n\nexport const CUSTOMER_PASSWORD_RESET_OPERATION = 'password-reset' as const\n\nexport type WebhookKnownOperation =\n | CollectionWebhookOperation\n | typeof CUSTOMER_PASSWORD_RESET_OPERATION\n | typeof COMMERCE_NOTIFICATION_OPERATION\n\nexport interface CustomerPasswordResetWebhookData {\n customerId: string | number\n email: string\n name: string\n resetPasswordToken: string\n resetPasswordExpiresAt: string\n}\n\nexport type CustomerPasswordResetWebhookEvent = WebhookEvent<\n 'customers',\n CustomerPasswordResetWebhookData\n> & {\n operation: typeof CUSTOMER_PASSWORD_RESET_OPERATION\n}\n\nexport type CustomerAuthWebhookEvent = CustomerPasswordResetWebhookEvent\n\nexport interface CustomerAuthWebhookHandlers {\n passwordReset?: (\n data: CustomerPasswordResetWebhookData,\n event: CustomerPasswordResetWebhookEvent,\n ) => MaybePromise<void>\n unhandled?: (event: WebhookEvent<string, unknown>) => MaybePromise<void>\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null\n}\n\nfunction hasString(value: Record<string, unknown>, key: string): boolean {\n return typeof value[key] === 'string'\n}\n\nfunction hasStringOrNumber(\n value: Record<string, unknown>,\n key: string,\n): boolean {\n return typeof value[key] === 'string' || typeof value[key] === 'number'\n}\n\nexport function isCustomerPasswordResetWebhookEvent(\n event: WebhookEvent<string, unknown>,\n): event is CustomerPasswordResetWebhookEvent {\n if (\n event.collection !== 'customers' ||\n event.operation !== CUSTOMER_PASSWORD_RESET_OPERATION ||\n !isRecord(event.data)\n ) {\n return false\n }\n\n return (\n hasStringOrNumber(event.data, 'customerId') &&\n hasString(event.data, 'email') &&\n hasString(event.data, 'name') &&\n hasString(event.data, 'resetPasswordToken') &&\n hasString(event.data, 'resetPasswordExpiresAt')\n )\n}\n\nexport function createCustomerAuthWebhookHandler(\n handlers: CustomerAuthWebhookHandlers,\n): WebhookHandler<string, unknown> {\n return async (event) => {\n if (isCustomerPasswordResetWebhookEvent(event) && handlers.passwordReset) {\n await handlers.passwordReset(event.data, event)\n return\n }\n\n await handlers.unhandled?.(event)\n }\n}\n\nasync function verifySignature(\n payload: string,\n secret: string,\n signature: string,\n timestamp: string,\n deliveryId: string,\n): Promise<boolean> {\n const encoder = new TextEncoder()\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['verify'],\n )\n // Validate and convert hex signature to Uint8Array\n if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {\n return false\n }\n const sigBytes = new Uint8Array(\n (signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16)),\n )\n // crypto.subtle.verify performs constant-time comparison internally\n return crypto.subtle.verify(\n 'HMAC',\n key,\n sigBytes,\n encoder.encode(`${timestamp}.${deliveryId}.${payload}`),\n )\n}\n\nfunction timestampIsFresh(\n timestamp: string,\n toleranceSeconds: number,\n): boolean {\n if (!/^\\d+$/.test(timestamp)) return false\n const timestampMs = Number(timestamp)\n if (!Number.isFinite(timestampMs)) return false\n const skewMs = Math.abs(Date.now() - timestampMs)\n return skewMs <= toleranceSeconds * 1000\n}\n\nexport async function handleWebhook<\n T extends Collection | string = Collection,\n TData = T extends Collection ? CollectionType<T> : Record<string, unknown>,\n>(\n request: Request,\n handler: WebhookHandler<T, TData>,\n options?: WebhookOptions,\n): Promise<Response> {\n try {\n const rawBody = await request.text()\n\n if (options?.secret) {\n const signature = request.headers.get('x-webhook-signature') || ''\n const timestamp = request.headers.get('x-webhook-timestamp') || ''\n const deliveryId = request.headers.get('x-webhook-delivery-id') || ''\n const toleranceSeconds = options.toleranceSeconds ?? 300\n const valid =\n Boolean(timestamp && deliveryId) &&\n timestampIsFresh(timestamp, toleranceSeconds) &&\n (await verifySignature(\n rawBody,\n options.secret,\n signature,\n timestamp,\n deliveryId,\n ))\n if (!valid) {\n return new Response(\n JSON.stringify({ error: 'Invalid webhook signature' }),\n { status: 401, headers: { 'Content-Type': 'application/json' } },\n )\n }\n } else {\n console.warn(\n '[@01.software/sdk] Webhook signature verification is disabled. ' +\n 'Set { secret } in handleWebhook() options to enable HMAC-SHA256 verification.',\n )\n }\n\n const body = JSON.parse(rawBody)\n\n if (!isValidWebhookEvent(body)) {\n return new Response(\n JSON.stringify({ error: 'Invalid webhook event format' }),\n { status: 400, headers: { 'Content-Type': 'application/json' } },\n )\n }\n\n await handler(body as WebhookEvent<T, TData>)\n\n return new Response(\n JSON.stringify({ success: true, message: 'Webhook processed' }),\n { status: 200, headers: { 'Content-Type': 'application/json' } },\n )\n } catch (error) {\n console.error('Webhook processing error:', error)\n\n return new Response(JSON.stringify({ error: 'Internal server error' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n}\n\nexport function createTypedWebhookHandler<T extends Collection>(\n collection: T,\n handler: (event: WebhookEvent<T>) => Promise<void> | void,\n): WebhookHandler<T> {\n return async (event: WebhookEvent<T>) => {\n if (event.collection !== collection) {\n throw new Error(\n `Expected collection \"${collection}\", got \"${event.collection}\"`,\n )\n }\n return handler(event)\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/core/webhook/validation.ts","../src/core/webhook/receiver.ts","../src/core/webhook/index.ts"],"sourcesContent":["export function isValidWebhookEventData(data: unknown): boolean {\n if (typeof data !== 'object' || data === null) return false\n const event = data as Record<string, unknown>\n return (\n typeof event.collection === 'string' &&\n typeof event.operation === 'string' &&\n event.operation.length > 0 &&\n typeof event.data === 'object' &&\n event.data !== null\n )\n}\n","import type { Collection } from '../client/types'\nimport type { CollectionType } from '../collection/types'\nimport type { WebhookEvent, WebhookHandler } from './index'\nimport { isValidWebhookEventData } from './validation'\n\nexport interface SignedWebhookOptions {\n /** Nonblank HMAC-SHA256 signing secret for this receiver. */\n secret: string\n /**\n * Max accepted skew for signed webhook delivery timestamps. Default: 300s.\n * Must be a number between 0 and 86400 inclusive; anything else is a\n * configuration error, because an unbounded or effectively unbounded window\n * would accept an arbitrarily old replay.\n */\n toleranceSeconds?: number\n}\n\nasync function verifySignature(\n rawBody: Uint8Array,\n secret: string,\n signature: string,\n timestamp: string,\n deliveryId: string,\n): Promise<boolean> {\n const encoder = new TextEncoder()\n const signedPrefix = encoder.encode(`${timestamp}.${deliveryId}.`)\n const signedPayload = new Uint8Array(signedPrefix.length + rawBody.length)\n signedPayload.set(signedPrefix)\n signedPayload.set(rawBody, signedPrefix.length)\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['verify'],\n )\n if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {\n return false\n }\n const sigBytes = new Uint8Array(\n (signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16)),\n )\n return crypto.subtle.verify('HMAC', key, sigBytes, signedPayload)\n}\n\nfunction timestampIsFresh(\n timestamp: string,\n toleranceSeconds: number,\n): boolean {\n if (!/^\\d+$/.test(timestamp)) return false\n const timestampMs = Number(timestamp)\n if (!Number.isFinite(timestampMs)) return false\n const skewMs = Math.abs(Date.now() - timestampMs)\n return skewMs <= toleranceSeconds * 1000\n}\n\n/**\n * Upper bound for the signed replay window. A merely finite tolerance is not\n * enough: `Number.MAX_VALUE * 1000` overflows to `Infinity`, and even without\n * overflow a value of that size accepts an arbitrarily old capture. The sender's\n * retry and dead-letter lifecycle completes far inside a day, so anything beyond\n * this is a misconfiguration rather than clock-skew tolerance.\n */\nconst MAX_TOLERANCE_SECONDS = 86_400\n\n/**\n * A signed receiver's replay window must stay bounded. A non-finite, negative,\n * or over-bound tolerance would silently accept an old capture, so it is a\n * configuration error rather than a request-authentication failure.\n */\nfunction toleranceIsValid(\n toleranceSeconds: unknown,\n): toleranceSeconds is number {\n return (\n typeof toleranceSeconds === 'number' &&\n Number.isFinite(toleranceSeconds) &&\n toleranceSeconds >= 0 &&\n toleranceSeconds <= MAX_TOLERANCE_SECONDS\n )\n}\n\nasync function processWebhookRequest<\n T extends Collection | string = Collection,\n TData = T extends Collection ? CollectionType<T> : Record<string, unknown>,\n>(\n request: Request,\n handler: WebhookHandler<T, TData>,\n verify?: (rawBody: Uint8Array) => Promise<Response | undefined>,\n): Promise<Response> {\n try {\n const rawBody = new Uint8Array(await request.arrayBuffer())\n const verificationFailure = await verify?.(rawBody)\n if (verificationFailure) return verificationFailure\n\n const body = JSON.parse(new TextDecoder().decode(rawBody))\n if (!isValidWebhookEventData(body)) {\n return new Response(\n JSON.stringify({ error: 'Invalid webhook event format' }),\n { status: 400, headers: { 'Content-Type': 'application/json' } },\n )\n }\n\n await handler(body as WebhookEvent<T, TData>)\n return new Response(\n JSON.stringify({ success: true, message: 'Webhook processed' }),\n { status: 200, headers: { 'Content-Type': 'application/json' } },\n )\n } catch (error) {\n console.error('Webhook processing error:', error)\n return new Response(JSON.stringify({ error: 'Internal server error' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n}\n\nexport async function handleSignedWebhook<\n T extends Collection | string = Collection,\n TData = T extends Collection ? CollectionType<T> : Record<string, unknown>,\n>(\n request: Request,\n handler: WebhookHandler<T, TData>,\n options: SignedWebhookOptions,\n): Promise<Response> {\n // Read each configuration value exactly once. Re-reading `options` would let\n // a getter return a valid secret to the guard and a different one to the\n // verifier, so the validated bytes are the only bytes this receiver uses.\n const secret: unknown = options?.secret\n const configuredTolerance: unknown = options?.toleranceSeconds\n // `??` so an absent or null tolerance keeps the documented default, exactly as\n // before this change; only a present, out-of-contract value is rejected.\n const toleranceSeconds = configuredTolerance ?? 300\n\n if (typeof secret !== 'string' || secret.trim().length === 0) {\n return new Response(\n JSON.stringify({ error: 'Webhook signing secret is not configured' }),\n { status: 500, headers: { 'Content-Type': 'application/json' } },\n )\n }\n\n // Reported separately so a tolerance mistake is not misdiagnosed as a bad secret.\n if (!toleranceIsValid(toleranceSeconds)) {\n return new Response(\n JSON.stringify({ error: 'Webhook signature tolerance is not valid' }),\n { status: 500, headers: { 'Content-Type': 'application/json' } },\n )\n }\n\n return processWebhookRequest(request, handler, async (rawBody) => {\n const signature = request.headers.get('x-webhook-signature') || ''\n const timestamp = request.headers.get('x-webhook-timestamp') || ''\n const deliveryId = request.headers.get('x-webhook-delivery-id') || ''\n const valid =\n Boolean(timestamp && deliveryId) &&\n timestampIsFresh(timestamp, toleranceSeconds) &&\n (await verifySignature(rawBody, secret, signature, timestamp, deliveryId))\n\n if (!valid) {\n return new Response(\n JSON.stringify({ error: 'Invalid webhook signature' }),\n { status: 401, headers: { 'Content-Type': 'application/json' } },\n )\n }\n })\n}\n\n/** Process a webhook only when its endpoint is intentionally unsigned. */\nexport async function handleUnsignedWebhook<\n T extends Collection | string = Collection,\n TData = T extends Collection ? CollectionType<T> : Record<string, unknown>,\n>(request: Request, handler: WebhookHandler<T, TData>): Promise<Response> {\n return processWebhookRequest(request, handler)\n}\n","import type { Collection } from '../client/types'\nimport type { CollectionType } from '../collection/types'\nimport { handleSignedWebhook as handleSignedWebhookInternal } from './receiver'\nimport { isValidWebhookEventData } from './validation'\n\nexport type WebhookOperation = string\n\n/**\n * Semantic event type for manual Admin Panel ordering changes.\n *\n * Order-change webhooks remain `operation: \"update\"` events for compatibility.\n * Do not filter on or emit `operation: \"reorder\"`; use this event type with\n * `isOrderChangedWebhookEvent()` to distinguish ordering from normal content\n * updates.\n */\nexport const ORDER_CHANGED_EVENT_TYPE = 'collection.orderChanged' as const\n\n/** Semantic event type for v1 commerce notification webhooks. */\nexport const COMMERCE_NOTIFICATION_EVENT_TYPE = 'commerce.notification' as const\n\n/** Collection webhook operations emitted for tenant collection writes. */\nexport type CollectionWebhookOperation = 'create' | 'update'\n\n/** Describes the ordered area affected by an order-change webhook. */\nexport type WebhookOrderScope =\n | { kind: 'collection'; collection: string }\n | { kind: 'join'; collection: string; field: string; id: string | number }\n\n/** Identifies the public moved document for the ordered scope. */\nexport type WebhookOrderMoved = {\n collection: string\n id: string | number\n relatedCollection?: string\n relatedId?: string | number\n}\n\n/** Metadata attached to `collection.orderChanged` webhook events. */\nexport type WebhookOrderChange = {\n type: 'order'\n source: 'payload-orderable'\n /**\n * Diagnostic Payload order field name.\n *\n * This may contain hidden implementation field names. Application routing\n * should use `change.scope` and `change.moved` instead.\n */\n orderableFieldName?: string\n previousOrder?: string | null\n nextOrder?: string | null\n scope: WebhookOrderScope\n moved: WebhookOrderMoved\n}\n\n/** Known semantic webhook change metadata. */\nexport type WebhookChange =\n | WebhookOrderChange\n | WebhookCommerceNotificationChange\n\nexport interface WebhookEvent<\n T extends Collection | string = Collection,\n TData = T extends Collection ? CollectionType<T> : Record<string, unknown>,\n> {\n collection: T\n operation: WebhookOperation\n data: TData\n eventType?: string\n change?: unknown\n timestamp?: string\n deliveryId?: string\n}\n\nexport type WebhookHandler<\n T extends Collection | string = Collection,\n TData = T extends Collection ? CollectionType<T> : Record<string, unknown>,\n> = (event: WebhookEvent<T, TData>) => Promise<void> | void\n\n/** @deprecated Use the signed receiver options from `@01.software/sdk/webhook`. */\nexport interface WebhookOptions {\n /** Nonblank HMAC-SHA256 signing secret for this receiver. */\n secret: string\n /** Max accepted skew for signed webhook delivery timestamps. Default: 300s. */\n toleranceSeconds?: number\n}\n\nexport function isValidWebhookEvent(data: unknown): data is WebhookEvent {\n return isValidWebhookEventData(data)\n}\n\n/** Webhook event emitted when manual Admin Panel ordering changes. */\nexport type OrderChangedWebhookEvent<\n T extends Collection | string = Collection,\n TData = T extends Collection ? CollectionType<T> : Record<string, unknown>,\n> = WebhookEvent<T, TData> & {\n operation: 'update'\n eventType: typeof ORDER_CHANGED_EVENT_TYPE\n change: WebhookOrderChange\n}\n\nexport const COMMERCE_NOTIFICATION_OPERATION = 'notification' as const\n\nexport const COMMERCE_NOTIFICATION_EVENTS = [\n 'orderPaid',\n 'orderCanceled',\n 'fulfillmentShipped',\n 'orderDelivered',\n 'returnRequested',\n 'returnApproved',\n 'returnRejected',\n 'returnCanceled',\n 'returnCompleted',\n 'paymentReconciliationRequired',\n 'paymentRefundRequiresAttention',\n 'membershipActivated',\n 'membershipPastDue',\n 'membershipExpired',\n 'membershipCancelled',\n] as const\n\nexport type CommerceNotificationEventName =\n (typeof COMMERCE_NOTIFICATION_EVENTS)[number]\n\nexport type CustomerCommerceNotificationEventName = Exclude<\n CommerceNotificationEventName,\n PaymentIncidentNotificationEventName | MembershipNotificationEventName\n>\n\nexport type CommerceNotificationSourceCollection =\n | 'orders'\n | 'fulfillments'\n | 'returns'\n | 'payment-sessions'\n | 'payment-refund-attempts'\n | 'customer-memberships'\n\nexport type CommerceNotificationSource = {\n collection: CommerceNotificationSourceCollection\n id: string | number\n}\n\nexport type PaymentIncidentNotificationEventName =\n | 'paymentReconciliationRequired'\n | 'paymentRefundRequiresAttention'\n\nexport type MembershipNotificationEventName =\n | 'membershipActivated'\n | 'membershipPastDue'\n | 'membershipExpired'\n | 'membershipCancelled'\n\nexport type MembershipNotificationData = {\n membershipId: string\n planId: string\n /** Tenant-scoped pseudonymous identifier; this is not a PII-free claim. */\n customerId: string\n fromStatus: string\n toStatus: 'active' | 'past_due' | 'expired' | 'cancelled'\n occurredAt: string\n}\n\nexport type PaymentIncidentNotificationSourceCollection =\n | 'payment-sessions'\n | 'payment-refund-attempts'\n\nexport type PaymentIncidentNotificationReason =\n | 'orderless_capture'\n | 'local_commit_ambiguous'\n\nexport type PaymentIncidentNotificationStatus =\n | 'reconciliation_required'\n | 'requires_attention'\n\nexport type PaymentIncidentProviderObject = {\n provider: string\n kind: 'payment' | 'refund'\n id: string\n}\n\nexport type PaymentIncidentMoney = {\n amountMinor: number\n currency: string\n}\n\ntype PaymentIncidentNotificationBase = {\n paymentSessionId: string\n checkoutId?: string\n orderId?: string\n providerObject: PaymentIncidentProviderObject\n money: PaymentIncidentMoney\n observedAt: string\n}\n\nexport type PaymentReconciliationRequiredNotificationData =\n PaymentIncidentNotificationBase & {\n source: {\n collection: 'payment-sessions'\n id: string\n }\n providerObject: PaymentIncidentProviderObject & { kind: 'payment' }\n status: 'reconciliation_required'\n reason: 'orderless_capture'\n refundAttemptId?: never\n }\n\nexport type PaymentRefundRequiresAttentionNotificationData =\n PaymentIncidentNotificationBase & {\n source: { collection: 'payment-refund-attempts'; id: string }\n refundAttemptId: string\n status: 'requires_attention'\n reason: 'local_commit_ambiguous'\n }\n\nexport type PaymentIncidentNotificationData =\n | PaymentReconciliationRequiredNotificationData\n | PaymentRefundRequiresAttentionNotificationData\n\nexport type CommerceNotificationData = {\n source?: CommerceNotificationSource\n orderId?: string | number\n orderNumber?: string\n status?: string\n totalAmount?: number\n currency?: string\n fulfillmentId?: string | number\n fulfillmentStatus?: string\n shippedAt?: string\n returnId?: string | number\n returnStatus?: string\n refundAmount?: number\n completedAt?: string\n paymentSessionId?: string\n checkoutId?: string\n refundAttemptId?: string\n providerObject?: PaymentIncidentProviderObject\n money?: PaymentIncidentMoney\n reason?: PaymentIncidentNotificationReason\n observedAt?: string\n membershipId?: string\n planId?: string\n customerId?: string\n fromStatus?: string\n toStatus?: string\n occurredAt?: string\n}\n\nexport type CommerceNotification = {\n event: CommerceNotificationEventName\n intentId: string\n dedupeKey: string\n orderId?: string\n fulfillmentId?: string\n returnId?: string\n paymentSessionId?: string\n refundAttemptId?: string\n membershipId?: string\n}\n\nexport type WebhookCommerceNotificationChange = {\n type: 'notification'\n source: 'commerce-notifications'\n event: CommerceNotificationEventName\n sourceCollection: CommerceNotificationSourceCollection\n sourceId: string | number\n}\n\n/** Webhook event emitted for v1 commerce notification deliveries. */\nexport type CommerceNotificationWebhookEvent = WebhookEvent<\n CommerceNotificationSourceCollection,\n CommerceNotificationData\n> & {\n operation: typeof COMMERCE_NOTIFICATION_OPERATION\n eventType: typeof COMMERCE_NOTIFICATION_EVENT_TYPE\n change?: WebhookCommerceNotificationChange\n notification: CommerceNotification\n}\n\nexport type CommerceEmailChannel = 'webhook' | 'provider'\n\nexport type CommerceEmailEventConfig = {\n enabled: boolean\n channel: CommerceEmailChannel\n template?: string\n}\n\nexport type CommerceEmailConfig = {\n version: 1\n commerceNotifications: Partial<\n Record<CustomerCommerceNotificationEventName, CommerceEmailEventConfig>\n >\n}\n\nexport type CommerceNotificationHandlerContext = {\n event: CommerceNotificationWebhookEvent\n idempotencyKey: string\n}\n\ntype MaybePromise<T> = T | Promise<T>\n\nexport type CommerceNotificationEventHandlers = Partial<\n Record<\n CommerceNotificationEventName,\n (context: CommerceNotificationHandlerContext) => MaybePromise<void>\n >\n> & {\n unhandled?: (event: WebhookEvent<string, unknown>) => MaybePromise<void>\n}\n\nexport type CommerceEmailHandlerContext = CommerceNotificationHandlerContext\n\nexport type CommerceEmailEventHandlers = Partial<\n Record<\n CustomerCommerceNotificationEventName,\n (context: CommerceEmailHandlerContext) => MaybePromise<void>\n >\n> & {\n unhandled?: (event: WebhookEvent<string, unknown>) => MaybePromise<void>\n}\n\nfunction isStringOrNumber(value: unknown): value is string | number {\n return typeof value === 'string' || typeof value === 'number'\n}\n\nfunction isWebhookOrderScope(value: unknown): value is WebhookOrderScope {\n if (!isRecord(value)) return false\n\n if (value.kind === 'collection') {\n return typeof value.collection === 'string'\n }\n\n if (value.kind === 'join') {\n return (\n typeof value.collection === 'string' &&\n typeof value.field === 'string' &&\n isStringOrNumber(value.id)\n )\n }\n\n return false\n}\n\nfunction isWebhookOrderMoved(value: unknown): value is WebhookOrderMoved {\n if (!isRecord(value)) return false\n\n return (\n typeof value.collection === 'string' &&\n isStringOrNumber(value.id) &&\n (value.relatedCollection === undefined ||\n typeof value.relatedCollection === 'string') &&\n (value.relatedId === undefined || isStringOrNumber(value.relatedId))\n )\n}\n\nfunction hasOptionalOrderValue(\n value: Record<string, unknown>,\n key: string,\n): boolean {\n return (\n value[key] === undefined ||\n value[key] === null ||\n typeof value[key] === 'string'\n )\n}\n\nfunction isWebhookOrderChange(value: unknown): value is WebhookOrderChange {\n if (!isRecord(value)) return false\n\n return (\n value.type === 'order' &&\n value.source === 'payload-orderable' &&\n (value.orderableFieldName === undefined ||\n typeof value.orderableFieldName === 'string') &&\n hasOptionalOrderValue(value, 'previousOrder') &&\n hasOptionalOrderValue(value, 'nextOrder') &&\n isWebhookOrderScope(value.scope) &&\n isWebhookOrderMoved(value.moved)\n )\n}\n\nfunction isCommerceNotificationEventName(\n value: unknown,\n): value is CommerceNotificationEventName {\n return (\n typeof value === 'string' &&\n (COMMERCE_NOTIFICATION_EVENTS as readonly string[]).includes(value)\n )\n}\n\nfunction isCommerceNotificationSourceCollection(\n value: unknown,\n): value is CommerceNotificationSourceCollection {\n return (\n value === 'orders' ||\n value === 'fulfillments' ||\n value === 'returns' ||\n value === 'payment-sessions' ||\n value === 'payment-refund-attempts' ||\n value === 'customer-memberships'\n )\n}\n\nfunction isCommerceNotificationData(\n value: unknown,\n): value is CommerceNotificationData {\n if (!isRecord(value)) return false\n if (\n value.source !== undefined &&\n (!isRecord(value.source) ||\n !isCommerceNotificationSourceCollection(value.source.collection) ||\n !isStringOrNumber(value.source.id))\n ) {\n return false\n }\n\n return (\n (value.orderId === undefined || isStringOrNumber(value.orderId)) &&\n (value.orderNumber === undefined ||\n typeof value.orderNumber === 'string') &&\n (value.status === undefined || typeof value.status === 'string') &&\n (value.totalAmount === undefined ||\n typeof value.totalAmount === 'number') &&\n (value.currency === undefined || typeof value.currency === 'string') &&\n (value.fulfillmentId === undefined ||\n isStringOrNumber(value.fulfillmentId)) &&\n (value.fulfillmentStatus === undefined ||\n typeof value.fulfillmentStatus === 'string') &&\n (value.shippedAt === undefined || typeof value.shippedAt === 'string') &&\n (value.returnId === undefined || isStringOrNumber(value.returnId)) &&\n (value.returnStatus === undefined ||\n typeof value.returnStatus === 'string') &&\n (value.refundAmount === undefined ||\n typeof value.refundAmount === 'number') &&\n (value.completedAt === undefined ||\n typeof value.completedAt === 'string') &&\n (value.paymentSessionId === undefined ||\n typeof value.paymentSessionId === 'string') &&\n (value.checkoutId === undefined || typeof value.checkoutId === 'string') &&\n (value.refundAttemptId === undefined ||\n typeof value.refundAttemptId === 'string') &&\n (value.reason === undefined || typeof value.reason === 'string') &&\n (value.observedAt === undefined || typeof value.observedAt === 'string') &&\n (value.providerObject === undefined ||\n (isRecord(value.providerObject) &&\n typeof value.providerObject.provider === 'string' &&\n (value.providerObject.kind === 'payment' ||\n value.providerObject.kind === 'refund') &&\n typeof value.providerObject.id === 'string')) &&\n (value.money === undefined ||\n (isRecord(value.money) &&\n Number.isSafeInteger(value.money.amountMinor) &&\n Number(value.money.amountMinor) > 0 &&\n typeof value.money.currency === 'string'))\n )\n}\n\nfunction isCommerceNotification(value: unknown): value is CommerceNotification {\n if (!isRecord(value)) return false\n return (\n isCommerceNotificationEventName(value.event) &&\n typeof value.intentId === 'string' &&\n value.intentId.length > 0 &&\n typeof value.dedupeKey === 'string' &&\n value.dedupeKey.length > 0 &&\n (value.orderId === undefined || typeof value.orderId === 'string') &&\n (value.fulfillmentId === undefined ||\n typeof value.fulfillmentId === 'string') &&\n (value.returnId === undefined || typeof value.returnId === 'string') &&\n (value.paymentSessionId === undefined ||\n typeof value.paymentSessionId === 'string') &&\n (value.refundAttemptId === undefined ||\n typeof value.refundAttemptId === 'string')\n )\n}\n\nfunction isPaymentIncidentNotificationData(\n value: CommerceNotificationData,\n event: PaymentIncidentNotificationEventName,\n): boolean {\n const reconciliation = event === 'paymentReconciliationRequired'\n const expectedCollection = reconciliation\n ? 'payment-sessions'\n : 'payment-refund-attempts'\n const expectedStatus = reconciliation\n ? 'reconciliation_required'\n : 'requires_attention'\n const expectedReason = reconciliation\n ? 'orderless_capture'\n : 'local_commit_ambiguous'\n\n return (\n value.source?.collection === expectedCollection &&\n typeof value.source.id === 'string' &&\n value.source.id.length > 0 &&\n typeof value.paymentSessionId === 'string' &&\n value.paymentSessionId.length > 0 &&\n isRecord(value.providerObject) &&\n typeof value.providerObject.provider === 'string' &&\n value.providerObject.provider.length > 0 &&\n (value.providerObject.kind === 'payment' ||\n value.providerObject.kind === 'refund') &&\n typeof value.providerObject.id === 'string' &&\n value.providerObject.id.length > 0 &&\n isRecord(value.money) &&\n Number.isSafeInteger(value.money.amountMinor) &&\n Number(value.money.amountMinor) > 0 &&\n typeof value.money.currency === 'string' &&\n value.money.currency.length > 0 &&\n value.status === expectedStatus &&\n value.reason === expectedReason &&\n typeof value.observedAt === 'string' &&\n value.observedAt.length > 0 &&\n !Number.isNaN(Date.parse(value.observedAt)) &&\n (value.checkoutId === undefined ||\n (typeof value.checkoutId === 'string' && value.checkoutId.length > 0)) &&\n (value.orderId === undefined ||\n (typeof value.orderId === 'string' && value.orderId.length > 0)) &&\n (reconciliation\n ? value.source.id === value.paymentSessionId &&\n value.providerObject.kind === 'payment' &&\n value.refundAttemptId === undefined\n : typeof value.refundAttemptId === 'string' &&\n value.refundAttemptId.length > 0 &&\n value.source.id === value.refundAttemptId)\n )\n}\n\nfunction isMembershipNotificationData(\n value: CommerceNotificationData,\n event: MembershipNotificationEventName,\n): boolean {\n const expectedStatus =\n event === 'membershipActivated'\n ? 'active'\n : event === 'membershipPastDue'\n ? 'past_due'\n : event === 'membershipExpired'\n ? 'expired'\n : 'cancelled'\n return (\n typeof value.membershipId === 'string' &&\n value.membershipId.length > 0 &&\n typeof value.planId === 'string' &&\n value.planId.length > 0 &&\n typeof value.customerId === 'string' &&\n value.customerId.length > 0 &&\n typeof value.fromStatus === 'string' &&\n value.fromStatus.length > 0 &&\n value.fromStatus !== value.toStatus &&\n value.toStatus === expectedStatus &&\n typeof value.occurredAt === 'string' &&\n !Number.isNaN(Date.parse(value.occurredAt))\n )\n}\n\nfunction isWebhookCommerceNotificationChange(\n value: unknown,\n): value is WebhookCommerceNotificationChange {\n if (!isRecord(value)) return false\n return (\n value.type === 'notification' &&\n value.source === 'commerce-notifications' &&\n isCommerceNotificationEventName(value.event) &&\n isCommerceNotificationSourceCollection(value.sourceCollection) &&\n isStringOrNumber(value.sourceId)\n )\n}\n\nfunction matchesOptionalId(\n actual: string | number | undefined,\n expected: string | number | undefined,\n): boolean {\n return actual === undefined || expected === undefined || actual === expected\n}\n\n/**\n * Returns true for semantic order-change webhooks emitted as update events.\n *\n * Use this guard with `ORDER_CHANGED_EVENT_TYPE` instead of filtering on\n * `operation: \"reorder\"`. Tenant order-change webhooks intentionally keep\n * `operation: \"update\"` for compatibility with existing webhook handlers.\n */\nexport function isOrderChangedWebhookEvent(\n event: unknown,\n): event is OrderChangedWebhookEvent<string, unknown> {\n if (\n !isValidWebhookEvent(event) ||\n event.operation !== 'update' ||\n event.eventType !== ORDER_CHANGED_EVENT_TYPE ||\n !isWebhookOrderChange(event.change)\n ) {\n return false\n }\n\n if (event.collection !== event.change.scope.collection) {\n return false\n }\n\n if (\n event.change.scope.kind === 'collection' &&\n event.change.moved.collection !== event.change.scope.collection\n ) {\n return false\n }\n\n return true\n}\n\n/**\n * Returns true for semantic commerce notification webhooks.\n *\n * Use `notification.intentId` and `notification.dedupeKey` for semantic\n * idempotency. `deliveryId`, when present, identifies a delivery attempt for\n * observability and is not the semantic notification identity.\n */\nexport function isCommerceNotificationWebhookEvent(\n event: unknown,\n): event is CommerceNotificationWebhookEvent {\n if (\n !isValidWebhookEvent(event) ||\n event.operation !== COMMERCE_NOTIFICATION_OPERATION ||\n event.eventType !== COMMERCE_NOTIFICATION_EVENT_TYPE ||\n !isCommerceNotificationData(event.data) ||\n !isCommerceNotification((event as { notification?: unknown }).notification)\n ) {\n return false\n }\n\n const notification = (event as unknown as CommerceNotificationWebhookEvent)\n .notification\n const data = event.data\n const change = event.change\n const sourceCollection = data.source?.collection ?? event.collection\n const sourceId = data.source?.id\n\n if (!isCommerceNotificationSourceCollection(sourceCollection)) return false\n if (\n data.source !== undefined &&\n event.collection !== data.source.collection\n ) {\n return false\n }\n\n if (change !== undefined) {\n if (!isWebhookCommerceNotificationChange(change)) return false\n if (change.sourceCollection !== sourceCollection) return false\n if (change.event !== notification.event) return false\n if (!matchesOptionalId(change.sourceId, sourceId)) return false\n }\n const changeSourceId = isWebhookCommerceNotificationChange(change)\n ? change.sourceId\n : undefined\n\n if (\n notification.event === 'paymentReconciliationRequired' ||\n notification.event === 'paymentRefundRequiresAttention'\n ) {\n if (!isPaymentIncidentNotificationData(data, notification.event)) {\n return false\n }\n const incidentData = data as PaymentIncidentNotificationData\n const expectedCollection =\n notification.event === 'paymentReconciliationRequired'\n ? 'payment-sessions'\n : 'payment-refund-attempts'\n const notificationSourceId =\n notification.event === 'paymentReconciliationRequired'\n ? notification.paymentSessionId\n : notification.refundAttemptId\n return (\n sourceCollection === expectedCollection &&\n typeof notification.paymentSessionId === 'string' &&\n notification.paymentSessionId === incidentData.paymentSessionId &&\n typeof notificationSourceId === 'string' &&\n notificationSourceId === incidentData.source.id &&\n matchesOptionalId(changeSourceId, incidentData.source.id)\n )\n }\n\n if (\n notification.event === 'membershipActivated' ||\n notification.event === 'membershipPastDue' ||\n notification.event === 'membershipExpired' ||\n notification.event === 'membershipCancelled'\n ) {\n return (\n sourceCollection === 'customer-memberships' &&\n isMembershipNotificationData(data, notification.event) &&\n typeof notification.membershipId === 'string' &&\n notification.membershipId === data.membershipId &&\n matchesOptionalId(changeSourceId, data.membershipId)\n )\n }\n\n if (\n notification.event === 'orderPaid' ||\n notification.event === 'orderCanceled' ||\n notification.event === 'orderDelivered'\n ) {\n return (\n sourceCollection === 'orders' &&\n typeof notification.orderId === 'string' &&\n notification.orderId.length > 0 &&\n matchesOptionalId(data.orderId, sourceId) &&\n matchesOptionalId(notification.orderId, sourceId) &&\n matchesOptionalId(data.orderId, notification.orderId) &&\n matchesOptionalId(changeSourceId, data.orderId ?? notification.orderId)\n )\n }\n\n if (notification.event === 'fulfillmentShipped') {\n return (\n sourceCollection === 'fulfillments' &&\n matchesOptionalId(data.fulfillmentId, sourceId) &&\n typeof notification.fulfillmentId === 'string' &&\n notification.fulfillmentId.length > 0 &&\n matchesOptionalId(notification.fulfillmentId, sourceId) &&\n matchesOptionalId(data.fulfillmentId, notification.fulfillmentId) &&\n matchesOptionalId(changeSourceId, notification.fulfillmentId)\n )\n }\n\n if (\n notification.event === 'returnRequested' ||\n notification.event === 'returnApproved' ||\n notification.event === 'returnRejected' ||\n notification.event === 'returnCanceled' ||\n notification.event === 'returnCompleted'\n ) {\n return (\n sourceCollection === 'returns' &&\n matchesOptionalId(data.returnId, sourceId) &&\n typeof notification.returnId === 'string' &&\n notification.returnId.length > 0 &&\n matchesOptionalId(notification.returnId, sourceId) &&\n matchesOptionalId(data.returnId, notification.returnId) &&\n matchesOptionalId(changeSourceId, notification.returnId)\n )\n }\n\n return false\n}\n\nexport function getCommerceNotificationIdempotencyKey(\n event: Pick<CommerceNotificationWebhookEvent, 'notification'>,\n): string {\n return `${event.notification.intentId}:${event.notification.dedupeKey}`\n}\n\nexport function defineCommerceEmailConfig<TConfig extends CommerceEmailConfig>(\n config: TConfig,\n): TConfig {\n return config\n}\n\nexport function createCommerceNotificationWebhookHandler(\n handlers: CommerceNotificationEventHandlers,\n): WebhookHandler<string, unknown> {\n return async (event) => {\n if (!isCommerceNotificationWebhookEvent(event)) {\n await handlers.unhandled?.(event)\n return\n }\n\n const handler = handlers[event.notification.event]\n if (!handler) {\n await handlers.unhandled?.(event)\n return\n }\n\n await handler({\n event,\n idempotencyKey: getCommerceNotificationIdempotencyKey(event),\n })\n }\n}\n\nexport function createCommerceEmailWebhookHandler(\n handlers: CommerceEmailEventHandlers,\n): WebhookHandler<string, unknown> {\n return createCommerceNotificationWebhookHandler(handlers)\n}\n\n/** Narrows a webhook event to a specific collection. */\nexport function isWebhookCollection<TCollection extends string>(\n event: unknown,\n collection: TCollection,\n): event is WebhookEvent<TCollection, unknown> {\n return isValidWebhookEvent(event) && event.collection === collection\n}\n\n/** Narrows a webhook event to a specific operation. */\nexport function isWebhookOperation<TOperation extends string>(\n event: unknown,\n operation: TOperation,\n): event is WebhookEvent<string, unknown> & { operation: TOperation } {\n return isValidWebhookEvent(event) && event.operation === operation\n}\n\nexport const CUSTOMER_PASSWORD_RESET_OPERATION = 'password-reset' as const\n\nexport type WebhookKnownOperation =\n | CollectionWebhookOperation\n | typeof CUSTOMER_PASSWORD_RESET_OPERATION\n | typeof COMMERCE_NOTIFICATION_OPERATION\n\nexport interface CustomerPasswordResetWebhookData {\n customerId: string | number\n email: string\n name: string\n resetPasswordToken: string\n resetPasswordExpiresAt: string\n}\n\nexport type CustomerPasswordResetWebhookEvent = WebhookEvent<\n 'customers',\n CustomerPasswordResetWebhookData\n> & {\n operation: typeof CUSTOMER_PASSWORD_RESET_OPERATION\n}\n\nexport type CustomerAuthWebhookEvent = CustomerPasswordResetWebhookEvent\n\nexport interface CustomerAuthWebhookHandlers {\n passwordReset?: (\n data: CustomerPasswordResetWebhookData,\n event: CustomerPasswordResetWebhookEvent,\n ) => MaybePromise<void>\n unhandled?: (event: WebhookEvent<string, unknown>) => MaybePromise<void>\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null\n}\n\nfunction hasString(value: Record<string, unknown>, key: string): boolean {\n return typeof value[key] === 'string'\n}\n\nfunction hasStringOrNumber(\n value: Record<string, unknown>,\n key: string,\n): boolean {\n return typeof value[key] === 'string' || typeof value[key] === 'number'\n}\n\nexport function isCustomerPasswordResetWebhookEvent(\n event: WebhookEvent<string, unknown>,\n): event is CustomerPasswordResetWebhookEvent {\n if (\n event.collection !== 'customers' ||\n event.operation !== CUSTOMER_PASSWORD_RESET_OPERATION ||\n !isRecord(event.data)\n ) {\n return false\n }\n\n return (\n hasStringOrNumber(event.data, 'customerId') &&\n hasString(event.data, 'email') &&\n hasString(event.data, 'name') &&\n hasString(event.data, 'resetPasswordToken') &&\n hasString(event.data, 'resetPasswordExpiresAt')\n )\n}\n\nexport function createCustomerAuthWebhookHandler(\n handlers: CustomerAuthWebhookHandlers,\n): WebhookHandler<string, unknown> {\n return async (event) => {\n if (isCustomerPasswordResetWebhookEvent(event) && handlers.passwordReset) {\n await handlers.passwordReset(event.data, event)\n return\n }\n\n await handlers.unhandled?.(event)\n }\n}\n\n/**\n * @deprecated Import `handleSignedWebhook()` from `@01.software/sdk/webhook`.\n * This compatibility alias is signed and fails closed when its required secret\n * is missing or blank.\n */\nexport async function handleWebhook<\n T extends Collection | string = Collection,\n TData = T extends Collection ? CollectionType<T> : Record<string, unknown>,\n>(\n request: Request,\n handler: WebhookHandler<T, TData>,\n options: WebhookOptions,\n): Promise<Response> {\n return handleSignedWebhookInternal(request, handler, options)\n}\n\nexport function createTypedWebhookHandler<T extends Collection>(\n collection: T,\n handler: (event: WebhookEvent<T>) => Promise<void> | void,\n): WebhookHandler<T> {\n return async (event: WebhookEvent<T>) => {\n if (event.collection !== collection) {\n throw new Error(\n `Expected collection \"${collection}\", got \"${event.collection}\"`,\n )\n }\n return handler(event)\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@01.software/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.56.1",
|
|
4
4
|
"description": "01.software SDK",
|
|
5
5
|
"author": "<office@01.works>",
|
|
6
6
|
"repository": {
|
|
@@ -124,6 +124,26 @@
|
|
|
124
124
|
"default": "./dist/storefront-cache.cjs"
|
|
125
125
|
}
|
|
126
126
|
},
|
|
127
|
+
"./embedded-admin": {
|
|
128
|
+
"import": {
|
|
129
|
+
"types": "./dist/embedded-admin.d.ts",
|
|
130
|
+
"default": "./dist/embedded-admin.js"
|
|
131
|
+
},
|
|
132
|
+
"require": {
|
|
133
|
+
"types": "./dist/embedded-admin.d.cts",
|
|
134
|
+
"default": "./dist/embedded-admin.cjs"
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
"./embedded-admin/server": {
|
|
138
|
+
"import": {
|
|
139
|
+
"types": "./dist/embedded-admin/server.d.ts",
|
|
140
|
+
"default": "./dist/embedded-admin/server.js"
|
|
141
|
+
},
|
|
142
|
+
"require": {
|
|
143
|
+
"types": "./dist/embedded-admin/server.d.cts",
|
|
144
|
+
"default": "./dist/embedded-admin/server.cjs"
|
|
145
|
+
}
|
|
146
|
+
},
|
|
127
147
|
"./ui/image": {
|
|
128
148
|
"import": {
|
|
129
149
|
"types": "./dist/ui/image.d.ts",
|
|
@@ -206,22 +226,23 @@
|
|
|
206
226
|
"CHANGELOG.md"
|
|
207
227
|
],
|
|
208
228
|
"devDependencies": {
|
|
209
|
-
"@arethetypeswrong/cli": "^0.18.
|
|
229
|
+
"@arethetypeswrong/cli": "^0.18.5",
|
|
210
230
|
"@mux/mux-player-react": "^3.13.0",
|
|
211
|
-
"@payloadcms/richtext-lexical": "3.
|
|
231
|
+
"@payloadcms/richtext-lexical": "3.89.0",
|
|
212
232
|
"@tanstack/react-query": "5.100.9",
|
|
213
233
|
"@testing-library/jest-dom": "^6.9.1",
|
|
214
234
|
"@testing-library/react": "16.3.2",
|
|
215
235
|
"@types/node": "^22.19.18",
|
|
216
236
|
"@types/react": "^19.2.14",
|
|
217
237
|
"@types/react-dom": "^19.2.3",
|
|
218
|
-
"@vitest/coverage-v8": "4.1.
|
|
238
|
+
"@vitest/coverage-v8": "4.1.11",
|
|
219
239
|
"@xyflow/react": "^12.11.2",
|
|
220
240
|
"eslint": "^9.39.4",
|
|
221
241
|
"hast-util-to-jsx-runtime": "^2.3.6",
|
|
222
242
|
"jsdom": "26.1.0",
|
|
223
243
|
"monaco-editor": "0.55.1",
|
|
224
|
-
"
|
|
244
|
+
"next": "16.3.3",
|
|
245
|
+
"payload": "3.89.0",
|
|
225
246
|
"postcss": "8.5.23",
|
|
226
247
|
"publint": "^0.3.20",
|
|
227
248
|
"quickjs-emscripten": "0.32.0",
|
|
@@ -232,12 +253,13 @@
|
|
|
232
253
|
"sucrase": "^3.35.1",
|
|
233
254
|
"tsup": "^8.5.1",
|
|
234
255
|
"typescript": "5.7.3",
|
|
235
|
-
"vitest": "^4.1.
|
|
256
|
+
"vitest": "^4.1.11",
|
|
236
257
|
"@01.software/http-contracts": "0.1.0",
|
|
237
258
|
"@repo/eslint-config": "0.0.0",
|
|
238
259
|
"@repo/typescript-config": "0.0.0"
|
|
239
260
|
},
|
|
240
261
|
"dependencies": {
|
|
262
|
+
"jose": "^6.2.3",
|
|
241
263
|
"qs-esm": "^8.0.1"
|
|
242
264
|
},
|
|
243
265
|
"peerDependencies": {
|