@whanext/core 0.19.3 → 0.19.5

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,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.19.5
4
+
5
+ ### Fixed
6
+ - Zapo protocol mutations (`MESSAGE_EDIT` and `REVOKE`) no longer trust the undocumented `offline` envelope flag after the live connection is open; stale mutations are filtered by connection time instead.
7
+ - Protocol edits/revokes are recognized both from `message_protocol` and from a raw `message.protocolMessage` fallback, with existing deduplication preventing double delivery.
8
+ - Edited-message wrappers are unwrapped before normalization, preserving the previous/current payload expected by `messageEdited`.
9
+ - Decrypted `message_addon` edits now accept both the typed `message_edit` form and nested `protocolMessage` forms.
10
+ - Protocol target lookup remains ID-first so PN/LID/participant addressing differences do not prevent recovering the cached original.
11
+
12
+
13
+ ## 0.19.4
14
+ ### Corrigido
15
+ - Chamadas no provider Zapo agora usam o evento nativo `call`, sem depender do plugin completo de VoIP.
16
+ - `rejectCall()` envia diretamente a sinalização mínima `<call><reject/></call>` pelo `client.lowlevel.sendNode()`, preservando `callId` e `callCreatorJid`.
17
+ - Removidas as dependências `@zapo-js/voip`, `@roamhq/wrtc` e `libmlow-wasm` do WhaNext Core; detectar/rejeitar chamadas não exige WebRTC nem binário nativo.
18
+ - O `callCreatorJid` técnico é mantido em cache limitado para que a rejeição use o endereço de protocolo correto mesmo quando o evento público expõe o `callerPnJid`.
19
+
20
+ ### Compatibilidade
21
+ - A API pública permanece igual: `app.on('call')`, `CallEvent` e `rejectCall(callId, from)` não mudam para consumidores.
22
+ - O suporte completo a aceitar/realizar chamadas VoIP não faz parte da API pública do WhaNext.
23
+
3
24
  ## 0.19.3
4
25
  ### Corrigido
5
26
  - Carregamento lazy do plugin VoIP do Zapo para evitar importar o runtime WebRTC em consumidores e testes que não inicializam o provider.
@@ -58,7 +58,7 @@ O provider usa `@zapo-js/media-utils`. Para processamento completo de imagem, v
58
58
 
59
59
  ## Chamadas
60
60
 
61
- O suporte a `app.on('call')` e `rejectCall()` usa o plugin oficial `@zapo-js/voip`. As dependências de runtime do plugin fazem parte do pacote do WhaNext v0.18.
61
+ A partir da v0.19.4, `app.on('call')` usa o evento `call` nativo do Zapo e `rejectCall()` envia somente a sinalização de rejeição pelo `client.lowlevel`. O WhaNext não carrega o stack completo de VoIP/WebRTC para detectar ou rejeitar chamadas.
62
62
 
63
63
  ## Checklist de atualização
64
64
 
