@adatechnology/meta-whatsapp-contracts 0.2.0-rc.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { b as WhatsAppMessage, f as WhatsAppStatus } from './webhook.types-PxhpmpzC.js';
2
- export { W as WhatsAppInteractive, a as WhatsAppMedia, c as WhatsAppMessageEcho, d as WhatsAppMessageStatusValue, e as WhatsAppOrder, g as WhatsAppWebhookPayload, w as whatsAppInteractiveSchema, h as whatsAppMediaSchema, i as whatsAppMessageEchoSchema, j as whatsAppMessageSchema, k as whatsAppMessageStatusSchema, l as whatsAppOrderSchema, m as whatsAppStatusSchema, n as whatsAppWebhookPayloadSchema } from './webhook.types-PxhpmpzC.js';
1
+ import { c as WhatsAppMessage, i as WhatsAppStatus, k as WhatsAppTemplateStatusUpdate, g as WhatsAppPhoneNumberQualityUpdate } from './webhook.types-DWaAhvNu.js';
2
+ export { W as WHATSAPP_WEBHOOK_FIELDS, a as WhatsAppInteractive, b as WhatsAppMedia, d as WhatsAppMessageEcho, e as WhatsAppMessageStatusValue, f as WhatsAppOrder, h as WhatsAppQualityEvent, j as WhatsAppTemplateStatusEvent, l as WhatsAppWebhookChange, m as WhatsAppWebhookField, n as WhatsAppWebhookPayload, o as WhatsAppWebhookValue, w as whatsAppInteractiveSchema, p as whatsAppMediaSchema, q as whatsAppMessageEchoSchema, r as whatsAppMessageSchema, s as whatsAppMessageStatusSchema, t as whatsAppOrderSchema, u as whatsAppPhoneNumberQualityUpdateSchema, v as whatsAppQualityEventSchema, x as whatsAppStatusSchema, y as whatsAppTemplateStatusEventSchema, z as whatsAppTemplateStatusUpdateSchema, A as whatsAppWebhookChangeSchema, B as whatsAppWebhookPayloadSchema, C as whatsAppWebhookValueSchema } from './webhook.types-DWaAhvNu.js';
3
3
  import { z } from 'zod';
4
4
 
5
5
  type SessionState = string;
@@ -84,6 +84,7 @@ declare const crossFlowKey: (target: string) => string;
84
84
  type FlowNodeData = {
85
85
  id: string;
86
86
  type: FlowNodeType;
87
+ label?: string;
87
88
  contextKey?: string;
88
89
  questionType?: FlowQuestionType;
89
90
  question?: string;
@@ -338,98 +339,49 @@ declare const flowGraphNodesSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
338
339
  }>]>>;
339
340
  }, z.ZodTypeAny, "passthrough">>>;
340
341
 
