@whanext/core 0.19.1 → 0.19.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.19.3
4
+ ### Corrigido
5
+ - Carregamento lazy do plugin VoIP do Zapo para evitar importar o runtime WebRTC em consumidores e testes que não inicializam o provider.
6
+ - Ajustes de tipagem dos testes com `exactOptionalPropertyTypes`.
7
+ - Provider Zapo agora usa `@zapo-js/voip` como único caminho de chamadas: eventos `voip_*` e `client.voip.rejectCall()`, sem fallback silencioso.
8
+ - `messageEdited` também reconhece edições criptografadas descriptografadas pelo Zapo em `message_addon`, além de `message_protocol`.
9
+ - `messageDeleted` e `messageEdited` ignoram protocolo de backlog durante bootstrap quando `processOfflineMessages` está desativado.
10
+ - Mensagens recebidas antes de `connection: open` deixam de ser publicadas como mensagens ao vivo; ainda entram no cache limitado para permitir recuperação posterior.
11
+ - Eventos `message` duplicados no mesmo runtime são descartados antes de chegar aos consumidores, evitando downloads repetidos de mídia/ViewOnce.
12
+
13
+ ### Compatibilidade
14
+ - Mudança corretiva e aditiva; a API pública de `CallEvent`, `messageDeleted` e `messageEdited` permanece inalterada.
15
+
16
+ ## 0.19.2
17
+
18
+ - Corrigido o download real de mídias citadas no provider Zapo: `downloadMedia()` agora entrega o `Proto.IMessage` bruto diretamente ao `downloadBytes()`, como suportado pela API do Zapo.
19
+ - Envelopes `viewOnceMessage`, `viewOnceMessageV2` e `viewOnceMessageV2Extension` são desembrulhados antes do download, evitando falhas ao responder imagens/vídeos de visualização única.
20
+ - A correção cobre replies comuns de imagem, vídeo, áudio e sticker, além de view-once.
21
+
22
+
3
23
  ## 0.19.1
4
24
 
5
25
  - Corrige menus de lista no provider Zapo usando `interactiveMessage` Native Flow com `single_select` em vez do `listMessage` legado.
package/dist/index.js CHANGED
@@ -2708,9 +2708,10 @@ var ZapoProvider = class {
2708
2708
  #logger;
2709
2709
  #messageStore = /* @__PURE__ */ new Map();
2710
2710
  #messageKeyStore = /* @__PURE__ */ new WeakMap();
2711
+ #deliveredMessageStore = /* @__PURE__ */ new Set();
2712
+ #handledProtocolStore = /* @__PURE__ */ new Set();
2711
2713
  #messageCacheSize;
2712
2714
  #client;
2713
- #voip;
2714
2715
  #intentionalClose = false;
2715
2716
  #reconnectAttempt = 0;
2716
2717
  #reconnectTimer;
@@ -2859,9 +2860,8 @@ var ZapoProvider = class {
2859
2860
  );
2860
2861
  }
2861
2862
  try {
2862
- const bytes = await this.#requireClient().message.downloadBytes(
2863
- message
2864
- );
2863
+ const mediaMessage = unwrapZapoMessageContent(message.message) ?? message.message;
2864
+ const bytes = await this.#requireClient().message.downloadBytes(mediaMessage);
2865
2865
  return {
2866
2866
  data: Buffer.from(bytes),
2867
2867
  kind: normalized.media.kind,
@@ -2966,15 +2966,8 @@ var ZapoProvider = class {
2966
2966
  await presence.sendChatstate(chatId, { state: value });
2967
2967
  }
2968
2968
  async rejectCall(callId, _from) {
2969
- this.#requireClient();
2970
- if (!this.#voip) {
2971
- throw new WhaNextError(
2972
- "PROVIDER_ERROR",
2973
- "WhatsApp call support is unavailable because the Zapo VoIP plugin could not be loaded.",
2974
- { recoverable: true }
2975
- );
2976
- }
2977
- await this.#voip.rejectCall(callId);
2969
+ const client = this.#requireClient();
2970
+ await client.voip.rejectCall(callId);
2978
2971
  }
2979
2972
  async #ensureClient() {
2980
2973
  if (this.#client) return this.#client;
@@ -3003,15 +2996,8 @@ var ZapoProvider = class {
3003
2996
  messageSecret: "sqlite"
3004
2997
  }
3005
2998
  });
