@whatalo/plugin-sdk 1.2.0 → 1.2.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/dist/adapters/express.cjs +101 -10
- package/dist/adapters/express.cjs.map +1 -1
- package/dist/adapters/express.d.cts +9 -7
- package/dist/adapters/express.d.ts +9 -7
- package/dist/adapters/express.mjs +104 -10
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/hono.cjs +94 -7
- package/dist/adapters/hono.cjs.map +1 -1
- package/dist/adapters/hono.d.cts +4 -3
- package/dist/adapters/hono.d.ts +4 -3
- package/dist/adapters/hono.mjs +97 -7
- package/dist/adapters/hono.mjs.map +1 -1
- package/dist/adapters/nextjs.cjs +94 -7
- package/dist/adapters/nextjs.cjs.map +1 -1
- package/dist/adapters/nextjs.d.cts +4 -3
- package/dist/adapters/nextjs.d.ts +4 -3
- package/dist/adapters/nextjs.mjs +97 -7
- package/dist/adapters/nextjs.mjs.map +1 -1
- package/dist/bridge/index.cjs +33 -2
- package/dist/bridge/index.cjs.map +1 -1
- package/dist/bridge/index.d.cts +17 -5
- package/dist/bridge/index.d.ts +17 -5
- package/dist/bridge/index.mjs +33 -2
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/client/index.cjs +33 -2
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +29 -1
- package/dist/client/index.d.ts +29 -1
- package/dist/client/index.mjs +33 -2
- package/dist/client/index.mjs.map +1 -1
- package/dist/index.cjs +146 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.mjs +146 -24
- package/dist/index.mjs.map +1 -1
- package/dist/manifest/index.cjs +2 -17
- package/dist/manifest/index.cjs.map +1 -1
- package/dist/manifest/index.d.cts +10 -4
- package/dist/manifest/index.d.ts +10 -4
- package/dist/manifest/index.mjs +2 -17
- package/dist/manifest/index.mjs.map +1 -1
- package/dist/{types-DcmArIC2.d.ts → types-B8BAV362.d.cts} +24 -1
- package/dist/{types-DcmArIC2.d.cts → types-B8BAV362.d.ts} +24 -1
- package/dist/types-BU71_xQm.d.cts +25 -0
- package/dist/types-BhtVJPgF.d.ts +25 -0
- package/dist/types-Bo9GbqrM.d.ts +133 -0
- package/dist/types-rA6kVlpV.d.cts +133 -0
- package/dist/webhooks/index.cjs +78 -3
- package/dist/webhooks/index.cjs.map +1 -1
- package/dist/webhooks/index.d.cts +11 -4
- package/dist/webhooks/index.d.ts +11 -4
- package/dist/webhooks/index.mjs +78 -3
- package/dist/webhooks/index.mjs.map +1 -1
- package/package.json +4 -1
- package/dist/types-C9mg4aQg.d.ts +0 -19
- package/dist/types-DMuHSL_a.d.cts +0 -19
- package/dist/types-DTjA3FHe.d.ts +0 -68
- package/dist/types-tHUO9u-c.d.cts +0 -68
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/manifest/validate.ts"],"sourcesContent":["import type { AppManifest } from \"./types.js\";\n\n/** Error thrown when manifest validation fails */\nexport class ManifestValidationError extends Error {\n readonly errors: Array<{ field: string; message: string }>;\n\n constructor(errors: Array<{ field: string; message: string }>) {\n const message = `Invalid app manifest: ${errors\n .map((e) => `${e.field}: ${e.message}`)\n .join(\"; \")}`;\n super(message);\n this.name = \"ManifestValidationError\";\n this.errors = errors;\n }\n}\n\nconst ID_REGEX = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;\nconst SEMVER_REGEX = /^\\d+\\.\\d+\\.\\d+$/;\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\nconst VALID_PERMISSIONS = new Set(
|
|
1
|
+
{"version":3,"sources":["../../src/manifest/validate.ts"],"sourcesContent":["import { SCOPE_LIST } from \"@whatalo/protocol/scopes\";\nimport type { AppManifest } from \"./types.js\";\n\n/** Error thrown when manifest validation fails */\nexport class ManifestValidationError extends Error {\n readonly errors: Array<{ field: string; message: string }>;\n\n constructor(errors: Array<{ field: string; message: string }>) {\n const message = `Invalid app manifest: ${errors\n .map((e) => `${e.field}: ${e.message}`)\n .join(\"; \")}`;\n super(message);\n this.name = \"ManifestValidationError\";\n this.errors = errors;\n }\n}\n\nconst ID_REGEX = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;\nconst SEMVER_REGEX = /^\\d+\\.\\d+\\.\\d+$/;\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\n/** Built from the central registry — no manual sync required. */\nconst VALID_PERMISSIONS = new Set(SCOPE_LIST.map((s) => s.token));\n\nconst VALID_CATEGORIES = new Set([\n \"analytics\",\n \"marketing\",\n \"shipping\",\n \"payments\",\n \"payment\",\n \"inventory\",\n \"communication\",\n \"productivity\",\n \"accounting\",\n \"developer\",\n \"other\",\n]);\n\n/**\n * Validate and return a typed AppManifest.\n * Throws ManifestValidationError with detailed field-level errors.\n */\nexport function defineApp(manifest: AppManifest): AppManifest {\n const errors: Array<{ field: string; message: string }> = [];\n\n // id\n if (!manifest.id || manifest.id.length < 3 || manifest.id.length > 50) {\n errors.push({ field: \"id\", message: \"Must be 3-50 characters\" });\n } else if (!ID_REGEX.test(manifest.id)) {\n errors.push({\n field: \"id\",\n message: \"Must be lowercase alphanumeric with hyphens\",\n });\n }\n\n // name\n if (!manifest.name || manifest.name.length < 3 || manifest.name.length > 50) {\n errors.push({ field: \"name\", message: \"Must be 3-50 characters\" });\n }\n\n // description\n if (\n !manifest.description ||\n manifest.description.length < 10 ||\n manifest.description.length > 200\n ) {\n errors.push({\n field: \"description\",\n message: \"Must be 10-200 characters\",\n });\n }\n\n // version\n if (!manifest.version || !SEMVER_REGEX.test(manifest.version)) {\n errors.push({\n field: \"version\",\n message: \"Must be valid semver (e.g., 1.0.0)\",\n });\n }\n\n // author\n if (!manifest.author?.name) {\n errors.push({ field: \"author.name\", message: \"Required\" });\n }\n if (!manifest.author?.email || !EMAIL_REGEX.test(manifest.author.email)) {\n errors.push({\n field: \"author.email\",\n message: \"Must be a valid email address\",\n });\n }\n\n // permissions\n if (!manifest.permissions || manifest.permissions.length === 0) {\n errors.push({\n field: \"permissions\",\n message: \"At least one permission is required\",\n });\n } else {\n for (const perm of manifest.permissions) {\n if (!VALID_PERMISSIONS.has(perm)) {\n errors.push({\n field: \"permissions\",\n message: `Invalid permission: ${perm}`,\n });\n }\n }\n }\n\n // appUrl\n if (!manifest.appUrl) {\n errors.push({ field: \"appUrl\", message: \"Required\" });\n }\n\n // pricing\n if (manifest.pricing !== \"free\" && manifest.pricing !== \"paid\") {\n errors.push({\n field: \"pricing\",\n message: 'Must be \"free\" or \"paid\"',\n });\n }\n\n // category\n if (!VALID_CATEGORIES.has(manifest.category)) {\n errors.push({\n field: \"category\",\n message: `Invalid category: ${manifest.category}`,\n });\n }\n\n if (errors.length > 0) {\n throw new ManifestValidationError(errors);\n }\n\n return manifest;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -288,5 +288,28 @@ interface AdjustInventoryParams {
|
|
|
288
288
|
quantity: number;
|
|
289
289
|
reason: string;
|
|
290
290
|
}
|
|
291
|
+
/** Country from the geo reference data */
|
|
292
|
+
interface GeoCountry {
|
|
293
|
+
iso2: string;
|
|
294
|
+
iso3: string;
|
|
295
|
+
name: string;
|
|
296
|
+
native_name: string | null;
|
|
297
|
+
phone_code: string;
|
|
298
|
+
currency_code: string;
|
|
299
|
+
emoji: string | null;
|
|
300
|
+
is_supported: boolean;
|
|
301
|
+
}
|
|
302
|
+
/** Province/state/department from the geo reference data */
|
|
303
|
+
interface GeoProvince {
|
|
304
|
+
id: number;
|
|
305
|
+
country_iso2: string;
|
|
306
|
+
code: string;
|
|
307
|
+
name: string;
|
|
308
|
+
}
|
|
309
|
+
/** Params for listing countries */
|
|
310
|
+
interface ListCountriesParams {
|
|
311
|
+
/** When true, returns all 250 countries instead of only supported ones */
|
|
312
|
+
all?: boolean;
|
|
313
|
+
}
|
|
291
314
|
|
|
292
|
-
export type { AdjustInventoryParams as A, CreateProductParams as C, Discount as D, InventoryItem as I, ListProductsParams as L, Order as O, PaginatedResponse as P, RateLimitInfo as R, SingleResponse as S, UpdateProductParams as U, Webhook as W, Product as a, ListOrdersParams as b, UpdateOrderStatusParams as c, UpdateOrderPaymentStatusParams as d, ListCustomersParams as e, Customer as f, ListCategoriesParams as g, Category as h, CreateCategoryParams as i, UpdateCategoryParams as j, ListDiscountsParams as k, CreateDiscountParams as l, UpdateDiscountParams as m, Store as n, UpsertWebhookParams as o, UpdateWebhookParams as p,
|
|
315
|
+
export type { AdjustInventoryParams as A, CreateProductParams as C, Discount as D, GeoCountry as G, InventoryItem as I, ListProductsParams as L, Order as O, PaginatedResponse as P, RateLimitInfo as R, SingleResponse as S, UpdateProductParams as U, Webhook as W, Product as a, ListOrdersParams as b, UpdateOrderStatusParams as c, UpdateOrderPaymentStatusParams as d, ListCustomersParams as e, Customer as f, ListCategoriesParams as g, Category as h, CreateCategoryParams as i, UpdateCategoryParams as j, ListDiscountsParams as k, CreateDiscountParams as l, UpdateDiscountParams as m, Store as n, UpsertWebhookParams as o, UpdateWebhookParams as p, ListCountriesParams as q, GeoProvince as r, WhataloClientOptions as s, OrderStatus as t, PaymentStatus as u };
|
|
@@ -288,5 +288,28 @@ interface AdjustInventoryParams {
|
|
|
288
288
|
quantity: number;
|
|
289
289
|
reason: string;
|
|
290
290
|
}
|
|
291
|
+
/** Country from the geo reference data */
|
|
292
|
+
interface GeoCountry {
|
|
293
|
+
iso2: string;
|
|
294
|
+
iso3: string;
|
|
295
|
+
name: string;
|
|
296
|
+
native_name: string | null;
|
|
297
|
+
phone_code: string;
|
|
298
|
+
currency_code: string;
|
|
299
|
+
emoji: string | null;
|
|
300
|
+
is_supported: boolean;
|
|
301
|
+
}
|
|
302
|
+
/** Province/state/department from the geo reference data */
|
|
303
|
+
interface GeoProvince {
|
|
304
|
+
id: number;
|
|
305
|
+
country_iso2: string;
|
|
306
|
+
code: string;
|
|
307
|
+
name: string;
|
|
308
|
+
}
|
|
309
|
+
/** Params for listing countries */
|
|
310
|
+
interface ListCountriesParams {
|
|
311
|
+
/** When true, returns all 250 countries instead of only supported ones */
|
|
312
|
+
all?: boolean;
|
|
313
|
+
}
|
|
291
314
|
|
|
292
|
-
export type { AdjustInventoryParams as A, CreateProductParams as C, Discount as D, InventoryItem as I, ListProductsParams as L, Order as O, PaginatedResponse as P, RateLimitInfo as R, SingleResponse as S, UpdateProductParams as U, Webhook as W, Product as a, ListOrdersParams as b, UpdateOrderStatusParams as c, UpdateOrderPaymentStatusParams as d, ListCustomersParams as e, Customer as f, ListCategoriesParams as g, Category as h, CreateCategoryParams as i, UpdateCategoryParams as j, ListDiscountsParams as k, CreateDiscountParams as l, UpdateDiscountParams as m, Store as n, UpsertWebhookParams as o, UpdateWebhookParams as p,
|
|
315
|
+
export type { AdjustInventoryParams as A, CreateProductParams as C, Discount as D, GeoCountry as G, InventoryItem as I, ListProductsParams as L, Order as O, PaginatedResponse as P, RateLimitInfo as R, SingleResponse as S, UpdateProductParams as U, Webhook as W, Product as a, ListOrdersParams as b, UpdateOrderStatusParams as c, UpdateOrderPaymentStatusParams as d, ListCustomersParams as e, Customer as f, ListCategoriesParams as g, Category as h, CreateCategoryParams as i, UpdateCategoryParams as j, ListDiscountsParams as k, CreateDiscountParams as l, UpdateDiscountParams as m, Store as n, UpsertWebhookParams as o, UpdateWebhookParams as p, ListCountriesParams as q, GeoProvince as r, WhataloClientOptions as s, OrderStatus as t, PaymentStatus as u };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { W as WebhookEvent, R as RuntimeWebhookPayload } from './types-rA6kVlpV.cjs';
|
|
2
|
+
|
|
3
|
+
/** Delivery metadata read from webhook headers. */
|
|
4
|
+
interface WebhookHandlerContext<E extends WebhookEvent = WebhookEvent> {
|
|
5
|
+
deliveryId: string;
|
|
6
|
+
event: E;
|
|
7
|
+
timestamp: string;
|
|
8
|
+
}
|
|
9
|
+
/** Handler function for a specific webhook event */
|
|
10
|
+
type WebhookEventHandler<E extends WebhookEvent> = (payload: RuntimeWebhookPayload<E>, context: WebhookHandlerContext<E>) => Promise<void> | void;
|
|
11
|
+
/** Map of event names to handler functions */
|
|
12
|
+
type WebhookHandlers = {
|
|
13
|
+
[E in WebhookEvent]?: WebhookEventHandler<E>;
|
|
14
|
+
};
|
|
15
|
+
/** Options for creating a webhook handler */
|
|
16
|
+
interface WebhookHandlerOptions {
|
|
17
|
+
/** The webhook signing secret */
|
|
18
|
+
secret: string;
|
|
19
|
+
/** Event handler map */
|
|
20
|
+
handlers: WebhookHandlers;
|
|
21
|
+
/** Called when an event has no registered handler */
|
|
22
|
+
onUnhandledEvent?: (event: WebhookEvent, payload: RuntimeWebhookPayload, context: WebhookHandlerContext) => Promise<void> | void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type { WebhookHandlerOptions as W, WebhookEventHandler as a, WebhookHandlerContext as b, WebhookHandlers as c };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { W as WebhookEvent, R as RuntimeWebhookPayload } from './types-Bo9GbqrM.js';
|
|
2
|
+
|
|
3
|
+
/** Delivery metadata read from webhook headers. */
|
|
4
|
+
interface WebhookHandlerContext<E extends WebhookEvent = WebhookEvent> {
|
|
5
|
+
deliveryId: string;
|
|
6
|
+
event: E;
|
|
7
|
+
timestamp: string;
|
|
8
|
+
}
|
|
9
|
+
/** Handler function for a specific webhook event */
|
|
10
|
+
type WebhookEventHandler<E extends WebhookEvent> = (payload: RuntimeWebhookPayload<E>, context: WebhookHandlerContext<E>) => Promise<void> | void;
|
|
11
|
+
/** Map of event names to handler functions */
|
|
12
|
+
type WebhookHandlers = {
|
|
13
|
+
[E in WebhookEvent]?: WebhookEventHandler<E>;
|
|
14
|
+
};
|
|
15
|
+
/** Options for creating a webhook handler */
|
|
16
|
+
interface WebhookHandlerOptions {
|
|
17
|
+
/** The webhook signing secret */
|
|
18
|
+
secret: string;
|
|
19
|
+
/** Event handler map */
|
|
20
|
+
handlers: WebhookHandlers;
|
|
21
|
+
/** Called when an event has no registered handler */
|
|
22
|
+
onUnhandledEvent?: (event: WebhookEvent, payload: RuntimeWebhookPayload, context: WebhookHandlerContext) => Promise<void> | void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type { WebhookHandlerOptions as W, WebhookEventHandler as a, WebhookHandlerContext as b, WebhookHandlers as c };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { PublicWebhookEvent } from '@whatalo/protocol/events';
|
|
2
|
+
import { O as Order, a as Product, f as Customer } from './types-B8BAV362.js';
|
|
3
|
+
|
|
4
|
+
declare const WEBHOOK_EVENTS: {
|
|
5
|
+
readonly ORDER_CREATED: "order.created";
|
|
6
|
+
readonly ORDER_UPDATED: "order.updated";
|
|
7
|
+
readonly ORDER_CANCELLED: "order.cancelled";
|
|
8
|
+
readonly ORDER_COMPLETED: "order.completed";
|
|
9
|
+
readonly PRODUCT_CREATED: "product.created";
|
|
10
|
+
readonly PRODUCT_UPDATED: "product.updated";
|
|
11
|
+
readonly PRODUCT_DELETED: "product.deleted";
|
|
12
|
+
readonly CUSTOMER_CREATED: "customer.created";
|
|
13
|
+
readonly CUSTOMER_UPDATED: "customer.updated";
|
|
14
|
+
readonly CHECKOUT_COMPLETED: "checkout.completed";
|
|
15
|
+
};
|
|
16
|
+
/** All supported webhook event types */
|
|
17
|
+
type WebhookEvent = PublicWebhookEvent;
|
|
18
|
+
/** Validate an event header against the public runtime webhook event set. */
|
|
19
|
+
declare function isWebhookEvent(value: string): value is WebhookEvent;
|
|
20
|
+
interface OrderCreatedWebhookData {
|
|
21
|
+
order: Order;
|
|
22
|
+
}
|
|
23
|
+
interface OrderUpdatedWebhookData {
|
|
24
|
+
order: Order;
|
|
25
|
+
changes: string[];
|
|
26
|
+
}
|
|
27
|
+
interface OrderCancelledWebhookData {
|
|
28
|
+
orderId: string;
|
|
29
|
+
reason?: string;
|
|
30
|
+
}
|
|
31
|
+
interface OrderCompletedWebhookData {
|
|
32
|
+
order: Order;
|
|
33
|
+
}
|
|
34
|
+
interface ProductCreatedWebhookData {
|
|
35
|
+
product: Product;
|
|
36
|
+
}
|
|
37
|
+
interface ProductUpdatedWebhookData {
|
|
38
|
+
product: Product;
|
|
39
|
+
changes: string[];
|
|
40
|
+
}
|
|
41
|
+
interface ProductDeletedWebhookProduct {
|
|
42
|
+
id: string;
|
|
43
|
+
}
|
|
44
|
+
interface ProductDeletedWebhookData {
|
|
45
|
+
product: ProductDeletedWebhookProduct;
|
|
46
|
+
}
|
|
47
|
+
interface CustomerCreatedWebhookData {
|
|
48
|
+
customer: Customer;
|
|
49
|
+
}
|
|
50
|
+
interface CustomerUpdatedWebhookData {
|
|
51
|
+
customer: Customer;
|
|
52
|
+
changes: string[];
|
|
53
|
+
}
|
|
54
|
+
interface CheckoutCompletedWebhookData {
|
|
55
|
+
order: RuntimeWebhookOrder;
|
|
56
|
+
customer: RuntimeWebhookCustomer;
|
|
57
|
+
store: RuntimeWebhookStore;
|
|
58
|
+
}
|
|
59
|
+
/** Map of webhook events to their payload data shapes */
|
|
60
|
+
interface WebhookEventData {
|
|
61
|
+
"order.created": OrderCreatedWebhookData;
|
|
62
|
+
"order.updated": OrderUpdatedWebhookData;
|
|
63
|
+
"order.cancelled": OrderCancelledWebhookData;
|
|
64
|
+
"order.completed": OrderCompletedWebhookData;
|
|
65
|
+
"product.created": ProductCreatedWebhookData;
|
|
66
|
+
"product.updated": ProductUpdatedWebhookData;
|
|
67
|
+
"product.deleted": ProductDeletedWebhookData;
|
|
68
|
+
"customer.created": CustomerCreatedWebhookData;
|
|
69
|
+
"customer.updated": CustomerUpdatedWebhookData;
|
|
70
|
+
"checkout.completed": CheckoutCompletedWebhookData;
|
|
71
|
+
}
|
|
72
|
+
/** Legacy envelope payload. Runtime webhook deliveries do not include the event field in the body. */
|
|
73
|
+
interface WebhookPayload<E extends WebhookEvent = WebhookEvent> {
|
|
74
|
+
event: E;
|
|
75
|
+
timestamp: string;
|
|
76
|
+
storeId: string;
|
|
77
|
+
data: WebhookEventData[E];
|
|
78
|
+
}
|
|
79
|
+
interface RuntimeWebhookStore {
|
|
80
|
+
id: string;
|
|
81
|
+
name?: string;
|
|
82
|
+
timezone: string;
|
|
83
|
+
}
|
|
84
|
+
interface RuntimeWebhookBasePayload {
|
|
85
|
+
event_id?: string;
|
|
86
|
+
occurred_at?: string;
|
|
87
|
+
store: RuntimeWebhookStore;
|
|
88
|
+
}
|
|
89
|
+
interface RuntimeWebhookOrder {
|
|
90
|
+
id: string;
|
|
91
|
+
[key: string]: unknown;
|
|
92
|
+
}
|
|
93
|
+
interface RuntimeWebhookProduct {
|
|
94
|
+
id: string;
|
|
95
|
+
[key: string]: unknown;
|
|
96
|
+
}
|
|
97
|
+
interface RuntimeWebhookCustomer {
|
|
98
|
+
id: string;
|
|
99
|
+
[key: string]: unknown;
|
|
100
|
+
}
|
|
101
|
+
interface RuntimeOrderWebhookPayload extends RuntimeWebhookBasePayload {
|
|
102
|
+
order: RuntimeWebhookOrder;
|
|
103
|
+
customer?: RuntimeWebhookCustomer;
|
|
104
|
+
}
|
|
105
|
+
interface RuntimeProductWebhookPayload extends RuntimeWebhookBasePayload {
|
|
106
|
+
product: RuntimeWebhookProduct;
|
|
107
|
+
}
|
|
108
|
+
interface RuntimeCustomerWebhookPayload extends RuntimeWebhookBasePayload {
|
|
109
|
+
customer: RuntimeWebhookCustomer;
|
|
110
|
+
}
|
|
111
|
+
interface RuntimeCheckoutCompletedWebhookPayload extends RuntimeWebhookBasePayload {
|
|
112
|
+
order: RuntimeWebhookOrder;
|
|
113
|
+
customer: RuntimeWebhookCustomer;
|
|
114
|
+
store: RuntimeWebhookStore;
|
|
115
|
+
}
|
|
116
|
+
interface RuntimeWebhookEventData {
|
|
117
|
+
"order.created": RuntimeOrderWebhookPayload;
|
|
118
|
+
"order.updated": RuntimeOrderWebhookPayload;
|
|
119
|
+
"order.cancelled": RuntimeOrderWebhookPayload;
|
|
120
|
+
"order.completed": RuntimeOrderWebhookPayload;
|
|
121
|
+
"product.created": RuntimeProductWebhookPayload;
|
|
122
|
+
"product.updated": RuntimeProductWebhookPayload;
|
|
123
|
+
"product.deleted": RuntimeProductWebhookPayload;
|
|
124
|
+
"customer.created": RuntimeCustomerWebhookPayload;
|
|
125
|
+
"customer.updated": RuntimeCustomerWebhookPayload;
|
|
126
|
+
"checkout.completed": RuntimeCheckoutCompletedWebhookPayload;
|
|
127
|
+
}
|
|
128
|
+
/** Runtime body delivered to subscriber endpoints. The event comes from X-Webhook-Event. */
|
|
129
|
+
type RuntimeWebhookPayload<E extends WebhookEvent = WebhookEvent> = RuntimeWebhookEventData[E];
|
|
130
|
+
/** Validate the minimum runtime payload shape before invoking typed handlers. */
|
|
131
|
+
declare function isRuntimeWebhookPayload<E extends WebhookEvent>(value: unknown, event: E): value is RuntimeWebhookPayload<E>;
|
|
132
|
+
|
|
133
|
+
export { type RuntimeWebhookPayload as R, type WebhookEvent as W, WEBHOOK_EVENTS as a, isWebhookEvent as b, type WebhookEventData as c, type WebhookPayload as d, type RuntimeWebhookBasePayload as e, type RuntimeWebhookEventData as f, type RuntimeWebhookStore as g, isRuntimeWebhookPayload as i };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { PublicWebhookEvent } from '@whatalo/protocol/events';
|
|
2
|
+
import { O as Order, a as Product, f as Customer } from './types-B8BAV362.cjs';
|
|
3
|
+
|
|
4
|
+
declare const WEBHOOK_EVENTS: {
|
|
5
|
+
readonly ORDER_CREATED: "order.created";
|
|
6
|
+
readonly ORDER_UPDATED: "order.updated";
|
|
7
|
+
readonly ORDER_CANCELLED: "order.cancelled";
|
|
8
|
+
readonly ORDER_COMPLETED: "order.completed";
|
|
9
|
+
readonly PRODUCT_CREATED: "product.created";
|
|
10
|
+
readonly PRODUCT_UPDATED: "product.updated";
|
|
11
|
+
readonly PRODUCT_DELETED: "product.deleted";
|
|
12
|
+
readonly CUSTOMER_CREATED: "customer.created";
|
|
13
|
+
readonly CUSTOMER_UPDATED: "customer.updated";
|
|
14
|
+
readonly CHECKOUT_COMPLETED: "checkout.completed";
|
|
15
|
+
};
|
|
16
|
+
/** All supported webhook event types */
|
|
17
|
+
type WebhookEvent = PublicWebhookEvent;
|
|
18
|
+
/** Validate an event header against the public runtime webhook event set. */
|
|
19
|
+
declare function isWebhookEvent(value: string): value is WebhookEvent;
|
|
20
|
+
interface OrderCreatedWebhookData {
|
|
21
|
+
order: Order;
|
|
22
|
+
}
|
|
23
|
+
interface OrderUpdatedWebhookData {
|
|
24
|
+
order: Order;
|
|
25
|
+
changes: string[];
|
|
26
|
+
}
|
|
27
|
+
interface OrderCancelledWebhookData {
|
|
28
|
+
orderId: string;
|
|
29
|
+
reason?: string;
|
|
30
|
+
}
|
|
31
|
+
interface OrderCompletedWebhookData {
|
|
32
|
+
order: Order;
|
|
33
|
+
}
|
|
34
|
+
interface ProductCreatedWebhookData {
|
|
35
|
+
product: Product;
|
|
36
|
+
}
|
|
37
|
+
interface ProductUpdatedWebhookData {
|
|
38
|
+
product: Product;
|
|
39
|
+
changes: string[];
|
|
40
|
+
}
|
|
41
|
+
interface ProductDeletedWebhookProduct {
|
|
42
|
+
id: string;
|
|
43
|
+
}
|
|
44
|
+
interface ProductDeletedWebhookData {
|
|
45
|
+
product: ProductDeletedWebhookProduct;
|
|
46
|
+
}
|
|
47
|
+
interface CustomerCreatedWebhookData {
|
|
48
|
+
customer: Customer;
|
|
49
|
+
}
|
|
50
|
+
interface CustomerUpdatedWebhookData {
|
|
51
|
+
customer: Customer;
|
|
52
|
+
changes: string[];
|
|
53
|
+
}
|
|
54
|
+
interface CheckoutCompletedWebhookData {
|
|
55
|
+
order: RuntimeWebhookOrder;
|
|
56
|
+
customer: RuntimeWebhookCustomer;
|
|
57
|
+
store: RuntimeWebhookStore;
|
|
58
|
+
}
|
|
59
|
+
/** Map of webhook events to their payload data shapes */
|
|
60
|
+
interface WebhookEventData {
|
|
61
|
+
"order.created": OrderCreatedWebhookData;
|
|
62
|
+
"order.updated": OrderUpdatedWebhookData;
|
|
63
|
+
"order.cancelled": OrderCancelledWebhookData;
|
|
64
|
+
"order.completed": OrderCompletedWebhookData;
|
|
65
|
+
"product.created": ProductCreatedWebhookData;
|
|
66
|
+
"product.updated": ProductUpdatedWebhookData;
|
|
67
|
+
"product.deleted": ProductDeletedWebhookData;
|
|
68
|
+
"customer.created": CustomerCreatedWebhookData;
|
|
69
|
+
"customer.updated": CustomerUpdatedWebhookData;
|
|
70
|
+
"checkout.completed": CheckoutCompletedWebhookData;
|
|
71
|
+
}
|
|
72
|
+
/** Legacy envelope payload. Runtime webhook deliveries do not include the event field in the body. */
|
|
73
|
+
interface WebhookPayload<E extends WebhookEvent = WebhookEvent> {
|
|
74
|
+
event: E;
|
|
75
|
+
timestamp: string;
|
|
76
|
+
storeId: string;
|
|
77
|
+
data: WebhookEventData[E];
|
|
78
|
+
}
|
|
79
|
+
interface RuntimeWebhookStore {
|
|
80
|
+
id: string;
|
|
81
|
+
name?: string;
|
|
82
|
+
timezone: string;
|
|
83
|
+
}
|
|
84
|
+
interface RuntimeWebhookBasePayload {
|
|
85
|
+
event_id?: string;
|
|
86
|
+
occurred_at?: string;
|
|
87
|
+
store: RuntimeWebhookStore;
|
|
88
|
+
}
|
|
89
|
+
interface RuntimeWebhookOrder {
|
|
90
|
+
id: string;
|
|
91
|
+
[key: string]: unknown;
|
|
92
|
+
}
|
|
93
|
+
interface RuntimeWebhookProduct {
|
|
94
|
+
id: string;
|
|
95
|
+
[key: string]: unknown;
|
|
96
|
+
}
|
|
97
|
+
interface RuntimeWebhookCustomer {
|
|
98
|
+
id: string;
|
|
99
|
+
[key: string]: unknown;
|
|
100
|
+
}
|
|
101
|
+
interface RuntimeOrderWebhookPayload extends RuntimeWebhookBasePayload {
|
|
102
|
+
order: RuntimeWebhookOrder;
|
|
103
|
+
customer?: RuntimeWebhookCustomer;
|
|
104
|
+
}
|
|
105
|
+
interface RuntimeProductWebhookPayload extends RuntimeWebhookBasePayload {
|
|
106
|
+
product: RuntimeWebhookProduct;
|
|
107
|
+
}
|
|
108
|
+
interface RuntimeCustomerWebhookPayload extends RuntimeWebhookBasePayload {
|
|
109
|
+
customer: RuntimeWebhookCustomer;
|
|
110
|
+
}
|
|
111
|
+
interface RuntimeCheckoutCompletedWebhookPayload extends RuntimeWebhookBasePayload {
|
|
112
|
+
order: RuntimeWebhookOrder;
|
|
113
|
+
customer: RuntimeWebhookCustomer;
|
|
114
|
+
store: RuntimeWebhookStore;
|
|
115
|
+
}
|
|
116
|
+
interface RuntimeWebhookEventData {
|
|
117
|
+
"order.created": RuntimeOrderWebhookPayload;
|
|
118
|
+
"order.updated": RuntimeOrderWebhookPayload;
|
|
119
|
+
"order.cancelled": RuntimeOrderWebhookPayload;
|
|
120
|
+
"order.completed": RuntimeOrderWebhookPayload;
|
|
121
|
+
"product.created": RuntimeProductWebhookPayload;
|
|
122
|
+
"product.updated": RuntimeProductWebhookPayload;
|
|
123
|
+
"product.deleted": RuntimeProductWebhookPayload;
|
|
124
|
+
"customer.created": RuntimeCustomerWebhookPayload;
|
|
125
|
+
"customer.updated": RuntimeCustomerWebhookPayload;
|
|
126
|
+
"checkout.completed": RuntimeCheckoutCompletedWebhookPayload;
|
|
127
|
+
}
|
|
128
|
+
/** Runtime body delivered to subscriber endpoints. The event comes from X-Webhook-Event. */
|
|
129
|
+
type RuntimeWebhookPayload<E extends WebhookEvent = WebhookEvent> = RuntimeWebhookEventData[E];
|
|
130
|
+
/** Validate the minimum runtime payload shape before invoking typed handlers. */
|
|
131
|
+
declare function isRuntimeWebhookPayload<E extends WebhookEvent>(value: unknown, event: E): value is RuntimeWebhookPayload<E>;
|
|
132
|
+
|
|
133
|
+
export { type RuntimeWebhookPayload as R, type WebhookEvent as W, WEBHOOK_EVENTS as a, isWebhookEvent as b, type WebhookEventData as c, type WebhookPayload as d, type RuntimeWebhookBasePayload as e, type RuntimeWebhookEventData as f, type RuntimeWebhookStore as g, isRuntimeWebhookPayload as i };
|
package/dist/webhooks/index.cjs
CHANGED
|
@@ -20,20 +20,35 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/webhooks/index.ts
|
|
21
21
|
var webhooks_exports = {};
|
|
22
22
|
__export(webhooks_exports, {
|
|
23
|
+
WEBHOOK_EVENTS: () => WEBHOOK_EVENTS,
|
|
24
|
+
isRuntimeWebhookPayload: () => isRuntimeWebhookPayload,
|
|
25
|
+
isWebhookEvent: () => isWebhookEvent,
|
|
23
26
|
verifyWebhook: () => verifyWebhook
|
|
24
27
|
});
|
|
25
28
|
module.exports = __toCommonJS(webhooks_exports);
|
|
26
29
|
|
|
27
30
|
// src/webhooks/verify.ts
|
|
28
31
|
var import_node_crypto = require("crypto");
|
|
32
|
+
var DEFAULT_TOLERANCE_SECONDS = 300;
|
|
29
33
|
function verifyWebhook({
|
|
30
34
|
payload,
|
|
31
35
|
signature,
|
|
32
|
-
|
|
36
|
+
timestamp,
|
|
37
|
+
secret,
|
|
38
|
+
toleranceSeconds = DEFAULT_TOLERANCE_SECONDS,
|
|
39
|
+
currentTime = Math.floor(Date.now() / 1e3)
|
|
33
40
|
}) {
|
|
34
|
-
if (!payload || !signature || !secret) return false;
|
|
35
|
-
const
|
|
41
|
+
if (!payload || !signature || !secret || timestamp === "") return false;
|
|
42
|
+
const timestampString = String(timestamp);
|
|
43
|
+
const timestampValue = typeof timestamp === "number" ? timestamp : Number(timestamp);
|
|
44
|
+
if (!Number.isInteger(timestampValue)) return false;
|
|
45
|
+
if (!Number.isFinite(toleranceSeconds) || toleranceSeconds < 0) return false;
|
|
46
|
+
if (!Number.isFinite(currentTime)) return false;
|
|
47
|
+
const ageSeconds = Math.abs(currentTime - timestampValue);
|
|
48
|
+
if (ageSeconds > toleranceSeconds) return false;
|
|
49
|
+
const expected = (0, import_node_crypto.createHmac)("sha256", secret).update(`${timestampString}.${payload}`, "utf8").digest("hex");
|
|
36
50
|
if (expected.length !== signature.length) return false;
|
|
51
|
+
if (!/^[a-f0-9]+$/i.test(signature)) return false;
|
|
37
52
|
try {
|
|
38
53
|
return (0, import_node_crypto.timingSafeEqual)(
|
|
39
54
|
Buffer.from(expected, "hex"),
|
|
@@ -43,8 +58,68 @@ function verifyWebhook({
|
|
|
43
58
|
return false;
|
|
44
59
|
}
|
|
45
60
|
}
|
|
61
|
+
|
|
62
|
+
// src/webhooks/types.ts
|
|
63
|
+
var import_events = require("@whatalo/protocol/events");
|
|
64
|
+
var WEBHOOK_EVENTS = import_events.PUBLIC_WEBHOOK_EVENTS;
|
|
65
|
+
function isWebhookEvent(value) {
|
|
66
|
+
return (0, import_events.isPublicWebhookEvent)(value);
|
|
67
|
+
}
|
|
68
|
+
function isRecord(value) {
|
|
69
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
70
|
+
}
|
|
71
|
+
function hasStringId(value) {
|
|
72
|
+
return isRecord(value) && typeof value.id === "string";
|
|
73
|
+
}
|
|
74
|
+
function isOptionalString(value, key) {
|
|
75
|
+
return value[key] === void 0 || typeof value[key] === "string";
|
|
76
|
+
}
|
|
77
|
+
function isRuntimeWebhookStore(value) {
|
|
78
|
+
return isRecord(value) && typeof value.id === "string" && typeof value.timezone === "string" && (value.name === void 0 || typeof value.name === "string");
|
|
79
|
+
}
|
|
80
|
+
function hasValidBasePayloadFields(value) {
|
|
81
|
+
if (!isOptionalString(value, "event_id")) return false;
|
|
82
|
+
if (!isOptionalString(value, "occurred_at")) return false;
|
|
83
|
+
return isRuntimeWebhookStore(value.store);
|
|
84
|
+
}
|
|
85
|
+
function isRuntimeOrderWebhookPayload(value) {
|
|
86
|
+
return hasValidBasePayloadFields(value) && hasStringId(value.order) && (value.customer === void 0 || hasStringId(value.customer));
|
|
87
|
+
}
|
|
88
|
+
function isRuntimeProductWebhookPayload(value) {
|
|
89
|
+
return hasValidBasePayloadFields(value) && hasStringId(value.product);
|
|
90
|
+
}
|
|
91
|
+
function isRuntimeCustomerWebhookPayload(value) {
|
|
92
|
+
return hasValidBasePayloadFields(value) && hasStringId(value.customer);
|
|
93
|
+
}
|
|
94
|
+
function isRuntimeCheckoutCompletedWebhookPayload(value) {
|
|
95
|
+
return hasValidBasePayloadFields(value) && hasStringId(value.order) && hasStringId(value.customer) && isRuntimeWebhookStore(value.store);
|
|
96
|
+
}
|
|
97
|
+
function isRuntimeWebhookPayload(value, event) {
|
|
98
|
+
if (!isRecord(value)) return false;
|
|
99
|
+
switch (event) {
|
|
100
|
+
case WEBHOOK_EVENTS.ORDER_CREATED:
|
|
101
|
+
case WEBHOOK_EVENTS.ORDER_UPDATED:
|
|
102
|
+
case WEBHOOK_EVENTS.ORDER_CANCELLED:
|
|
103
|
+
case WEBHOOK_EVENTS.ORDER_COMPLETED:
|
|
104
|
+
return isRuntimeOrderWebhookPayload(value);
|
|
105
|
+
case WEBHOOK_EVENTS.PRODUCT_CREATED:
|
|
106
|
+
case WEBHOOK_EVENTS.PRODUCT_UPDATED:
|
|
107
|
+
case WEBHOOK_EVENTS.PRODUCT_DELETED:
|
|
108
|
+
return isRuntimeProductWebhookPayload(value);
|
|
109
|
+
case WEBHOOK_EVENTS.CUSTOMER_CREATED:
|
|
110
|
+
case WEBHOOK_EVENTS.CUSTOMER_UPDATED:
|
|
111
|
+
return isRuntimeCustomerWebhookPayload(value);
|
|
112
|
+
case WEBHOOK_EVENTS.CHECKOUT_COMPLETED:
|
|
113
|
+
return isRuntimeCheckoutCompletedWebhookPayload(value);
|
|
114
|
+
default:
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
46
118
|
// Annotate the CommonJS export names for ESM import in node:
|
|
47
119
|
0 && (module.exports = {
|
|
120
|
+
WEBHOOK_EVENTS,
|
|
121
|
+
isRuntimeWebhookPayload,
|
|
122
|
+
isWebhookEvent,
|
|
48
123
|
verifyWebhook
|
|
49
124
|
});
|
|
50
125
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/webhooks/index.ts","../../src/webhooks/verify.ts"],"sourcesContent":["export { verifyWebhook } from \"./verify.js\";\nexport type { VerifyWebhookParams } from \"./verify.js\";\nexport type {\n WebhookEvent,\n WebhookEventData,\n WebhookPayload,\n} from \"./types.js\";\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\nexport interface VerifyWebhookParams {\n /** Raw request body as a string (NOT parsed JSON) */\n payload: string;\n /** Value of the X-Webhook-Signature header */\n signature: string;\n /** App's webhook secret (provided during app installation) */\n secret: string;\n}\n\n/**\n * Verify webhook signature using HMAC-SHA256.\n * Uses timing-safe comparison to prevent timing attacks.\n */\nexport function verifyWebhook({\n payload,\n signature,\n secret,\n}: VerifyWebhookParams): boolean {\n if (!payload || !signature || !secret) return false;\n\n const expected = createHmac(\"sha256\", secret)\n .update(payload, \"utf8\")\n .digest(\"hex\");\n\n if (expected.length !== signature.length) return false;\n\n try {\n return timingSafeEqual(\n Buffer.from(expected, \"hex\"),\n Buffer.from(signature, \"hex\")\n );\n } catch {\n return false;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,yBAA4C;AAerC,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AACF,GAAiC;AAC/B,MAAI,CAAC,WAAW,CAAC,aAAa,CAAC,OAAQ,QAAO;AAE9C,QAAM,eAAW,+BAAW,UAAU,MAAM,EACzC,OAAO,SAAS,MAAM,EACtB,OAAO,KAAK;AAEf,MAAI,SAAS,WAAW,UAAU,OAAQ,QAAO;AAEjD,MAAI;AACF,eAAO;AAAA,MACL,OAAO,KAAK,UAAU,KAAK;AAAA,MAC3B,OAAO,KAAK,WAAW,KAAK;AAAA,IAC9B;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/webhooks/index.ts","../../src/webhooks/verify.ts","../../src/webhooks/types.ts"],"sourcesContent":["export { verifyWebhook } from \"./verify.js\";\nexport { WEBHOOK_EVENTS, isRuntimeWebhookPayload, isWebhookEvent } from \"./types.js\";\nexport type { VerifyWebhookParams } from \"./verify.js\";\nexport type {\n WebhookEvent,\n WebhookEventData,\n WebhookPayload,\n RuntimeWebhookBasePayload,\n RuntimeWebhookEventData,\n RuntimeWebhookPayload,\n RuntimeWebhookStore,\n} from \"./types.js\";\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\nconst DEFAULT_TOLERANCE_SECONDS = 300;\n\nexport interface VerifyWebhookParams {\n /** Raw request body as a string (NOT parsed JSON) */\n payload: string;\n /** Value of the X-Webhook-Signature header */\n signature: string;\n /** Value of the X-Webhook-Timestamp header (Unix epoch in seconds) */\n timestamp: string | number;\n /** App's webhook secret (provided during app installation) */\n secret: string;\n /** Maximum timestamp drift allowed in seconds. Defaults to 300 seconds. */\n toleranceSeconds?: number;\n /** Current Unix epoch time in seconds. Intended for tests. */\n currentTime?: number;\n}\n\n/**\n * Verify webhook signature using HMAC-SHA256 over `${timestamp}.${payload}`.\n * Uses timing-safe comparison to prevent timing attacks.\n */\nexport function verifyWebhook({\n payload,\n signature,\n timestamp,\n secret,\n toleranceSeconds = DEFAULT_TOLERANCE_SECONDS,\n currentTime = Math.floor(Date.now() / 1000),\n}: VerifyWebhookParams): boolean {\n if (!payload || !signature || !secret || timestamp === \"\") return false;\n\n const timestampString = String(timestamp);\n const timestampValue =\n typeof timestamp === \"number\" ? timestamp : Number(timestamp);\n\n if (!Number.isInteger(timestampValue)) return false;\n if (!Number.isFinite(toleranceSeconds) || toleranceSeconds < 0) return false;\n if (!Number.isFinite(currentTime)) return false;\n\n const ageSeconds = Math.abs(currentTime - timestampValue);\n if (ageSeconds > toleranceSeconds) return false;\n\n const expected = createHmac(\"sha256\", secret)\n .update(`${timestampString}.${payload}`, \"utf8\")\n .digest(\"hex\");\n\n if (expected.length !== signature.length) return false;\n if (!/^[a-f0-9]+$/i.test(signature)) return false;\n\n try {\n return timingSafeEqual(\n Buffer.from(expected, \"hex\"),\n Buffer.from(signature, \"hex\")\n );\n } catch {\n return false;\n }\n}\n","import {\n PUBLIC_WEBHOOK_EVENTS,\n isPublicWebhookEvent,\n type PublicWebhookEvent,\n} from \"@whatalo/protocol/events\";\nimport type { Product, Order, Customer } from \"../client/types.js\";\n\nexport const WEBHOOK_EVENTS = PUBLIC_WEBHOOK_EVENTS;\n\n/** All supported webhook event types */\nexport type WebhookEvent = PublicWebhookEvent;\n\n/** Validate an event header against the public runtime webhook event set. */\nexport function isWebhookEvent(value: string): value is WebhookEvent {\n return isPublicWebhookEvent(value);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction hasStringId(value: unknown): value is { id: string } {\n return isRecord(value) && typeof value.id === \"string\";\n}\n\nfunction isOptionalString(\n value: Record<string, unknown>,\n key: string\n): boolean {\n return value[key] === undefined || typeof value[key] === \"string\";\n}\n\nfunction isRuntimeWebhookStore(value: unknown): value is RuntimeWebhookStore {\n return (\n isRecord(value) &&\n typeof value.id === \"string\" &&\n typeof value.timezone === \"string\" &&\n (value.name === undefined || typeof value.name === \"string\")\n );\n}\n\nfunction hasValidBasePayloadFields(value: Record<string, unknown>): boolean {\n if (!isOptionalString(value, \"event_id\")) return false;\n if (!isOptionalString(value, \"occurred_at\")) return false;\n\n return isRuntimeWebhookStore(value.store);\n}\n\nfunction isRuntimeOrderWebhookPayload(value: Record<string, unknown>): boolean {\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.order) &&\n (value.customer === undefined || hasStringId(value.customer))\n );\n}\n\nfunction isRuntimeProductWebhookPayload(value: Record<string, unknown>): boolean {\n return hasValidBasePayloadFields(value) && hasStringId(value.product);\n}\n\nfunction isRuntimeCustomerWebhookPayload(value: Record<string, unknown>): boolean {\n return hasValidBasePayloadFields(value) && hasStringId(value.customer);\n}\n\nfunction isRuntimeCheckoutCompletedWebhookPayload(\n value: Record<string, unknown>\n): boolean {\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.order) &&\n hasStringId(value.customer) &&\n isRuntimeWebhookStore(value.store)\n );\n}\n\nexport interface OrderCreatedWebhookData {\n order: Order;\n}\n\nexport interface OrderUpdatedWebhookData {\n order: Order;\n changes: string[];\n}\n\nexport interface OrderCancelledWebhookData {\n orderId: string;\n reason?: string;\n}\n\nexport interface OrderCompletedWebhookData {\n order: Order;\n}\n\nexport interface ProductCreatedWebhookData {\n product: Product;\n}\n\nexport interface ProductUpdatedWebhookData {\n product: Product;\n changes: string[];\n}\n\nexport interface ProductDeletedWebhookProduct {\n id: string;\n}\n\nexport interface ProductDeletedWebhookData {\n product: ProductDeletedWebhookProduct;\n}\n\nexport interface CustomerCreatedWebhookData {\n customer: Customer;\n}\n\nexport interface CustomerUpdatedWebhookData {\n customer: Customer;\n changes: string[];\n}\n\nexport interface CheckoutCompletedWebhookData {\n order: RuntimeWebhookOrder;\n customer: RuntimeWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\n/** Map of webhook events to their payload data shapes */\nexport interface WebhookEventData {\n \"order.created\": OrderCreatedWebhookData;\n \"order.updated\": OrderUpdatedWebhookData;\n \"order.cancelled\": OrderCancelledWebhookData;\n \"order.completed\": OrderCompletedWebhookData;\n \"product.created\": ProductCreatedWebhookData;\n \"product.updated\": ProductUpdatedWebhookData;\n \"product.deleted\": ProductDeletedWebhookData;\n \"customer.created\": CustomerCreatedWebhookData;\n \"customer.updated\": CustomerUpdatedWebhookData;\n \"checkout.completed\": CheckoutCompletedWebhookData;\n}\n\n/** Legacy envelope payload. Runtime webhook deliveries do not include the event field in the body. */\nexport interface WebhookPayload<E extends WebhookEvent = WebhookEvent> {\n event: E;\n timestamp: string;\n storeId: string;\n data: WebhookEventData[E];\n}\n\nexport interface RuntimeWebhookStore {\n id: string;\n name?: string;\n timezone: string;\n}\n\nexport interface RuntimeWebhookBasePayload {\n event_id?: string;\n occurred_at?: string;\n store: RuntimeWebhookStore;\n}\n\nexport interface RuntimeWebhookOrder {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookProduct {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookCustomer {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeOrderWebhookPayload extends RuntimeWebhookBasePayload {\n order: RuntimeWebhookOrder;\n customer?: RuntimeWebhookCustomer;\n}\n\nexport interface RuntimeProductWebhookPayload extends RuntimeWebhookBasePayload {\n product: RuntimeWebhookProduct;\n}\n\nexport interface RuntimeCustomerWebhookPayload extends RuntimeWebhookBasePayload {\n customer: RuntimeWebhookCustomer;\n}\n\nexport interface RuntimeCheckoutCompletedWebhookPayload\n extends RuntimeWebhookBasePayload {\n order: RuntimeWebhookOrder;\n customer: RuntimeWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\nexport interface RuntimeWebhookEventData {\n \"order.created\": RuntimeOrderWebhookPayload;\n \"order.updated\": RuntimeOrderWebhookPayload;\n \"order.cancelled\": RuntimeOrderWebhookPayload;\n \"order.completed\": RuntimeOrderWebhookPayload;\n \"product.created\": RuntimeProductWebhookPayload;\n \"product.updated\": RuntimeProductWebhookPayload;\n \"product.deleted\": RuntimeProductWebhookPayload;\n \"customer.created\": RuntimeCustomerWebhookPayload;\n \"customer.updated\": RuntimeCustomerWebhookPayload;\n \"checkout.completed\": RuntimeCheckoutCompletedWebhookPayload;\n}\n\n/** Runtime body delivered to subscriber endpoints. The event comes from X-Webhook-Event. */\nexport type RuntimeWebhookPayload<E extends WebhookEvent = WebhookEvent> =\n RuntimeWebhookEventData[E];\n\n/** Validate the minimum runtime payload shape before invoking typed handlers. */\nexport function isRuntimeWebhookPayload<E extends WebhookEvent>(\n value: unknown,\n event: E\n): value is RuntimeWebhookPayload<E> {\n if (!isRecord(value)) return false;\n\n switch (event) {\n case WEBHOOK_EVENTS.ORDER_CREATED:\n case WEBHOOK_EVENTS.ORDER_UPDATED:\n case WEBHOOK_EVENTS.ORDER_CANCELLED:\n case WEBHOOK_EVENTS.ORDER_COMPLETED:\n return isRuntimeOrderWebhookPayload(value);\n case WEBHOOK_EVENTS.PRODUCT_CREATED:\n case WEBHOOK_EVENTS.PRODUCT_UPDATED:\n case WEBHOOK_EVENTS.PRODUCT_DELETED:\n return isRuntimeProductWebhookPayload(value);\n case WEBHOOK_EVENTS.CUSTOMER_CREATED:\n case WEBHOOK_EVENTS.CUSTOMER_UPDATED:\n return isRuntimeCustomerWebhookPayload(value);\n case WEBHOOK_EVENTS.CHECKOUT_COMPLETED:\n return isRuntimeCheckoutCompletedWebhookPayload(value);\n default:\n return false;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,18 +1,25 @@
|
|
|
1
|
-
export { W as WebhookEvent,
|
|
2
|
-
import '
|
|
1
|
+
export { e as RuntimeWebhookBasePayload, f as RuntimeWebhookEventData, R as RuntimeWebhookPayload, g as RuntimeWebhookStore, a as WEBHOOK_EVENTS, W as WebhookEvent, c as WebhookEventData, d as WebhookPayload, i as isRuntimeWebhookPayload, b as isWebhookEvent } from '../types-rA6kVlpV.cjs';
|
|
2
|
+
import '@whatalo/protocol/events';
|
|
3
|
+
import '../types-B8BAV362.cjs';
|
|
3
4
|
|
|
4
5
|
interface VerifyWebhookParams {
|
|
5
6
|
/** Raw request body as a string (NOT parsed JSON) */
|
|
6
7
|
payload: string;
|
|
7
8
|
/** Value of the X-Webhook-Signature header */
|
|
8
9
|
signature: string;
|
|
10
|
+
/** Value of the X-Webhook-Timestamp header (Unix epoch in seconds) */
|
|
11
|
+
timestamp: string | number;
|
|
9
12
|
/** App's webhook secret (provided during app installation) */
|
|
10
13
|
secret: string;
|
|
14
|
+
/** Maximum timestamp drift allowed in seconds. Defaults to 300 seconds. */
|
|
15
|
+
toleranceSeconds?: number;
|
|
16
|
+
/** Current Unix epoch time in seconds. Intended for tests. */
|
|
17
|
+
currentTime?: number;
|
|
11
18
|
}
|
|
12
19
|
/**
|
|
13
|
-
* Verify webhook signature using HMAC-SHA256
|
|
20
|
+
* Verify webhook signature using HMAC-SHA256 over `${timestamp}.${payload}`.
|
|
14
21
|
* Uses timing-safe comparison to prevent timing attacks.
|
|
15
22
|
*/
|
|
16
|
-
declare function verifyWebhook({ payload, signature, secret, }: VerifyWebhookParams): boolean;
|
|
23
|
+
declare function verifyWebhook({ payload, signature, timestamp, secret, toleranceSeconds, currentTime, }: VerifyWebhookParams): boolean;
|
|
17
24
|
|
|
18
25
|
export { type VerifyWebhookParams, verifyWebhook };
|
package/dist/webhooks/index.d.ts
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
|
-
export { W as WebhookEvent,
|
|
2
|
-
import '
|
|
1
|
+
export { e as RuntimeWebhookBasePayload, f as RuntimeWebhookEventData, R as RuntimeWebhookPayload, g as RuntimeWebhookStore, a as WEBHOOK_EVENTS, W as WebhookEvent, c as WebhookEventData, d as WebhookPayload, i as isRuntimeWebhookPayload, b as isWebhookEvent } from '../types-Bo9GbqrM.js';
|
|
2
|
+
import '@whatalo/protocol/events';
|
|
3
|
+
import '../types-B8BAV362.js';
|
|
3
4
|
|
|
4
5
|
interface VerifyWebhookParams {
|
|
5
6
|
/** Raw request body as a string (NOT parsed JSON) */
|
|
6
7
|
payload: string;
|
|
7
8
|
/** Value of the X-Webhook-Signature header */
|
|
8
9
|
signature: string;
|
|
10
|
+
/** Value of the X-Webhook-Timestamp header (Unix epoch in seconds) */
|
|
11
|
+
timestamp: string | number;
|
|
9
12
|
/** App's webhook secret (provided during app installation) */
|
|
10
13
|
secret: string;
|
|
14
|
+
/** Maximum timestamp drift allowed in seconds. Defaults to 300 seconds. */
|
|
15
|
+
toleranceSeconds?: number;
|
|
16
|
+
/** Current Unix epoch time in seconds. Intended for tests. */
|
|
17
|
+
currentTime?: number;
|
|
11
18
|
}
|
|
12
19
|
/**
|
|
13
|
-
* Verify webhook signature using HMAC-SHA256
|
|
20
|
+
* Verify webhook signature using HMAC-SHA256 over `${timestamp}.${payload}`.
|
|
14
21
|
* Uses timing-safe comparison to prevent timing attacks.
|
|
15
22
|
*/
|
|
16
|
-
declare function verifyWebhook({ payload, signature, secret, }: VerifyWebhookParams): boolean;
|
|
23
|
+
declare function verifyWebhook({ payload, signature, timestamp, secret, toleranceSeconds, currentTime, }: VerifyWebhookParams): boolean;
|
|
17
24
|
|
|
18
25
|
export { type VerifyWebhookParams, verifyWebhook };
|