idosell 0.4.41 → 0.4.43
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 +6 -0
- package/dist/app.d.ts +19 -3
- package/dist/webhooks.js +38 -9
- package/dist/webhooks.normalizer.js +2 -1
- package/package.json +1 -1
package/changelog.md
CHANGED
|
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.4.43] - 2025-06-07
|
|
9
|
+
### Changed
|
|
10
|
+
- Webhook now supports HMAC checking and groups of webhooks
|
|
11
|
+
- Added raw data to webhook headers and handlers
|
|
12
|
+
|
|
13
|
+
|
|
8
14
|
## [0.4.41] - 2025-06-07
|
|
9
15
|
### Added
|
|
10
16
|
- Added webook support
|
package/dist/app.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
2
|
+
SearchProductsResponse,
|
|
3
3
|
GetRmaResponse,
|
|
4
4
|
GetReturnsResponse,
|
|
5
5
|
GetOrdersResponse,
|
|
@@ -117,6 +117,7 @@ export interface IdosellErrorFaultStructure {
|
|
|
117
117
|
export interface NormalizedIaiRequest<T = unknown> {
|
|
118
118
|
headers: IaiWebhookHeaders;
|
|
119
119
|
body: T;
|
|
120
|
+
rawBody: string
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
export declare const WEBHOOK_OBJECT_TYPE: {
|
|
@@ -162,9 +163,9 @@ export declare const WEBHOOK_EVENT_TYPE: {
|
|
|
162
163
|
export type WebhookObjectType = typeof WEBHOOK_OBJECT_TYPE[keyof typeof WEBHOOK_OBJECT_TYPE];
|
|
163
164
|
export type WebhookEventType = typeof WEBHOOK_EVENT_TYPE[keyof typeof WEBHOOK_EVENT_TYPE];
|
|
164
165
|
|
|
165
|
-
interface ObjectBodyMap {
|
|
166
|
+
export interface ObjectBodyMap {
|
|
166
167
|
"client": GetClientsResponse;
|
|
167
|
-
"product":
|
|
168
|
+
"product": SearchProductsResponse;
|
|
168
169
|
"order": GetOrdersResponse;
|
|
169
170
|
"return": GetReturnsResponse;
|
|
170
171
|
"rma": GetRmaResponse;
|
|
@@ -214,16 +215,27 @@ export interface IaiWebhookHeaders {
|
|
|
214
215
|
panelId: number;
|
|
215
216
|
signature: string;
|
|
216
217
|
webhookTime: Date;
|
|
218
|
+
raw: Record<string, string>
|
|
217
219
|
}
|
|
218
220
|
|
|
219
221
|
export interface WebhookContext<E extends WebhookEventType> {
|
|
220
222
|
headers: IaiWebhookHeaders;
|
|
221
223
|
body: ObjectBodyMap[EventObjectMap[E]];
|
|
224
|
+
rawBody: string;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export interface WebhookObjectContext<O extends WebhookObjectType> {
|
|
228
|
+
headers: IaiWebhookHeaders;
|
|
229
|
+
body: ObjectBodyMap[O];
|
|
230
|
+
rawBody: string;
|
|
222
231
|
}
|
|
223
232
|
|
|
224
233
|
export type WebhookHandler<E extends WebhookEventType> =
|
|
225
234
|
(ctx: WebhookContext<E>) => Promise<void> | void;
|
|
226
235
|
|
|
236
|
+
export type WebhookObjectHandler<O extends WebhookObjectType> =
|
|
237
|
+
(ctx: WebhookObjectContext<O>) => Promise<void> | void;
|
|
238
|
+
|
|
227
239
|
export type HeaderValidator =
|
|
228
240
|
(headers: IaiWebhookHeaders) => Promise<boolean> | boolean;
|
|
229
241
|
|
|
@@ -234,13 +246,17 @@ export type DispatchResult =
|
|
|
234
246
|
|
|
235
247
|
export declare class WebhookChain {
|
|
236
248
|
validateHeaders(validator: HeaderValidator): this;
|
|
249
|
+
validateSignature(hmacKey: string): this;
|
|
237
250
|
on<E extends WebhookEventType>(eventType: E, handler: WebhookHandler<E>): this;
|
|
251
|
+
on<O extends WebhookObjectType>(objectType: O, handler: WebhookObjectHandler<O>): this;
|
|
238
252
|
handle(req: import("node:http").IncomingMessage | Request): Promise<DispatchResult>;
|
|
239
253
|
}
|
|
240
254
|
|
|
241
255
|
export type Webhooks = {
|
|
242
256
|
validateHeaders(validator: HeaderValidator): WebhookChain;
|
|
257
|
+
validateSignature(hmacKey: string): WebhookChain;
|
|
243
258
|
on<E extends WebhookEventType>(eventType: E, handler: WebhookHandler<E>): WebhookChain;
|
|
259
|
+
on<O extends WebhookObjectType>(objectType: O, handler: WebhookObjectHandler<O>): WebhookChain;
|
|
244
260
|
};
|
|
245
261
|
|
|
246
262
|
export {};
|
package/dist/webhooks.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* eslint-disable no-unused-vars */
|
|
2
2
|
import { normalizeIaiRequest, WebhookValidationError } from "./webhooks.normalizer.js";
|
|
3
|
+
import { createHmac } from "node:crypto";
|
|
3
4
|
// ─── Runtime constants ────────────────────────────────────────────────────────
|
|
4
5
|
export const WEBHOOK_OBJECT_TYPE = {
|
|
5
6
|
CLIENT: "client",
|
|
@@ -74,19 +75,37 @@ const EVENT_OBJECT_MAP = {
|
|
|
74
75
|
export class WebhookChain {
|
|
75
76
|
slots = [];
|
|
76
77
|
validator = null;
|
|
78
|
+
hmacKey;
|
|
77
79
|
validateHeaders(validator) {
|
|
78
80
|
this.validator = validator;
|
|
79
81
|
return this;
|
|
80
82
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Validate the request signature using HMAC SHA256.
|
|
85
|
+
* @param hmacKey Secret key used to compute the HMAC.
|
|
86
|
+
* @throws WebhookValidationError if the computed signature does not match the header.
|
|
87
|
+
*/
|
|
88
|
+
validateSignature(hmacKey) {
|
|
89
|
+
this.hmacKey = hmacKey;
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
on(eventTypeOrObject, handler) {
|
|
93
|
+
const isObjectType = Object.values(WEBHOOK_OBJECT_TYPE).includes(eventTypeOrObject);
|
|
94
|
+
if (isObjectType) {
|
|
95
|
+
const matchedEvents = Object.entries(EVENT_OBJECT_MAP)
|
|
96
|
+
.filter(([, obj]) => obj === eventTypeOrObject)
|
|
97
|
+
.map(([evt]) => evt);
|
|
98
|
+
for (const evt of matchedEvents) {
|
|
99
|
+
this.slots.push({ eventType: evt, fn: handler });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
this.slots.push({ eventType: eventTypeOrObject, fn: handler });
|
|
104
|
+
}
|
|
86
105
|
return this;
|
|
87
106
|
}
|
|
88
107
|
async handle(req) {
|
|
89
|
-
const { headers, body } = await normalizeIaiRequest(req);
|
|
108
|
+
const { headers, body, rawBody } = await normalizeIaiRequest(req);
|
|
90
109
|
if (this.validator !== null) {
|
|
91
110
|
let valid = false;
|
|
92
111
|
try {
|
|
@@ -99,6 +118,13 @@ export class WebhookChain {
|
|
|
99
118
|
return { matched: false, eventType: null, reason: "validation_failed" };
|
|
100
119
|
}
|
|
101
120
|
}
|
|
121
|
+
// Validate signature if a key was provided
|
|
122
|
+
if (this.hmacKey) {
|
|
123
|
+
const computed = createHmac("sha256", this.hmacKey).update(rawBody).digest("hex");
|
|
124
|
+
if (computed !== headers.signature) {
|
|
125
|
+
throw new WebhookValidationError("Invalid webhook signature", "x-iai-signature");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
102
128
|
const incomingEvent = headers.eventType;
|
|
103
129
|
const expectedObject = EVENT_OBJECT_MAP[incomingEvent];
|
|
104
130
|
if (expectedObject !== undefined && headers.objectType !== expectedObject) {
|
|
@@ -108,7 +134,7 @@ export class WebhookChain {
|
|
|
108
134
|
if (!slot) {
|
|
109
135
|
return { matched: false, eventType: incomingEvent };
|
|
110
136
|
}
|
|
111
|
-
await slot.fn({ headers, body });
|
|
137
|
+
await slot.fn({ headers, body, rawBody });
|
|
112
138
|
return { matched: true, eventType: slot.eventType };
|
|
113
139
|
}
|
|
114
140
|
}
|
|
@@ -117,8 +143,11 @@ export const webhooks = {
|
|
|
117
143
|
validateHeaders(validator) {
|
|
118
144
|
return new WebhookChain().validateHeaders(validator);
|
|
119
145
|
},
|
|
120
|
-
|
|
121
|
-
return new WebhookChain().
|
|
146
|
+
validateSignature(hmacKey) {
|
|
147
|
+
return new WebhookChain().validateSignature(hmacKey);
|
|
122
148
|
},
|
|
149
|
+
on: ((eventTypeOrObject, handler) => {
|
|
150
|
+
return new WebhookChain().on(eventTypeOrObject, handler);
|
|
151
|
+
}),
|
|
123
152
|
};
|
|
124
153
|
export default webhooks;
|
|
@@ -50,6 +50,7 @@ function extractHeaders(raw) {
|
|
|
50
50
|
panelId: parseIntHeader(raw, "x-iai-panel-id"),
|
|
51
51
|
signature: requireHeader(raw, "x-iai-signature"),
|
|
52
52
|
webhookTime: parseDateHeader(raw, "x-iai-webhook-time"),
|
|
53
|
+
raw
|
|
53
54
|
};
|
|
54
55
|
}
|
|
55
56
|
// ─── Type guard ──────────────────────────────────────────────────────────────
|
|
@@ -136,5 +137,5 @@ export async function normalizeIaiRequest(req) {
|
|
|
136
137
|
throw new WebhookValidationError("Request body must be a JSON object", "body");
|
|
137
138
|
}
|
|
138
139
|
const headers = extractHeaders(flatHeaders);
|
|
139
|
-
return { headers, body };
|
|
140
|
+
return { headers, body, rawBody };
|
|
140
141
|
}
|