3006
- const plugins = [];
3007
- try {
3008
- const { voipPlugin } = await import("@zapo-js/voip");
3009
- plugins.push(voipPlugin({ logLevel: "warn" }));
3010
- } catch (error) {
3011
- this.#logger.warn("Zapo VoIP support is unavailable; call events and rejection are disabled.", {
3012
- error: error instanceof Error ? error.message : String(error)
3013
- });
3014
- }
2999
+ const { voipPlugin } = await import("@zapo-js/voip");
3000
+ const voip = voipPlugin({ logLevel: "warn" });
3015
3001
  const client = new WaClient({
3016
3002
  store,
3017
3003
  sessionId: this.#options.sessionId ?? "default",
@@ -3024,10 +3010,9 @@ var ZapoProvider = class {
3024
3010
  persistAllSecrets: true
3025
3011
  },
3026
3012
  media: { processor: createMediaProcessor() },
3027
- plugins
3013
+ plugins: [voip]
3028
3014
  }, new WhaNextZapoLogger(this.#logger));
3029
3015
  this.#client = client;
3030
- this.#voip = client.voip;
3031
3016
  this.#bind(client);
3032
3017
  return client;
3033
3018
  }
@@ -3059,49 +3044,60 @@ var ZapoProvider = class {
3059
3044
  client.on("message_protocol", (event) => {
3060
3045
  this.#handleProtocolEvent(event);
3061
3046
  });
3047
+ client.on("message_addon", (event) => {
3048
+ this.#handleAddonEvent(event);
3049
+ });
3062
3050
  client.on("group", (event) => {
3063
3051
  this.#handleGroupEvent(event);
3064
3052
  });
3065
- if (this.#voip) {
3066
- const voipClient = client;
3067
- voipClient.on("voip_call_incoming", (event) => {
3068
- const call = this.#normalizeVoipCall(event, "offer");
3069
- if (call) void this.#events.emit("call", call);
3070
- });
3071
- voipClient.on("voip_call_state", (event) => {
3072
- if (event.stateData?.state === "ended") return;
3073
- const call = this.#normalizeVoipCall(event);
3074
- if (call && call.status !== "offer") void this.#events.emit("call", call);
3075
- });
3076
- voipClient.on("voip_call_ended", (event) => {
3077
- const call = this.#normalizeVoipCall(
3078
- event,
3079
- this.#callEndStatus(event.stateData?.endReason)
3080
- );
3081
- if (call) void this.#events.emit("call", call);
3082
- });
3083
- }
3053
+ const voipClient = client;
3054
+ voipClient.on("voip_call_incoming", (event) => {
3055
+ const call = this.#normalizeVoipCall(event, "offer");
3056
+ if (call) void this.#events.emit("call", call);
3057
+ });
3058
+ voipClient.on("voip_call_state", (event) => {
3059
+ const state = event.stateData?.state;
3060
+ if (state === "incoming_ringing" || state === "ended") return;
3061
+ const call = this.#normalizeVoipCall(event);
3062
+ if (call) void this.#events.emit("call", call);
3063
+ });
3064
+ voipClient.on("voip_call_ended", (event) => {
3065
+ const call = this.#normalizeVoipCall(
3066
+ event,
3067
+ this.#callEndStatus(event.stateData?.endReason)
3068
+ );
3069
+ if (call) void this.#events.emit("call", call);
3070
+ });
3084
3071
  client.on("connection", (event) => {
3085
3072
  void this.#handleConnectionEvent(client, event);
3086
3073
  });
3087
3074
  }
3088
3075
  #handleMessage(event) {
3089
3076
  const stored = event;