package/README.md CHANGED
@@ -994,6 +994,8 @@ app.on('call', async (call) => {
994
994
 
995
995
  `call.status` reflete o ciclo da chamada (`offer`, `ringing`, `preaccept`, `timeout`, `reject`, `accept`). `call.isVideo` e `call.isGroup` indicam o tipo. Somente chamadas em `offer` podem ser rejeitadas.
996
996
 
997
+ No provider Zapo, detecção e rejeição usam apenas a sinalização nativa de chamadas e `client.lowlevel.sendNode()`. O WhaNext não carrega o stack completo de VoIP/WebRTC para esse fluxo.
998
+
997
999
  ## Erros
998
1000
 
999
1001
  ```ts
package/dist/index.js CHANGED
@@ -2388,9 +2388,13 @@ import {
2388
2388
  // src/provider/zapo/normalize-message.ts
2389
2389
  function unwrapZapoMessageContent(input) {
2390
2390
  if (!input) return void 0;
2391
- const nested = input.ephemeralMessage?.message ?? input.viewOnceMessage?.message ?? input.viewOnceMessageV2?.message ?? viewOnceV2ExtensionMessage(input) ?? input.deviceSentMessage?.message ?? input.documentWithCaptionMessage?.message;
2391
+ const nested = input.ephemeralMessage?.message ?? input.viewOnceMessage?.message ?? input.viewOnceMessageV2?.message ?? viewOnceV2ExtensionMessage(input) ?? input.deviceSentMessage?.message ?? input.documentWithCaptionMessage?.message ?? editedWrapperMessage(input);
2392
2392
  return nested ? unwrapZapoMessageContent(nested) : input;
2393
2393
  }
2394
+ function editedWrapperMessage(input) {
2395
+ const edited = input.editedMessage;
2396
+ return edited?.message ?? void 0;
2397
+ }
2394
2398
  function viewOnceV2ExtensionMessage(input) {
2395
2399
  const extension = input.viewOnceMessageV2Extension;
2396
2400
  return extension?.message ?? void 0;
@@ -2710,6 +2714,7 @@ var ZapoProvider = class {
2710
2714
  #messageKeyStore = /* @__PURE__ */ new WeakMap();
2711
2715
  #deliveredMessageStore = /* @__PURE__ */ new Set();
2712
2716
  #handledProtocolStore = /* @__PURE__ */ new Set();
2717
+ #callCreatorStore = /* @__PURE__ */ new Map();
2713
2718
  #messageCacheSize;
2714
2719
  #client;
2715
2720
  #intentionalClose = false;
@@ -2965,9 +2970,26 @@ var ZapoProvider = class {
2965
2970
  const presence = this.#requireClient().presence;
2966
2971
  await presence.sendChatstate(chatId, { state: value });
2967
2972
  }
2968
- async rejectCall(callId, _from) {
2973
+ async rejectCall(callId, from) {
2969
2974
  const client = this.#requireClient();
2970
- await client.voip.rejectCall(callId);
2975
+ const creator = this.#callCreatorStore.get(callId) ?? from;
2976
+ if (!creator) {
2977
+ throw new WhaNextError("ARGUMENT_INVALID", "Call creator is required to reject a call.");
2978
+ }
2979
+ const lowlevel = client.lowlevel;
2980
+ await lowlevel.sendNode({
2981
+ tag: "call",
2982
+ attrs: { to: creator },
2983
+ content: [
2984
+ {
2985
+ tag: "reject",
2986
+ attrs: {
2987
+ "call-id": callId,
2988
+ "call-creator": creator
2989
+ }
2990
+ }
2991
+ ]
2992
+ });
2971
2993
  }
2972
2994
  async #ensureClient() {
2973
2995
  if (this.#client) return this.#client;
@@ -2996,8 +3018,6 @@ var ZapoProvider = class {
2996
3018
  messageSecret: "sqlite"
2997
3019
  }
2998
3020
  });
2999
- const { voipPlugin } = await import("@zapo-js/voip");
3000
- const voip = voipPlugin({ logLevel: "warn" });
3001
3021
  const client = new WaClient({
3002
3022
  store,
3003
3023
  sessionId: this.#options.sessionId ?? "default",
@@ -3009,8 +3029,7 @@ var ZapoProvider = class {
3009
3029
  autoDecrypt: true,
3010
3030
  persistAllSecrets: true
3011
3031
  },
3012
- media: { processor: createMediaProcessor() },
3013
- plugins: [voip]
3032
+ media: { processor: createMediaProcessor() }
3014
3033
  }, new WhaNextZapoLogger(this.#logger));
3015
3034
  this.#client = client;
3016
3035
  this.#bind(client);
@@ -3027,6 +3046,14 @@ var ZapoProvider = class {
3027
3046
  this.#pairingRequired = false;
3028
3047
  });
3029
3048
  client.on("message", (event) => {
3049
+ const protocol = this.#protocolMessage(event);
3050
+ if (protocol && this.#isMessageMutationProtocol(protocol.type)) {
3051
+ this.#handleProtocolEvent({
3052
+ ...event,
3053
+ protocolMessage: protocol
3054
+ });
3055
+ return;
3056
+ }
3030
3057
  this.#handleMessage(event);
3031
3058
  });
3032
3059
  client.on("message_send", (event) => {
@@ -3050,22 +3077,8 @@ var ZapoProvider = class {
3050
3077
  client.on("group", (event) => {
3051
3078
  this.#handleGroupEvent(event);
3052
3079
  });
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
- );
3080
+ client.on("call", (event) => {
3081
+ const call = this.#normalizeCall(event);
3069
3082
  if (call) void this.#events.emit("call", call);
3070
3083
  });
3071
3084
  client.on("connection", (event) => {
@@ -3108,26 +3121,56 @@ var ZapoProvider = class {
3108
3121
  void this.#events.emit("message", message);
3109
3122
  }
3110
3123
  #handleAddonEvent(event) {
3111
- if (event.kind !== "message_edit" || !event.targetMessageId) return;
3112
- const editedMessage = this.#addonEditedMessage(event.decrypted);
3124
+ const decrypted = this.#addonRecord(event.decrypted);
3125
+ const protocol = this.#addonProtocolMessage(decrypted);
3126
+ const kind = event.kind ?? this.#stringField(decrypted, "kind") ?? this.#stringField(decrypted, "type");
3127
+ const isEdit = kind === "message_edit" || protocol?.type === proto.Message.ProtocolMessage.Type.MESSAGE_EDIT;
3128
+ if (!isEdit) return;
3129
+ const targetMessageId = event.targetMessageId ?? this.#stringField(decrypted, "targetMessageId") ?? this.#stringField(decrypted, "targetMessageID") ?? protocol?.key?.id ?? void 0;
3130
+ if (!targetMessageId) return;
3131
+ const editedMessage = protocol?.editedMessage ?? this.#addonEditedMessage(event.decrypted);
3113
3132
  if (!editedMessage) return;
3133
+ const protocolKey = protocol?.key;
3114
3134
  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 } : {}
3135
+ ...protocolKey ?? {},
3136
+ id: targetMessageId,
3137
+ ...protocolKey?.remoteJid !== void 0 ? { remoteJid: protocolKey.remoteJid } : event.key.remoteJid !== void 0 ? { remoteJid: event.key.remoteJid } : {},
3138
+ ...protocolKey?.fromMe !== void 0 ? { fromMe: protocolKey.fromMe } : event.key.fromMe !== void 0 ? { fromMe: event.key.fromMe } : {},
3139
+ ...protocolKey?.participant !== void 0 ? { participant: protocolKey.participant } : event.key.participant !== void 0 ? { participant: event.key.participant } : {},
3140
+ ...protocolKey?.participantAlt !== void 0 ? { participantAlt: protocolKey.participantAlt } : event.key.participantAlt !== void 0 ? { participantAlt: event.key.participantAlt } : {}
3120
3141
  };
3121
3142
  this.#handleProtocolEvent({
3122
3143
  key: event.key,
3123
- ...event.offline !== void 0 ? { offline: event.offline } : {},
3144
+ ...event.timestampSeconds !== void 0 ? { timestampSeconds: event.timestampSeconds } : {},
3124
3145
  protocolMessage: {
3125
3146
  type: proto.Message.ProtocolMessage.Type.MESSAGE_EDIT,
3126
3147
  key: target,
3127
- editedMessage
3148
+ editedMessage,
3149
+ ...protocol?.timestampMs !== void 0 ? { timestampMs: protocol.timestampMs } : {}
3128
3150
  }
3129
3151
  });
3130
3152
  }
3153
+ #addonRecord(value) {
3154
+ return value && typeof value === "object" ? value : void 0;
3155
+ }
3156
+ #stringField(record, field) {
3157
+ const value = record?.[field];
3158
+ return typeof value === "string" && value.length > 0 ? value : void 0;
3159
+ }
3160
+ #addonProtocolMessage(record) {
3161
+ const direct = record?.protocolMessage;
3162
+ if (direct && typeof direct === "object") {
3163
+ return direct;
3164
+ }
3165
+ const message = record?.message;
3166
+ if (message && typeof message === "object") {
3167
+ const nested = message.protocolMessage;
3168
+ if (nested && typeof nested === "object") {
3169
+ return nested;
3170
+ }
3171
+ }
3172
+ return void 0;
3173
+ }
3131
3174
  #addonEditedMessage(value) {
3132
3175
  if (!value || typeof value !== "object") return void 0;
3133
3176
  const record = value;
@@ -3137,24 +3180,57 @@ var ZapoProvider = class {
3137
3180
  if (edited2 && typeof edited2 === "object") return edited2;
3138
3181
  }
3139
3182
  const edited = record.editedMessage;
3140
- if (edited && typeof edited === "object") return edited;
3183
+ if (edited && typeof edited === "object") {
3184
+ const nested = edited.message;
3185
+ return nested && typeof nested === "object" ? nested : edited;
3186
+ }
3141
3187
  const message = record.message;
3142
- if (message && typeof message === "object") return message;
3188
+ if (message && typeof message === "object") {
3189
+ const messageRecord = message;
3190
+ const nestedEdited = messageRecord.editedMessage;
3191
+ if (nestedEdited && typeof nestedEdited === "object") {
3192
+ const nested = nestedEdited.message;
3193
+ if (nested && typeof nested === "object") return nested;
3194
+ }
3195
+ return message;
3196
+ }
3143
3197
  return value;
3144
3198
  }
3199
+ #protocolMessage(event) {
3200
+ if (event.protocolMessage) return event.protocolMessage;
3201
+ const content = unwrapZapoMessageContent(event.message);
3202
+ return content?.protocolMessage;
3203
+ }
3204
+ #isMessageMutationProtocol(type) {
3205
+ return type === proto.Message.ProtocolMessage.Type.REVOKE || type === proto.Message.ProtocolMessage.Type.MESSAGE_EDIT;
3206
+ }
3207
+ #shouldIgnoreProtocolEvent(event) {
3208
+ if (this.#options.processOfflineMessages === true) return false;
3209
+ if (!this.#connected) return true;
3210
+ if (!this.#connectedAtSeconds || event.timestampSeconds == null) return false;
3211
+ const timestamp = toSeconds(event.timestampSeconds);
3212
+ if (timestamp === void 0) return false;
3213
+ return timestamp < this.#connectedAtSeconds - 3;
3214
+ }
3145
3215
  #handleProtocolEvent(event) {
3146
- if (this.#isOfflineMessage(event)) {
3147
- this.#logger.debug("Ignored protocol event queued before the current live connection.", {
3216
+ if (this.#shouldIgnoreProtocolEvent(event)) {
3217
+ this.#logger.debug("Ignored protocol mutation from before the current live connection.", {
3148
3218
  messageId: event.key.id ?? void 0,
3149
3219
  chatId: event.key.remoteJid ?? void 0,
3150
3220
  timestampSeconds: event.timestampSeconds ?? void 0
3151
3221
  });
3152
3222
  return;
3153
3223
  }
3154
- const protocol = event.protocolMessage ?? event.message?.protocolMessage;
3155
- if (!protocol) return;
3224
+ const protocol = this.#protocolMessage(event);
3225
+ if (!protocol || !this.#isMessageMutationProtocol(protocol.type)) return;
3156
3226
  const protocolKey = protocol.key;
3157
- if (!protocolKey?.id) return;
3227
+ if (!protocolKey?.id) {
3228
+ this.#logger.debug("Ignored Zapo protocol mutation without a target message id.", {
3229
+ messageId: event.key.id ?? void 0,
3230
+ protocolType: protocol.type ?? void 0
3231
+ });
3232
+ return;
3233
+ }
3158
3234
  const remoteJid = protocolKey.remoteJid ?? event.key.remoteJid;
3159
3235
  const participant = protocolKey.participant ?? event.key.participant;
3160
3236
  const participantAlt = protocolKey.participantAlt ?? event.key.participantAlt;
@@ -3164,9 +3240,10 @@ var ZapoProvider = class {
3164
3240
  ...participant !== void 0 ? { participant } : {},
3165
3241
  ...participantAlt !== void 0 ? { participantAlt } : {}
3166
3242
  };
3167
- if (!target.remoteJid) return;
3168
3243
  const stored = this.#findStoredMessage(target);
3169
- const type = protocol?.type;
3244
+ if (!target.remoteJid && stored?.key.remoteJid) target.remoteJid = stored.key.remoteJid;
3245
+ if (!target.remoteJid) return;
3246
+ const type = protocol.type;
3170
3247
  const mutationKey = this.#protocolDeliveryKey(event, target, type);
3171
3248
  if (mutationKey && this.#handledProtocolStore.has(mutationKey)) {
3172
3249
  this.#logger.debug("Ignored duplicate Zapo protocol event.", {
@@ -3180,6 +3257,12 @@ var ZapoProvider = class {
3180
3257
  const previous2 = stored ? normalizeZapoMessage(stored) : void 0;
3181
3258
  const deletedByMe = event.key.fromMe === true;
3182
3259
  const deletedById = event.key.participant ?? event.key.participantAlt ?? (deletedByMe ? this.getCurrentUserIds()[0] : event.key.remoteJid ?? void 0);
3260
+ if (!previous2) {
3261
+ this.#logger.debug("Zapo revoke target was not present in the recent message cache.", {
3262
+ targetMessageId: target.id ?? void 0,
3263
+ chatId: target.remoteJid ?? void 0
3264
+ });
3265
+ }
3183
3266
  void this.#events.emit("messageDeleted", {
3184
3267
  key: previous2?.keys ?? normalizeZapoKey(target),
3185
3268
  ...previous2 ? { message: previous2 } : {},
@@ -3189,9 +3272,8 @@ var ZapoProvider = class {
3189
3272
  });
3190
3273
  return;
3191
3274
  }
3192
- if (type !== proto.Message.ProtocolMessage.Type.MESSAGE_EDIT || !protocol.editedMessage) {
3193
- return;
3194
- }
3275
+ const editedContent = protocol.editedMessage ? this.#unwrapEditedContent(protocol.editedMessage) : void 0;
3276
+ if (!editedContent) return;
3195
3277
  const pushName = event.pushName ?? stored?.pushName;
3196
3278
  const edited = {
3197
3279
  ...stored ?? {},
@@ -3199,7 +3281,7 @@ var ZapoProvider = class {
3199
3281
  ...stored?.key ?? {},
3200
3282
  ...target
3201
3283
  },
3202
- message: protocol.editedMessage,
3284
+ message: editedContent,
3203
3285
  timestampSeconds: toSeconds(protocol.timestampMs) ?? event.timestampSeconds ?? stored?.timestampSeconds ?? Math.floor(Date.now() / 1e3),
3204
3286
  ...pushName !== void 0 ? { pushName } : {}
3205
3287
  };
@@ -3210,6 +3292,12 @@ var ZapoProvider = class {
3210
3292
  this.#remember(edited);
3211
3293
  const editedByMe = event.key.fromMe === true;
3212
3294
  const editedById = event.key.participant ?? event.key.participantAlt ?? (editedByMe ? this.getCurrentUserIds()[0] : event.key.remoteJid ?? void 0);
3295
+ if (!previous) {
3296
+ this.#logger.debug("Zapo edit target was not present in the recent message cache.", {
3297
+ targetMessageId: target.id ?? void 0,
3298
+ chatId: target.remoteJid ?? void 0
3299
+ });
3300
+ }
3213
3301
  void this.#events.emit("messageEdited", {
3214
3302
  key: message.keys,
3215
3303
  ...previous ? { previous } : {},
@@ -3219,6 +3307,10 @@ var ZapoProvider = class {
3219
3307
  editedAt: message.timestamp
3220
3308
  });
3221
3309
  }
3310
+ #unwrapEditedContent(message) {
3311
+ const wrapper = message.editedMessage;
3312
+ return wrapper?.message ?? message;
3313
+ }
3222
3314
  #handleGroupEvent(event) {
3223
3315
  const groupId = event.groupJid;
3224
3316
  if (!groupId) return;
@@ -3571,49 +3663,49 @@ var ZapoProvider = class {
3571
3663
  timestamp: /* @__PURE__ */ new Date()
3572
3664
  };
3573
3665
  }
3574
- #normalizeVoipCall(event, forcedStatus) {
3666
+ #normalizeCall(event) {
3575
3667
  const id = event.callId;
3576
- const from = event.callerPn ?? event.peerJid ?? event.callCreator;
3577
- const chatId = event.groupJid ?? event.peerJid ?? from;
3668
+ const creator = event.callCreatorJid ?? void 0;
3669
+ const from = event.callerPnJid ?? creator;
3670
+ const chatId = event.groupJid ?? from;
3578
3671
  if (!id || !from || !chatId) return void 0;
3672
+ if (creator) {
3673
+ this.#callCreatorStore.delete(id);
3674
+ this.#callCreatorStore.set(id, creator);
3675
+ while (this.#callCreatorStore.size > 256) {
3676
+ const oldest = this.#callCreatorStore.keys().next().value;
3677
+ if (!oldest) break;
3678
+ this.#callCreatorStore.delete(oldest);
3679
+ }
3680
+ }
3681
+ const timestampMs = toNumber2(event.timestampMs);
3579
3682
  return {
3580
3683
  id,
3581
3684
  chatId,
3582
3685
  from,
3583
- status: forcedStatus ?? this.#callStatus(event.stateData?.state),
3584
- isVideo: event.mediaType === "video",
3686
+ status: this.#callStatus(event.type),
3687
+ isVideo: event.isVideo === true,
3585
3688
  isGroup: Boolean(event.groupJid),
3586
- date: event.createdAt ?? /* @__PURE__ */ new Date()
3689
+ date: timestampMs && timestampMs > 0 ? new Date(timestampMs) : /* @__PURE__ */ new Date()
3587
3690
  };
3588
3691
  }
3589
3692
  #callStatus(status) {
3590
3693
  switch (status?.toLowerCase()) {
3591
3694
  case "offer":
3592
- case "initiating":
3593
3695
  return "offer";
3594
3696
  case "ringing":
3595
- case "incoming_ringing":
3596
3697
  return "ringing";
3597
3698
  case "preaccept":
3598
- case "connecting":
3599
3699
  return "preaccept";
3600
3700
  case "accept":
3601
- case "accepted":
3602
- case "active":
3603
3701
  return "accept";
3604
3702
  case "reject":
3605
- case "rejected":
3606
3703
  case "terminate":
3607
- case "terminated":
3608
- case "ended":
3609
3704
  return "reject";
3610
3705
  default:
3611
3706
  return "timeout";
3612
3707
  }
3613
3708
  }
3614
- #callEndStatus(reason) {
3615
- return reason?.toLowerCase() === "timeout" ? "timeout" : "reject";
3616
- }
3617
3709
  #groupAction(action) {
3618
3710
  const value = action?.toLowerCase() ?? "";
3619
3711
  if (value.includes("promote")) return "promote";
@@ -3773,6 +3865,12 @@ var WhaNextZapoLogger = class _WhaNextZapoLogger {
3773
3865
  return context ? { ...this.#context, ...context } : this.#context;
3774
3866
  }
3775
3867
  };
3868
+ function toNumber2(value) {
3869
+ if (typeof value === "number") return value;
3870
+ if (value?.toNumber) return value.toNumber();
3871
+ if (typeof value?.low === "number") return value.low;
3872
+ return void 0;
3873
+ }
3776
3874
  function toSeconds(value) {
3777
3875
  const raw = typeof value === "number" ? value : value?.toNumber ? value.toNumber() : value?.low;
3778
3876
  if (raw === void 0) return void 0;