341
- interface WhatsAppSettings {
342
- templateName: string;
343
- templateLanguage: string;
344
- templateVariables: string[];
345
- welcomeMessage: string;
346
- farewellMessage: string;
347
- }
348
- interface TemplateVariablesMap {
349
- [index: number]: string;
350
- }
351
- interface TemplateConfig {
352
- name: string;
353
- category: 'UTILITY' | 'MARKETING';
354
- language: string;
355
- headerType: 'NONE' | 'TEXT';
356
- headerText?: string;
357
- bodyText: string;
358
- footerText?: string;
359
- }
360
- interface WhatsAppTemplateSummary {
361
- id: string;
362
- name: string;
363
- shortId: string;
364
- displayName: string;
365
- status: string;
366
- category: string;
367
- language: string;
368
- bodyText: string | null;
369
- variableCount: number;
370
- }
371
- interface CreateTemplateResult {
372
- ok: boolean;
373
- message: string;
374
- status?: string;
375
- }
376
-
377
- type MessageHookOutcome = {
378
- outcome: 'handled';
379
- } | {
380
- outcome: 'continue';
342
+ /**
343
+ * Tetos da Meta para escolha interativa.
344
+ *
345
+ * Ficam no contrato porque quem escreve o texto da opção precisa deles antes de publicar: passar do
346
+ * limite não degrada nada, a Graph API recusa a mensagem inteira e o cliente vê silêncio. Botão é
347
+ * mais curto que linha de lista, e o emoji conta como caractere.
348
+ */
349
+ declare const WHATSAPP_CHOICE_LIMIT: {
350
+ readonly BUTTONS: 3;
351
+ readonly BUTTON_TITLE_LENGTH: 20;
352
+ readonly LIST_ROWS: 10;
353
+ readonly LIST_ROW_TITLE_LENGTH: 24;
381
354
  };
382
- interface MetaWhatsAppHooks {
383
- onMessageReceived?: (message: WhatsAppMessage, session: ConversationSession) => Promise<MessageHookOutcome>;
384
- onStatusUpdate?: (status: WhatsAppStatus, session: ConversationSession | null) => Promise<void>;
385
- onSessionExpired?: (session: ConversationSession) => Promise<void>;
386
- onHumanRequested?: (session: ConversationSession) => Promise<void>;
387
- /**
388
- * Mídia recebida do cliente, já persistida como mensagem e pronta para ser copiada da Meta para
389
- * o storage do host — normalmente enfileirando um job.
390
- *
391
- * Hook próprio, e não parte do `onMessageReceived`, porque aquele pertence ao fluxo do bot: ele
392
- * nem é chamado quando a conversa está em atendimento humano, que é exatamente quando o cliente
393
- * manda documento para o atendente. Ligar ingestão lá perderia esses arquivos em silêncio.
394
- *
395
- * A URL de download da Meta expira, então quem implementa deve tratar como trabalho urgente.
396
- */
397
- onMediaReceived?: (media: InboundMediaDescriptor) => Promise<void>;
398
- /**
399
- * Falha ao enviar UM arquivo da biblioteca num nó `send_media`.
400
- *
401
- * O erro é entregue aqui em vez de propagado porque um PDF que não subiu não pode travar a
402
- * conversa num nó automático — o cliente ficaria parado sem nenhuma mensagem. O módulo segue
403
- * para o próximo arquivo e para o próximo nó; observar e alertar é do host.
404
- */
405
- onFlowMediaError?: (error: unknown, details: {
406
- flowKey: string;
407
- nodeId: string;
408
- uploadId: string;
409
- }) => void;
410
- }
411
- type InboundMediaDescriptor = {
412
- readonly companyId: string;
413
- readonly messageId: string;
414
- readonly whatsappNumber: string;
415
- /** ID da mídia na Meta — a origem do download, que expira. */
416
- readonly sourceMediaId: string;
417
- readonly mimeType: string;
418
- readonly filename?: string;
355
+ type SendChannelMediaCommon = {
356
+ to: string;
357
+ mimeType: string;
358
+ filename: string;
359
+ caption?: string;
419
360
  };
420
-
361
+ /**
362
+ * Ou o binário, ou um `mediaId` já conhecido — nunca nenhum dos dois.
363
+ *
364
+ * União em vez de dois campos opcionais: com ambos opcionais, "esqueci de passar os dois" compila e
365
+ * só quebra na chamada à Meta, em produção.
366
+ */
367
+ type SendChannelMediaParams = (SendChannelMediaCommon & {
368
+ buffer: Buffer;
369
+ mediaId?: string | undefined;
370
+ }) | (SendChannelMediaCommon & {
371
+ buffer?: undefined;
372
+ mediaId: string;
373
+ });
421
374
  interface ChannelAdapterInterface {
422
375
  sendText(to: string, body: string): Promise<{
423
376
  externalMessageId: string | null;
424
377
  }>;
425
- sendMedia(params: {
426
- to: string;
427
- buffer: Buffer;
428
- mimeType: string;
429
- filename: string;
430
- caption?: string;
431
- }): Promise<{
378
+ /**
379
+ * Envia arquivo. Com `mediaId` conhecido, o binário não sobe de novo — a Meta aceita reusar o id
380
+ * por 30 dias, e o `mediaId` devolvido é o que permite guardá-lo para os próximos destinatários.
381
+ */
382
+ sendMedia(params: SendChannelMediaParams): Promise<{
432
383
  externalMessageId: string | null;
384
+ mediaId?: string | undefined;
433
385
  }>;
434
386
  sendTemplate(params: {
435
387
  to: string;
@@ -450,6 +402,43 @@ interface ChannelAdapterInterface {
450
402
  }): Promise<{
451
403
  externalMessageId: string | null;
452
404
  }>;
405
+ /**
406
+ * Botão de resposta rápida — opcional porque a lista sempre serve de alternativa.
407
+ *
408
+ * Exigi-lo na porta quebraria todo dublê de teste e todo canal já escrito, para uma capacidade
409
+ * que nem todo canal tem: quem não implementa continua caindo na lista, que é o que o WhatsApp
410
+ * fazia antes. A Meta limita a 3 botões, e o provider recusa o quarto.
411
+ */
412
+ sendInteractiveButtons?(params: {
413
+ to: string;
414
+ body: string;
415
+ buttons: {
416
+ id: string;
417
+ title: string;
418
+ }[];
419
+ }): Promise<{
420
+ externalMessageId: string | null;
421
+ }>;
422
+ /**
423
+ * Vitrine de produtos — opcional pelo mesmo motivo do botão: canal que não sabe enviar produto
424
+ * continua funcionando, e a ausência do método é o que desliga o recurso, sem flag `hasCatalog`.
425
+ *
426
+ * A Meta limita a 30 itens por mensagem, distribuídos em até 10 seções; o provider recusa acima
427
+ * disso. Quem monta a lista corta antes — mandar tudo e ver a Graph API recusar a mensagem
428
+ * inteira deixa o cliente no silêncio.
429
+ */
430
+ sendProductList?(params: {
431
+ to: string;
432
+ headerText: string;
433
+ body: string;
434
+ footerText?: string;
435
+ sections: {
436
+ title: string;
437
+ retailerIds: string[];
438
+ }[];
439
+ }): Promise<{
440
+ externalMessageId: string | null;
441
+ }>;
453
442
  fetchMediaAsBase64(mediaId: string): Promise<{
454
443
  data: string;
455
444
  mimeType: string;
@@ -546,6 +535,174 @@ interface FlowActionRegistry {
546
535
  registerFlowAction(kind: FlowActionKind, handler: FlowActionHandler): void;
547
536
  }
548
537
 
538
+ /**
539
+ * Quando transcrever nota de voz.
540
+ *
541
+ * Vive no contrato, e não só no módulo, porque atravessa três fronteiras: o painel escolhe, a API
542
+ * transporta e o módulo obedece. Duas definições concorrentes divergiriam no primeiro valor novo.
543
+ */
544
+ type TranscriptionMode = 'auto' | 'onDemand';
545
+ interface WhatsAppSettings {
546
+ templateName: string;
547
+ templateLanguage: string;
548
+ templateVariables: string[];
549
+ welcomeMessage: string;
550
+ farewellMessage: string;
551
+ /**
552
+ * Transcrição de áudio ligada para ESTA empresa.
553
+ *
554
+ * Tri-state de propósito: `null` significa "não decidido pelo painel", e aí vale o padrão que o
555
+ * host injetou (tipicamente ambiente). Se fosse `boolean` com padrão `false`, atualizar o módulo
556
+ * desligaria a transcrição de quem já a tinha ligada por variável de ambiente — regressão
557
+ * silenciosa num recurso que estava funcionando.
558
+ *
559
+ * Ligar aqui não cria capacidade: sem engine e chave injetados no host, não há o que transcrever.
560
+ * Ambiente decide se É POSSÍVEL; isto decide se é para FAZER.
561
+ */
562
+ transcriptionEnabled: boolean | null;
563
+ /** `null` = herda o modo injetado pelo host. */
564
+ transcriptionMode: TranscriptionMode | null;
565
+ }
566
+ interface TemplateVariablesMap {
567
+ [index: number]: string;
568
+ }
569
+ interface TemplateConfig {
570
+ name: string;
571
+ category: 'UTILITY' | 'MARKETING';
572
+ language: string;
573
+ headerType: 'NONE' | 'TEXT';
574
+ headerText?: string;
575
+ bodyText: string;
576
+ footerText?: string;
577
+ }
578
+ interface WhatsAppTemplateSummary {
579
+ id: string;
580
+ name: string;
581
+ shortId: string;
582
+ displayName: string;
583
+ status: string;
584
+ category: string;
585
+ language: string;
586
+ bodyText: string | null;
587
+ variableCount: number;
588
+ }
589
+ interface CreateTemplateResult {
590
+ ok: boolean;
591
+ message: string;
592
+ status?: string;
593
+ }
594
+
595
+ type MessageHookOutcome = {
596
+ outcome: 'handled';
597
+ } | {
598
+ outcome: 'continue';
599
+ };
600
+ interface MetaWhatsAppHooks {
601
+ onMessageReceived?: (message: WhatsAppMessage, session: ConversationSession) => Promise<MessageHookOutcome>;
602
+ onStatusUpdate?: (status: WhatsAppStatus, session: ConversationSession | null) => Promise<void>;
603
+ onSessionExpired?: (session: ConversationSession) => Promise<void>;
604
+ onHumanRequested?: (session: ConversationSession) => Promise<void>;
605
+ /**
606
+ * Mídia recebida do cliente, já persistida como mensagem e pronta para ser copiada da Meta para
607
+ * o storage do host — normalmente enfileirando um job.
608
+ *
609
+ * Hook próprio, e não parte do `onMessageReceived`, porque aquele pertence ao fluxo do bot: ele
610
+ * nem é chamado quando a conversa está em atendimento humano, que é exatamente quando o cliente
611
+ * manda documento para o atendente. Ligar ingestão lá perderia esses arquivos em silêncio.
612
+ *
613
+ * A URL de download da Meta expira, então quem implementa deve tratar como trabalho urgente.
614
+ */
615
+ onMediaReceived?: (media: InboundMediaDescriptor) => Promise<void>;
616
+ /**
617
+ * Falha ao enviar UM arquivo da biblioteca num nó `send_media`.
618
+ *
619
+ * O erro é entregue aqui em vez de propagado porque um PDF que não subiu não pode travar a
620
+ * conversa num nó automático — o cliente ficaria parado sem nenhuma mensagem. O módulo segue
621
+ * para o próximo arquivo e para o próximo nó; observar e alertar é do host.
622
+ */
623
+ onFlowMediaError?: (error: unknown, details: {
624
+ flowKey: string;
625
+ nodeId: string;
626
+ uploadId: string;
627
+ }) => void;
628
+ /**
629
+ * Falha ao enviar a vitrine de produtos num nó `send_product_list`.
630
+ *
631
+ * Hook próprio, e não o de mídia: aqui não há `uploadId` — o que falhou foi a montagem da
632
+ * vitrine a partir do catálogo, e o host precisa saber qual nó ficou mudo.
633
+ */
634
+ onFlowProductListError?: (error: unknown, details: {
635
+ flowKey: string;
636
+ nodeId: string;
637
+ }) => void;
638
+ /**
639
+ * Transcrição de áudio falhou de forma **retriável** (cota estourada, rede, 5xx) — o áudio segue
640
+ * transcritível e alguém precisa tentar de novo.
641
+ *
642
+ * É hook, e não exceção propagada, porque a mídia JÁ foi copiada para o storage quando isto
643
+ * acontece: deixar o erro subir marcaria a ingestão inteira como falha e o job reprocessaria o
644
+ * download de um binário que está salvo. E é hook, e não retentativa interna, porque o módulo não
645
+ * tem fila — quem sabe reenfileirar com atraso é o host, que já tem uma.
646
+ *
647
+ * Sem implementar, o áudio fica com `transcription_status = 'pending'` e nada o retoma: a
648
+ * transcrição não se perde nem mente, mas só sai se pedirem sob demanda.
649
+ */
650
+ onTranscriptionDeferred?: (details: TranscriptionDeferredDescriptor) => Promise<void> | void;
651
+ /**
652
+ * A Meta mudou o status de um template (aprovado, rejeitado, pausado).
653
+ *
654
+ * É um evento de conta, não de conversa: não tem `session` nem número de cliente, e chega mesmo
655
+ * quando nenhuma conversa está acontecendo. Sem implementar, a única forma de descobrir que um
656
+ * template foi rejeitado é alguém abrir o painel da Meta e reparar.
657
+ */
658
+ onTemplateStatusUpdate?: (update: WhatsAppTemplateStatusUpdate) => Promise<void> | void;
659
+ /**
660
+ * Qualidade ou limite de envio do número mudou (`FLAGGED` = qualidade caiu e o número corre risco
661
+ * de restrição; `DOWNGRADE` = teto de envio reduzido).
662
+ *
663
+ * Vale tratar como alerta operacional: quando vira restrição de fato, as mensagens já estão
664
+ * falhando, e o histórico de qualidade que explicaria a queda tem retenção curta no painel.
665
+ */
666
+ onPhoneNumberQualityUpdate?: (update: WhatsAppPhoneNumberQualityUpdate) => Promise<void> | void;
667
+ /**
668
+ * Chegou um `field` que este módulo não sabe tratar — ou um que sabe, mas com corpo fora do
669
+ * schema (versão nova da Cloud API, campo assinado sem handler).
670
+ *
671
+ * Existe para que o webhook nunca seja um buraco negro: descartar em silêncio é indistinguível de
672
+ * webhook que parou de chegar, e foi assim que os eventos de template ficaram invisíveis até
673
+ * alguém procurar. Observar aqui é do host; o módulo não decide que é erro.
674
+ */
675
+ onUnhandledWebhookEvent?: (details: UnhandledWebhookEventDescriptor) => Promise<void> | void;
676
+ }
677
+ type UnhandledWebhookEventDescriptor = {
678
+ /** O `changes[].field` como a Meta mandou; `undefined` quando o payload nem trouxe o campo. */
679
+ readonly field: string | undefined;
680
+ /** Por que não foi tratado: sem handler para o field, ou corpo que não bate com o schema dele. */
681
+ readonly reason: 'unknown-field' | 'invalid-shape';
682
+ /** O `value` cru, para diagnóstico. Nunca logar inteiro: pode conter dado de cliente. */
683
+ readonly value: unknown;
684
+ };
685
+ type TranscriptionDeferredDescriptor = {
686
+ readonly companyId: string;
687
+ readonly messageId: string;
688
+ readonly whatsappNumber: string;
689
+ /** Onde o áudio já está salvo — quem retomar lê daqui, não da Meta (cuja URL expira). */
690
+ readonly uploadId: string;
691
+ /** Do `Retry-After` do engine, quando informado. É o intervalo mínimo a respeitar. */
692
+ readonly retryAfterSeconds?: number;
693
+ readonly reason: 'rate-limited' | 'transient-failure';
694
+ readonly error: unknown;
695
+ };
696
+ type InboundMediaDescriptor = {
697
+ readonly companyId: string;
698
+ readonly messageId: string;
699
+ readonly whatsappNumber: string;
700
+ /** ID da mídia na Meta — a origem do download, que expira. */
701
+ readonly sourceMediaId: string;
702
+ readonly mimeType: string;
703
+ readonly filename?: string;
704
+ };
705
+
549
706
  declare class MetaWhatsAppError extends Error {
550
707
  readonly statusCode: number;
551
708
  readonly code: string;
@@ -559,6 +716,9 @@ declare const META_WHATSAPP_ERROR_CODES: {
559
716
  readonly CONFIG_MISSING: "META_WHATSAPP_CONFIG_MISSING";
560
717
  readonly TEMPLATE_NOT_CONFIGURED: "META_WHATSAPP_TEMPLATE_NOT_CONFIGURED";
561
718
  readonly SESSION_NOT_FOUND: "META_WHATSAPP_SESSION_NOT_FOUND";
719
+ readonly AUDIO_NOT_INGESTED: "META_WHATSAPP_AUDIO_NOT_INGESTED";
720
+ readonly MESSAGE_NOT_AUDIO: "META_WHATSAPP_MESSAGE_NOT_AUDIO";
721
+ readonly TRANSCRIPTION_DISABLED: "META_WHATSAPP_TRANSCRIPTION_DISABLED";
562
722
  };
563
723
  declare class WindowExpiredError extends MetaWhatsAppError {
564
724
  readonly hoursSinceLastMessage?: number | undefined;
@@ -579,5 +739,64 @@ declare class TemplateNotConfiguredError extends MetaWhatsAppError {
579
739
  declare class SessionNotFoundError extends MetaWhatsAppError {
580
740
  constructor(whatsappNumber: string);
581
741
  }
742
+ /**
743
+ * Pediram transcrição de um áudio que ainda não foi copiado da Meta para o storage.
744
+ *
745
+ * 409 e não 404: a mensagem existe e o áudio vai chegar — a ingestão é assíncrona e o atendente
746
+ * simplesmente clicou antes de ela terminar. É o único erro de transcrição em que "tente de novo em
747
+ * alguns segundos" é a orientação correta para a interface.
748
+ */
749
+ declare class AudioNotIngestedError extends MetaWhatsAppError {
750
+ constructor(messageId: string);
751
+ }
752
+ /**
753
+ * Transcrição está desligada para esta empresa.
754
+ *
755
+ * Guarda de última linha, não o caminho normal: o painel lê as configurações e nem desenha o botão
756
+ * quando está desligado. Existe para o caso de a tela estar com dado velho, ou de alguém chamar a
757
+ * rota direto — e é `409` (estado atual conflita com a operação), não `403`: ninguém está sem
758
+ * permissão, o recurso está desligado por escolha e liga a qualquer momento.
759
+ */
760
+ declare class TranscriptionDisabledError extends MetaWhatsAppError {
761
+ constructor();
762
+ }
763
+ /** Pediram transcrição de mensagem que não é áudio. Nunca melhora com retentativa. */
764
+ declare class MessageNotAudioError extends MetaWhatsAppError {
765
+ constructor(messageId: string, type: string);
766
+ }
767
+
768
+ /**
769
+ * Mídia que veio do simulador de conversa, não da Meta.
770
+ *
771
+ * Vive no `contracts` porque é literalmente um contrato entre as duas pontas: o front gera o id, o
772
+ * backend o resolve. Nasceu duplicado num produto (duas constantes iguais, cada uma com um
773
+ * comentário pedindo "precisa bater com a outra") e por isso o segundo produto não herdou nada —
774
+ * este pacote é o único lugar em que os dois lados já se encontram.
775
+ *
776
+ * O simulador manda **webhook**, e webhook da Meta carrega referência de mídia (um id), nunca o
777
+ * binário. Então o arquivo gravado no navegador precisa existir em algum lugar que o servidor
778
+ * alcance, e o id precisa dizer onde — é essa a convenção deste arquivo.
779
+ *
780
+ * Existe no pacote, e não em cada host, porque a convenção tem DOIS lados que precisam concordar
781
+ * (quem gera o id no front, quem o resolve no backend). Onde ela ficou no host, o resultado foi
782
+ * exatamente o previsível: duas constantes iguais em pacotes diferentes, cada uma com um comentário
783
+ * pedindo "precisa bater com a outra" — e o segundo produto não herdou nada e ficou sem o recurso.
784
+ */
785
+ /**
786
+ * Prefixo que marca o id como local.
787
+ *
788
+ * Dois-pontos no fim de propósito: id da Meta é alfanumérico com `_` e `-`, então o separador torna
789
+ * a colisão impossível em vez de improvável.
790
+ */
791
+ declare const PREVIEW_MEDIA_ID_PREFIX = "preview-upload:";
792
+ /** Monta o id que o simulador manda no webhook. */
793
+ declare function toPreviewMediaId(uploadId: string): string;
794
+ /**
795
+ * `uploadId` embutido no id, ou `undefined` quando a mídia veio mesmo da Meta.
796
+ *
797
+ * Devolver `undefined` para id sem prefixo é o que mantém o caminho normal intacto: o adaptador só
798
+ * desvia para o storage quando o prefixo está lá.
799
+ */
800
+ declare function resolvePreviewUploadId(mediaId: string): string | undefined;
582
801
 
583
- export { CROSS_FLOW_PREFIX, type CacheInterface, type CatalogPort, type CatalogProduct, type ChannelAdapterInterface, ConfigMissingError, type ConversationMessage, type ConversationSession, type ConversationSummary, type CreateTemplateResult, DuplicateWebhookDeliveryError, FLOW_ACTION_KIND, type FlowActionHandler, type FlowActionKind, type FlowActionRegistry, type FlowActionResult, type FlowConditionOperator, type FlowGraphData, type FlowGraphSummary, type FlowNodeData, type FlowNodeNext, type FlowNodeType, type FlowQuestionType, type InboundMediaDescriptor, InvalidWebhookSignatureError, type ListConversationsParams, type ListMessagesParams, type LiveFlowPosition, META_WHATSAPP_ERROR_CODES, type MessageDirection, type MessageHookOutcome, type MessageSender, type MessageStatus, MetaWhatsAppError, type MetaWhatsAppHooks, type ObjectStorageInterface, type RealtimeNotifierInterface, type SessionMode, SessionNotFoundError, type SessionState, type SubjectResolverInterface, type TemplateConfig, TemplateNotConfiguredError, type TemplateVariablesMap, WhatsAppMessage, type WhatsAppSettings, WhatsAppStatus, type WhatsAppTemplateSummary, WindowExpiredError, crossFlowKey, flowGraphNodesSchema, flowNodeDataSchema, flowNodeNextSchema, isCrossFlowTarget };
802
+ export { AudioNotIngestedError, CROSS_FLOW_PREFIX, type CacheInterface, type CatalogPort, type CatalogProduct, type ChannelAdapterInterface, ConfigMissingError, type ConversationMessage, type ConversationSession, type ConversationSummary, type CreateTemplateResult, DuplicateWebhookDeliveryError, FLOW_ACTION_KIND, type FlowActionHandler, type FlowActionKind, type FlowActionRegistry, type FlowActionResult, type FlowConditionOperator, type FlowGraphData, type FlowGraphSummary, type FlowNodeData, type FlowNodeNext, type FlowNodeType, type FlowQuestionType, type InboundMediaDescriptor, InvalidWebhookSignatureError, type ListConversationsParams, type ListMessagesParams, type LiveFlowPosition, META_WHATSAPP_ERROR_CODES, type MessageDirection, type MessageHookOutcome, MessageNotAudioError, type MessageSender, type MessageStatus, MetaWhatsAppError, type MetaWhatsAppHooks, type ObjectStorageInterface, PREVIEW_MEDIA_ID_PREFIX, type RealtimeNotifierInterface, type SendChannelMediaParams, type SessionMode, SessionNotFoundError, type SessionState, type SubjectResolverInterface, type TemplateConfig, TemplateNotConfiguredError, type TemplateVariablesMap, type TranscriptionDeferredDescriptor, TranscriptionDisabledError, type TranscriptionMode, type UnhandledWebhookEventDescriptor, WHATSAPP_CHOICE_LIMIT, WhatsAppMessage, WhatsAppPhoneNumberQualityUpdate, type WhatsAppSettings, WhatsAppStatus, WhatsAppTemplateStatusUpdate, type WhatsAppTemplateSummary, WindowExpiredError, crossFlowKey, flowGraphNodesSchema, flowNodeDataSchema, flowNodeNextSchema, isCrossFlowTarget, resolvePreviewUploadId, toPreviewMediaId };
package/dist/index.js CHANGED
@@ -75,22 +75,70 @@ var whatsAppStatusSchema = z.object({
75
75
  timestamp: z.string(),
76
76
  recipient_id: z.string().optional()
77
77
  });
78
+ var WHATSAPP_WEBHOOK_FIELDS = {
79
+ MESSAGES: "messages",
80
+ MESSAGE_ECHOES: "message_echoes",
81
+ TEMPLATE_STATUS_UPDATE: "message_template_status_update",
82
+ PHONE_NUMBER_QUALITY_UPDATE: "phone_number_quality_update"
83
+ };
84
+ var whatsAppTemplateStatusEventSchema = z.enum([
85
+ "APPROVED",
86
+ "REJECTED",
87
+ "PENDING",
88
+ "PAUSED",
89
+ "PENDING_DELETION",
90
+ "DISABLED",
91
+ "FLAGGED"
92
+ ]);
93
+ var whatsAppTemplateStatusUpdateSchema = z.object({
94
+ event: whatsAppTemplateStatusEventSchema,
95
+ // A Meta manda o id do template como número em alguns eventos e como string em outros; o resto
96
+ // do sistema trata id como string, então normalizamos na fronteira em vez de espalhar `String()`.
97
+ message_template_id: z.union([
98
+ z.string(),
99
+ z.number()
100
+ ]).transform((value) => String(value)),
101
+ message_template_name: z.string(),
102
+ message_template_language: z.string(),
103
+ // Só vem em REJECTED/PAUSED/DISABLED, e a Meta às vezes manda `null` em vez de omitir.
104
+ reason: z.string().nullish(),
105
+ disable_date: z.string().optional()
106
+ });
107
+ var whatsAppQualityEventSchema = z.enum([
108
+ "FLAGGED",
109
+ "UNFLAGGED",
110
+ "ONBOARDING",
111
+ "UPGRADE",
112
+ "DOWNGRADE"
113
+ ]);
114
+ var whatsAppPhoneNumberQualityUpdateSchema = z.object({
115
+ display_phone_number: z.string(),
116
+ event: whatsAppQualityEventSchema,
117
+ // Tier de envio (`TIER_1K`, `TIER_10K`, …). Ausente em evento que não mexe no limite.
118
+ current_limit: z.string().optional(),
119
+ old_limit: z.string().optional()
120
+ });
121
+ var whatsAppWebhookValueSchema = z.object({
122
+ messaging_product: z.string().optional(),
123
+ messages: z.array(whatsAppMessageSchema).optional(),
124
+ message_echoes: z.array(whatsAppMessageEchoSchema).optional(),
125
+ statuses: z.array(whatsAppStatusSchema).optional(),
126
+ metadata: z.object({
127
+ display_phone_number: z.string(),
128
+ phone_number_id: z.string()
129
+ }).optional()
130
+ }).passthrough();
131
+ var whatsAppWebhookChangeSchema = z.object({
132
+ // Qual assinatura disparou. Opcional porque payload antigo de fixture não tem, e porque a
133
+ // ausência precisa degradar para "trata como mensagem", que é o comportamento histórico.
134
+ field: z.string().optional(),
135
+ value: whatsAppWebhookValueSchema
136
+ });
78
137
  var whatsAppWebhookPayloadSchema = z.object({
79
138
  object: z.string(),
80
139
  entry: z.array(z.object({
81
140
  id: z.string(),
82
- changes: z.array(z.object({
83
- value: z.object({
84
- messaging_product: z.string(),
85
- messages: z.array(whatsAppMessageSchema).optional(),
86
- message_echoes: z.array(whatsAppMessageEchoSchema).optional(),
87
- statuses: z.array(whatsAppStatusSchema).optional(),
88
- metadata: z.object({
89
- display_phone_number: z.string(),
90
- phone_number_id: z.string()
91
- }).optional()
92
- })
93
- }))
141
+ changes: z.array(whatsAppWebhookChangeSchema)
94
142
  }))
95
143
  });
96
144
 
@@ -158,6 +206,14 @@ var flowNodeDataSchema = z2.object({
158
206
  }).passthrough();
159
207
  var flowGraphNodesSchema = z2.record(z2.string(), flowNodeDataSchema);
160
208
 
209
+ // src/providers.ts
210
+ var WHATSAPP_CHOICE_LIMIT = {
211
+ BUTTONS: 3,
212
+ BUTTON_TITLE_LENGTH: 20,
213
+ LIST_ROWS: 10,
214
+ LIST_ROW_TITLE_LENGTH: 24
215
+ };
216
+
161
217
  // src/errors.ts
162
218
  var MetaWhatsAppError = class extends Error {
163
219
  static {
@@ -177,7 +233,10 @@ var META_WHATSAPP_ERROR_CODES = {
177
233
  DUPLICATE_DELIVERY: "META_WHATSAPP_DUPLICATE_DELIVERY",
178
234
  CONFIG_MISSING: "META_WHATSAPP_CONFIG_MISSING",
179
235
  TEMPLATE_NOT_CONFIGURED: "META_WHATSAPP_TEMPLATE_NOT_CONFIGURED",
180
- SESSION_NOT_FOUND: "META_WHATSAPP_SESSION_NOT_FOUND"
236
+ SESSION_NOT_FOUND: "META_WHATSAPP_SESSION_NOT_FOUND",
237
+ AUDIO_NOT_INGESTED: "META_WHATSAPP_AUDIO_NOT_INGESTED",
238
+ MESSAGE_NOT_AUDIO: "META_WHATSAPP_MESSAGE_NOT_AUDIO",
239
+ TRANSCRIPTION_DISABLED: "META_WHATSAPP_TRANSCRIPTION_DISABLED"
181
240
  };
182
241
  var WindowExpiredError = class extends MetaWhatsAppError {
183
242
  static {
@@ -236,29 +295,84 @@ var SessionNotFoundError = class extends MetaWhatsAppError {
236
295
  });
237
296
  }
238
297
  };
298
+ var AudioNotIngestedError = class extends MetaWhatsAppError {
299
+ static {
300
+ __name(this, "AudioNotIngestedError");
301
+ }
302
+ constructor(messageId) {
303
+ super("\xC1udio ainda est\xE1 sendo copiado \u2014 tente novamente em alguns segundos.", 409, META_WHATSAPP_ERROR_CODES.AUDIO_NOT_INGESTED, {
304
+ messageId
305
+ });
306
+ }
307
+ };
308
+ var TranscriptionDisabledError = class extends MetaWhatsAppError {
309
+ static {
310
+ __name(this, "TranscriptionDisabledError");
311
+ }
312
+ constructor() {
313
+ super("Transcri\xE7\xE3o de \xE1udio est\xE1 desligada para esta empresa.", 409, META_WHATSAPP_ERROR_CODES.TRANSCRIPTION_DISABLED);
314
+ }
315
+ };
316
+ var MessageNotAudioError = class extends MetaWhatsAppError {
317
+ static {
318
+ __name(this, "MessageNotAudioError");
319
+ }
320
+ constructor(messageId, type) {
321
+ super(`Mensagem ${messageId} \xE9 do tipo "${type}" \u2014 s\xF3 \xE1udio \xE9 transcrito.`, 422, META_WHATSAPP_ERROR_CODES.MESSAGE_NOT_AUDIO, {
322
+ messageId,
323
+ type
324
+ });
325
+ }
326
+ };
327
+
328
+ // src/previewMedia.types.ts
329
+ var PREVIEW_MEDIA_ID_PREFIX = "preview-upload:";
330
+ function toPreviewMediaId(uploadId) {
331
+ return `${PREVIEW_MEDIA_ID_PREFIX}${uploadId}`;
332
+ }
333
+ __name(toPreviewMediaId, "toPreviewMediaId");
334
+ function resolvePreviewUploadId(mediaId) {
335
+ if (!mediaId.startsWith(PREVIEW_MEDIA_ID_PREFIX)) return void 0;
336
+ return mediaId.slice(PREVIEW_MEDIA_ID_PREFIX.length) || void 0;
337
+ }
338
+ __name(resolvePreviewUploadId, "resolvePreviewUploadId");
239
339
  export {
340
+ AudioNotIngestedError,
240
341
  CROSS_FLOW_PREFIX,
241
342
  ConfigMissingError,
242
343
  DuplicateWebhookDeliveryError,
243
344
  FLOW_ACTION_KIND,
244
345
  InvalidWebhookSignatureError,
245
346
  META_WHATSAPP_ERROR_CODES,
347
+ MessageNotAudioError,
246
348
  MetaWhatsAppError,
349
+ PREVIEW_MEDIA_ID_PREFIX,
247
350
  SessionNotFoundError,
248
351
  TemplateNotConfiguredError,
352
+ TranscriptionDisabledError,
353
+ WHATSAPP_CHOICE_LIMIT,
354
+ WHATSAPP_WEBHOOK_FIELDS,
249
355
  WindowExpiredError,
250
356
  crossFlowKey,
251
357
  flowGraphNodesSchema,
252
358
  flowNodeDataSchema,
253
359
  flowNodeNextSchema,
254
360
  isCrossFlowTarget,
361
+ resolvePreviewUploadId,
362
+ toPreviewMediaId,
255
363
  whatsAppInteractiveSchema,
256
364
  whatsAppMediaSchema,
257
365
  whatsAppMessageEchoSchema,
258
366
  whatsAppMessageSchema,
259
367
  whatsAppMessageStatusSchema,
260
368
  whatsAppOrderSchema,
369
+ whatsAppPhoneNumberQualityUpdateSchema,
370
+ whatsAppQualityEventSchema,
261
371
  whatsAppStatusSchema,
262
- whatsAppWebhookPayloadSchema
372
+ whatsAppTemplateStatusEventSchema,
373
+ whatsAppTemplateStatusUpdateSchema,
374
+ whatsAppWebhookChangeSchema,
375
+ whatsAppWebhookPayloadSchema,
376
+ whatsAppWebhookValueSchema
263
377
  };
264
378
  //# sourceMappingURL=index.js.map