waba-toolkit 0.1.2 → 0.2.0

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/index.js CHANGED
@@ -1,3 +1,42 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ WABAClient: () => WABAClient,
24
+ WABAError: () => WABAError,
25
+ WABAMediaError: () => WABAMediaError,
26
+ WABANetworkError: () => WABANetworkError,
27
+ WABASignatureError: () => WABASignatureError,
28
+ classifyMessage: () => classifyMessage,
29
+ classifyWebhook: () => classifyWebhook,
30
+ extractMediaId: () => extractMediaId,
31
+ getCallId: () => getCallId,
32
+ getContactInfo: () => getContactInfo,
33
+ getMessageId: () => getMessageId,
34
+ getMessageTimestamp: () => getMessageTimestamp,
35
+ isMediaMessage: () => isMediaMessage,
36
+ verifyWebhookSignature: () => verifyWebhookSignature
37
+ });
38
+ module.exports = __toCommonJS(index_exports);
39
+
1
40
  // src/errors.ts
2
41
  var WABAError = class extends Error {
3
42
  constructor(message, code, details) {
@@ -34,7 +73,7 @@ var WABASignatureError = class extends WABAError {
34
73
  };
35
74
 
36
75
  // src/verify.ts
37
- import { createHmac, timingSafeEqual } from "crypto";
76
+ var import_node_crypto = require("crypto");
38
77
  function verifyWebhookSignature(options) {
39
78
  const { signature, rawBody, appSecret } = options;
40
79
  if (!appSecret) {
@@ -44,7 +83,7 @@ function verifyWebhookSignature(options) {
44
83
  return false;
45
84
  }
46
85
  const signatureHash = signature.startsWith("sha256=") ? signature.slice(7) : signature;
47
- const hmac = createHmac("sha256", appSecret);
86
+ const hmac = (0, import_node_crypto.createHmac)("sha256", appSecret);
48
87
  const bodyBuffer = typeof rawBody === "string" ? Buffer.from(rawBody, "utf-8") : rawBody;
49
88
  const expectedHash = hmac.update(bodyBuffer).digest("hex");
50
89
  const signatureBuffer = Buffer.from(signatureHash, "utf-8");
@@ -52,7 +91,7 @@ function verifyWebhookSignature(options) {
52
91
  if (signatureBuffer.length !== expectedBuffer.length) {
53
92
  return false;
54
93
  }
55
- return timingSafeEqual(signatureBuffer, expectedBuffer);
94
+ return (0, import_node_crypto.timingSafeEqual)(signatureBuffer, expectedBuffer);
56
95
  }
57
96
 
58
97
  // src/client.ts
@@ -249,7 +288,7 @@ function getContactInfo(webhook) {
249
288
  const change = entry?.changes?.[0];
250
289
  const value = change?.value;
251
290
  if (!value || !("contacts" in value) || !value.contacts?.length) {
252
- return void 0;
291
+ return null;
253
292
  }
254
293
  const contact = value.contacts[0];
255
294
  const metadata = value.metadata;
@@ -263,7 +302,38 @@ function getMessageTimestamp(message) {
263
302
  const epochSeconds = parseInt(message.timestamp, 10);
264
303
  return new Date(epochSeconds * 1e3);
265
304
  }
266
- export {
305
+ function getMessageId(webhook) {
306
+ const entry = webhook.entry?.[0];
307
+ const change = entry?.changes?.[0];
308
+ const value = change?.value;
309
+ if (!value) {
310
+ return null;
311
+ }
312
+ if ("messages" in value) {
313
+ const messageValue = value;
314
+ return messageValue.messages?.[0]?.id ?? null;
315
+ }
316
+ if ("statuses" in value) {
317
+ const statusValue = value;
318
+ return statusValue.statuses?.[0]?.id ?? null;
319
+ }
320
+ return null;
321
+ }
322
+ function getCallId(webhook) {
323
+ const entry = webhook.entry?.[0];
324
+ const change = entry?.changes?.[0];
325
+ const value = change?.value;
326
+ if (!value) {
327
+ return null;
328
+ }
329
+ if ("calls" in value) {
330
+ const callValue = value;
331
+ return callValue.calls?.[0]?.id ?? null;
332
+ }
333
+ return null;
334
+ }
335
+ // Annotate the CommonJS export names for ESM import in node:
336
+ 0 && (module.exports = {
267
337
  WABAClient,
268
338
  WABAError,
269
339
  WABAMediaError,
@@ -272,9 +342,11 @@ export {
272
342
  classifyMessage,
273
343
  classifyWebhook,
274
344
  extractMediaId,
345
+ getCallId,
275
346
  getContactInfo,
347
+ getMessageId,
276
348
  getMessageTimestamp,
277
349
  isMediaMessage,
278
350
  verifyWebhookSignature
279
- };
351
+ });
280
352
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/verify.ts","../src/client.ts","../src/webhooks/classify.ts","../src/webhooks/messages.ts","../src/helpers.ts"],"sourcesContent":["/** Base error class for all WABA errors */\nexport class WABAError extends Error {\n constructor(\n message: string,\n public readonly code?: number,\n public readonly details?: unknown\n ) {\n super(message);\n this.name = 'WABAError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Error for media-related failures (404, access denied) */\nexport class WABAMediaError extends WABAError {\n constructor(\n message: string,\n public readonly mediaId: string,\n code?: number\n ) {\n super(message, code);\n this.name = 'WABAMediaError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Error for network/connection failures */\nexport class WABANetworkError extends WABAError {\n override readonly cause?: Error;\n\n constructor(message: string, cause?: Error) {\n super(message);\n this.name = 'WABANetworkError';\n this.cause = cause;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Error for invalid webhook signatures */\nexport class WABASignatureError extends WABAError {\n constructor(message: string = 'Invalid webhook signature') {\n super(message);\n this.name = 'WABASignatureError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import { createHmac, timingSafeEqual } from 'node:crypto';\nimport { WABASignatureError } from './errors.js';\n\nexport interface VerifyWebhookSignatureOptions {\n /** X-Hub-Signature-256 header value */\n signature: string | undefined;\n /** Raw request body (NOT parsed JSON) */\n rawBody: Buffer | string;\n /** Meta App Secret */\n appSecret: string | undefined;\n}\n\n/**\n * Verifies webhook signature using HMAC-SHA256.\n * Uses timing-safe comparison to prevent timing attacks.\n *\n * @throws {WABASignatureError} If appSecret is not provided\n * @returns true if signature is valid, false otherwise\n */\nexport function verifyWebhookSignature(\n options: VerifyWebhookSignatureOptions\n): boolean {\n const { signature, rawBody, appSecret } = options;\n\n if (!appSecret) {\n throw new WABASignatureError('appSecret is required for webhook verification');\n }\n\n if (!signature) {\n return false;\n }\n\n // Remove 'sha256=' prefix if present\n const signatureHash = signature.startsWith('sha256=')\n ? signature.slice(7)\n : signature;\n\n // Compute expected signature\n const hmac = createHmac('sha256', appSecret);\n const bodyBuffer =\n typeof rawBody === 'string' ? Buffer.from(rawBody, 'utf-8') : rawBody;\n const expectedHash = hmac.update(bodyBuffer).digest('hex');\n\n // Convert to buffers for timing-safe comparison\n const signatureBuffer = Buffer.from(signatureHash, 'utf-8');\n const expectedBuffer = Buffer.from(expectedHash, 'utf-8');\n\n // Ensure same length before comparison\n if (signatureBuffer.length !== expectedBuffer.length) {\n return false;\n }\n\n return timingSafeEqual(signatureBuffer, expectedBuffer);\n}\n","import type { WABAClientOptions, GetMediaOptions } from './types/client.js';\nimport type {\n MediaMetadata,\n MediaStreamResult,\n MediaBufferResult,\n RawMediaResponse,\n} from './types/media.js';\nimport { WABAMediaError, WABANetworkError, WABASignatureError } from './errors.js';\nimport { verifyWebhookSignature } from './verify.js';\n\nconst DEFAULT_API_VERSION = 'v22.0';\nconst DEFAULT_BASE_URL = 'https://graph.facebook.com';\n\n/**\n * Client for WhatsApp Business API media operations.\n */\nexport class WABAClient {\n private readonly accessToken: string;\n private readonly appSecret?: string;\n private readonly apiVersion: string;\n private readonly baseUrl: string;\n\n constructor(options: WABAClientOptions) {\n this.accessToken = options.accessToken;\n this.appSecret = options.appSecret;\n this.apiVersion = options.apiVersion ?? DEFAULT_API_VERSION;\n this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n }\n\n /**\n * Verifies webhook signature using HMAC-SHA256.\n * Requires appSecret to be set in constructor options.\n *\n * @throws {WABASignatureError} If appSecret was not provided in constructor\n * @returns true if signature is valid, false otherwise\n */\n verifyWebhook(signature: string | undefined, rawBody: Buffer | string): boolean {\n if (!this.appSecret) {\n throw new WABASignatureError(\n 'appSecret is required for webhook verification. Pass it in WABAClientOptions.'\n );\n }\n return verifyWebhookSignature({ signature, rawBody, appSecret: this.appSecret });\n }\n\n /**\n * Fetches media by ID from WhatsApp Business API.\n *\n * Step 1: GET /{apiVersion}/{mediaId} → retrieves temporary URL + metadata\n * Step 2: GET {temporaryUrl} → downloads binary content\n *\n * @throws {WABAMediaError} - Media not found (404) or access denied\n * @throws {WABANetworkError} - Network/connection failures\n */\n async getMedia(mediaId: string): Promise<MediaStreamResult>;\n async getMedia(\n mediaId: string,\n options: { asBuffer: true }\n ): Promise<MediaBufferResult>;\n async getMedia(\n mediaId: string,\n options?: GetMediaOptions\n ): Promise<MediaStreamResult | MediaBufferResult>;\n async getMedia(\n mediaId: string,\n options?: GetMediaOptions\n ): Promise<MediaStreamResult | MediaBufferResult> {\n // Step 1: Get media metadata and temporary URL\n const metadata = await this.fetchMediaMetadata(mediaId);\n\n // Step 2: Download the actual media\n const response = await this.downloadMedia(metadata.url, mediaId);\n\n if (options?.asBuffer) {\n const buffer = await response.arrayBuffer();\n return {\n ...metadata,\n buffer,\n };\n }\n\n if (!response.body) {\n throw new WABAMediaError('Response body is null', mediaId);\n }\n\n return {\n ...metadata,\n stream: response.body,\n };\n }\n\n private async fetchMediaMetadata(mediaId: string): Promise<MediaMetadata> {\n const url = `${this.baseUrl}/${this.apiVersion}/${mediaId}`;\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${this.accessToken}`,\n },\n });\n } catch (error) {\n throw new WABANetworkError(\n `Failed to fetch media metadata: ${error instanceof Error ? error.message : 'Unknown error'}`,\n error instanceof Error ? error : undefined\n );\n }\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => 'Unknown error');\n throw new WABAMediaError(\n `Failed to fetch media metadata: ${response.status} ${errorBody}`,\n mediaId,\n response.status\n );\n }\n\n const data = (await response.json()) as RawMediaResponse;\n\n // Normalize snake_case to camelCase\n return {\n id: data.id,\n mimeType: data.mime_type,\n sha256: data.sha256,\n fileSize: parseInt(data.file_size, 10),\n url: data.url,\n };\n }\n\n private async downloadMedia(url: string, mediaId: string): Promise<Response> {\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${this.accessToken}`,\n },\n });\n } catch (error) {\n throw new WABANetworkError(\n `Failed to download media: ${error instanceof Error ? error.message : 'Unknown error'}`,\n error instanceof Error ? error : undefined\n );\n }\n\n if (!response.ok) {\n throw new WABAMediaError(\n `Failed to download media: ${response.status}`,\n mediaId,\n response.status\n );\n }\n\n return response;\n }\n}\n","import type {\n WebhookPayload,\n WebhookClassification,\n MessageWebhookValue,\n StatusWebhookValue,\n CallWebhookValue,\n} from '../types/webhooks.js';\n\n/**\n * Classifies a webhook payload into its type.\n * Returns a discriminated union for type-safe handling.\n */\nexport function classifyWebhook(payload: WebhookPayload): WebhookClassification {\n const entry = payload.entry?.[0];\n const change = entry?.changes?.[0];\n const value = change?.value;\n\n if (!value) {\n return { type: 'unknown', payload };\n }\n\n // Check for calls array (call webhooks)\n if ('calls' in value && Array.isArray(value.calls)) {\n return { type: 'call', payload: value as CallWebhookValue };\n }\n\n // Check for statuses array (status webhooks)\n if ('statuses' in value && Array.isArray(value.statuses)) {\n return { type: 'status', payload: value as StatusWebhookValue };\n }\n\n // Check for messages array (message webhooks)\n if ('messages' in value && Array.isArray(value.messages)) {\n return { type: 'message', payload: value as MessageWebhookValue };\n }\n\n // Check for errors without messages (error webhook)\n if ('errors' in value && Array.isArray(value.errors)) {\n return { type: 'message', payload: value as MessageWebhookValue };\n }\n\n return { type: 'unknown', payload };\n}\n","import type {\n IncomingMessage,\n MessageClassification,\n TextMessage,\n ImageMessage,\n AudioMessage,\n VideoMessage,\n DocumentMessage,\n StickerMessage,\n LocationMessage,\n ContactsMessage,\n InteractiveMessage,\n ReactionMessage,\n ButtonMessage,\n OrderMessage,\n SystemMessage,\n ReferralMessage,\n UnsupportedMessage,\n} from '../types/messages.js';\n\n/**\n * Classifies an incoming message by its type.\n * Returns a discriminated union for type-safe handling.\n */\nexport function classifyMessage(message: IncomingMessage): MessageClassification {\n switch (message.type) {\n case 'text':\n return { type: 'text', message: message as TextMessage };\n case 'image':\n return { type: 'image', message: message as ImageMessage };\n case 'audio':\n return { type: 'audio', message: message as AudioMessage };\n case 'video':\n return { type: 'video', message: message as VideoMessage };\n case 'document':\n return { type: 'document', message: message as DocumentMessage };\n case 'sticker':\n return { type: 'sticker', message: message as StickerMessage };\n case 'location':\n return { type: 'location', message: message as LocationMessage };\n case 'contacts':\n return { type: 'contacts', message: message as ContactsMessage };\n case 'interactive':\n return { type: 'interactive', message: message as InteractiveMessage };\n case 'reaction':\n return { type: 'reaction', message: message as ReactionMessage };\n case 'button':\n return { type: 'button', message: message as ButtonMessage };\n case 'order':\n return { type: 'order', message: message as OrderMessage };\n case 'system':\n return { type: 'system', message: message as SystemMessage };\n case 'referral':\n return { type: 'referral', message: message as ReferralMessage };\n default:\n return { type: 'unsupported', message: message as UnsupportedMessage };\n }\n}\n","import type {\n IncomingMessage,\n MediaMessage,\n ImageMessage,\n AudioMessage,\n VideoMessage,\n DocumentMessage,\n StickerMessage,\n} from './types/messages.js';\nimport type { WebhookPayload } from './types/webhooks.js';\n\nconst MEDIA_TYPES = new Set(['image', 'audio', 'video', 'document', 'sticker']);\n\n/**\n * Type guard: returns true if message contains downloadable media.\n * Narrows type to messages with image/audio/video/document/sticker.\n */\nexport function isMediaMessage(message: IncomingMessage): message is MediaMessage {\n return MEDIA_TYPES.has(message.type);\n}\n\n/**\n * Extracts media ID from any media message type.\n * Returns undefined if message has no media.\n */\nexport function extractMediaId(message: IncomingMessage): string | undefined {\n if (!isMediaMessage(message)) {\n return undefined;\n }\n\n switch (message.type) {\n case 'image':\n return (message as ImageMessage).image.id;\n case 'audio':\n return (message as AudioMessage).audio.id;\n case 'video':\n return (message as VideoMessage).video.id;\n case 'document':\n return (message as DocumentMessage).document.id;\n case 'sticker':\n return (message as StickerMessage).sticker.id;\n default:\n return undefined;\n }\n}\n\nexport interface ContactInfo {\n waId: string;\n profileName: string | undefined;\n phoneNumberId: string;\n}\n\n/**\n * Extracts sender info from webhook payload.\n * Returns undefined if the webhook doesn't contain message contact info.\n */\nexport function getContactInfo(webhook: WebhookPayload): ContactInfo | undefined {\n const entry = webhook.entry?.[0];\n const change = entry?.changes?.[0];\n const value = change?.value;\n\n if (!value || !('contacts' in value) || !value.contacts?.length) {\n return undefined;\n }\n\n const contact = value.contacts[0];\n const metadata = value.metadata;\n\n return {\n waId: contact.wa_id,\n profileName: contact.profile?.name,\n phoneNumberId: metadata.phone_number_id,\n };\n}\n\n/**\n * Parses message timestamp to Date object.\n * WhatsApp timestamps are Unix epoch seconds as strings.\n */\nexport function getMessageTimestamp(message: IncomingMessage): Date {\n const epochSeconds = parseInt(message.timestamp, 10);\n return new Date(epochSeconds * 1000);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/verify.ts","../src/client.ts","../src/webhooks/classify.ts","../src/webhooks/messages.ts","../src/helpers.ts"],"sourcesContent":["// Main client\nexport { WABAClient } from './client.js';\n\n// Webhook classification\nexport { classifyWebhook, classifyMessage } from './webhooks/index.js';\n\n// Signature verification\nexport { verifyWebhookSignature } from './verify.js';\nexport type { VerifyWebhookSignatureOptions } from './verify.js';\n\n// Utility helpers\nexport {\n isMediaMessage,\n extractMediaId,\n getContactInfo,\n getMessageTimestamp,\n getMessageId,\n getCallId,\n} from './helpers.js';\nexport type { ContactInfo } from './helpers.js';\n\n// Error classes\nexport {\n WABAError,\n WABAMediaError,\n WABANetworkError,\n WABASignatureError,\n} from './errors.js';\n\n// Types\nexport type {\n // Client types\n WABAClientOptions,\n GetMediaOptions,\n // Media types\n MediaMetadata,\n MediaStreamResult,\n MediaBufferResult,\n // Webhook types\n WebhookPayload,\n WebhookEntry,\n WebhookChange,\n WebhookValue,\n WebhookContact,\n WebhookMetadata,\n WebhookError,\n MessageWebhookValue,\n StatusWebhookValue,\n CallWebhookValue,\n MessageStatus,\n ConversationObject,\n PricingObject,\n StatusEntry,\n CallEntry,\n WebhookClassification,\n // Message types\n MessageContext,\n IncomingMessageBase,\n TextMessage,\n MediaObject,\n ImageMessage,\n AudioMessage,\n VideoMessage,\n DocumentMessage,\n StickerMessage,\n LocationMessage,\n ContactCard,\n ContactsMessage,\n ButtonReply,\n ListReply,\n InteractiveMessage,\n ReactionMessage,\n ButtonMessage,\n OrderMessage,\n SystemMessage,\n ReferralMessage,\n UnsupportedMessage,\n IncomingMessage,\n MediaMessage,\n MessageClassification,\n} from './types/index.js';\n","/** Base error class for all WABA errors */\nexport class WABAError extends Error {\n constructor(\n message: string,\n public readonly code?: number,\n public readonly details?: unknown\n ) {\n super(message);\n this.name = 'WABAError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Error for media-related failures (404, access denied) */\nexport class WABAMediaError extends WABAError {\n constructor(\n message: string,\n public readonly mediaId: string,\n code?: number\n ) {\n super(message, code);\n this.name = 'WABAMediaError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Error for network/connection failures */\nexport class WABANetworkError extends WABAError {\n override readonly cause?: Error;\n\n constructor(message: string, cause?: Error) {\n super(message);\n this.name = 'WABANetworkError';\n this.cause = cause;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Error for invalid webhook signatures */\nexport class WABASignatureError extends WABAError {\n constructor(message: string = 'Invalid webhook signature') {\n super(message);\n this.name = 'WABASignatureError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import { createHmac, timingSafeEqual } from 'node:crypto';\nimport { WABASignatureError } from './errors.js';\n\nexport interface VerifyWebhookSignatureOptions {\n /** X-Hub-Signature-256 header value */\n signature: string | undefined;\n /** Raw request body (NOT parsed JSON) */\n rawBody: Buffer | string;\n /** Meta App Secret */\n appSecret: string | undefined;\n}\n\n/**\n * Verifies webhook signature using HMAC-SHA256.\n * Uses timing-safe comparison to prevent timing attacks.\n *\n * @throws {WABASignatureError} If appSecret is not provided\n * @returns true if signature is valid, false otherwise\n */\nexport function verifyWebhookSignature(\n options: VerifyWebhookSignatureOptions\n): boolean {\n const { signature, rawBody, appSecret } = options;\n\n if (!appSecret) {\n throw new WABASignatureError('appSecret is required for webhook verification');\n }\n\n if (!signature) {\n return false;\n }\n\n // Remove 'sha256=' prefix if present\n const signatureHash = signature.startsWith('sha256=')\n ? signature.slice(7)\n : signature;\n\n // Compute expected signature\n const hmac = createHmac('sha256', appSecret);\n const bodyBuffer =\n typeof rawBody === 'string' ? Buffer.from(rawBody, 'utf-8') : rawBody;\n const expectedHash = hmac.update(bodyBuffer).digest('hex');\n\n // Convert to buffers for timing-safe comparison\n const signatureBuffer = Buffer.from(signatureHash, 'utf-8');\n const expectedBuffer = Buffer.from(expectedHash, 'utf-8');\n\n // Ensure same length before comparison\n if (signatureBuffer.length !== expectedBuffer.length) {\n return false;\n }\n\n return timingSafeEqual(signatureBuffer, expectedBuffer);\n}\n","import type { WABAClientOptions, GetMediaOptions } from './types/client.js';\nimport type {\n MediaMetadata,\n MediaStreamResult,\n MediaBufferResult,\n RawMediaResponse,\n} from './types/media.js';\nimport { WABAMediaError, WABANetworkError, WABASignatureError } from './errors.js';\nimport { verifyWebhookSignature } from './verify.js';\n\nconst DEFAULT_API_VERSION = 'v22.0';\nconst DEFAULT_BASE_URL = 'https://graph.facebook.com';\n\n/**\n * Client for WhatsApp Business API media operations.\n */\nexport class WABAClient {\n private readonly accessToken: string;\n private readonly appSecret?: string;\n private readonly apiVersion: string;\n private readonly baseUrl: string;\n\n constructor(options: WABAClientOptions) {\n this.accessToken = options.accessToken;\n this.appSecret = options.appSecret;\n this.apiVersion = options.apiVersion ?? DEFAULT_API_VERSION;\n this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n }\n\n /**\n * Verifies webhook signature using HMAC-SHA256.\n * Requires appSecret to be set in constructor options.\n *\n * @throws {WABASignatureError} If appSecret was not provided in constructor\n * @returns true if signature is valid, false otherwise\n */\n verifyWebhook(signature: string | undefined, rawBody: Buffer | string): boolean {\n if (!this.appSecret) {\n throw new WABASignatureError(\n 'appSecret is required for webhook verification. Pass it in WABAClientOptions.'\n );\n }\n return verifyWebhookSignature({ signature, rawBody, appSecret: this.appSecret });\n }\n\n /**\n * Fetches media by ID from WhatsApp Business API.\n *\n * Step 1: GET /{apiVersion}/{mediaId} → retrieves temporary URL + metadata\n * Step 2: GET {temporaryUrl} → downloads binary content\n *\n * @throws {WABAMediaError} - Media not found (404) or access denied\n * @throws {WABANetworkError} - Network/connection failures\n */\n async getMedia(mediaId: string): Promise<MediaStreamResult>;\n async getMedia(\n mediaId: string,\n options: { asBuffer: true }\n ): Promise<MediaBufferResult>;\n async getMedia(\n mediaId: string,\n options?: GetMediaOptions\n ): Promise<MediaStreamResult | MediaBufferResult>;\n async getMedia(\n mediaId: string,\n options?: GetMediaOptions\n ): Promise<MediaStreamResult | MediaBufferResult> {\n // Step 1: Get media metadata and temporary URL\n const metadata = await this.fetchMediaMetadata(mediaId);\n\n // Step 2: Download the actual media\n const response = await this.downloadMedia(metadata.url, mediaId);\n\n if (options?.asBuffer) {\n const buffer = await response.arrayBuffer();\n return {\n ...metadata,\n buffer,\n };\n }\n\n if (!response.body) {\n throw new WABAMediaError('Response body is null', mediaId);\n }\n\n return {\n ...metadata,\n stream: response.body,\n };\n }\n\n private async fetchMediaMetadata(mediaId: string): Promise<MediaMetadata> {\n const url = `${this.baseUrl}/${this.apiVersion}/${mediaId}`;\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${this.accessToken}`,\n },\n });\n } catch (error) {\n throw new WABANetworkError(\n `Failed to fetch media metadata: ${error instanceof Error ? error.message : 'Unknown error'}`,\n error instanceof Error ? error : undefined\n );\n }\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => 'Unknown error');\n throw new WABAMediaError(\n `Failed to fetch media metadata: ${response.status} ${errorBody}`,\n mediaId,\n response.status\n );\n }\n\n const data = (await response.json()) as RawMediaResponse;\n\n // Normalize snake_case to camelCase\n return {\n id: data.id,\n mimeType: data.mime_type,\n sha256: data.sha256,\n fileSize: parseInt(data.file_size, 10),\n url: data.url,\n };\n }\n\n private async downloadMedia(url: string, mediaId: string): Promise<Response> {\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${this.accessToken}`,\n },\n });\n } catch (error) {\n throw new WABANetworkError(\n `Failed to download media: ${error instanceof Error ? error.message : 'Unknown error'}`,\n error instanceof Error ? error : undefined\n );\n }\n\n if (!response.ok) {\n throw new WABAMediaError(\n `Failed to download media: ${response.status}`,\n mediaId,\n response.status\n );\n }\n\n return response;\n }\n}\n","import type {\n WebhookPayload,\n WebhookClassification,\n MessageWebhookValue,\n StatusWebhookValue,\n CallWebhookValue,\n} from '../types/webhooks.js';\n\n/**\n * Classifies a webhook payload into its type.\n * Returns a discriminated union for type-safe handling.\n */\nexport function classifyWebhook(payload: WebhookPayload): WebhookClassification {\n const entry = payload.entry?.[0];\n const change = entry?.changes?.[0];\n const value = change?.value;\n\n if (!value) {\n return { type: 'unknown', payload };\n }\n\n // Check for calls array (call webhooks)\n if ('calls' in value && Array.isArray(value.calls)) {\n return { type: 'call', payload: value as CallWebhookValue };\n }\n\n // Check for statuses array (status webhooks)\n if ('statuses' in value && Array.isArray(value.statuses)) {\n return { type: 'status', payload: value as StatusWebhookValue };\n }\n\n // Check for messages array (message webhooks)\n if ('messages' in value && Array.isArray(value.messages)) {\n return { type: 'message', payload: value as MessageWebhookValue };\n }\n\n // Check for errors without messages (error webhook)\n if ('errors' in value && Array.isArray(value.errors)) {\n return { type: 'message', payload: value as MessageWebhookValue };\n }\n\n return { type: 'unknown', payload };\n}\n","import type {\n IncomingMessage,\n MessageClassification,\n TextMessage,\n ImageMessage,\n AudioMessage,\n VideoMessage,\n DocumentMessage,\n StickerMessage,\n LocationMessage,\n ContactsMessage,\n InteractiveMessage,\n ReactionMessage,\n ButtonMessage,\n OrderMessage,\n SystemMessage,\n ReferralMessage,\n UnsupportedMessage,\n} from '../types/messages.js';\n\n/**\n * Classifies an incoming message by its type.\n * Returns a discriminated union for type-safe handling.\n */\nexport function classifyMessage(message: IncomingMessage): MessageClassification {\n switch (message.type) {\n case 'text':\n return { type: 'text', message: message as TextMessage };\n case 'image':\n return { type: 'image', message: message as ImageMessage };\n case 'audio':\n return { type: 'audio', message: message as AudioMessage };\n case 'video':\n return { type: 'video', message: message as VideoMessage };\n case 'document':\n return { type: 'document', message: message as DocumentMessage };\n case 'sticker':\n return { type: 'sticker', message: message as StickerMessage };\n case 'location':\n return { type: 'location', message: message as LocationMessage };\n case 'contacts':\n return { type: 'contacts', message: message as ContactsMessage };\n case 'interactive':\n return { type: 'interactive', message: message as InteractiveMessage };\n case 'reaction':\n return { type: 'reaction', message: message as ReactionMessage };\n case 'button':\n return { type: 'button', message: message as ButtonMessage };\n case 'order':\n return { type: 'order', message: message as OrderMessage };\n case 'system':\n return { type: 'system', message: message as SystemMessage };\n case 'referral':\n return { type: 'referral', message: message as ReferralMessage };\n default:\n return { type: 'unsupported', message: message as UnsupportedMessage };\n }\n}\n","import type {\n IncomingMessage,\n MediaMessage,\n ImageMessage,\n AudioMessage,\n VideoMessage,\n DocumentMessage,\n StickerMessage,\n} from './types/messages.js';\nimport type {\n WebhookPayload,\n MessageWebhookValue,\n StatusWebhookValue,\n CallWebhookValue,\n} from './types/webhooks.js';\n\nconst MEDIA_TYPES = new Set(['image', 'audio', 'video', 'document', 'sticker']);\n\n/**\n * Type guard: returns true if message contains downloadable media.\n * Narrows type to messages with image/audio/video/document/sticker.\n */\nexport function isMediaMessage(message: IncomingMessage): message is MediaMessage {\n return MEDIA_TYPES.has(message.type);\n}\n\n/**\n * Extracts media ID from any media message type.\n * Returns undefined if message has no media.\n */\nexport function extractMediaId(message: IncomingMessage): string | undefined {\n if (!isMediaMessage(message)) {\n return undefined;\n }\n\n switch (message.type) {\n case 'image':\n return (message as ImageMessage).image.id;\n case 'audio':\n return (message as AudioMessage).audio.id;\n case 'video':\n return (message as VideoMessage).video.id;\n case 'document':\n return (message as DocumentMessage).document.id;\n case 'sticker':\n return (message as StickerMessage).sticker.id;\n default:\n return undefined;\n }\n}\n\nexport interface ContactInfo {\n waId: string;\n profileName: string | undefined;\n phoneNumberId: string;\n}\n\n/**\n * Extracts sender info from webhook payload.\n * Returns null if the webhook doesn't contain message contact info.\n */\nexport function getContactInfo(webhook: WebhookPayload): ContactInfo | null {\n const entry = webhook.entry?.[0];\n const change = entry?.changes?.[0];\n const value = change?.value;\n\n if (!value || !('contacts' in value) || !value.contacts?.length) {\n return null;\n }\n\n const contact = value.contacts[0];\n const metadata = value.metadata;\n\n return {\n waId: contact.wa_id,\n profileName: contact.profile?.name,\n phoneNumberId: metadata.phone_number_id,\n };\n}\n\n/**\n * Parses message timestamp to Date object.\n * WhatsApp timestamps are Unix epoch seconds as strings.\n */\nexport function getMessageTimestamp(message: IncomingMessage): Date {\n const epochSeconds = parseInt(message.timestamp, 10);\n return new Date(epochSeconds * 1000);\n}\n\n/**\n * Extracts message ID from message or status webhook.\n * Returns null if not a message/status webhook or ID not present.\n */\nexport function getMessageId(webhook: WebhookPayload): string | null {\n const entry = webhook.entry?.[0];\n const change = entry?.changes?.[0];\n const value = change?.value;\n\n if (!value) {\n return null;\n }\n\n // Try message webhook\n if ('messages' in value) {\n const messageValue = value as MessageWebhookValue;\n return messageValue.messages?.[0]?.id ?? null;\n }\n\n // Try status webhook\n if ('statuses' in value) {\n const statusValue = value as StatusWebhookValue;\n return statusValue.statuses?.[0]?.id ?? null;\n }\n\n return null;\n}\n\n/**\n * Extracts call ID from call webhook.\n * Returns null if not a call webhook or ID not present.\n */\nexport function getCallId(webhook: WebhookPayload): string | null {\n const entry = webhook.entry?.[0];\n const change = entry?.changes?.[0];\n const value = change?.value;\n\n if (!value) {\n return null;\n }\n\n // Check if it's a call webhook\n if ('calls' in value) {\n const callValue = value as CallWebhookValue;\n return callValue.calls?.[0]?.id ?? null;\n }\n\n return null;\n}\n"],"mappings":"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","names":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,312 @@
1
+ // src/errors.ts
2
+ var WABAError = class extends Error {
3
+ constructor(message, code, details) {
4
+ super(message);
5
+ this.code = code;
6
+ this.details = details;
7
+ this.name = "WABAError";
8
+ Object.setPrototypeOf(this, new.target.prototype);
9
+ }
10
+ };
11
+ var WABAMediaError = class extends WABAError {
12
+ constructor(message, mediaId, code) {
13
+ super(message, code);
14
+ this.mediaId = mediaId;
15
+ this.name = "WABAMediaError";
16
+ Object.setPrototypeOf(this, new.target.prototype);
17
+ }
18
+ };
19
+ var WABANetworkError = class extends WABAError {
20
+ cause;
21
+ constructor(message, cause) {
22
+ super(message);
23
+ this.name = "WABANetworkError";
24
+ this.cause = cause;
25
+ Object.setPrototypeOf(this, new.target.prototype);
26
+ }
27
+ };
28
+ var WABASignatureError = class extends WABAError {
29
+ constructor(message = "Invalid webhook signature") {
30
+ super(message);
31
+ this.name = "WABASignatureError";
32
+ Object.setPrototypeOf(this, new.target.prototype);
33
+ }
34
+ };
35
+
36
+ // src/verify.ts
37
+ import { createHmac, timingSafeEqual } from "crypto";
38
+ function verifyWebhookSignature(options) {
39
+ const { signature, rawBody, appSecret } = options;
40
+ if (!appSecret) {
41
+ throw new WABASignatureError("appSecret is required for webhook verification");
42
+ }
43
+ if (!signature) {
44
+ return false;
45
+ }
46
+ const signatureHash = signature.startsWith("sha256=") ? signature.slice(7) : signature;
47
+ const hmac = createHmac("sha256", appSecret);
48
+ const bodyBuffer = typeof rawBody === "string" ? Buffer.from(rawBody, "utf-8") : rawBody;
49
+ const expectedHash = hmac.update(bodyBuffer).digest("hex");
50
+ const signatureBuffer = Buffer.from(signatureHash, "utf-8");
51
+ const expectedBuffer = Buffer.from(expectedHash, "utf-8");
52
+ if (signatureBuffer.length !== expectedBuffer.length) {
53
+ return false;
54
+ }
55
+ return timingSafeEqual(signatureBuffer, expectedBuffer);
56
+ }
57
+
58
+ // src/client.ts
59
+ var DEFAULT_API_VERSION = "v22.0";
60
+ var DEFAULT_BASE_URL = "https://graph.facebook.com";
61
+ var WABAClient = class {
62
+ accessToken;
63
+ appSecret;
64
+ apiVersion;
65
+ baseUrl;
66
+ constructor(options) {
67
+ this.accessToken = options.accessToken;
68
+ this.appSecret = options.appSecret;
69
+ this.apiVersion = options.apiVersion ?? DEFAULT_API_VERSION;
70
+ this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
71
+ }
72
+ /**
73
+ * Verifies webhook signature using HMAC-SHA256.
74
+ * Requires appSecret to be set in constructor options.
75
+ *
76
+ * @throws {WABASignatureError} If appSecret was not provided in constructor
77
+ * @returns true if signature is valid, false otherwise
78
+ */
79
+ verifyWebhook(signature, rawBody) {
80
+ if (!this.appSecret) {
81
+ throw new WABASignatureError(
82
+ "appSecret is required for webhook verification. Pass it in WABAClientOptions."
83
+ );
84
+ }
85
+ return verifyWebhookSignature({ signature, rawBody, appSecret: this.appSecret });
86
+ }
87
+ async getMedia(mediaId, options) {
88
+ const metadata = await this.fetchMediaMetadata(mediaId);
89
+ const response = await this.downloadMedia(metadata.url, mediaId);
90
+ if (options?.asBuffer) {
91
+ const buffer = await response.arrayBuffer();
92
+ return {
93
+ ...metadata,
94
+ buffer
95
+ };
96
+ }
97
+ if (!response.body) {
98
+ throw new WABAMediaError("Response body is null", mediaId);
99
+ }
100
+ return {
101
+ ...metadata,
102
+ stream: response.body
103
+ };
104
+ }
105
+ async fetchMediaMetadata(mediaId) {
106
+ const url = `${this.baseUrl}/${this.apiVersion}/${mediaId}`;
107
+ let response;
108
+ try {
109
+ response = await fetch(url, {
110
+ method: "GET",
111
+ headers: {
112
+ Authorization: `Bearer ${this.accessToken}`
113
+ }
114
+ });
115
+ } catch (error) {
116
+ throw new WABANetworkError(
117
+ `Failed to fetch media metadata: ${error instanceof Error ? error.message : "Unknown error"}`,
118
+ error instanceof Error ? error : void 0
119
+ );
120
+ }
121
+ if (!response.ok) {
122
+ const errorBody = await response.text().catch(() => "Unknown error");
123
+ throw new WABAMediaError(
124
+ `Failed to fetch media metadata: ${response.status} ${errorBody}`,
125
+ mediaId,
126
+ response.status
127
+ );
128
+ }
129
+ const data = await response.json();
130
+ return {
131
+ id: data.id,
132
+ mimeType: data.mime_type,
133
+ sha256: data.sha256,
134
+ fileSize: parseInt(data.file_size, 10),
135
+ url: data.url
136
+ };
137
+ }
138
+ async downloadMedia(url, mediaId) {
139
+ let response;
140
+ try {
141
+ response = await fetch(url, {
142
+ method: "GET",
143
+ headers: {
144
+ Authorization: `Bearer ${this.accessToken}`
145
+ }
146
+ });
147
+ } catch (error) {
148
+ throw new WABANetworkError(
149
+ `Failed to download media: ${error instanceof Error ? error.message : "Unknown error"}`,
150
+ error instanceof Error ? error : void 0
151
+ );
152
+ }
153
+ if (!response.ok) {
154
+ throw new WABAMediaError(
155
+ `Failed to download media: ${response.status}`,
156
+ mediaId,
157
+ response.status
158
+ );
159
+ }
160
+ return response;
161
+ }
162
+ };
163
+
164
+ // src/webhooks/classify.ts
165
+ function classifyWebhook(payload) {
166
+ const entry = payload.entry?.[0];
167
+ const change = entry?.changes?.[0];
168
+ const value = change?.value;
169
+ if (!value) {
170
+ return { type: "unknown", payload };
171
+ }
172
+ if ("calls" in value && Array.isArray(value.calls)) {
173
+ return { type: "call", payload: value };
174
+ }
175
+ if ("statuses" in value && Array.isArray(value.statuses)) {
176
+ return { type: "status", payload: value };
177
+ }
178
+ if ("messages" in value && Array.isArray(value.messages)) {
179
+ return { type: "message", payload: value };
180
+ }
181
+ if ("errors" in value && Array.isArray(value.errors)) {
182
+ return { type: "message", payload: value };
183
+ }
184
+ return { type: "unknown", payload };
185
+ }
186
+
187
+ // src/webhooks/messages.ts
188
+ function classifyMessage(message) {
189
+ switch (message.type) {
190
+ case "text":
191
+ return { type: "text", message };
192
+ case "image":
193
+ return { type: "image", message };
194
+ case "audio":
195
+ return { type: "audio", message };
196
+ case "video":
197
+ return { type: "video", message };
198
+ case "document":
199
+ return { type: "document", message };
200
+ case "sticker":
201
+ return { type: "sticker", message };
202
+ case "location":
203
+ return { type: "location", message };
204
+ case "contacts":
205
+ return { type: "contacts", message };
206
+ case "interactive":
207
+ return { type: "interactive", message };
208
+ case "reaction":
209
+ return { type: "reaction", message };
210
+ case "button":
211
+ return { type: "button", message };
212
+ case "order":
213
+ return { type: "order", message };
214
+ case "system":
215
+ return { type: "system", message };
216
+ case "referral":
217
+ return { type: "referral", message };
218
+ default:
219
+ return { type: "unsupported", message };
220
+ }
221
+ }
222
+
223
+ // src/helpers.ts
224
+ var MEDIA_TYPES = /* @__PURE__ */ new Set(["image", "audio", "video", "document", "sticker"]);
225
+ function isMediaMessage(message) {
226
+ return MEDIA_TYPES.has(message.type);
227
+ }
228
+ function extractMediaId(message) {
229
+ if (!isMediaMessage(message)) {
230
+ return void 0;
231
+ }
232
+ switch (message.type) {
233
+ case "image":
234
+ return message.image.id;
235
+ case "audio":
236
+ return message.audio.id;
237
+ case "video":
238
+ return message.video.id;
239
+ case "document":
240
+ return message.document.id;
241
+ case "sticker":
242
+ return message.sticker.id;
243
+ default:
244
+ return void 0;
245
+ }
246
+ }
247
+ function getContactInfo(webhook) {
248
+ const entry = webhook.entry?.[0];
249
+ const change = entry?.changes?.[0];
250
+ const value = change?.value;
251
+ if (!value || !("contacts" in value) || !value.contacts?.length) {
252
+ return null;
253
+ }
254
+ const contact = value.contacts[0];
255
+ const metadata = value.metadata;
256
+ return {
257
+ waId: contact.wa_id,
258
+ profileName: contact.profile?.name,
259
+ phoneNumberId: metadata.phone_number_id
260
+ };
261
+ }
262
+ function getMessageTimestamp(message) {
263
+ const epochSeconds = parseInt(message.timestamp, 10);
264
+ return new Date(epochSeconds * 1e3);
265
+ }
266
+ function getMessageId(webhook) {
267
+ const entry = webhook.entry?.[0];
268
+ const change = entry?.changes?.[0];
269
+ const value = change?.value;
270
+ if (!value) {
271
+ return null;
272
+ }
273
+ if ("messages" in value) {
274
+ const messageValue = value;
275
+ return messageValue.messages?.[0]?.id ?? null;
276
+ }
277
+ if ("statuses" in value) {
278
+ const statusValue = value;
279
+ return statusValue.statuses?.[0]?.id ?? null;
280
+ }
281
+ return null;
282
+ }
283
+ function getCallId(webhook) {
284
+ const entry = webhook.entry?.[0];
285
+ const change = entry?.changes?.[0];
286
+ const value = change?.value;
287
+ if (!value) {
288
+ return null;
289
+ }
290
+ if ("calls" in value) {
291
+ const callValue = value;
292
+ return callValue.calls?.[0]?.id ?? null;
293
+ }
294
+ return null;
295
+ }
296
+ export {
297
+ WABAClient,
298
+ WABAError,
299
+ WABAMediaError,
300
+ WABANetworkError,
301
+ WABASignatureError,
302
+ classifyMessage,
303
+ classifyWebhook,
304
+ extractMediaId,
305
+ getCallId,
306
+ getContactInfo,
307
+ getMessageId,
308
+ getMessageTimestamp,
309
+ isMediaMessage,
310
+ verifyWebhookSignature
311
+ };
312
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/verify.ts","../src/client.ts","../src/webhooks/classify.ts","../src/webhooks/messages.ts","../src/helpers.ts"],"sourcesContent":["/** Base error class for all WABA errors */\nexport class WABAError extends Error {\n constructor(\n message: string,\n public readonly code?: number,\n public readonly details?: unknown\n ) {\n super(message);\n this.name = 'WABAError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Error for media-related failures (404, access denied) */\nexport class WABAMediaError extends WABAError {\n constructor(\n message: string,\n public readonly mediaId: string,\n code?: number\n ) {\n super(message, code);\n this.name = 'WABAMediaError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Error for network/connection failures */\nexport class WABANetworkError extends WABAError {\n override readonly cause?: Error;\n\n constructor(message: string, cause?: Error) {\n super(message);\n this.name = 'WABANetworkError';\n this.cause = cause;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Error for invalid webhook signatures */\nexport class WABASignatureError extends WABAError {\n constructor(message: string = 'Invalid webhook signature') {\n super(message);\n this.name = 'WABASignatureError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import { createHmac, timingSafeEqual } from 'node:crypto';\nimport { WABASignatureError } from './errors.js';\n\nexport interface VerifyWebhookSignatureOptions {\n /** X-Hub-Signature-256 header value */\n signature: string | undefined;\n /** Raw request body (NOT parsed JSON) */\n rawBody: Buffer | string;\n /** Meta App Secret */\n appSecret: string | undefined;\n}\n\n/**\n * Verifies webhook signature using HMAC-SHA256.\n * Uses timing-safe comparison to prevent timing attacks.\n *\n * @throws {WABASignatureError} If appSecret is not provided\n * @returns true if signature is valid, false otherwise\n */\nexport function verifyWebhookSignature(\n options: VerifyWebhookSignatureOptions\n): boolean {\n const { signature, rawBody, appSecret } = options;\n\n if (!appSecret) {\n throw new WABASignatureError('appSecret is required for webhook verification');\n }\n\n if (!signature) {\n return false;\n }\n\n // Remove 'sha256=' prefix if present\n const signatureHash = signature.startsWith('sha256=')\n ? signature.slice(7)\n : signature;\n\n // Compute expected signature\n const hmac = createHmac('sha256', appSecret);\n const bodyBuffer =\n typeof rawBody === 'string' ? Buffer.from(rawBody, 'utf-8') : rawBody;\n const expectedHash = hmac.update(bodyBuffer).digest('hex');\n\n // Convert to buffers for timing-safe comparison\n const signatureBuffer = Buffer.from(signatureHash, 'utf-8');\n const expectedBuffer = Buffer.from(expectedHash, 'utf-8');\n\n // Ensure same length before comparison\n if (signatureBuffer.length !== expectedBuffer.length) {\n return false;\n }\n\n return timingSafeEqual(signatureBuffer, expectedBuffer);\n}\n","import type { WABAClientOptions, GetMediaOptions } from './types/client.js';\nimport type {\n MediaMetadata,\n MediaStreamResult,\n MediaBufferResult,\n RawMediaResponse,\n} from './types/media.js';\nimport { WABAMediaError, WABANetworkError, WABASignatureError } from './errors.js';\nimport { verifyWebhookSignature } from './verify.js';\n\nconst DEFAULT_API_VERSION = 'v22.0';\nconst DEFAULT_BASE_URL = 'https://graph.facebook.com';\n\n/**\n * Client for WhatsApp Business API media operations.\n */\nexport class WABAClient {\n private readonly accessToken: string;\n private readonly appSecret?: string;\n private readonly apiVersion: string;\n private readonly baseUrl: string;\n\n constructor(options: WABAClientOptions) {\n this.accessToken = options.accessToken;\n this.appSecret = options.appSecret;\n this.apiVersion = options.apiVersion ?? DEFAULT_API_VERSION;\n this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n }\n\n /**\n * Verifies webhook signature using HMAC-SHA256.\n * Requires appSecret to be set in constructor options.\n *\n * @throws {WABASignatureError} If appSecret was not provided in constructor\n * @returns true if signature is valid, false otherwise\n */\n verifyWebhook(signature: string | undefined, rawBody: Buffer | string): boolean {\n if (!this.appSecret) {\n throw new WABASignatureError(\n 'appSecret is required for webhook verification. Pass it in WABAClientOptions.'\n );\n }\n return verifyWebhookSignature({ signature, rawBody, appSecret: this.appSecret });\n }\n\n /**\n * Fetches media by ID from WhatsApp Business API.\n *\n * Step 1: GET /{apiVersion}/{mediaId} → retrieves temporary URL + metadata\n * Step 2: GET {temporaryUrl} → downloads binary content\n *\n * @throws {WABAMediaError} - Media not found (404) or access denied\n * @throws {WABANetworkError} - Network/connection failures\n */\n async getMedia(mediaId: string): Promise<MediaStreamResult>;\n async getMedia(\n mediaId: string,\n options: { asBuffer: true }\n ): Promise<MediaBufferResult>;\n async getMedia(\n mediaId: string,\n options?: GetMediaOptions\n ): Promise<MediaStreamResult | MediaBufferResult>;\n async getMedia(\n mediaId: string,\n options?: GetMediaOptions\n ): Promise<MediaStreamResult | MediaBufferResult> {\n // Step 1: Get media metadata and temporary URL\n const metadata = await this.fetchMediaMetadata(mediaId);\n\n // Step 2: Download the actual media\n const response = await this.downloadMedia(metadata.url, mediaId);\n\n if (options?.asBuffer) {\n const buffer = await response.arrayBuffer();\n return {\n ...metadata,\n buffer,\n };\n }\n\n if (!response.body) {\n throw new WABAMediaError('Response body is null', mediaId);\n }\n\n return {\n ...metadata,\n stream: response.body,\n };\n }\n\n private async fetchMediaMetadata(mediaId: string): Promise<MediaMetadata> {\n const url = `${this.baseUrl}/${this.apiVersion}/${mediaId}`;\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${this.accessToken}`,\n },\n });\n } catch (error) {\n throw new WABANetworkError(\n `Failed to fetch media metadata: ${error instanceof Error ? error.message : 'Unknown error'}`,\n error instanceof Error ? error : undefined\n );\n }\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => 'Unknown error');\n throw new WABAMediaError(\n `Failed to fetch media metadata: ${response.status} ${errorBody}`,\n mediaId,\n response.status\n );\n }\n\n const data = (await response.json()) as RawMediaResponse;\n\n // Normalize snake_case to camelCase\n return {\n id: data.id,\n mimeType: data.mime_type,\n sha256: data.sha256,\n fileSize: parseInt(data.file_size, 10),\n url: data.url,\n };\n }\n\n private async downloadMedia(url: string, mediaId: string): Promise<Response> {\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${this.accessToken}`,\n },\n });\n } catch (error) {\n throw new WABANetworkError(\n `Failed to download media: ${error instanceof Error ? error.message : 'Unknown error'}`,\n error instanceof Error ? error : undefined\n );\n }\n\n if (!response.ok) {\n throw new WABAMediaError(\n `Failed to download media: ${response.status}`,\n mediaId,\n response.status\n );\n }\n\n return response;\n }\n}\n","import type {\n WebhookPayload,\n WebhookClassification,\n MessageWebhookValue,\n StatusWebhookValue,\n CallWebhookValue,\n} from '../types/webhooks.js';\n\n/**\n * Classifies a webhook payload into its type.\n * Returns a discriminated union for type-safe handling.\n */\nexport function classifyWebhook(payload: WebhookPayload): WebhookClassification {\n const entry = payload.entry?.[0];\n const change = entry?.changes?.[0];\n const value = change?.value;\n\n if (!value) {\n return { type: 'unknown', payload };\n }\n\n // Check for calls array (call webhooks)\n if ('calls' in value && Array.isArray(value.calls)) {\n return { type: 'call', payload: value as CallWebhookValue };\n }\n\n // Check for statuses array (status webhooks)\n if ('statuses' in value && Array.isArray(value.statuses)) {\n return { type: 'status', payload: value as StatusWebhookValue };\n }\n\n // Check for messages array (message webhooks)\n if ('messages' in value && Array.isArray(value.messages)) {\n return { type: 'message', payload: value as MessageWebhookValue };\n }\n\n // Check for errors without messages (error webhook)\n if ('errors' in value && Array.isArray(value.errors)) {\n return { type: 'message', payload: value as MessageWebhookValue };\n }\n\n return { type: 'unknown', payload };\n}\n","import type {\n IncomingMessage,\n MessageClassification,\n TextMessage,\n ImageMessage,\n AudioMessage,\n VideoMessage,\n DocumentMessage,\n StickerMessage,\n LocationMessage,\n ContactsMessage,\n InteractiveMessage,\n ReactionMessage,\n ButtonMessage,\n OrderMessage,\n SystemMessage,\n ReferralMessage,\n UnsupportedMessage,\n} from '../types/messages.js';\n\n/**\n * Classifies an incoming message by its type.\n * Returns a discriminated union for type-safe handling.\n */\nexport function classifyMessage(message: IncomingMessage): MessageClassification {\n switch (message.type) {\n case 'text':\n return { type: 'text', message: message as TextMessage };\n case 'image':\n return { type: 'image', message: message as ImageMessage };\n case 'audio':\n return { type: 'audio', message: message as AudioMessage };\n case 'video':\n return { type: 'video', message: message as VideoMessage };\n case 'document':\n return { type: 'document', message: message as DocumentMessage };\n case 'sticker':\n return { type: 'sticker', message: message as StickerMessage };\n case 'location':\n return { type: 'location', message: message as LocationMessage };\n case 'contacts':\n return { type: 'contacts', message: message as ContactsMessage };\n case 'interactive':\n return { type: 'interactive', message: message as InteractiveMessage };\n case 'reaction':\n return { type: 'reaction', message: message as ReactionMessage };\n case 'button':\n return { type: 'button', message: message as ButtonMessage };\n case 'order':\n return { type: 'order', message: message as OrderMessage };\n case 'system':\n return { type: 'system', message: message as SystemMessage };\n case 'referral':\n return { type: 'referral', message: message as ReferralMessage };\n default:\n return { type: 'unsupported', message: message as UnsupportedMessage };\n }\n}\n","import type {\n IncomingMessage,\n MediaMessage,\n ImageMessage,\n AudioMessage,\n VideoMessage,\n DocumentMessage,\n StickerMessage,\n} from './types/messages.js';\nimport type {\n WebhookPayload,\n MessageWebhookValue,\n StatusWebhookValue,\n CallWebhookValue,\n} from './types/webhooks.js';\n\nconst MEDIA_TYPES = new Set(['image', 'audio', 'video', 'document', 'sticker']);\n\n/**\n * Type guard: returns true if message contains downloadable media.\n * Narrows type to messages with image/audio/video/document/sticker.\n */\nexport function isMediaMessage(message: IncomingMessage): message is MediaMessage {\n return MEDIA_TYPES.has(message.type);\n}\n\n/**\n * Extracts media ID from any media message type.\n * Returns undefined if message has no media.\n */\nexport function extractMediaId(message: IncomingMessage): string | undefined {\n if (!isMediaMessage(message)) {\n return undefined;\n }\n\n switch (message.type) {\n case 'image':\n return (message as ImageMessage).image.id;\n case 'audio':\n return (message as AudioMessage).audio.id;\n case 'video':\n return (message as VideoMessage).video.id;\n case 'document':\n return (message as DocumentMessage).document.id;\n case 'sticker':\n return (message as StickerMessage).sticker.id;\n default:\n return undefined;\n }\n}\n\nexport interface ContactInfo {\n waId: string;\n profileName: string | undefined;\n phoneNumberId: string;\n}\n\n/**\n * Extracts sender info from webhook payload.\n * Returns null if the webhook doesn't contain message contact info.\n */\nexport function getContactInfo(webhook: WebhookPayload): ContactInfo | null {\n const entry = webhook.entry?.[0];\n const change = entry?.changes?.[0];\n const value = change?.value;\n\n if (!value || !('contacts' in value) || !value.contacts?.length) {\n return null;\n }\n\n const contact = value.contacts[0];\n const metadata = value.metadata;\n\n return {\n waId: contact.wa_id,\n profileName: contact.profile?.name,\n phoneNumberId: metadata.phone_number_id,\n };\n}\n\n/**\n * Parses message timestamp to Date object.\n * WhatsApp timestamps are Unix epoch seconds as strings.\n */\nexport function getMessageTimestamp(message: IncomingMessage): Date {\n const epochSeconds = parseInt(message.timestamp, 10);\n return new Date(epochSeconds * 1000);\n}\n\n/**\n * Extracts message ID from message or status webhook.\n * Returns null if not a message/status webhook or ID not present.\n */\nexport function getMessageId(webhook: WebhookPayload): string | null {\n const entry = webhook.entry?.[0];\n const change = entry?.changes?.[0];\n const value = change?.value;\n\n if (!value) {\n return null;\n }\n\n // Try message webhook\n if ('messages' in value) {\n const messageValue = value as MessageWebhookValue;\n return messageValue.messages?.[0]?.id ?? null;\n }\n\n // Try status webhook\n if ('statuses' in value) {\n const statusValue = value as StatusWebhookValue;\n return statusValue.statuses?.[0]?.id ?? null;\n }\n\n return null;\n}\n\n/**\n * Extracts call ID from call webhook.\n * Returns null if not a call webhook or ID not present.\n */\nexport function getCallId(webhook: WebhookPayload): string | null {\n const entry = webhook.entry?.[0];\n const change = entry?.changes?.[0];\n const value = change?.value;\n\n if (!value) {\n return null;\n }\n\n // Check if it's a call webhook\n if ('calls' in value) {\n const callValue = value as CallWebhookValue;\n return callValue.calls?.[0]?.id ?? null;\n }\n\n return null;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,15 +1,18 @@
1
1
  {
2
2
  "name": "waba-toolkit",
3
- "version": "0.1.2",
4
- "description": "Minimal, type-safe toolkit for WhatsApp Business API webhook processing and media handling",
5
- "type": "module",
3
+ "version": "0.2.0",
4
+ "description": "Type-safe, zero-dependency WhatsApp Business API toolkit for webhooks, media downloads, and signature verification",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
6
8
  "engines": {
7
9
  "node": ">=20.0.0"
8
10
  },
9
11
  "exports": {
10
12
  ".": {
11
13
  "types": "./dist/index.d.ts",
12
- "import": "./dist/index.js"
14
+ "import": "./dist/index.mjs",
15
+ "require": "./dist/index.js"
13
16
  }
14
17
  },
15
18
  "files": [
@@ -26,9 +29,17 @@
26
29
  "whatsapp",
27
30
  "waba",
28
31
  "whatsapp-business-api",
32
+ "whatsapp-cloud-api",
29
33
  "webhook",
30
- "meta"
34
+ "meta",
35
+ "cloud-api",
36
+ "messaging",
37
+ "typescript"
31
38
  ],
39
+ "homepage": "https://github.com/teknicus/waba-toolkit",
40
+ "bugs": {
41
+ "url": "https://github.com/teknicus/waba-toolkit/issues"
42
+ },
32
43
  "license": "MIT",
33
44
  "devDependencies": {
34
45
  "@types/node": "^25.0.3",
@@ -36,5 +47,8 @@
36
47
  "typescript": "^5.0.0",
37
48
  "vitest": "^4.0.16"
38
49
  },
39
- "repository": "github:teknicus/waba-toolkit"
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "https://github.com/teknicus/waba-toolkit.git"
53
+ }
40
54
  }