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 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
- GetProductsResponse,
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": GetProductsResponse;
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
- on(eventType, handler) {
82
- this.slots.push({
83
- eventType,
84
- fn: handler,
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
- on(eventType, handler) {
121
- return new WebhookChain().on(eventType, handler);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "idosell",
3
- "version": "0.4.41",
3
+ "version": "0.4.43",
4
4
  "description": "Idosell 3 REST connector",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/gateways.d.ts",