@maroonedsoftware/whatsapp 0.0.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/README.md +226 -0
- package/dist/chunk-ASMXL2NK.js +19 -0
- package/dist/chunk-ASMXL2NK.js.map +1 -0
- package/dist/client/whatsapp.client.d.ts +44 -0
- package/dist/client/whatsapp.client.d.ts.map +1 -0
- package/dist/comms.d.ts +23 -0
- package/dist/comms.d.ts.map +1 -0
- package/dist/comms.js +139 -0
- package/dist/comms.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +382 -0
- package/dist/index.js.map +1 -0
- package/dist/whatsapp.config.d.ts +32 -0
- package/dist/whatsapp.config.d.ts.map +1 -0
- package/dist/whatsapp.dispatcher.d.ts +79 -0
- package/dist/whatsapp.dispatcher.d.ts.map +1 -0
- package/dist/whatsapp.error.d.ts +19 -0
- package/dist/whatsapp.error.d.ts.map +1 -0
- package/dist/whatsapp.message.handler.d.ts +146 -0
- package/dist/whatsapp.message.handler.d.ts.map +1 -0
- package/dist/whatsapp.signature.d.ts +49 -0
- package/dist/whatsapp.signature.d.ts.map +1 -0
- package/dist/whatsapp.signature.policy.d.ts +57 -0
- package/dist/whatsapp.signature.policy.d.ts.map +1 -0
- package/dist/whatsapp.webhook.d.ts +48 -0
- package/dist/whatsapp.webhook.d.ts.map +1 -0
- package/package.json +72 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__name,
|
|
3
|
+
interactiveReplyId
|
|
4
|
+
} from "./chunk-ASMXL2NK.js";
|
|
5
|
+
|
|
6
|
+
// src/whatsapp.config.ts
|
|
7
|
+
import { Injectable } from "injectkit";
|
|
8
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
9
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
10
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
11
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
12
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
13
|
+
}
|
|
14
|
+
__name(_ts_decorate, "_ts_decorate");
|
|
15
|
+
var WHATSAPP_DEFAULT_GRAPH_API_VERSION = "v21.0";
|
|
16
|
+
var WhatsAppConfig = class {
|
|
17
|
+
static {
|
|
18
|
+
__name(this, "WhatsAppConfig");
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
WhatsAppConfig = _ts_decorate([
|
|
22
|
+
Injectable()
|
|
23
|
+
], WhatsAppConfig);
|
|
24
|
+
|
|
25
|
+
// src/whatsapp.error.ts
|
|
26
|
+
import { ServerkitError } from "@maroonedsoftware/errors";
|
|
27
|
+
var WhatsAppError = class extends ServerkitError {
|
|
28
|
+
static {
|
|
29
|
+
__name(this, "WhatsAppError");
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var IsWhatsAppError = /* @__PURE__ */ __name((error) => error instanceof WhatsAppError, "IsWhatsAppError");
|
|
33
|
+
|
|
34
|
+
// src/whatsapp.signature.ts
|
|
35
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
36
|
+
var WHATSAPP_SIGNATURE_HEADER = "X-Hub-Signature-256";
|
|
37
|
+
var verifyWhatsAppSignature = /* @__PURE__ */ __name((input) => {
|
|
38
|
+
const { appSecret, rawBody, signature } = input;
|
|
39
|
+
if (!signature) {
|
|
40
|
+
throw new WhatsAppError("WhatsApp request missing X-Hub-Signature-256 header").withInternalDetails({
|
|
41
|
+
reason: "missing_signature"
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
const expected = `sha256=${createHmac("sha256", appSecret).update(rawBody).digest("hex")}`;
|
|
45
|
+
const expectedBuf = Buffer.from(expected, "utf8");
|
|
46
|
+
const providedBuf = Buffer.from(signature, "utf8");
|
|
47
|
+
if (expectedBuf.length !== providedBuf.length || !timingSafeEqual(expectedBuf, providedBuf)) {
|
|
48
|
+
throw new WhatsAppError("WhatsApp request signature does not match").withInternalDetails({
|
|
49
|
+
reason: "invalid_signature"
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}, "verifyWhatsAppSignature");
|
|
53
|
+
|
|
54
|
+
// src/whatsapp.webhook.ts
|
|
55
|
+
import { timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
56
|
+
var WHATSAPP_HUB_MODE_PARAM = "hub.mode";
|
|
57
|
+
var WHATSAPP_HUB_VERIFY_TOKEN_PARAM = "hub.verify_token";
|
|
58
|
+
var WHATSAPP_HUB_CHALLENGE_PARAM = "hub.challenge";
|
|
59
|
+
var verifyWhatsAppWebhook = /* @__PURE__ */ __name((input) => {
|
|
60
|
+
const { verifyToken, mode, token, challenge } = input;
|
|
61
|
+
if (mode !== "subscribe") {
|
|
62
|
+
throw new WhatsAppError('WhatsApp webhook verification mode is not "subscribe"').withInternalDetails({
|
|
63
|
+
reason: "invalid_mode",
|
|
64
|
+
mode
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
const expectedBuf = Buffer.from(verifyToken, "utf8");
|
|
68
|
+
const providedBuf = Buffer.from(token ?? "", "utf8");
|
|
69
|
+
if (expectedBuf.length !== providedBuf.length || !timingSafeEqual2(expectedBuf, providedBuf)) {
|
|
70
|
+
throw new WhatsAppError("WhatsApp webhook verify token does not match").withInternalDetails({
|
|
71
|
+
reason: "invalid_verify_token"
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
if (!challenge) {
|
|
75
|
+
throw new WhatsAppError("WhatsApp webhook verification missing hub.challenge").withInternalDetails({
|
|
76
|
+
reason: "missing_challenge"
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return challenge;
|
|
80
|
+
}, "verifyWhatsAppWebhook");
|
|
81
|
+
|
|
82
|
+
// src/whatsapp.signature.policy.ts
|
|
83
|
+
import { Injectable as Injectable2 } from "injectkit";
|
|
84
|
+
import { Policy } from "@maroonedsoftware/policies";
|
|
85
|
+
function _ts_decorate2(decorators, target, key, desc) {
|
|
86
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
87
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
88
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
89
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
90
|
+
}
|
|
91
|
+
__name(_ts_decorate2, "_ts_decorate");
|
|
92
|
+
var WHATSAPP_SIGNATURE_POLICY = "whatsapp.signature.valid";
|
|
93
|
+
var WhatsAppSignaturePolicy = class extends Policy {
|
|
94
|
+
static {
|
|
95
|
+
__name(this, "WhatsAppSignaturePolicy");
|
|
96
|
+
}
|
|
97
|
+
async evaluate(context, _envelope) {
|
|
98
|
+
const { rawBody, getHeader, options } = context;
|
|
99
|
+
const body = typeof rawBody === "string" ? rawBody : Buffer.from(rawBody).toString("utf8");
|
|
100
|
+
try {
|
|
101
|
+
verifyWhatsAppSignature({
|
|
102
|
+
appSecret: options.appSecret,
|
|
103
|
+
rawBody: body,
|
|
104
|
+
signature: getHeader(WHATSAPP_SIGNATURE_HEADER)
|
|
105
|
+
});
|
|
106
|
+
return this.allow();
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (!IsWhatsAppError(error)) throw error;
|
|
109
|
+
const internalDetails = error.internalDetails ?? {};
|
|
110
|
+
const reason = typeof internalDetails.reason === "string" ? internalDetails.reason : "invalid_signature";
|
|
111
|
+
return this.deny(reason, void 0, {
|
|
112
|
+
message: error.message,
|
|
113
|
+
...internalDetails
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
WhatsAppSignaturePolicy = _ts_decorate2([
|
|
119
|
+
Injectable2()
|
|
120
|
+
], WhatsAppSignaturePolicy);
|
|
121
|
+
|
|
122
|
+
// src/whatsapp.dispatcher.ts
|
|
123
|
+
import { Injectable as Injectable3 } from "injectkit";
|
|
124
|
+
import { Logger } from "@maroonedsoftware/logger";
|
|
125
|
+
function _ts_decorate3(decorators, target, key, desc) {
|
|
126
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
127
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
128
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
129
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
130
|
+
}
|
|
131
|
+
__name(_ts_decorate3, "_ts_decorate");
|
|
132
|
+
function _ts_metadata(k, v) {
|
|
133
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
134
|
+
}
|
|
135
|
+
__name(_ts_metadata, "_ts_metadata");
|
|
136
|
+
var WhatsAppMessageHandlerMap = class extends Map {
|
|
137
|
+
static {
|
|
138
|
+
__name(this, "WhatsAppMessageHandlerMap");
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
WhatsAppMessageHandlerMap = _ts_decorate3([
|
|
142
|
+
Injectable3()
|
|
143
|
+
], WhatsAppMessageHandlerMap);
|
|
144
|
+
var WhatsAppInteractiveHandlerMap = class extends Map {
|
|
145
|
+
static {
|
|
146
|
+
__name(this, "WhatsAppInteractiveHandlerMap");
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
WhatsAppInteractiveHandlerMap = _ts_decorate3([
|
|
150
|
+
Injectable3()
|
|
151
|
+
], WhatsAppInteractiveHandlerMap);
|
|
152
|
+
var WhatsAppStatusHandlerMap = class extends Map {
|
|
153
|
+
static {
|
|
154
|
+
__name(this, "WhatsAppStatusHandlerMap");
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
WhatsAppStatusHandlerMap = _ts_decorate3([
|
|
158
|
+
Injectable3()
|
|
159
|
+
], WhatsAppStatusHandlerMap);
|
|
160
|
+
var WhatsAppDispatcher = class {
|
|
161
|
+
static {
|
|
162
|
+
__name(this, "WhatsAppDispatcher");
|
|
163
|
+
}
|
|
164
|
+
messages;
|
|
165
|
+
interactives;
|
|
166
|
+
statuses;
|
|
167
|
+
logger;
|
|
168
|
+
constructor(messages, interactives, statuses, logger) {
|
|
169
|
+
this.messages = messages;
|
|
170
|
+
this.interactives = interactives;
|
|
171
|
+
this.statuses = statuses;
|
|
172
|
+
this.logger = logger;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Walks the batched webhook body, dispatching every message and status it
|
|
176
|
+
* contains. Resolves once all handlers have settled.
|
|
177
|
+
*/
|
|
178
|
+
async dispatchWebhook(body) {
|
|
179
|
+
for (const entry of body.entry ?? []) {
|
|
180
|
+
for (const change of entry.changes ?? []) {
|
|
181
|
+
const value = change.value;
|
|
182
|
+
if (!value) continue;
|
|
183
|
+
for (const message of value.messages ?? []) {
|
|
184
|
+
await this.dispatchMessage(message, entry.id, value);
|
|
185
|
+
}
|
|
186
|
+
for (const status of value.statuses ?? []) {
|
|
187
|
+
await this.dispatchStatus(status, entry.id, value);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Dispatch a single message. Interactive and quick-reply-button messages are
|
|
194
|
+
* routed by their developer-defined id ({@link WhatsAppInteractiveHandlerMap})
|
|
195
|
+
* first; everything else (and id-less interactives with no specific handler)
|
|
196
|
+
* falls back to the message-type map ({@link WhatsAppMessageHandlerMap}).
|
|
197
|
+
*/
|
|
198
|
+
async dispatchMessage(message, wabaId, value) {
|
|
199
|
+
const context = {
|
|
200
|
+
phoneNumberId: value.metadata.phone_number_id,
|
|
201
|
+
displayPhoneNumber: value.metadata.display_phone_number,
|
|
202
|
+
wabaId,
|
|
203
|
+
contact: value.contacts?.find((c) => c.wa_id === message.from) ?? value.contacts?.[0],
|
|
204
|
+
value
|
|
205
|
+
};
|
|
206
|
+
const replyId = interactiveReplyId(message);
|
|
207
|
+
if (replyId) {
|
|
208
|
+
const interactive = this.interactives.get(replyId);
|
|
209
|
+
if (interactive) {
|
|
210
|
+
await interactive.handle(message, context);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const handler = this.messages.get(message.type);
|
|
215
|
+
if (!handler) {
|
|
216
|
+
this.logger.debug("No WhatsApp message handler registered", {
|
|
217
|
+
type: message.type,
|
|
218
|
+
replyId
|
|
219
|
+
});
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
await handler.handle(message, context);
|
|
223
|
+
}
|
|
224
|
+
/** Dispatch a single delivery status, keyed by `status.status`. */
|
|
225
|
+
async dispatchStatus(status, wabaId, value) {
|
|
226
|
+
const handler = this.statuses.get(status.status);
|
|
227
|
+
if (!handler) {
|
|
228
|
+
this.logger.debug("No WhatsApp status handler registered", {
|
|
229
|
+
status: status.status
|
|
230
|
+
});
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
await handler.handle(status, {
|
|
234
|
+
phoneNumberId: value.metadata.phone_number_id,
|
|
235
|
+
displayPhoneNumber: value.metadata.display_phone_number,
|
|
236
|
+
wabaId,
|
|
237
|
+
value
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
WhatsAppDispatcher = _ts_decorate3([
|
|
242
|
+
Injectable3(),
|
|
243
|
+
_ts_metadata("design:type", Function),
|
|
244
|
+
_ts_metadata("design:paramtypes", [
|
|
245
|
+
typeof WhatsAppMessageHandlerMap === "undefined" ? Object : WhatsAppMessageHandlerMap,
|
|
246
|
+
typeof WhatsAppInteractiveHandlerMap === "undefined" ? Object : WhatsAppInteractiveHandlerMap,
|
|
247
|
+
typeof WhatsAppStatusHandlerMap === "undefined" ? Object : WhatsAppStatusHandlerMap,
|
|
248
|
+
typeof Logger === "undefined" ? Object : Logger
|
|
249
|
+
])
|
|
250
|
+
], WhatsAppDispatcher);
|
|
251
|
+
|
|
252
|
+
// src/client/whatsapp.client.ts
|
|
253
|
+
import { Injectable as Injectable4 } from "injectkit";
|
|
254
|
+
import { Logger as Logger2 } from "@maroonedsoftware/logger";
|
|
255
|
+
function _ts_decorate4(decorators, target, key, desc) {
|
|
256
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
257
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
258
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
259
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
260
|
+
}
|
|
261
|
+
__name(_ts_decorate4, "_ts_decorate");
|
|
262
|
+
function _ts_metadata2(k, v) {
|
|
263
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
264
|
+
}
|
|
265
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
266
|
+
var WHATSAPP_GRAPH_API_HOST = "https://graph.facebook.com";
|
|
267
|
+
var WhatsAppClient = class {
|
|
268
|
+
static {
|
|
269
|
+
__name(this, "WhatsAppClient");
|
|
270
|
+
}
|
|
271
|
+
config;
|
|
272
|
+
logger;
|
|
273
|
+
version;
|
|
274
|
+
constructor(config, logger) {
|
|
275
|
+
this.config = config;
|
|
276
|
+
this.logger = logger;
|
|
277
|
+
this.version = config.graphApiVersion ?? WHATSAPP_DEFAULT_GRAPH_API_VERSION;
|
|
278
|
+
}
|
|
279
|
+
/** Sends a message via `POST /{phoneNumberId}/messages`. The body is forwarded verbatim. */
|
|
280
|
+
sendMessage(body) {
|
|
281
|
+
return this.request("POST", `/${this.config.phoneNumberId}/messages`, body);
|
|
282
|
+
}
|
|
283
|
+
/** Convenience helper for a plain text message. */
|
|
284
|
+
sendText(to, body, options = {}) {
|
|
285
|
+
return this.sendMessage({
|
|
286
|
+
messaging_product: "whatsapp",
|
|
287
|
+
recipient_type: "individual",
|
|
288
|
+
to,
|
|
289
|
+
type: "text",
|
|
290
|
+
text: {
|
|
291
|
+
preview_url: options.previewUrl ?? false,
|
|
292
|
+
body
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
/** Convenience helper for an interactive message (buttons / list). */
|
|
297
|
+
sendInteractive(to, interactive) {
|
|
298
|
+
return this.sendMessage({
|
|
299
|
+
messaging_product: "whatsapp",
|
|
300
|
+
recipient_type: "individual",
|
|
301
|
+
to,
|
|
302
|
+
type: "interactive",
|
|
303
|
+
interactive
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
/** Marks an inbound message as read via the messages endpoint. */
|
|
307
|
+
markAsRead(messageId) {
|
|
308
|
+
return this.sendMessage({
|
|
309
|
+
messaging_product: "whatsapp",
|
|
310
|
+
status: "read",
|
|
311
|
+
message_id: messageId
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Low-level request helper. Prefixes the Graph API host + version, sets JSON
|
|
316
|
+
* headers, adds the `Authorization: Bearer <accessToken>` header, and throws
|
|
317
|
+
* {@link WhatsAppError} on a non-2xx response.
|
|
318
|
+
*
|
|
319
|
+
* Returns the parsed JSON body, or `undefined` for empty responses.
|
|
320
|
+
*/
|
|
321
|
+
async request(method, path, body) {
|
|
322
|
+
const url = `${WHATSAPP_GRAPH_API_HOST}/${this.version}${path}`;
|
|
323
|
+
const response = await fetch(url, {
|
|
324
|
+
method,
|
|
325
|
+
headers: {
|
|
326
|
+
"content-type": "application/json",
|
|
327
|
+
authorization: `Bearer ${this.config.accessToken}`
|
|
328
|
+
},
|
|
329
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
330
|
+
});
|
|
331
|
+
if (!response.ok) {
|
|
332
|
+
const text2 = await response.text().catch(() => "");
|
|
333
|
+
this.logger.warn("WhatsApp Graph API call returned non-OK status", {
|
|
334
|
+
status: response.status,
|
|
335
|
+
method,
|
|
336
|
+
path
|
|
337
|
+
});
|
|
338
|
+
throw new WhatsAppError(`WhatsApp Graph API call ${method} ${path} returned ${response.status}`).withInternalDetails({
|
|
339
|
+
status: response.status,
|
|
340
|
+
body: text2,
|
|
341
|
+
url
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
const text = await response.text().catch(() => "");
|
|
345
|
+
if (!text) return void 0;
|
|
346
|
+
try {
|
|
347
|
+
return JSON.parse(text);
|
|
348
|
+
} catch {
|
|
349
|
+
return text;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
WhatsAppClient = _ts_decorate4([
|
|
354
|
+
Injectable4(),
|
|
355
|
+
_ts_metadata2("design:type", Function),
|
|
356
|
+
_ts_metadata2("design:paramtypes", [
|
|
357
|
+
typeof WhatsAppConfig === "undefined" ? Object : WhatsAppConfig,
|
|
358
|
+
typeof Logger2 === "undefined" ? Object : Logger2
|
|
359
|
+
])
|
|
360
|
+
], WhatsAppClient);
|
|
361
|
+
export {
|
|
362
|
+
IsWhatsAppError,
|
|
363
|
+
WHATSAPP_DEFAULT_GRAPH_API_VERSION,
|
|
364
|
+
WHATSAPP_GRAPH_API_HOST,
|
|
365
|
+
WHATSAPP_HUB_CHALLENGE_PARAM,
|
|
366
|
+
WHATSAPP_HUB_MODE_PARAM,
|
|
367
|
+
WHATSAPP_HUB_VERIFY_TOKEN_PARAM,
|
|
368
|
+
WHATSAPP_SIGNATURE_HEADER,
|
|
369
|
+
WHATSAPP_SIGNATURE_POLICY,
|
|
370
|
+
WhatsAppClient,
|
|
371
|
+
WhatsAppConfig,
|
|
372
|
+
WhatsAppDispatcher,
|
|
373
|
+
WhatsAppError,
|
|
374
|
+
WhatsAppInteractiveHandlerMap,
|
|
375
|
+
WhatsAppMessageHandlerMap,
|
|
376
|
+
WhatsAppSignaturePolicy,
|
|
377
|
+
WhatsAppStatusHandlerMap,
|
|
378
|
+
interactiveReplyId,
|
|
379
|
+
verifyWhatsAppSignature,
|
|
380
|
+
verifyWhatsAppWebhook
|
|
381
|
+
};
|
|
382
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/whatsapp.config.ts","../src/whatsapp.error.ts","../src/whatsapp.signature.ts","../src/whatsapp.webhook.ts","../src/whatsapp.signature.policy.ts","../src/whatsapp.dispatcher.ts","../src/client/whatsapp.client.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */\nimport { Injectable } from 'injectkit';\n\n/** Default Graph API version used by {@link import('./client/whatsapp.client.js').WhatsAppClient}. */\nexport const WHATSAPP_DEFAULT_GRAPH_API_VERSION = 'v21.0';\n\n/**\n * Configuration for the WhatsApp package. Declared as an abstract `@Injectable()`\n * class so it doubles as a DI token (mirrors the `Logger` pattern in\n * `@maroonedsoftware/logger` and `SlackConfig` in `@maroonedsoftware/slack`).\n *\n * Consumers register a concrete value at bootstrap, typically resolved from\n * `AppConfig`:\n *\n * ```ts\n * const whatsappConfig = appConfig.getAs<WhatsAppConfig>('whatsapp');\n * container.register(WhatsAppConfig, { useValue: whatsappConfig });\n * ```\n *\n * Services in this package take `WhatsAppConfig` directly in their constructor.\n */\nexport interface WhatsAppConfig {\n /** Graph API access token (system-user or app token) sent as `Authorization: Bearer`. */\n accessToken: string;\n /** Phone number ID the bot sends from (`POST /{phoneNumberId}/messages`). */\n phoneNumberId: string;\n /** App secret used to verify the `X-Hub-Signature-256` HMAC on incoming webhooks. */\n appSecret: string;\n /** Token echoed during the webhook verification (`GET`) handshake. */\n verifyToken: string;\n /** Graph API version. Defaults to {@link WHATSAPP_DEFAULT_GRAPH_API_VERSION}. */\n graphApiVersion?: string;\n}\n\n@Injectable()\nexport abstract class WhatsAppConfig implements WhatsAppConfig {}\n","import { ServerkitError } from '@maroonedsoftware/errors';\n\n/**\n * Domain error raised by the WhatsApp package for non-HTTP failures (e.g. a\n * Graph API call returned a non-2xx status, signature verification failed,\n * webhook verification handshake failed).\n *\n * Extends {@link ServerkitError} so `errorMiddleware` renders a 500 with\n * `{ message, details }` if one of these escapes a route handler. Inside route\n * handlers, throw `httpError(...)` directly for status-coded responses.\n */\nexport class WhatsAppError extends ServerkitError {}\n\n/**\n * Type guard for {@link WhatsAppError}. Narrows `unknown` to `WhatsAppError` so\n * `details`, `internalDetails`, and the chainable setters are accessible without\n * further checks. Returns `true` for any subclass.\n */\nexport const IsWhatsAppError = (error: unknown): error is WhatsAppError => error instanceof WhatsAppError;\n","import { createHmac, timingSafeEqual } from 'node:crypto';\nimport { WhatsAppError } from './whatsapp.error.js';\n\n/**\n * Reason codes attached to {@link WhatsAppError.internalDetails} when signature\n * verification fails. Useful for callers that want to log structured reasons\n * without pattern-matching on error messages.\n */\nexport type WhatsAppSignatureFailureReason = 'missing_signature' | 'invalid_signature';\n\n/** Header carrying the `sha256=`-prefixed payload HMAC Meta sends with each webhook. */\nexport const WHATSAPP_SIGNATURE_HEADER = 'X-Hub-Signature-256';\n\n/**\n * Inputs to {@link verifyWhatsAppSignature}. All values are taken verbatim from\n * the request — the helper does no header lookups or body reads of its own.\n */\nexport type VerifyWhatsAppSignatureInput = {\n /** App secret (`WhatsAppConfig.appSecret`). */\n appSecret: string;\n /** Raw, unparsed request body — exactly as Meta sent it. */\n rawBody: string;\n /** Value of the `X-Hub-Signature-256` header (e.g. `\"sha256=abc123…\"`). */\n signature: string | undefined;\n};\n\n/**\n * Verifies a WhatsApp Cloud API webhook signature against the app secret.\n *\n * Meta signs the raw request body with `HMAC-SHA256(appSecret, rawBody)` and\n * sends the hex digest as `X-Hub-Signature-256: sha256=<hex>`. Unlike Slack\n * there is no timestamp in the scheme, so there is no replay window.\n *\n * Pure: no request/context coupling. The caller extracts the header and raw body\n * from whatever transport it's using and passes them in.\n *\n * @throws {@link WhatsAppError} on any failure. The error's\n * `internalDetails.reason` is one of {@link WhatsAppSignatureFailureReason};\n * map to HTTP 401 at the route boundary.\n *\n * @example\n * ```ts\n * try {\n * verifyWhatsAppSignature({\n * appSecret: config.appSecret,\n * rawBody,\n * signature: req.headers['x-hub-signature-256'],\n * });\n * } catch (err) {\n * throw httpError(401).withCause(err);\n * }\n * ```\n */\nexport const verifyWhatsAppSignature = (input: VerifyWhatsAppSignatureInput): void => {\n const { appSecret, rawBody, signature } = input;\n\n if (!signature) {\n throw new WhatsAppError('WhatsApp request missing X-Hub-Signature-256 header').withInternalDetails({\n reason: 'missing_signature' satisfies WhatsAppSignatureFailureReason,\n });\n }\n\n const expected = `sha256=${createHmac('sha256', appSecret).update(rawBody).digest('hex')}`;\n const expectedBuf = Buffer.from(expected, 'utf8');\n const providedBuf = Buffer.from(signature, 'utf8');\n\n // timingSafeEqual throws on length mismatch — short-circuit so the caller gets\n // a uniform \"invalid_signature\" error instead of a crypto exception.\n if (expectedBuf.length !== providedBuf.length || !timingSafeEqual(expectedBuf, providedBuf)) {\n throw new WhatsAppError('WhatsApp request signature does not match').withInternalDetails({\n reason: 'invalid_signature' satisfies WhatsAppSignatureFailureReason,\n });\n }\n};\n","import { timingSafeEqual } from 'node:crypto';\nimport { WhatsAppError } from './whatsapp.error.js';\n\n/** Reason codes attached to {@link WhatsAppError.internalDetails} when the verification handshake fails. */\nexport type WhatsAppVerificationFailureReason = 'invalid_mode' | 'invalid_verify_token' | 'missing_challenge';\n\n/** Query-parameter names Meta sends on the verification (`GET`) request. */\nexport const WHATSAPP_HUB_MODE_PARAM = 'hub.mode';\nexport const WHATSAPP_HUB_VERIFY_TOKEN_PARAM = 'hub.verify_token';\nexport const WHATSAPP_HUB_CHALLENGE_PARAM = 'hub.challenge';\n\n/**\n * Inputs to {@link verifyWhatsAppWebhook}. Values are taken verbatim from the\n * verification request's query string.\n */\nexport type VerifyWhatsAppWebhookInput = {\n /** Configured token to match against (`WhatsAppConfig.verifyToken`). */\n verifyToken: string;\n /** Value of the `hub.mode` query parameter (Meta sends `\"subscribe\"`). */\n mode: string | undefined;\n /** Value of the `hub.verify_token` query parameter. */\n token: string | undefined;\n /** Value of the `hub.challenge` query parameter, echoed back on success. */\n challenge: string | undefined;\n};\n\n/**\n * Verifies the WhatsApp webhook subscription handshake.\n *\n * When you register a webhook, Meta sends a one-off `GET` with\n * `hub.mode=subscribe`, `hub.verify_token=<yours>`, and a random\n * `hub.challenge`. You must confirm the token matches and respond with the\n * challenge value (HTTP 200, `text/plain`).\n *\n * @returns The `hub.challenge` value to write back as the plain-text response.\n * @throws {@link WhatsAppError} when the mode is not `subscribe`, the token does\n * not match (constant-time compared), or the challenge is absent. The error's\n * `internalDetails.reason` is a {@link WhatsAppVerificationFailureReason}; map\n * to HTTP 403 at the route boundary.\n *\n * @example\n * ```ts\n * router.get('/whatsapp/webhook', (ctx) => {\n * ctx.body = verifyWhatsAppWebhook({\n * verifyToken: ctx.container.get(WhatsAppConfig).verifyToken,\n * mode: ctx.query['hub.mode'],\n * token: ctx.query['hub.verify_token'],\n * challenge: ctx.query['hub.challenge'],\n * });\n * });\n * ```\n */\nexport const verifyWhatsAppWebhook = (input: VerifyWhatsAppWebhookInput): string => {\n const { verifyToken, mode, token, challenge } = input;\n\n if (mode !== 'subscribe') {\n throw new WhatsAppError('WhatsApp webhook verification mode is not \"subscribe\"').withInternalDetails({\n reason: 'invalid_mode' satisfies WhatsAppVerificationFailureReason,\n mode,\n });\n }\n\n const expectedBuf = Buffer.from(verifyToken, 'utf8');\n const providedBuf = Buffer.from(token ?? '', 'utf8');\n if (expectedBuf.length !== providedBuf.length || !timingSafeEqual(expectedBuf, providedBuf)) {\n throw new WhatsAppError('WhatsApp webhook verify token does not match').withInternalDetails({\n reason: 'invalid_verify_token' satisfies WhatsAppVerificationFailureReason,\n });\n }\n\n if (!challenge) {\n throw new WhatsAppError('WhatsApp webhook verification missing hub.challenge').withInternalDetails({\n reason: 'missing_challenge' satisfies WhatsAppVerificationFailureReason,\n });\n }\n\n return challenge;\n};\n","import { Injectable } from 'injectkit';\nimport { Policy, PolicyEnvelope, PolicyResult } from '@maroonedsoftware/policies';\nimport { WhatsAppConfig } from './whatsapp.config.js';\nimport { IsWhatsAppError } from './whatsapp.error.js';\nimport { verifyWhatsAppSignature, WHATSAPP_SIGNATURE_HEADER, type WhatsAppSignatureFailureReason } from './whatsapp.signature.js';\n\n/**\n * Policy name under which {@link WhatsAppSignaturePolicy} is registered. Use as\n * the key when wiring your `PolicyRegistryMap`, and pass to `PolicyService.check`.\n */\nexport const WHATSAPP_SIGNATURE_POLICY = 'whatsapp.signature.valid' as const;\n\n/**\n * Configuration the {@link WhatsAppSignaturePolicy} reads. A structural subset of\n * {@link WhatsAppConfig}, so a `WhatsAppConfig` value satisfies it directly — e.g.\n * `requireSignature<WhatsAppSignatureOptions>('whatsapp')` with the WhatsApp\n * config stored under that `AppConfig` key.\n */\nexport type WhatsAppSignatureOptions = Pick<WhatsAppConfig, 'appSecret'>;\n\n/**\n * Context for {@link WhatsAppSignaturePolicy}: the raw request bytes, a\n * case-insensitive header accessor, and the {@link WhatsAppSignatureOptions}.\n *\n * Structurally compatible with `@maroonedsoftware/koa`'s\n * `SignaturePolicyContext<WhatsAppSignatureOptions>`, so the koa\n * `requireSignature` middleware can drive this policy without the whatsapp\n * package depending on koa.\n */\nexport interface WhatsAppSignaturePolicyContext {\n /** Raw, unparsed request body — exactly as Meta sent it (from `ctx.rawBody`). */\n rawBody: string | Uint8Array;\n /** Case-insensitive request header accessor (Koa's `ctx.get`); returns `''` when absent. */\n getHeader: (name: string) => string;\n /** WhatsApp signing configuration. */\n options: WhatsAppSignatureOptions;\n}\n\n/**\n * Policy form of {@link verifyWhatsAppSignature}: verifies a WhatsApp webhook\n * request against the app secret (HMAC-SHA256 over the raw body, `sha256=`-prefixed).\n *\n * Delegates to {@link verifyWhatsAppSignature} so the crypto logic has a single\n * source of truth, but answers as a {@link PolicyResult} rather than throwing:\n * allows on success, denies on failure with the helper's\n * {@link WhatsAppSignatureFailureReason} as the denial `reason` — never the app\n * secret on the wire.\n *\n * Registered by default under {@link WHATSAPP_SIGNATURE_POLICY}.\n *\n * @example\n * ```ts\n * const result = await policyService.check(WHATSAPP_SIGNATURE_POLICY, {\n * rawBody: ctx.rawBody,\n * getHeader: name => ctx.get(name),\n * options: ctx.container.get(WhatsAppConfig),\n * });\n * if (isPolicyResultDenied(result)) throw httpError(401);\n * ```\n */\n@Injectable()\nexport class WhatsAppSignaturePolicy extends Policy<WhatsAppSignaturePolicyContext> {\n async evaluate(context: WhatsAppSignaturePolicyContext, _envelope: PolicyEnvelope): Promise<PolicyResult> {\n const { rawBody, getHeader, options } = context;\n\n // Meta signs the raw text body; `ctx.rawBody` may arrive as a Buffer.\n const body = typeof rawBody === 'string' ? rawBody : Buffer.from(rawBody).toString('utf8');\n\n try {\n verifyWhatsAppSignature({ appSecret: options.appSecret, rawBody: body, signature: getHeader(WHATSAPP_SIGNATURE_HEADER) });\n return this.allow();\n } catch (error) {\n if (!IsWhatsAppError(error)) throw error;\n\n const internalDetails = error.internalDetails ?? {};\n const reason = typeof internalDetails.reason === 'string' ? internalDetails.reason : ('invalid_signature' satisfies WhatsAppSignatureFailureReason);\n return this.deny(reason, undefined, { message: error.message, ...internalDetails });\n }\n }\n}\n","import { Injectable } from 'injectkit';\nimport { Logger } from '@maroonedsoftware/logger';\nimport {\n interactiveReplyId,\n WhatsAppInteractiveHandler,\n WhatsAppMessageHandler,\n WhatsAppStatusHandler,\n type WhatsAppMessage,\n type WhatsAppMessageContext,\n type WhatsAppStatus,\n type WhatsAppValue,\n type WhatsAppWebhookBody,\n} from './whatsapp.message.handler.js';\n\n/**\n * Injectable map of WhatsApp message `type` → {@link WhatsAppMessageHandler}.\n *\n * @example\n * ```ts\n * const messages = new WhatsAppMessageHandlerMap();\n * messages.set('text', container.get(TextMessageHandler));\n * container.register(WhatsAppMessageHandlerMap, { useValue: messages });\n * ```\n */\n@Injectable()\nexport class WhatsAppMessageHandlerMap extends Map<string, WhatsAppMessageHandler> {}\n\n/**\n * Injectable map of interactive reply id → {@link WhatsAppInteractiveHandler}.\n * Keys are the developer-defined ids produced by `interactiveReplyId(message)`.\n *\n * @example\n * ```ts\n * const interactives = new WhatsAppInteractiveHandlerMap();\n * interactives.set('confirm_order', container.get(ConfirmOrderHandler));\n * container.register(WhatsAppInteractiveHandlerMap, { useValue: interactives });\n * ```\n */\n@Injectable()\nexport class WhatsAppInteractiveHandlerMap extends Map<string, WhatsAppInteractiveHandler> {}\n\n/**\n * Injectable map of delivery status value (`sent`/`delivered`/`read`/`failed`) →\n * {@link WhatsAppStatusHandler}.\n */\n@Injectable()\nexport class WhatsAppStatusHandlerMap extends Map<string, WhatsAppStatusHandler> {}\n\n/**\n * Single entry point for dispatching parsed WhatsApp Cloud API webhook bodies to\n * registered handlers. Transport-agnostic: the consumer receives the HTTP\n * request, verifies the signature, parses the JSON body, calls\n * {@link WhatsAppDispatcher.dispatchWebhook}, and acks `200`.\n *\n * A webhook body is a batch — the dispatcher walks every entry → change → value,\n * dispatching each message and status. WhatsApp retries any non-2xx, so handlers\n * should ack quickly and offload slow work.\n *\n * @example Koa route (POST — message delivery)\n * ```ts\n * router.post('/whatsapp/webhook', async (ctx) => {\n * const raw = await rawBody(ctx.req, { encoding: 'utf8' });\n * verifyWhatsAppSignature({\n * appSecret: ctx.container.get(WhatsAppConfig).appSecret,\n * rawBody: raw,\n * signature: ctx.get('x-hub-signature-256'),\n * });\n * await ctx.container.get(WhatsAppDispatcher).dispatchWebhook(JSON.parse(raw));\n * ctx.status = 200;\n * });\n * ```\n */\n@Injectable()\nexport class WhatsAppDispatcher {\n constructor(\n private readonly messages: WhatsAppMessageHandlerMap,\n private readonly interactives: WhatsAppInteractiveHandlerMap,\n private readonly statuses: WhatsAppStatusHandlerMap,\n private readonly logger: Logger,\n ) {}\n\n /**\n * Walks the batched webhook body, dispatching every message and status it\n * contains. Resolves once all handlers have settled.\n */\n async dispatchWebhook(body: WhatsAppWebhookBody): Promise<void> {\n for (const entry of body.entry ?? []) {\n for (const change of entry.changes ?? []) {\n const value = change.value;\n if (!value) continue;\n for (const message of value.messages ?? []) {\n await this.dispatchMessage(message, entry.id, value);\n }\n for (const status of value.statuses ?? []) {\n await this.dispatchStatus(status, entry.id, value);\n }\n }\n }\n }\n\n /**\n * Dispatch a single message. Interactive and quick-reply-button messages are\n * routed by their developer-defined id ({@link WhatsAppInteractiveHandlerMap})\n * first; everything else (and id-less interactives with no specific handler)\n * falls back to the message-type map ({@link WhatsAppMessageHandlerMap}).\n */\n private async dispatchMessage(message: WhatsAppMessage, wabaId: string, value: WhatsAppValue): Promise<void> {\n const context: WhatsAppMessageContext = {\n phoneNumberId: value.metadata.phone_number_id,\n displayPhoneNumber: value.metadata.display_phone_number,\n wabaId,\n contact: value.contacts?.find(c => c.wa_id === message.from) ?? value.contacts?.[0],\n value,\n };\n\n const replyId = interactiveReplyId(message);\n if (replyId) {\n const interactive = this.interactives.get(replyId);\n if (interactive) {\n await interactive.handle(message, context);\n return;\n }\n }\n\n const handler = this.messages.get(message.type);\n if (!handler) {\n this.logger.debug('No WhatsApp message handler registered', { type: message.type, replyId });\n return;\n }\n await handler.handle(message, context);\n }\n\n /** Dispatch a single delivery status, keyed by `status.status`. */\n private async dispatchStatus(status: WhatsAppStatus, wabaId: string, value: WhatsAppValue): Promise<void> {\n const handler = this.statuses.get(status.status);\n if (!handler) {\n this.logger.debug('No WhatsApp status handler registered', { status: status.status });\n return;\n }\n await handler.handle(status, { phoneNumberId: value.metadata.phone_number_id, displayPhoneNumber: value.metadata.display_phone_number, wabaId, value });\n }\n}\n","import { Injectable } from 'injectkit';\nimport { Logger } from '@maroonedsoftware/logger';\nimport { WhatsAppConfig, WHATSAPP_DEFAULT_GRAPH_API_VERSION } from '../whatsapp.config.js';\nimport { WhatsAppError } from '../whatsapp.error.js';\n\n/** Base host for the Meta Graph API. */\nexport const WHATSAPP_GRAPH_API_HOST = 'https://graph.facebook.com';\n\n/** HTTP methods used by {@link WhatsAppClient.request}. */\ntype WhatsAppHttpMethod = 'GET' | 'POST' | 'DELETE';\n\n/**\n * Thin DI-friendly wrapper around the WhatsApp Cloud API built on `fetch` (no\n * SDK). Constructed once per request scope (or as a singleton, depending on how\n * the consumer registers it) and exposes typed helpers for the most common\n * messaging calls, plus a generic {@link request} escape hatch.\n *\n * @example\n * ```ts\n * await container.get(WhatsAppClient).sendText('15551234567', 'hello');\n * await container.get(WhatsAppClient).markAsRead('wamid.abc');\n * ```\n */\n@Injectable()\nexport class WhatsAppClient {\n private readonly version: string;\n\n constructor(\n private readonly config: WhatsAppConfig,\n private readonly logger: Logger,\n ) {\n this.version = config.graphApiVersion ?? WHATSAPP_DEFAULT_GRAPH_API_VERSION;\n }\n\n /** Sends a message via `POST /{phoneNumberId}/messages`. The body is forwarded verbatim. */\n sendMessage(body: Record<string, unknown>): Promise<unknown> {\n return this.request('POST', `/${this.config.phoneNumberId}/messages`, body);\n }\n\n /** Convenience helper for a plain text message. */\n sendText(to: string, body: string, options: { previewUrl?: boolean } = {}): Promise<unknown> {\n return this.sendMessage({\n messaging_product: 'whatsapp',\n recipient_type: 'individual',\n to,\n type: 'text',\n text: { preview_url: options.previewUrl ?? false, body },\n });\n }\n\n /** Convenience helper for an interactive message (buttons / list). */\n sendInteractive(to: string, interactive: Record<string, unknown>): Promise<unknown> {\n return this.sendMessage({ messaging_product: 'whatsapp', recipient_type: 'individual', to, type: 'interactive', interactive });\n }\n\n /** Marks an inbound message as read via the messages endpoint. */\n markAsRead(messageId: string): Promise<unknown> {\n return this.sendMessage({ messaging_product: 'whatsapp', status: 'read', message_id: messageId });\n }\n\n /**\n * Low-level request helper. Prefixes the Graph API host + version, sets JSON\n * headers, adds the `Authorization: Bearer <accessToken>` header, and throws\n * {@link WhatsAppError} on a non-2xx response.\n *\n * Returns the parsed JSON body, or `undefined` for empty responses.\n */\n async request(method: WhatsAppHttpMethod, path: string, body?: unknown): Promise<unknown> {\n const url = `${WHATSAPP_GRAPH_API_HOST}/${this.version}${path}`;\n const response = await fetch(url, {\n method,\n headers: { 'content-type': 'application/json', authorization: `Bearer ${this.config.accessToken}` },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n this.logger.warn('WhatsApp Graph API call returned non-OK status', { status: response.status, method, path });\n throw new WhatsAppError(`WhatsApp Graph API call ${method} ${path} returned ${response.status}`).withInternalDetails({\n status: response.status,\n body: text,\n url,\n });\n }\n\n const text = await response.text().catch(() => '');\n if (!text) return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n }\n}\n"],"mappings":"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","names":["Injectable","WHATSAPP_DEFAULT_GRAPH_API_VERSION","WhatsAppConfig","ServerkitError","WhatsAppError","ServerkitError","IsWhatsAppError","error","createHmac","timingSafeEqual","WHATSAPP_SIGNATURE_HEADER","verifyWhatsAppSignature","input","appSecret","rawBody","signature","WhatsAppError","withInternalDetails","reason","expected","createHmac","update","digest","expectedBuf","Buffer","from","providedBuf","length","timingSafeEqual","timingSafeEqual","WHATSAPP_HUB_MODE_PARAM","WHATSAPP_HUB_VERIFY_TOKEN_PARAM","WHATSAPP_HUB_CHALLENGE_PARAM","verifyWhatsAppWebhook","input","verifyToken","mode","token","challenge","WhatsAppError","withInternalDetails","reason","expectedBuf","Buffer","from","providedBuf","length","timingSafeEqual","Injectable","Policy","WHATSAPP_SIGNATURE_POLICY","WhatsAppSignaturePolicy","Policy","evaluate","context","_envelope","rawBody","getHeader","options","body","Buffer","from","toString","verifyWhatsAppSignature","appSecret","signature","WHATSAPP_SIGNATURE_HEADER","allow","error","IsWhatsAppError","internalDetails","reason","deny","undefined","message","Injectable","Logger","WhatsAppMessageHandlerMap","Map","WhatsAppInteractiveHandlerMap","WhatsAppStatusHandlerMap","WhatsAppDispatcher","messages","interactives","statuses","logger","dispatchWebhook","body","entry","change","changes","value","message","dispatchMessage","id","status","dispatchStatus","wabaId","context","phoneNumberId","metadata","phone_number_id","displayPhoneNumber","display_phone_number","contact","contacts","find","c","wa_id","from","replyId","interactiveReplyId","interactive","get","handle","handler","type","debug","Injectable","Logger","WHATSAPP_GRAPH_API_HOST","WhatsAppClient","version","config","logger","graphApiVersion","WHATSAPP_DEFAULT_GRAPH_API_VERSION","sendMessage","body","request","phoneNumberId","sendText","to","options","messaging_product","recipient_type","type","text","preview_url","previewUrl","sendInteractive","interactive","markAsRead","messageId","status","message_id","method","path","url","response","fetch","headers","authorization","accessToken","undefined","JSON","stringify","ok","catch","warn","WhatsAppError","withInternalDetails","parse"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Default Graph API version used by {@link import('./client/whatsapp.client.js').WhatsAppClient}. */
|
|
2
|
+
export declare const WHATSAPP_DEFAULT_GRAPH_API_VERSION = "v21.0";
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for the WhatsApp package. Declared as an abstract `@Injectable()`
|
|
5
|
+
* class so it doubles as a DI token (mirrors the `Logger` pattern in
|
|
6
|
+
* `@maroonedsoftware/logger` and `SlackConfig` in `@maroonedsoftware/slack`).
|
|
7
|
+
*
|
|
8
|
+
* Consumers register a concrete value at bootstrap, typically resolved from
|
|
9
|
+
* `AppConfig`:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* const whatsappConfig = appConfig.getAs<WhatsAppConfig>('whatsapp');
|
|
13
|
+
* container.register(WhatsAppConfig, { useValue: whatsappConfig });
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* Services in this package take `WhatsAppConfig` directly in their constructor.
|
|
17
|
+
*/
|
|
18
|
+
export interface WhatsAppConfig {
|
|
19
|
+
/** Graph API access token (system-user or app token) sent as `Authorization: Bearer`. */
|
|
20
|
+
accessToken: string;
|
|
21
|
+
/** Phone number ID the bot sends from (`POST /{phoneNumberId}/messages`). */
|
|
22
|
+
phoneNumberId: string;
|
|
23
|
+
/** App secret used to verify the `X-Hub-Signature-256` HMAC on incoming webhooks. */
|
|
24
|
+
appSecret: string;
|
|
25
|
+
/** Token echoed during the webhook verification (`GET`) handshake. */
|
|
26
|
+
verifyToken: string;
|
|
27
|
+
/** Graph API version. Defaults to {@link WHATSAPP_DEFAULT_GRAPH_API_VERSION}. */
|
|
28
|
+
graphApiVersion?: string;
|
|
29
|
+
}
|
|
30
|
+
export declare abstract class WhatsAppConfig implements WhatsAppConfig {
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=whatsapp.config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whatsapp.config.d.ts","sourceRoot":"","sources":["../src/whatsapp.config.ts"],"names":[],"mappings":"AAGA,sGAAsG;AACtG,eAAO,MAAM,kCAAkC,UAAU,CAAC;AAE1D;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,cAAc;IAC7B,yFAAyF;IACzF,WAAW,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,aAAa,EAAE,MAAM,CAAC;IACtB,qFAAqF;IACrF,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,WAAW,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,8BACsB,cAAe,YAAW,cAAc;CAAG"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Logger } from '@maroonedsoftware/logger';
|
|
2
|
+
import { WhatsAppInteractiveHandler, WhatsAppMessageHandler, WhatsAppStatusHandler, type WhatsAppWebhookBody } from './whatsapp.message.handler.js';
|
|
3
|
+
/**
|
|
4
|
+
* Injectable map of WhatsApp message `type` → {@link WhatsAppMessageHandler}.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* const messages = new WhatsAppMessageHandlerMap();
|
|
9
|
+
* messages.set('text', container.get(TextMessageHandler));
|
|
10
|
+
* container.register(WhatsAppMessageHandlerMap, { useValue: messages });
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export declare class WhatsAppMessageHandlerMap extends Map<string, WhatsAppMessageHandler> {
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Injectable map of interactive reply id → {@link WhatsAppInteractiveHandler}.
|
|
17
|
+
* Keys are the developer-defined ids produced by `interactiveReplyId(message)`.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const interactives = new WhatsAppInteractiveHandlerMap();
|
|
22
|
+
* interactives.set('confirm_order', container.get(ConfirmOrderHandler));
|
|
23
|
+
* container.register(WhatsAppInteractiveHandlerMap, { useValue: interactives });
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare class WhatsAppInteractiveHandlerMap extends Map<string, WhatsAppInteractiveHandler> {
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Injectable map of delivery status value (`sent`/`delivered`/`read`/`failed`) →
|
|
30
|
+
* {@link WhatsAppStatusHandler}.
|
|
31
|
+
*/
|
|
32
|
+
export declare class WhatsAppStatusHandlerMap extends Map<string, WhatsAppStatusHandler> {
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Single entry point for dispatching parsed WhatsApp Cloud API webhook bodies to
|
|
36
|
+
* registered handlers. Transport-agnostic: the consumer receives the HTTP
|
|
37
|
+
* request, verifies the signature, parses the JSON body, calls
|
|
38
|
+
* {@link WhatsAppDispatcher.dispatchWebhook}, and acks `200`.
|
|
39
|
+
*
|
|
40
|
+
* A webhook body is a batch — the dispatcher walks every entry → change → value,
|
|
41
|
+
* dispatching each message and status. WhatsApp retries any non-2xx, so handlers
|
|
42
|
+
* should ack quickly and offload slow work.
|
|
43
|
+
*
|
|
44
|
+
* @example Koa route (POST — message delivery)
|
|
45
|
+
* ```ts
|
|
46
|
+
* router.post('/whatsapp/webhook', async (ctx) => {
|
|
47
|
+
* const raw = await rawBody(ctx.req, { encoding: 'utf8' });
|
|
48
|
+
* verifyWhatsAppSignature({
|
|
49
|
+
* appSecret: ctx.container.get(WhatsAppConfig).appSecret,
|
|
50
|
+
* rawBody: raw,
|
|
51
|
+
* signature: ctx.get('x-hub-signature-256'),
|
|
52
|
+
* });
|
|
53
|
+
* await ctx.container.get(WhatsAppDispatcher).dispatchWebhook(JSON.parse(raw));
|
|
54
|
+
* ctx.status = 200;
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export declare class WhatsAppDispatcher {
|
|
59
|
+
private readonly messages;
|
|
60
|
+
private readonly interactives;
|
|
61
|
+
private readonly statuses;
|
|
62
|
+
private readonly logger;
|
|
63
|
+
constructor(messages: WhatsAppMessageHandlerMap, interactives: WhatsAppInteractiveHandlerMap, statuses: WhatsAppStatusHandlerMap, logger: Logger);
|
|
64
|
+
/**
|
|
65
|
+
* Walks the batched webhook body, dispatching every message and status it
|
|
66
|
+
* contains. Resolves once all handlers have settled.
|
|
67
|
+
*/
|
|
68
|
+
dispatchWebhook(body: WhatsAppWebhookBody): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Dispatch a single message. Interactive and quick-reply-button messages are
|
|
71
|
+
* routed by their developer-defined id ({@link WhatsAppInteractiveHandlerMap})
|
|
72
|
+
* first; everything else (and id-less interactives with no specific handler)
|
|
73
|
+
* falls back to the message-type map ({@link WhatsAppMessageHandlerMap}).
|
|
74
|
+
*/
|
|
75
|
+
private dispatchMessage;
|
|
76
|
+
/** Dispatch a single delivery status, keyed by `status.status`. */
|
|
77
|
+
private dispatchStatus;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=whatsapp.dispatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whatsapp.dispatcher.d.ts","sourceRoot":"","sources":["../src/whatsapp.dispatcher.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAEL,0BAA0B,EAC1B,sBAAsB,EACtB,qBAAqB,EAKrB,KAAK,mBAAmB,EACzB,MAAM,+BAA+B,CAAC;AAEvC;;;;;;;;;GASG;AACH,qBACa,yBAA0B,SAAQ,GAAG,CAAC,MAAM,EAAE,sBAAsB,CAAC;CAAG;AAErF;;;;;;;;;;GAUG;AACH,qBACa,6BAA8B,SAAQ,GAAG,CAAC,MAAM,EAAE,0BAA0B,CAAC;CAAG;AAE7F;;;GAGG;AACH,qBACa,wBAAyB,SAAQ,GAAG,CAAC,MAAM,EAAE,qBAAqB,CAAC;CAAG;AAEnF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBACa,kBAAkB;IAE3B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAHN,QAAQ,EAAE,yBAAyB,EACnC,YAAY,EAAE,6BAA6B,EAC3C,QAAQ,EAAE,wBAAwB,EAClC,MAAM,EAAE,MAAM;IAGjC;;;OAGG;IACG,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAe/D;;;;;OAKG;YACW,eAAe;IA0B7B,mEAAmE;YACrD,cAAc;CAQ7B"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ServerkitError } from '@maroonedsoftware/errors';
|
|
2
|
+
/**
|
|
3
|
+
* Domain error raised by the WhatsApp package for non-HTTP failures (e.g. a
|
|
4
|
+
* Graph API call returned a non-2xx status, signature verification failed,
|
|
5
|
+
* webhook verification handshake failed).
|
|
6
|
+
*
|
|
7
|
+
* Extends {@link ServerkitError} so `errorMiddleware` renders a 500 with
|
|
8
|
+
* `{ message, details }` if one of these escapes a route handler. Inside route
|
|
9
|
+
* handlers, throw `httpError(...)` directly for status-coded responses.
|
|
10
|
+
*/
|
|
11
|
+
export declare class WhatsAppError extends ServerkitError {
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Type guard for {@link WhatsAppError}. Narrows `unknown` to `WhatsAppError` so
|
|
15
|
+
* `details`, `internalDetails`, and the chainable setters are accessible without
|
|
16
|
+
* further checks. Returns `true` for any subclass.
|
|
17
|
+
*/
|
|
18
|
+
export declare const IsWhatsAppError: (error: unknown) => error is WhatsAppError;
|
|
19
|
+
//# sourceMappingURL=whatsapp.error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whatsapp.error.d.ts","sourceRoot":"","sources":["../src/whatsapp.error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1D;;;;;;;;GAQG;AACH,qBAAa,aAAc,SAAQ,cAAc;CAAG;AAEpD;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,aAA+C,CAAC"}
|