@adatechnology/meta-whatsapp-module 0.2.0-rc.29 → 0.2.0-rc.30

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.cjs CHANGED
@@ -33,6 +33,7 @@ __export(index_exports, {
33
33
  FlowMediaRepository: () => FlowMediaRepository,
34
34
  GetFlowGraphUseCase: () => GetFlowGraphUseCase,
35
35
  GetLiveFlowPositionsUseCase: () => GetLiveFlowPositionsUseCase,
36
+ InboundEffectsDispatcher: () => InboundEffectsDispatcher,
36
37
  IngestInboundMediaUseCase: () => IngestInboundMediaUseCase,
37
38
  InvalidFlowGraphError: () => InvalidFlowGraphError,
38
39
  ListCompanyDocumentsUseCase: () => ListCompanyDocumentsUseCase,
@@ -46,6 +47,7 @@ __export(index_exports, {
46
47
  OptimisticLockError: () => OptimisticLockError,
47
48
  PREVIEW_MEDIA_ID_PREFIX: () => import_meta_whatsapp_contracts8.PREVIEW_MEDIA_ID_PREFIX,
48
49
  PRODUCT_LIST_LIMIT: () => PRODUCT_LIST_LIMIT,
50
+ ProcessInboundDispatchUseCase: () => ProcessInboundDispatchUseCase,
49
51
  PurgeExpiredDocumentsUseCase: () => PurgeExpiredDocumentsUseCase,
50
52
  ReceiveWebhookUseCase: () => ReceiveWebhookUseCase,
51
53
  ReleaseConversationUseCase: () => ReleaseConversationUseCase,
@@ -59,9 +61,12 @@ __export(index_exports, {
59
61
  TRANSCRIPTION_STATUS: () => TRANSCRIPTION_STATUS,
60
62
  TakeoverConversationUseCase: () => TakeoverConversationUseCase,
61
63
  TranscribeAudioUseCase: () => TranscribeAudioUseCase,
64
+ WEBHOOK_CLAIM_TTL_SECONDS: () => import_meta_graph_core2.WEBHOOK_CLAIM_TTL_SECONDS,
62
65
  WEBHOOK_NONCE_TTL_SECONDS: () => import_meta_graph_core2.WEBHOOK_NONCE_TTL_SECONDS,
63
66
  WhatsAppChannelAdapter: () => WhatsAppChannelAdapter,
67
+ buildInboundJobId: () => buildInboundJobId,
64
68
  claimWebhookDelivery: () => claimWebhookDelivery,
69
+ confirmWebhookDelivery: () => confirmWebhookDelivery,
65
70
  createMetaWhatsAppModule: () => createMetaWhatsAppModule,
66
71
  createSendMediaAction: () => createSendMediaAction,
67
72
  createSendProductListAction: () => createSendProductListAction,
@@ -84,6 +89,7 @@ __export(index_exports, {
84
89
  sessions: () => sessions,
85
90
  settings: () => settings,
86
91
  toPreviewMediaId: () => import_meta_whatsapp_contracts8.toPreviewMediaId,
92
+ toSessionContract: () => toSessionContract,
87
93
  transcriptionRetryAfterSeconds: () => transcriptionRetryAfterSeconds,
88
94
  verifyWebhookChallenge: () => verifyWebhookChallenge,
89
95
  verifyWebhookSignature: () => verifyWebhookSignature
@@ -2099,14 +2105,21 @@ function verifyWebhookSignature(params) {
2099
2105
  if (!(0, import_meta_graph_core2.isValidWebhookSignature)(params)) throw new import_meta_whatsapp_contracts10.InvalidWebhookSignatureError();
2100
2106
  }
2101
2107
  __name(verifyWebhookSignature, "verifyWebhookSignature");
2102
- async function claimWebhookDelivery(params) {
2103
- const key = (0, import_meta_graph_core2.buildWebhookDeliveryKey)({
2108
+ function deliveryKey(signatureHeader) {
2109
+ return (0, import_meta_graph_core2.buildWebhookDeliveryKey)({
2104
2110
  namespace: WEBHOOK_NONCE_NAMESPACE,
2105
- signatureHeader: params.signatureHeader
2111
+ signatureHeader
2106
2112
  });
2107
- return params.nonceStore.setIfAbsent(key, params.ttlSeconds ?? import_meta_graph_core2.WEBHOOK_NONCE_TTL_SECONDS);
2113
+ }
2114
+ __name(deliveryKey, "deliveryKey");
2115
+ async function claimWebhookDelivery(params) {
2116
+ return params.nonceStore.setIfAbsent(deliveryKey(params.signatureHeader), params.ttlSeconds ?? import_meta_graph_core2.WEBHOOK_CLAIM_TTL_SECONDS);
2108
2117
  }
2109
2118
  __name(claimWebhookDelivery, "claimWebhookDelivery");
2119
+ async function confirmWebhookDelivery(params) {
2120
+ await params.nonceStore.confirm?.(deliveryKey(params.signatureHeader), params.ttlSeconds ?? import_meta_graph_core2.WEBHOOK_NONCE_TTL_SECONDS);
2121
+ }
2122
+ __name(confirmWebhookDelivery, "confirmWebhookDelivery");
2110
2123
 
2111
2124
  // src/channel/IngestInboundMedia.use-case.ts
2112
2125
  var import_drizzle_orm8 = require("drizzle-orm");
@@ -2427,7 +2440,11 @@ function extractMediaDescriptor(message) {
2427
2440
  }
2428
2441
  __name(extractMediaDescriptor, "extractMediaDescriptor");
2429
2442
 
2430
- // src/channel/ReceiveWebhook.use-case.ts
2443
+ // src/channel/inboundDispatch.ts
2444
+ function buildInboundJobId(job) {
2445
+ return job.kind === "message" ? `wa-inbound-message:${job.message.id}` : `wa-inbound-status:${job.status.id}:${job.status.status}`;
2446
+ }
2447
+ __name(buildInboundJobId, "buildInboundJobId");
2431
2448
  function toSessionContract(row) {
2432
2449
  return {
2433
2450
  id: row.id,
@@ -2448,6 +2465,65 @@ function toSessionContract(row) {
2448
2465
  };
2449
2466
  }
2450
2467
  __name(toSessionContract, "toSessionContract");
2468
+ function extractAnswer(message) {
2469
+ const interactive = message.interactive;
2470
+ if (interactive?.button_reply) return interactive.button_reply.id;
2471
+ if (interactive?.list_reply) return interactive.list_reply.id;
2472
+ return message.text?.body;
2473
+ }
2474
+ __name(extractAnswer, "extractAnswer");
2475
+ var InboundEffectsDispatcher = class {
2476
+ static {
2477
+ __name(this, "InboundEffectsDispatcher");
2478
+ }
2479
+ params;
2480
+ constructor(params) {
2481
+ this.params = params;
2482
+ }
2483
+ async run(job) {
2484
+ if (job.kind === "message") return this.runMessageEffects(job);
2485
+ return this.runStatusEffects(job);
2486
+ }
2487
+ async runMessageEffects(job) {
2488
+ const { companyId, message, savedMessageId, media } = job;
2489
+ if (media) {
2490
+ await this.params.hooks?.onMediaReceived?.({
2491
+ companyId,
2492
+ messageId: savedMessageId,
2493
+ whatsappNumber: message.from,
2494
+ sourceMediaId: media.sourceMediaId,
2495
+ mimeType: media.mimeType,
2496
+ ...media.filename ? {
2497
+ filename: media.filename
2498
+ } : {}
2499
+ });
2500
+ }
2501
+ const sessionRow = await this.params.sessionRepository.getContext(companyId, message.from);
2502
+ if (!sessionRow) return;
2503
+ if (sessionRow.mode === "human") return;
2504
+ const outcome = await this.params.hooks?.onMessageReceived?.(message, toSessionContract(sessionRow));
2505
+ if (outcome?.outcome === "handled") return;
2506
+ void extractAnswer(message);
2507
+ }
2508
+ async runStatusEffects(job) {
2509
+ const sessionRow = await this.params.sessionRepository.getContext(job.companyId, job.whatsappNumber);
2510
+ await this.params.hooks?.onStatusUpdate?.(job.status, sessionRow ? toSessionContract(sessionRow) : null);
2511
+ }
2512
+ };
2513
+ var ProcessInboundDispatchUseCase = class {
2514
+ static {
2515
+ __name(this, "ProcessInboundDispatchUseCase");
2516
+ }
2517
+ dispatcher;
2518
+ constructor(dispatcher) {
2519
+ this.dispatcher = dispatcher;
2520
+ }
2521
+ async execute(job) {
2522
+ await this.dispatcher.run(job);
2523
+ }
2524
+ };
2525
+
2526
+ // src/channel/ReceiveWebhook.use-case.ts
2451
2527
  function extractContent(message) {
2452
2528
  if (message.text?.body) return message.text.body;
2453
2529
  const interactive = message.interactive;
@@ -2460,13 +2536,6 @@ function extractContent(message) {
2460
2536
  return message.image?.caption ?? message.document?.caption ?? null;
2461
2537
  }
2462
2538
  __name(extractContent, "extractContent");
2463
- function extractAnswer(message) {
2464
- const interactive = message.interactive;
2465
- if (interactive?.button_reply) return interactive.button_reply.id;
2466
- if (interactive?.list_reply) return interactive.list_reply.id;
2467
- return message.text?.body;
2468
- }
2469
- __name(extractAnswer, "extractAnswer");
2470
2539
  function extractPayload(message) {
2471
2540
  const payload = {};
2472
2541
  if (message.interactive) payload["interactive"] = message.interactive;
@@ -2487,7 +2556,17 @@ var ReceiveWebhookUseCase = class {
2487
2556
  params;
2488
2557
  constructor(params) {
2489
2558
  this.params = params;
2559
+ this.dispatcher = new InboundEffectsDispatcher({
2560
+ sessionRepository: params.sessionRepository,
2561
+ ...params.hooks ? {
2562
+ hooks: params.hooks
2563
+ } : {},
2564
+ ...params.realtime ? {
2565
+ realtime: params.realtime
2566
+ } : {}
2567
+ });
2490
2568
  }
2569
+ dispatcher;
2491
2570
  async execute(input) {
2492
2571
  verifyWebhookSignature({
2493
2572
  rawBody: input.rawBody,
@@ -2526,6 +2605,10 @@ var ReceiveWebhookUseCase = class {
2526
2605
  }
2527
2606
  }
2528
2607
  }
2608
+ await confirmWebhookDelivery({
2609
+ nonceStore: this.params.nonceStore,
2610
+ signatureHeader: input.signatureHeader
2611
+ });
2529
2612
  return {
2530
2613
  duplicate: false,
2531
2614
  messagesProcessed,
@@ -2548,24 +2631,16 @@ var ReceiveWebhookUseCase = class {
2548
2631
  });
2549
2632
  if (!saved) return;
2550
2633
  const media = extractMediaDescriptor(saved);
2551
- if (media) {
2552
- await this.params.hooks?.onMediaReceived?.({
2553
- companyId,
2554
- messageId: saved.id,
2555
- whatsappNumber: message.from,
2556
- sourceMediaId: media.sourceMediaId,
2557
- mimeType: media.mimeType,
2558
- ...media.filename ? {
2559
- filename: media.filename
2560
- } : {}
2561
- });
2562
- }
2563
- const sessionRow = await this.params.sessionRepository.getContext(companyId, message.from);
2564
- if (!sessionRow) return;
2565
- if (sessionRow.mode === "human") return;
2566
- const outcome = await this.params.hooks?.onMessageReceived?.(message, toSessionContract(sessionRow));
2567
- if (outcome?.outcome === "handled") return;
2568
- void extractAnswer(message);
2634
+ await this.dispatch({
2635
+ kind: "message",
2636
+ companyId,
2637
+ message,
2638
+ savedMessageId: saved.id,
2639
+ ...media ? {
2640
+ media
2641
+ } : {},
2642
+ receivedAt: Date.now()
2643
+ });
2569
2644
  }
2570
2645
  async handleStatus(companyId, status) {
2571
2646
  const updated = await this.params.messageRepository.updateMessageStatus(companyId, status.id, status.status);
@@ -2574,8 +2649,24 @@ var ReceiveWebhookUseCase = class {
2574
2649
  waMessageId: status.id,
2575
2650
  status: status.status
2576
2651
  });
2577
- const sessionRow = await this.params.sessionRepository.getContext(companyId, updated.whatsappNumber);
2578
- await this.params.hooks?.onStatusUpdate?.(status, sessionRow ? toSessionContract(sessionRow) : null);
2652
+ await this.dispatch({
2653
+ kind: "status",
2654
+ companyId,
2655
+ status,
2656
+ whatsappNumber: updated.whatsappNumber,
2657
+ receivedAt: Date.now()
2658
+ });
2659
+ }
2660
+ // Com fila configurada, os efeitos saem da requisição do webhook; sem ela, rodam aqui mesmo e o
2661
+ // comportamento é o de sempre. É o mesmo `InboundEffectsDispatcher` nos dois caminhos.
2662
+ async dispatch(job) {
2663
+ if (this.params.inboundQueue) {
2664
+ await this.params.inboundQueue.enqueue(job, {
2665
+ jobId: buildInboundJobId(job)
2666
+ });
2667
+ return;
2668
+ }
2669
+ await this.dispatcher.run(job);
2579
2670
  }
2580
2671
  };
2581
2672
 
@@ -2889,6 +2980,7 @@ __name(redeemSseTicket, "redeemSseTicket");
2889
2980
  FlowMediaRepository,
2890
2981
  GetFlowGraphUseCase,
2891
2982
  GetLiveFlowPositionsUseCase,
2983
+ InboundEffectsDispatcher,
2892
2984
  IngestInboundMediaUseCase,
2893
2985
  InvalidFlowGraphError,
2894
2986
  ListCompanyDocumentsUseCase,
@@ -2902,6 +2994,7 @@ __name(redeemSseTicket, "redeemSseTicket");
2902
2994
  OptimisticLockError,
2903
2995
  PREVIEW_MEDIA_ID_PREFIX,
2904
2996
  PRODUCT_LIST_LIMIT,
2997
+ ProcessInboundDispatchUseCase,
2905
2998
  PurgeExpiredDocumentsUseCase,
2906
2999
  ReceiveWebhookUseCase,
2907
3000
  ReleaseConversationUseCase,
@@ -2915,9 +3008,12 @@ __name(redeemSseTicket, "redeemSseTicket");
2915
3008
  TRANSCRIPTION_STATUS,
2916
3009
  TakeoverConversationUseCase,
2917
3010
  TranscribeAudioUseCase,
3011
+ WEBHOOK_CLAIM_TTL_SECONDS,
2918
3012
  WEBHOOK_NONCE_TTL_SECONDS,
2919
3013
  WhatsAppChannelAdapter,
3014
+ buildInboundJobId,
2920
3015
  claimWebhookDelivery,
3016
+ confirmWebhookDelivery,
2921
3017
  createMetaWhatsAppModule,
2922
3018
  createSendMediaAction,
2923
3019
  createSendProductListAction,
@@ -2940,6 +3036,7 @@ __name(redeemSseTicket, "redeemSseTicket");
2940
3036
  sessions,
2941
3037
  settings,
2942
3038
  toPreviewMediaId,
3039
+ toSessionContract,
2943
3040
  transcriptionRetryAfterSeconds,
2944
3041
  verifyWebhookChallenge,
2945
3042
  verifyWebhookSignature