fix: interactive buttons not rendering (viewOnceMessage → relayMessage)#2651
fix: interactive buttons not rendering (viewOnceMessage → relayMessage)#2651n8nfelipe wants to merge 1 commit into
Conversation
- Replaced viewOnceMessage wrapper with direct interactiveMessage - Used relayMessage with additionalNodes (biz/interactive/native_flow/bot) - Added generateMessageIDV2 import from baileys - Buttons now render correctly on WhatsApp clients Fixes evolution-foundation#1234
Reviewer's GuideRefactors WhatsApp interactive button sending to stop wrapping messages in viewOnceMessage and instead directly build and relay interactiveMessage with correct biz/native_flow metadata and message IDs, adding validation of the target JID and custom relayMessage handling. Sequence diagram for updated WhatsApp interactiveMessage relay flowsequenceDiagram
actor User
participant Api as ApiServer
participant BS as BaileysStartupService
participant WA as WhatsAppClient
User->>Api: HTTP sendInteractiveButtons
Api->>BS: buttonMessage(data)
BS->>BS: whatsappNumber({ numbers })
BS-->>BS: validate isWA.exists / isJidGroup / broadcast
alt data.delay
BS->>WA: presenceSubscribe(sender)
BS->>WA: sendPresenceUpdate(composing, sender)
BS->>BS: delay(data.delay)
BS->>WA: sendPresenceUpdate(paused, sender)
end
opt data.thumbnailUrl
BS->>BS: prepareMediaMessage({ mediatype, media })
end
BS->>BS: build interactiveMessage
BS->>BS: generateMessageIDV2()
BS->>BS: generateWAMessageFromContent(sender, message, options)
BS->>BS: build additionalNodes(biz, interactive, native_flow, bot)
BS->>WA: relayMessage(sender, message, { messageId, additionalNodes })
WA-->>BS: id
BS-->>Api: WAMessage (interactive buttons)
Api-->>User: HTTP 200 (buttons rendered)
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- The
(isArray(value) && value.length) === 0check in the cleanup loop is likely incorrect due to operator precedence and will never behave as intended; it should be split or rewritten asisArray(value) && value.length === 0. - The new
const isWA = (await this.whatsappNumber({ numbers: [data.number] }))?.shift();line can returnundefinedwhen the number lookup fails, causingisWA.existsandisWA.jidto throw; add a guard for!isWAbefore accessing its properties.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `(isArray(value) && value.length) === 0` check in the cleanup loop is likely incorrect due to operator precedence and will never behave as intended; it should be split or rewritten as `isArray(value) && value.length === 0`.
- The new `const isWA = (await this.whatsappNumber({ numbers: [data.number] }))?.shift();` line can return `undefined` when the number lookup fails, causing `isWA.exists` and `isWA.jid` to throw; add a guard for `!isWA` before accessing its properties.
## Individual Comments
### Comment 1
<location path="src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts" line_range="3343-3344" />
<code_context>
- mentionsEveryOne: data?.mentionsEveryOne,
- mentioned: data?.mentioned,
- });
+ const isWA = (await this.whatsappNumber({ numbers: [data.number] }))?.shift();
+ if (!isWA.exists && !isJidGroup(isWA.jid) && !isWA.jid.includes('@broadcast')) {
+ throw new BadRequestException(isWA);
}
</code_context>
<issue_to_address>
**issue (bug_risk):** Guard against `isWA` being undefined before accessing its properties.
Because `whatsappNumber` is optional-chained, `isWA` can be `undefined` (e.g. empty result), and `isWA.exists` / `isWA.jid` will then throw. Add a guard before using its properties, for example:
```ts
const isWA = (await this.whatsappNumber({ numbers: [data.number] }))?.shift();
if (!isWA) {
// handle missing lookup
}
if (!isWA.exists && !isJidGroup(isWA.jid) && !isWA.jid.includes('@broadcast')) {
throw new BadRequestException(isWA);
}
```
</issue_to_address>
### Comment 2
<location path="src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts" line_range="3458-3459" />
<code_context>
+ fromMe: true,
+ };
+
+ for (const [key, value] of Object.entries(m)) {
+ if (!value || (isArray(value) && value.length) === 0) {
+ delete m[key];
+ }
</code_context>
<issue_to_address>
**issue (bug_risk):** The cleanup condition will incorrectly delete most non-array properties from `m`.
`(isArray(value) && value.length) === 0` is evaluated as `((isArray(value) && value.length) === 0)`. For non-arrays, `isArray(value)` is `false`, so the left side is `false`, and `false === 0` is `true`, causing deletion even when `value` is truthy. You probably want `isArray(value) && value.length === 0`, and ideally a more explicit condition that only deletes empty arrays rather than all falsy values.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| const isWA = (await this.whatsappNumber({ numbers: [data.number] }))?.shift(); | ||
| if (!isWA.exists && !isJidGroup(isWA.jid) && !isWA.jid.includes('@broadcast')) { |
There was a problem hiding this comment.
issue (bug_risk): Guard against isWA being undefined before accessing its properties.
Because whatsappNumber is optional-chained, isWA can be undefined (e.g. empty result), and isWA.exists / isWA.jid will then throw. Add a guard before using its properties, for example:
const isWA = (await this.whatsappNumber({ numbers: [data.number] }))?.shift();
if (!isWA) {
// handle missing lookup
}
if (!isWA.exists && !isJidGroup(isWA.jid) && !isWA.jid.includes('@broadcast')) {
throw new BadRequestException(isWA);
}| for (const [key, value] of Object.entries(m)) { | ||
| if (!value || (isArray(value) && value.length) === 0) { |
There was a problem hiding this comment.
issue (bug_risk): The cleanup condition will incorrectly delete most non-array properties from m.
(isArray(value) && value.length) === 0 is evaluated as ((isArray(value) && value.length) === 0). For non-arrays, isArray(value) is false, so the left side is false, and false === 0 is true, causing deletion even when value is truthy. You probably want isArray(value) && value.length === 0, and ideally a more explicit condition that only deletes empty arrays rather than all falsy values.
|
send to develop (we dont accept anything to main) @n8nfelipe |
Problem
Interactive buttons (reply, URL, copy, call, PIX) are not rendering in WhatsApp messages. The message arrives but buttons don't appear.
Root Cause
The
buttonMessage()function inwhatsapp.baileys.service.tswas wrapping the content inviewOnceMessage, which does not support interactive buttons.Solution
Replaced
viewOnceMessagewrapper with directinteractiveMessageand usedrelayMessagewith properadditionalNodes:biznode withinteractiveandnative_flowchildrenbotnode for private chats (biz_bot: '1')generateMessageIDV2for proper message ID generationChanges
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.tsTesting
Summary by Sourcery
Fix WhatsApp interactive button messages by sending native interactive messages via relayMessage with proper business and bot metadata.
Bug Fixes:
Enhancements: