better-zap 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/LICENSE +15 -0
- package/README.md +11 -0
- package/dist/client-ColqW3Zc.d.mts +769 -0
- package/dist/client-D5Lgtacj.d.cts +769 -0
- package/dist/client.cjs +51 -0
- package/dist/client.d.cts +2 -0
- package/dist/client.d.mts +2 -0
- package/dist/client.mjs +50 -0
- package/dist/index.cjs +580 -0
- package/dist/index.d.cts +123 -0
- package/dist/index.d.mts +123 -0
- package/dist/index.mjs +566 -0
- package/package.json +45 -0
|
@@ -0,0 +1,769 @@
|
|
|
1
|
+
//#region src/types/whatsapp.types.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Meta WhatsApp Cloud API v25.0 Type Definitions
|
|
4
|
+
* https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages
|
|
5
|
+
*/
|
|
6
|
+
interface WhatsAppTextMessage {
|
|
7
|
+
messaging_product: "whatsapp";
|
|
8
|
+
recipient_type: "individual";
|
|
9
|
+
to: string;
|
|
10
|
+
type: "text";
|
|
11
|
+
text: {
|
|
12
|
+
preview_url?: boolean;
|
|
13
|
+
body: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
interface WhatsAppTemplateMessage {
|
|
17
|
+
messaging_product: "whatsapp";
|
|
18
|
+
to: string;
|
|
19
|
+
type: "template";
|
|
20
|
+
template: {
|
|
21
|
+
name: string;
|
|
22
|
+
language: {
|
|
23
|
+
code: string;
|
|
24
|
+
};
|
|
25
|
+
components?: TemplateComponent[];
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
interface WhatsAppInteractiveButtonsMessage {
|
|
29
|
+
messaging_product: "whatsapp";
|
|
30
|
+
recipient_type: "individual";
|
|
31
|
+
to: string;
|
|
32
|
+
type: "interactive";
|
|
33
|
+
interactive: {
|
|
34
|
+
type: "button";
|
|
35
|
+
body: {
|
|
36
|
+
text: string;
|
|
37
|
+
};
|
|
38
|
+
action: {
|
|
39
|
+
buttons: Array<{
|
|
40
|
+
type: "reply";
|
|
41
|
+
reply: {
|
|
42
|
+
id: string;
|
|
43
|
+
title: string;
|
|
44
|
+
};
|
|
45
|
+
}>;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
interface WhatsAppInteractiveListMessage {
|
|
50
|
+
messaging_product: "whatsapp";
|
|
51
|
+
recipient_type: "individual";
|
|
52
|
+
to: string;
|
|
53
|
+
type: "interactive";
|
|
54
|
+
interactive: {
|
|
55
|
+
type: "list";
|
|
56
|
+
body: {
|
|
57
|
+
text: string;
|
|
58
|
+
};
|
|
59
|
+
action: {
|
|
60
|
+
button: string;
|
|
61
|
+
sections: Array<{
|
|
62
|
+
title: string;
|
|
63
|
+
rows: Array<{
|
|
64
|
+
id: string;
|
|
65
|
+
title: string;
|
|
66
|
+
description?: string;
|
|
67
|
+
}>;
|
|
68
|
+
}>;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
interface WhatsAppInteractiveMediaCarouselMessage {
|
|
73
|
+
messaging_product: "whatsapp";
|
|
74
|
+
recipient_type: "individual";
|
|
75
|
+
to: string;
|
|
76
|
+
type: "interactive";
|
|
77
|
+
interactive: {
|
|
78
|
+
type: "carousel";
|
|
79
|
+
body: {
|
|
80
|
+
text: string;
|
|
81
|
+
};
|
|
82
|
+
action: {
|
|
83
|
+
cards: WhatsAppCarouselCard[];
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
interface InteractiveMediaCarouselCardInput {
|
|
88
|
+
header: {
|
|
89
|
+
type: "image" | "video";
|
|
90
|
+
link: string;
|
|
91
|
+
};
|
|
92
|
+
bodyText?: string;
|
|
93
|
+
button: {
|
|
94
|
+
displayText: string;
|
|
95
|
+
url: string;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
interface SendInteractiveMediaCarouselData {
|
|
99
|
+
to: string;
|
|
100
|
+
body: string;
|
|
101
|
+
cards: InteractiveMediaCarouselCardInput[];
|
|
102
|
+
}
|
|
103
|
+
interface WhatsAppCarouselCard {
|
|
104
|
+
card_index: number;
|
|
105
|
+
type: "cta_url";
|
|
106
|
+
header: {
|
|
107
|
+
type: "image" | "video";
|
|
108
|
+
image?: {
|
|
109
|
+
link: string;
|
|
110
|
+
};
|
|
111
|
+
video?: {
|
|
112
|
+
link: string;
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
body?: {
|
|
116
|
+
text: string;
|
|
117
|
+
};
|
|
118
|
+
action: {
|
|
119
|
+
name: "cta_url";
|
|
120
|
+
parameters: {
|
|
121
|
+
display_text: string;
|
|
122
|
+
url: string;
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
interface WhatsAppLocationMessage {
|
|
127
|
+
messaging_product: "whatsapp";
|
|
128
|
+
recipient_type: "individual";
|
|
129
|
+
to: string;
|
|
130
|
+
type: "location";
|
|
131
|
+
location: {
|
|
132
|
+
latitude: number;
|
|
133
|
+
longitude: number;
|
|
134
|
+
name: string;
|
|
135
|
+
address: string;
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
interface TemplateComponent {
|
|
139
|
+
type: "header" | "body" | "button";
|
|
140
|
+
sub_type?: "url" | "quick_reply";
|
|
141
|
+
index?: string;
|
|
142
|
+
parameters: TemplateParameter[];
|
|
143
|
+
}
|
|
144
|
+
interface TemplateParameter {
|
|
145
|
+
type: "text" | "currency" | "date_time" | "image" | "document" | "video" | "location" | "payload";
|
|
146
|
+
parameter_name?: string;
|
|
147
|
+
text?: string;
|
|
148
|
+
currency?: {
|
|
149
|
+
fallback_value: string;
|
|
150
|
+
code: string;
|
|
151
|
+
amount_1000: number;
|
|
152
|
+
};
|
|
153
|
+
date_time?: {
|
|
154
|
+
fallback_value: string;
|
|
155
|
+
};
|
|
156
|
+
image?: {
|
|
157
|
+
link: string;
|
|
158
|
+
};
|
|
159
|
+
video?: {
|
|
160
|
+
link: string;
|
|
161
|
+
};
|
|
162
|
+
document?: {
|
|
163
|
+
link: string;
|
|
164
|
+
};
|
|
165
|
+
location?: {
|
|
166
|
+
latitude: number;
|
|
167
|
+
longitude: number;
|
|
168
|
+
name: string;
|
|
169
|
+
address: string;
|
|
170
|
+
};
|
|
171
|
+
payload?: string;
|
|
172
|
+
}
|
|
173
|
+
interface SendMessageResponse {
|
|
174
|
+
messaging_product: "whatsapp";
|
|
175
|
+
contacts: Array<{
|
|
176
|
+
input: string;
|
|
177
|
+
wa_id: string;
|
|
178
|
+
}>;
|
|
179
|
+
messages: Array<{
|
|
180
|
+
id: string;
|
|
181
|
+
}>;
|
|
182
|
+
}
|
|
183
|
+
interface SendMessageError {
|
|
184
|
+
error: {
|
|
185
|
+
message: string;
|
|
186
|
+
type: string;
|
|
187
|
+
code: number;
|
|
188
|
+
error_subcode?: number;
|
|
189
|
+
error_data?: {
|
|
190
|
+
messaging_product: string;
|
|
191
|
+
details: string;
|
|
192
|
+
};
|
|
193
|
+
fbtrace_id: string;
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
interface WebhookPayload {
|
|
197
|
+
object: "whatsapp_business_account";
|
|
198
|
+
entry: WebhookEntry[];
|
|
199
|
+
}
|
|
200
|
+
interface WebhookEntry {
|
|
201
|
+
id: string;
|
|
202
|
+
changes: WebhookChange[];
|
|
203
|
+
}
|
|
204
|
+
interface WebhookChange {
|
|
205
|
+
value: WebhookValue;
|
|
206
|
+
field: "messages";
|
|
207
|
+
}
|
|
208
|
+
interface WebhookValue {
|
|
209
|
+
messaging_product: "whatsapp";
|
|
210
|
+
metadata: {
|
|
211
|
+
display_phone_number: string;
|
|
212
|
+
phone_number_id: string;
|
|
213
|
+
};
|
|
214
|
+
contacts?: WebhookContact[];
|
|
215
|
+
messages?: IncomingMessage[];
|
|
216
|
+
statuses?: MessageStatus[];
|
|
217
|
+
errors?: WebhookError[];
|
|
218
|
+
}
|
|
219
|
+
interface WebhookContact {
|
|
220
|
+
profile: {
|
|
221
|
+
name: string;
|
|
222
|
+
};
|
|
223
|
+
wa_id: string;
|
|
224
|
+
}
|
|
225
|
+
interface IncomingMessage {
|
|
226
|
+
from: string;
|
|
227
|
+
id: string;
|
|
228
|
+
timestamp: string;
|
|
229
|
+
type: "text" | "image" | "audio" | "video" | "document" | "location" | "contacts" | "interactive" | "button" | "reaction" | "sticker";
|
|
230
|
+
text?: {
|
|
231
|
+
body: string;
|
|
232
|
+
};
|
|
233
|
+
image?: MediaMessage;
|
|
234
|
+
audio?: MediaMessage;
|
|
235
|
+
video?: MediaMessage;
|
|
236
|
+
document?: MediaMessage & {
|
|
237
|
+
filename?: string;
|
|
238
|
+
};
|
|
239
|
+
location?: {
|
|
240
|
+
latitude: number;
|
|
241
|
+
longitude: number;
|
|
242
|
+
name?: string;
|
|
243
|
+
address?: string;
|
|
244
|
+
};
|
|
245
|
+
button?: {
|
|
246
|
+
text: string;
|
|
247
|
+
payload: string;
|
|
248
|
+
};
|
|
249
|
+
interactive?: {
|
|
250
|
+
type: "button_reply" | "list_reply";
|
|
251
|
+
button_reply?: {
|
|
252
|
+
id: string;
|
|
253
|
+
title: string;
|
|
254
|
+
};
|
|
255
|
+
list_reply?: {
|
|
256
|
+
id: string;
|
|
257
|
+
title: string;
|
|
258
|
+
description?: string;
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
context?: {
|
|
262
|
+
from: string;
|
|
263
|
+
id: string;
|
|
264
|
+
};
|
|
265
|
+
referral?: {
|
|
266
|
+
source_url: string;
|
|
267
|
+
source_type: "ad" | "post";
|
|
268
|
+
source_id: string;
|
|
269
|
+
headline: string;
|
|
270
|
+
body: string;
|
|
271
|
+
ctwa_clid?: string;
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
interface MediaMessage {
|
|
275
|
+
id: string;
|
|
276
|
+
mime_type: string;
|
|
277
|
+
sha256?: string;
|
|
278
|
+
caption?: string;
|
|
279
|
+
}
|
|
280
|
+
interface MessageStatus {
|
|
281
|
+
id: string;
|
|
282
|
+
status: "sent" | "delivered" | "read" | "failed";
|
|
283
|
+
timestamp: string;
|
|
284
|
+
recipient_id: string;
|
|
285
|
+
conversation?: {
|
|
286
|
+
id: string;
|
|
287
|
+
origin: {
|
|
288
|
+
type: "business_initiated" | "user_initiated" | "referral_conversion";
|
|
289
|
+
};
|
|
290
|
+
expiration_timestamp?: string;
|
|
291
|
+
};
|
|
292
|
+
pricing?: {
|
|
293
|
+
billable: boolean;
|
|
294
|
+
pricing_model: "CBP";
|
|
295
|
+
category: "business_initiated" | "user_initiated" | "referral_conversion";
|
|
296
|
+
};
|
|
297
|
+
errors?: MessageError[];
|
|
298
|
+
}
|
|
299
|
+
interface MessageError {
|
|
300
|
+
code: number;
|
|
301
|
+
title: string;
|
|
302
|
+
message: string;
|
|
303
|
+
error_data?: {
|
|
304
|
+
details: string;
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
interface WebhookError {
|
|
308
|
+
code: number;
|
|
309
|
+
title: string;
|
|
310
|
+
message: string;
|
|
311
|
+
error_data: {
|
|
312
|
+
details: string;
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
interface SendResult {
|
|
316
|
+
success: boolean;
|
|
317
|
+
messageId?: string;
|
|
318
|
+
error?: string;
|
|
319
|
+
errorCode?: number;
|
|
320
|
+
httpStatus?: number;
|
|
321
|
+
}
|
|
322
|
+
type Conversation = {
|
|
323
|
+
id: string;
|
|
324
|
+
phone: string;
|
|
325
|
+
contactName: string | null;
|
|
326
|
+
unreadCount: number;
|
|
327
|
+
status: string;
|
|
328
|
+
lastMessageAt: string;
|
|
329
|
+
lastMessagePreview: string | null;
|
|
330
|
+
lastDirection: string;
|
|
331
|
+
messageCount: number;
|
|
332
|
+
lastIncomingMessageAt: string | null;
|
|
333
|
+
};
|
|
334
|
+
type UIMessageStatus = "sent" | "delivered" | "read" | "failed";
|
|
335
|
+
type UIMessage = {
|
|
336
|
+
id: string;
|
|
337
|
+
phone?: string;
|
|
338
|
+
content: string | null;
|
|
339
|
+
direction: "incoming" | "outgoing";
|
|
340
|
+
status: UIMessageStatus | string;
|
|
341
|
+
sentAt: string;
|
|
342
|
+
templateName?: string | null;
|
|
343
|
+
messageType?: string | null;
|
|
344
|
+
metadata?: Record<string, any> | null;
|
|
345
|
+
};
|
|
346
|
+
//#endregion
|
|
347
|
+
//#region src/types/config.d.ts
|
|
348
|
+
/** Configuration for {@link WhatsAppService}. */
|
|
349
|
+
interface WhatsAppConfig {
|
|
350
|
+
/** System User access token for the WhatsApp Cloud API. */
|
|
351
|
+
token: string;
|
|
352
|
+
/** Phone number ID registered in Meta Business Manager. */
|
|
353
|
+
phoneId: string;
|
|
354
|
+
/** Set to `"development"` to skip real API calls and return mock responses. */
|
|
355
|
+
environment?: string;
|
|
356
|
+
}
|
|
357
|
+
//#endregion
|
|
358
|
+
//#region src/types/sync-events.d.ts
|
|
359
|
+
type ConversationSummary = Conversation;
|
|
360
|
+
type NewMessageEvent = {
|
|
361
|
+
type: "NEW_MESSAGE";
|
|
362
|
+
message: WhatsAppLogRecord;
|
|
363
|
+
conversation: ConversationSummary;
|
|
364
|
+
};
|
|
365
|
+
type StatusUpdateEvent = {
|
|
366
|
+
type: "STATUS_UPDATE";
|
|
367
|
+
waMessageId: string;
|
|
368
|
+
status: WhatsAppStatus;
|
|
369
|
+
timestamp: string;
|
|
370
|
+
deliveredAt?: string | null;
|
|
371
|
+
readAt?: string | null;
|
|
372
|
+
};
|
|
373
|
+
type ConversationUpdateEvent = {
|
|
374
|
+
type: "CONVERSATION_UPDATE";
|
|
375
|
+
conversationId: string;
|
|
376
|
+
updates: Partial<ConversationSummary>;
|
|
377
|
+
};
|
|
378
|
+
type SyncEvent = NewMessageEvent | StatusUpdateEvent | ConversationUpdateEvent;
|
|
379
|
+
//#endregion
|
|
380
|
+
//#region src/logger.d.ts
|
|
381
|
+
/**
|
|
382
|
+
* Centralized Logger for Better Zap
|
|
383
|
+
*
|
|
384
|
+
* Lightweight, injectable logger with structured JSON output.
|
|
385
|
+
* Cloudflare Workers compatible (no external dependencies).
|
|
386
|
+
*/
|
|
387
|
+
type LogLevel = "debug" | "info" | "warn" | "error";
|
|
388
|
+
/** User-facing configuration (on betterZap config). */
|
|
389
|
+
interface LoggerConfig {
|
|
390
|
+
disabled?: boolean;
|
|
391
|
+
level?: LogLevel;
|
|
392
|
+
log?: (level: LogLevel, message: string, context: Record<string, unknown>) => void;
|
|
393
|
+
}
|
|
394
|
+
/** Internal logger interface injected into services. */
|
|
395
|
+
interface Logger {
|
|
396
|
+
debug(message: string, context?: Record<string, unknown>): void;
|
|
397
|
+
info(message: string, context?: Record<string, unknown>): void;
|
|
398
|
+
warn(message: string, context?: Record<string, unknown>): void;
|
|
399
|
+
error(message: string, context?: Record<string, unknown>): void;
|
|
400
|
+
}
|
|
401
|
+
declare function createLogger(config?: LoggerConfig): Logger;
|
|
402
|
+
declare const noopLogger: Logger;
|
|
403
|
+
declare function serializeError(err: unknown): Record<string, unknown>;
|
|
404
|
+
//#endregion
|
|
405
|
+
//#region src/services/message-logger.service.d.ts
|
|
406
|
+
type WhatsAppDirection = "incoming" | "outgoing";
|
|
407
|
+
type WhatsAppStatus = "sent" | "delivered" | "read" | "failed";
|
|
408
|
+
declare const WHATSAPP_MESSAGE_TYPES: readonly ["queue_position", "next_in_line", "queue_optin", "marketing", "bot_reply", "reminder", "satisfaction", "incoming"];
|
|
409
|
+
type WhatsAppMessageType = (typeof WHATSAPP_MESSAGE_TYPES)[number];
|
|
410
|
+
interface WhatsAppLogRecord {
|
|
411
|
+
id: string;
|
|
412
|
+
conversationId: string;
|
|
413
|
+
userId?: string | null;
|
|
414
|
+
phone: string;
|
|
415
|
+
waMessageId?: string | null;
|
|
416
|
+
direction: WhatsAppDirection;
|
|
417
|
+
messageType: WhatsAppMessageType;
|
|
418
|
+
content: string | null;
|
|
419
|
+
templateName?: string | null;
|
|
420
|
+
status: WhatsAppStatus;
|
|
421
|
+
errorMessage?: string | null;
|
|
422
|
+
metadata?: any;
|
|
423
|
+
sentAt: string;
|
|
424
|
+
deliveredAt?: string | null;
|
|
425
|
+
readAt?: string | null;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Interface for database persistence of WhatsApp logs.
|
|
429
|
+
* Decouples Better Zap from any specific application database package.
|
|
430
|
+
*/
|
|
431
|
+
interface WhatsAppLogStore {
|
|
432
|
+
createWhatsAppLog(params: {
|
|
433
|
+
phone: string;
|
|
434
|
+
userId?: string;
|
|
435
|
+
contactName?: string;
|
|
436
|
+
direction: WhatsAppDirection;
|
|
437
|
+
messageType: WhatsAppMessageType;
|
|
438
|
+
content: string;
|
|
439
|
+
templateName?: string;
|
|
440
|
+
waMessageId?: string;
|
|
441
|
+
status: WhatsAppStatus;
|
|
442
|
+
errorMessage?: string;
|
|
443
|
+
metadata?: any;
|
|
444
|
+
sentAt: string;
|
|
445
|
+
}): Promise<WhatsAppLogRecord>;
|
|
446
|
+
getMessageByWaId(waMessageId: string): Promise<WhatsAppLogRecord | null>;
|
|
447
|
+
updateWhatsAppLogByWaMessageId(waMessageId: string, updates: Partial<WhatsAppLogRecord>): Promise<void>;
|
|
448
|
+
/**
|
|
449
|
+
* Atomically update status only if the new status advances the lifecycle.
|
|
450
|
+
* Returns true if the row was updated, false if skipped (duplicate/regression).
|
|
451
|
+
*
|
|
452
|
+
* Progression: sent(1) → delivered(2) → read(3). failed(4) always wins.
|
|
453
|
+
*/
|
|
454
|
+
updateStatusIfProgressed(waMessageId: string, newStatus: WhatsAppStatus, updates: Partial<WhatsAppLogRecord>): Promise<boolean>;
|
|
455
|
+
getConversationById(conversationId: string): Promise<ConversationSummary | null>;
|
|
456
|
+
getConversationByPhone(phone: string): Promise<ConversationSummary | null>;
|
|
457
|
+
getConversations(): Promise<Array<{
|
|
458
|
+
id: string;
|
|
459
|
+
phone: string;
|
|
460
|
+
contactName: string | null;
|
|
461
|
+
unreadCount: number;
|
|
462
|
+
status: string;
|
|
463
|
+
lastMessageAt: string;
|
|
464
|
+
lastMessagePreview: string | null;
|
|
465
|
+
lastDirection: string;
|
|
466
|
+
messageCount: number;
|
|
467
|
+
}>>;
|
|
468
|
+
getMessagesByConversationPaginated(conversationId: string, cursor?: string | null, limit?: number): Promise<Array<{
|
|
469
|
+
id: string;
|
|
470
|
+
phone?: string;
|
|
471
|
+
content: string | null;
|
|
472
|
+
direction: string;
|
|
473
|
+
status: string;
|
|
474
|
+
sentAt: string;
|
|
475
|
+
templateName?: string | null;
|
|
476
|
+
messageType?: string | null;
|
|
477
|
+
metadata?: any;
|
|
478
|
+
}>>;
|
|
479
|
+
/**
|
|
480
|
+
* Check if there's a recent outgoing message to this phone within N hours.
|
|
481
|
+
* Used to approximate Meta's 24h conversation window and for cooldown checks.
|
|
482
|
+
*/
|
|
483
|
+
hasRecentOutgoingMessage(phone: string, withinHours: number): Promise<boolean>;
|
|
484
|
+
}
|
|
485
|
+
interface MessageLoggerNotifier {
|
|
486
|
+
notify(event: SyncEvent): Promise<void>;
|
|
487
|
+
}
|
|
488
|
+
declare class MessageLoggerService {
|
|
489
|
+
private store;
|
|
490
|
+
private notifier?;
|
|
491
|
+
private log;
|
|
492
|
+
constructor(store: WhatsAppLogStore, log: Logger, notifier?: MessageLoggerNotifier | undefined);
|
|
493
|
+
private notify;
|
|
494
|
+
/**
|
|
495
|
+
* Check if a message with this waMessageId was already processed.
|
|
496
|
+
*/
|
|
497
|
+
isDuplicate(waMessageId: string): Promise<boolean>;
|
|
498
|
+
/**
|
|
499
|
+
* Log outgoing message for LGPD compliance
|
|
500
|
+
*/
|
|
501
|
+
logOutgoing(params: {
|
|
502
|
+
phone: string;
|
|
503
|
+
userId?: string;
|
|
504
|
+
messageType: WhatsAppMessageType;
|
|
505
|
+
content: string;
|
|
506
|
+
result: SendResult;
|
|
507
|
+
templateName?: string;
|
|
508
|
+
metadata?: Record<string, any>;
|
|
509
|
+
}): Promise<string>;
|
|
510
|
+
/**
|
|
511
|
+
* Update message status from webhook callback.
|
|
512
|
+
* Only applies if the new status advances the lifecycle (atomic, no race conditions).
|
|
513
|
+
* Returns true if the update was applied, false if skipped.
|
|
514
|
+
*/
|
|
515
|
+
updateStatus(waMessageId: string, status: WhatsAppStatus, timestamp: string, errorMessage?: string): Promise<boolean>;
|
|
516
|
+
/**
|
|
517
|
+
* Check if there's a recent outgoing message to this phone within N hours.
|
|
518
|
+
*
|
|
519
|
+
* @param {string} phone : The phone number to check (in E.164 format)
|
|
520
|
+
* @param {number} [withinHours=24] : Time in hours to look for incoming messages
|
|
521
|
+
*/
|
|
522
|
+
hasRecentOutgoingMessage(phone: string, withinHours?: number): Promise<boolean>;
|
|
523
|
+
/**
|
|
524
|
+
* Log incoming message (for audit trail)
|
|
525
|
+
*/
|
|
526
|
+
logIncoming(params: {
|
|
527
|
+
phone: string;
|
|
528
|
+
waMessageId: string;
|
|
529
|
+
content: string;
|
|
530
|
+
senderName?: string;
|
|
531
|
+
metadata?: Record<string, unknown>;
|
|
532
|
+
}): Promise<void>;
|
|
533
|
+
}
|
|
534
|
+
//#endregion
|
|
535
|
+
//#region src/services/whatsapp.service.d.ts
|
|
536
|
+
interface OutgoingLoggingMetadata {
|
|
537
|
+
userId?: string;
|
|
538
|
+
messageType: WhatsAppMessageType;
|
|
539
|
+
/** Human-readable content of the message to be stored in the log */
|
|
540
|
+
content: string;
|
|
541
|
+
/** Optional metadata for the log entry (e.g. campaign ID, additional context) */
|
|
542
|
+
metadata?: Record<string, any>;
|
|
543
|
+
}
|
|
544
|
+
declare class WhatsAppService {
|
|
545
|
+
private baseUrl;
|
|
546
|
+
private token;
|
|
547
|
+
private isDev;
|
|
548
|
+
private logger;
|
|
549
|
+
private log;
|
|
550
|
+
constructor(config: WhatsAppConfig, logger: MessageLoggerService, log: Logger);
|
|
551
|
+
/** Send a text message (within 24h service window only). */
|
|
552
|
+
sendText(to: string, body: string, logging?: Omit<OutgoingLoggingMetadata, "content">): Promise<SendResult>;
|
|
553
|
+
/** Send a template message (works outside service window). */
|
|
554
|
+
sendTemplate(to: string, templateName: string, languageCode?: string, components?: TemplateComponent[], logging?: OutgoingLoggingMetadata): Promise<SendResult>;
|
|
555
|
+
/** Send an interactive message with reply buttons (up to 3). */
|
|
556
|
+
sendInteractiveButtons(to: string, bodyText: string, buttons: Array<{
|
|
557
|
+
id: string;
|
|
558
|
+
title: string;
|
|
559
|
+
}>, logging?: Omit<OutgoingLoggingMetadata, "content">): Promise<SendResult>;
|
|
560
|
+
/** Send an interactive list message with sections and rows. */
|
|
561
|
+
sendInteractiveList(to: string, bodyText: string, buttonLabel: string, sections: Array<{
|
|
562
|
+
title: string;
|
|
563
|
+
rows: Array<{
|
|
564
|
+
id: string;
|
|
565
|
+
title: string;
|
|
566
|
+
description?: string;
|
|
567
|
+
}>;
|
|
568
|
+
}>, logging?: Omit<OutgoingLoggingMetadata, "content">): Promise<SendResult>;
|
|
569
|
+
/** Send an interactive media carousel message (2-10 cards). */
|
|
570
|
+
sendInteractiveMediaCarousel(data: SendInteractiveMediaCarouselData, logging?: Omit<OutgoingLoggingMetadata, "content">): Promise<SendResult>;
|
|
571
|
+
/** Send a location pin message. */
|
|
572
|
+
sendLocation(to: string, latitude: number, longitude: number, name: string, address: string, logging?: Omit<OutgoingLoggingMetadata, "content">): Promise<SendResult>;
|
|
573
|
+
/**
|
|
574
|
+
* Mark an inbound message as read.
|
|
575
|
+
*
|
|
576
|
+
* @see https://developers.facebook.com/docs/whatsapp/cloud-api/messages/mark-messages-as-read
|
|
577
|
+
*/
|
|
578
|
+
markAsRead(messageId: string): Promise<SendResult>;
|
|
579
|
+
/**
|
|
580
|
+
* Show or hide a typing indicator in the chat.
|
|
581
|
+
* When starting, the indicator auto-dismisses after 25 seconds or when a message is sent.
|
|
582
|
+
*
|
|
583
|
+
* @see https://developers.facebook.com/docs/whatsapp/cloud-api/typing-indicators/
|
|
584
|
+
*/
|
|
585
|
+
typingIndicator(messageId: string, action?: "typing_on" | "typing_off"): Promise<SendResult>;
|
|
586
|
+
/**
|
|
587
|
+
* Add a reaction to a message.
|
|
588
|
+
*
|
|
589
|
+
* @see https://developers.facebook.com/docs/whatsapp/cloud-api/messages/reaction-messages
|
|
590
|
+
*/
|
|
591
|
+
sendReaction(to: string, messageId: string, emoji: string): Promise<SendResult>;
|
|
592
|
+
/** Core send method with retry logic (2 retries, exponential backoff). */
|
|
593
|
+
private send;
|
|
594
|
+
/** Actually performs the network request with retries. */
|
|
595
|
+
private performRequest;
|
|
596
|
+
}
|
|
597
|
+
//#endregion
|
|
598
|
+
//#region src/template-registry.d.ts
|
|
599
|
+
type Simplify<TValue> = { [TKey in keyof TValue]: TValue[TKey] } & {};
|
|
600
|
+
type TemplateParameterInputMap = {
|
|
601
|
+
text: string;
|
|
602
|
+
payload: string;
|
|
603
|
+
location: {
|
|
604
|
+
latitude: number;
|
|
605
|
+
longitude: number;
|
|
606
|
+
name: string;
|
|
607
|
+
address: string;
|
|
608
|
+
};
|
|
609
|
+
image: {
|
|
610
|
+
link: string;
|
|
611
|
+
};
|
|
612
|
+
video: {
|
|
613
|
+
link: string;
|
|
614
|
+
};
|
|
615
|
+
document: {
|
|
616
|
+
link: string;
|
|
617
|
+
};
|
|
618
|
+
currency: {
|
|
619
|
+
fallback_value: string;
|
|
620
|
+
code: string;
|
|
621
|
+
amount_1000: number;
|
|
622
|
+
};
|
|
623
|
+
date_time: {
|
|
624
|
+
fallback_value: string;
|
|
625
|
+
};
|
|
626
|
+
};
|
|
627
|
+
type SupportedTemplateParameterType = keyof TemplateParameterInputMap;
|
|
628
|
+
interface TemplateParameterDefinition<TType extends SupportedTemplateParameterType = SupportedTemplateParameterType> {
|
|
629
|
+
name: string;
|
|
630
|
+
type: TType;
|
|
631
|
+
}
|
|
632
|
+
type TemplateComponentDefinition = {
|
|
633
|
+
type: "header";
|
|
634
|
+
parameters: readonly TemplateParameterDefinition[];
|
|
635
|
+
} | {
|
|
636
|
+
type: "body";
|
|
637
|
+
parameters: readonly TemplateParameterDefinition[];
|
|
638
|
+
} | {
|
|
639
|
+
type: "button";
|
|
640
|
+
subType: "url" | "quick_reply";
|
|
641
|
+
index: string;
|
|
642
|
+
parameters: readonly TemplateParameterDefinition[];
|
|
643
|
+
};
|
|
644
|
+
interface TemplateDefinition {
|
|
645
|
+
language: string;
|
|
646
|
+
components?: readonly TemplateComponentDefinition[];
|
|
647
|
+
}
|
|
648
|
+
type TemplateRegistry = Record<string, TemplateDefinition>;
|
|
649
|
+
type TemplateParameterDefinitionUnion<TTemplate extends TemplateDefinition> = TTemplate["components"] extends readonly TemplateComponentDefinition[] ? TTemplate["components"][number]["parameters"][number] : never;
|
|
650
|
+
type TemplateParameterInput<TParameter extends TemplateParameterDefinition> = TParameter["type"] extends keyof TemplateParameterInputMap ? TemplateParameterInputMap[TParameter["type"]] : never;
|
|
651
|
+
type TemplateParams<TTemplate extends TemplateDefinition> = Simplify<{ [TParameter in TemplateParameterDefinitionUnion<TTemplate> as TParameter["name"]]: TemplateParameterInput<TParameter> }>;
|
|
652
|
+
type TemplateName<TTemplates extends TemplateRegistry> = Extract<keyof TTemplates, string>;
|
|
653
|
+
type TypedTemplateOptions<TTemplates extends TemplateRegistry, TName extends TemplateName<TTemplates>> = {
|
|
654
|
+
language?: string;
|
|
655
|
+
params: TemplateParams<TTemplates[TName]>;
|
|
656
|
+
};
|
|
657
|
+
declare const EMPTY_TEMPLATE_REGISTRY: {};
|
|
658
|
+
declare function defineTemplates<const TTemplates extends TemplateRegistry>(templates: TTemplates): TTemplates;
|
|
659
|
+
declare function hasConfiguredTemplates<TTemplates extends TemplateRegistry>(templates: TTemplates): boolean;
|
|
660
|
+
declare function getTemplateNames<TTemplates extends TemplateRegistry>(templates: TTemplates): TemplateName<TTemplates>[];
|
|
661
|
+
declare function serializeTemplateFromRegistry(templates: TemplateRegistry, templateName: string, options: {
|
|
662
|
+
language?: string;
|
|
663
|
+
params: Record<string, unknown>;
|
|
664
|
+
}): {
|
|
665
|
+
language: string;
|
|
666
|
+
components?: TemplateComponent[];
|
|
667
|
+
};
|
|
668
|
+
declare function serializeTemplateFromRegistry<TTemplates extends TemplateRegistry, TName extends TemplateName<TTemplates>>(templates: TTemplates, templateName: TName, options: TypedTemplateOptions<TTemplates, TName>): {
|
|
669
|
+
language: string;
|
|
670
|
+
components?: TemplateComponent[];
|
|
671
|
+
};
|
|
672
|
+
//#endregion
|
|
673
|
+
//#region src/client.d.ts
|
|
674
|
+
interface ZapClientOptions<TTemplates extends TemplateRegistry = {}> {
|
|
675
|
+
/**
|
|
676
|
+
* Base URL for the API. Defaults to `window.location.origin` in the browser.
|
|
677
|
+
* Useful for SSR or custom proxy setups.
|
|
678
|
+
*/
|
|
679
|
+
baseURL?: string;
|
|
680
|
+
/**
|
|
681
|
+
* Base path for all routes. Must match the `basePath` used in `betterZap()`.
|
|
682
|
+
* @default "/api/whatsapp"
|
|
683
|
+
*/
|
|
684
|
+
basePath?: string;
|
|
685
|
+
/**
|
|
686
|
+
* Custom fetch implementation. Defaults to the global `fetch`.
|
|
687
|
+
*/
|
|
688
|
+
fetch?: typeof fetch;
|
|
689
|
+
/**
|
|
690
|
+
* Optional template registry used only for type inference.
|
|
691
|
+
* The HTTP server still owns runtime serialization and validation.
|
|
692
|
+
*/
|
|
693
|
+
templates?: TTemplates;
|
|
694
|
+
}
|
|
695
|
+
interface SendTextParams {
|
|
696
|
+
to: string;
|
|
697
|
+
body: string;
|
|
698
|
+
messageType?: string;
|
|
699
|
+
userId?: string;
|
|
700
|
+
metadata?: Record<string, unknown>;
|
|
701
|
+
}
|
|
702
|
+
interface SendTemplateRawParams {
|
|
703
|
+
to: string;
|
|
704
|
+
template: string;
|
|
705
|
+
language?: string;
|
|
706
|
+
components?: unknown[];
|
|
707
|
+
logging?: OutgoingLoggingMetadata;
|
|
708
|
+
}
|
|
709
|
+
type SendTemplateTypedParams<TTemplates extends TemplateRegistry, TName extends TemplateName<TTemplates>> = {
|
|
710
|
+
to: string;
|
|
711
|
+
template: TName;
|
|
712
|
+
language?: string;
|
|
713
|
+
params: TemplateParams<TTemplates[TName]>;
|
|
714
|
+
logging?: OutgoingLoggingMetadata;
|
|
715
|
+
};
|
|
716
|
+
type SendTemplateMethod<TTemplates extends TemplateRegistry> = [TemplateName<TTemplates>] extends [never] ? (params: SendTemplateRawParams) => Promise<SendResult> : <TName extends TemplateName<TTemplates>>(params: SendTemplateTypedParams<TTemplates, TName>) => Promise<SendResult>;
|
|
717
|
+
interface SendInteractiveParams {
|
|
718
|
+
to: string;
|
|
719
|
+
type: "button" | "list" | "carousel";
|
|
720
|
+
body: string;
|
|
721
|
+
buttons?: Array<{
|
|
722
|
+
id: string;
|
|
723
|
+
title: string;
|
|
724
|
+
}>;
|
|
725
|
+
buttonLabel?: string;
|
|
726
|
+
sections?: Array<{
|
|
727
|
+
title: string;
|
|
728
|
+
rows: Array<{
|
|
729
|
+
id: string;
|
|
730
|
+
title: string;
|
|
731
|
+
description?: string;
|
|
732
|
+
}>;
|
|
733
|
+
}>;
|
|
734
|
+
cards?: SendInteractiveMediaCarouselData["cards"];
|
|
735
|
+
messageType?: string;
|
|
736
|
+
userId?: string;
|
|
737
|
+
metadata?: Record<string, unknown>;
|
|
738
|
+
}
|
|
739
|
+
interface SendLocationParams {
|
|
740
|
+
to: string;
|
|
741
|
+
latitude: number;
|
|
742
|
+
longitude: number;
|
|
743
|
+
name: string;
|
|
744
|
+
address: string;
|
|
745
|
+
messageType?: string;
|
|
746
|
+
userId?: string;
|
|
747
|
+
metadata?: Record<string, unknown>;
|
|
748
|
+
}
|
|
749
|
+
interface GetMessagesOptions {
|
|
750
|
+
cursor?: string;
|
|
751
|
+
limit?: number;
|
|
752
|
+
}
|
|
753
|
+
interface ZapClient<TTemplates extends TemplateRegistry = {}> {
|
|
754
|
+
send: {
|
|
755
|
+
text(params: SendTextParams): Promise<SendResult>;
|
|
756
|
+
template: SendTemplateMethod<TTemplates>;
|
|
757
|
+
templateRaw(params: SendTemplateRawParams): Promise<SendResult>;
|
|
758
|
+
interactive(params: SendInteractiveParams): Promise<SendResult>;
|
|
759
|
+
location(params: SendLocationParams): Promise<SendResult>;
|
|
760
|
+
};
|
|
761
|
+
conversations: {
|
|
762
|
+
list(): Promise<Conversation[]>;
|
|
763
|
+
get(phone: string): Promise<Conversation | null>;
|
|
764
|
+
messages(phone: string, opts?: GetMessagesOptions): Promise<UIMessage[]>;
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
declare function createZapClient<TTemplates extends TemplateRegistry = {}>(options?: ZapClientOptions<TTemplates>): ZapClient<TTemplates>;
|
|
768
|
+
//#endregion
|
|
769
|
+
export { WebhookContact as $, noopLogger as A, InteractiveMediaCarouselCardInput as B, WhatsAppLogStore as C, Logger as D, LogLevel as E, StatusUpdateEvent as F, SendMessageError as G, MessageError as H, SyncEvent as I, TemplateComponent as J, SendMessageResponse as K, WhatsAppConfig as L, ConversationSummary as M, ConversationUpdateEvent as N, LoggerConfig as O, NewMessageEvent as P, WebhookChange as Q, Conversation as R, WhatsAppLogRecord as S, WhatsAppStatus as T, MessageStatus as U, MediaMessage as V, SendInteractiveMediaCarouselData as W, UIMessage as X, TemplateParameter as Y, UIMessageStatus as Z, WhatsAppService as _, TemplateComponentDefinition as a, WhatsAppInteractiveButtonsMessage as at, WHATSAPP_MESSAGE_TYPES as b, TemplateParameterDefinition as c, WhatsAppLocationMessage as ct, TemplateRegistry as d, WebhookEntry as et, defineTemplates as f, OutgoingLoggingMetadata as g, serializeTemplateFromRegistry as h, SupportedTemplateParameterType as i, WhatsAppCarouselCard as it, serializeError as j, createLogger as k, TemplateParameterInputMap as l, WhatsAppTemplateMessage as lt, hasConfiguredTemplates as m, createZapClient as n, WebhookPayload as nt, TemplateDefinition as o, WhatsAppInteractiveListMessage as ot, getTemplateNames as p, SendResult as q, EMPTY_TEMPLATE_REGISTRY as r, WebhookValue as rt, TemplateName as s, WhatsAppInteractiveMediaCarouselMessage as st, ZapClient as t, WebhookError as tt, TemplateParams as u, WhatsAppTextMessage as ut, MessageLoggerNotifier as v, WhatsAppMessageType as w, WhatsAppDirection as x, MessageLoggerService as y, IncomingMessage as z };
|