3077
+ if (stored.key?.id && stored.message) this.#remember(stored);
3078
+ const quoted = extractQuotedZapoMessage(event);
3079
+ if (quoted?.key.id && quoted.message) {
3080
+ this.#remember(quoted);
3081
+ }
3090
3082
  if (this.#isOfflineMessage(stored)) {
3091
- this.#logger.debug("Ignored message queued before the current connection.", {
3083
+ this.#logger.debug("Ignored message queued before the current live connection.", {
3092
3084
  messageId: stored.key.id ?? void 0,
3093
3085
  chatId: stored.key.remoteJid ?? void 0,
3094
3086
  timestampSeconds: stored.timestampSeconds ?? void 0
3095
3087
  });
3096
3088
  return;
3097
3089
  }
3098
- if (stored.key?.id && stored.message) this.#remember(stored);
3099
- const quoted = extractQuotedZapoMessage(event);
3100
- if (quoted?.key.id && quoted.message) {
3101
- this.#remember(quoted);
3090
+ const deliveryKey = this.#messageDeliveryKey(stored.key);
3091
+ if (stored.key.id && this.#deliveredMessageStore.has(deliveryKey)) {
3092
+ this.#logger.debug("Ignored duplicate Zapo message event.", {
3093
+ messageId: stored.key.id,
3094
+ chatId: stored.key.remoteJid ?? void 0
3095
+ });
3096
+ return;
3102
3097
  }
3103
3098
  const message = normalizeZapoMessage(event);
3104
3099
  if (!message) return;
3100
+ if (stored.key.id) this.#rememberDeliveredMessage(deliveryKey);
3105
3101
  this.#messageKeyStore.set(message.keys, stored);
3106
3102
  if (message.quoted && quoted?.message) {
3107
3103
  this.#messageKeyStore.set(
@@ -3111,7 +3107,50 @@ var ZapoProvider = class {
3111
3107
  }
3112
3108
  void this.#events.emit("message", message);
3113
3109
  }
3110
+ #handleAddonEvent(event) {
3111
+ if (event.kind !== "message_edit" || !event.targetMessageId) return;
3112
+ const editedMessage = this.#addonEditedMessage(event.decrypted);
3113
+ if (!editedMessage) return;
3114
+ const target = {
3115
+ id: event.targetMessageId,
3116
+ ...event.key.remoteJid !== void 0 ? { remoteJid: event.key.remoteJid } : {},
3117
+ ...event.key.fromMe !== void 0 ? { fromMe: event.key.fromMe } : {},
3118
+ ...event.key.participant !== void 0 ? { participant: event.key.participant } : {},
3119
+ ...event.key.participantAlt !== void 0 ? { participantAlt: event.key.participantAlt } : {}
3120
+ };
3121
+ this.#handleProtocolEvent({
3122
+ key: event.key,
3123
+ ...event.offline !== void 0 ? { offline: event.offline } : {},
3124
+ protocolMessage: {
3125
+ type: proto.Message.ProtocolMessage.Type.MESSAGE_EDIT,
3126
+ key: target,
3127
+ editedMessage
3128
+ }
3129
+ });
3130
+ }
3131
+ #addonEditedMessage(value) {
3132
+ if (!value || typeof value !== "object") return void 0;
3133
+ const record = value;
3134
+ const protocol = record.protocolMessage;
3135
+ if (protocol && typeof protocol === "object") {
3136
+ const edited2 = protocol.editedMessage;
3137
+ if (edited2 && typeof edited2 === "object") return edited2;
3138
+ }
3139
+ const edited = record.editedMessage;
3140
+ if (edited && typeof edited === "object") return edited;
3141
+ const message = record.message;
3142
+ if (message && typeof message === "object") return message;
3143
+ return value;
3144
+ }
3114
3145
  #handleProtocolEvent(event) {
3146
+ if (this.#isOfflineMessage(event)) {
3147
+ this.#logger.debug("Ignored protocol event queued before the current live connection.", {
3148
+ messageId: event.key.id ?? void 0,
3149
+ chatId: event.key.remoteJid ?? void 0,
3150
+ timestampSeconds: event.timestampSeconds ?? void 0
3151
+ });
3152
+ return;
3153
+ }
3115
3154
  const protocol = event.protocolMessage ?? event.message?.protocolMessage;
3116
3155
  if (!protocol) return;
3117
3156
  const protocolKey = protocol.key;
@@ -3128,7 +3167,16 @@ var ZapoProvider = class {
3128
3167
  if (!target.remoteJid) return;
3129
3168
  const stored = this.#findStoredMessage(target);
3130
3169
  const type = protocol?.type;
3170
+ const mutationKey = this.#protocolDeliveryKey(event, target, type);
3171
+ if (mutationKey && this.#handledProtocolStore.has(mutationKey)) {
3172
+ this.#logger.debug("Ignored duplicate Zapo protocol event.", {
3173
+ messageId: event.key.id ?? void 0,
3174
+ targetMessageId: target.id ?? void 0
3175
+ });
3176
+ return;
3177
+ }
3131
3178
  if (type === proto.Message.ProtocolMessage.Type.REVOKE) {
3179
+ if (mutationKey) this.#rememberHandledProtocol(mutationKey);
3132
3180
  const previous2 = stored ? normalizeZapoMessage(stored) : void 0;
3133
3181
  const deletedByMe = event.key.fromMe === true;
3134
3182
  const deletedById = event.key.participant ?? event.key.participantAlt ?? (deletedByMe ? this.getCurrentUserIds()[0] : event.key.remoteJid ?? void 0);
@@ -3158,6 +3206,7 @@ var ZapoProvider = class {
3158
3206
  const message = normalizeZapoMessage(edited);
3159
3207
  if (!message) return;
3160
3208
  const previous = stored ? normalizeZapoMessage(stored) : void 0;
3209
+ if (mutationKey) this.#rememberHandledProtocol(mutationKey);
3161
3210
  this.#remember(edited);
3162
3211
  const editedByMe = event.key.fromMe === true;
3163
3212
  const editedById = event.key.participant ?? event.key.participantAlt ?? (editedByMe ? this.getCurrentUserIds()[0] : event.key.remoteJid ?? void 0);
@@ -3608,9 +3657,33 @@ var ZapoProvider = class {
3608
3657
  #messageStoreKey(key) {
3609
3658
  return `${key.remoteJid ?? ""}:${key.id ?? ""}:${key.participant ?? key.participantAlt ?? ""}`;
3610
3659
  }
3660
+ #messageDeliveryKey(key) {
3661
+ return `${key.remoteJid ?? ""}:${key.id ?? ""}`;
3662
+ }
3663
+ #protocolDeliveryKey(event, target, type) {
3664
+ if (type == null || !event.key.id || !target.id) return void 0;
3665
+ return `${type}:${event.key.remoteJid ?? target.remoteJid ?? ""}:${event.key.id}:${target.id}`;
3666
+ }
3667
+ #rememberHandledProtocol(key) {
3668
+ this.#handledProtocolStore.delete(key);
3669
+ this.#handledProtocolStore.add(key);
3670
+ while (this.#handledProtocolStore.size > this.#messageCacheSize) {
3671
+ const oldest = this.#handledProtocolStore.values().next().value;
3672
+ if (oldest) this.#handledProtocolStore.delete(oldest);
3673
+ }
3674
+ }
3675
+ #rememberDeliveredMessage(key) {
3676
+ this.#deliveredMessageStore.delete(key);
3677
+ this.#deliveredMessageStore.add(key);
3678
+ while (this.#deliveredMessageStore.size > this.#messageCacheSize) {
3679
+ const oldest = this.#deliveredMessageStore.values().next().value;
3680
+ if (oldest) this.#deliveredMessageStore.delete(oldest);
3681
+ }
3682
+ }
3611
3683
  #isOfflineMessage(message) {
3612
3684
  if (this.#options.processOfflineMessages === true) return false;
3613
3685
  if (message.offline === true) return true;
3686
+ if (!this.#connected) return true;
3614
3687
  if (!this.#connectedAtSeconds || message.timestampSeconds == null) return false;
3615
3688
  const timestamp = toSeconds(message.timestampSeconds);
3616
3689
  if (timestamp === void 0) return false;