feat: implement alwaysOnline presence flag (was a no-op)#91
feat: implement alwaysOnline presence flag (was a no-op)#91michelspinelli wants to merge 1 commit into
Conversation
The `alwaysOnline` advanced setting exists on the instance model, the REST API and Swagger, but it was never wired into the presence logic. On every connect the device was marked `PresenceAvailable` unconditionally and the periodic `schedulePresenceUpdates` job kept re-asserting it, so the linked device stayed permanently "available". WhatsApp then delivers messages to that active session and suppresses push notifications on the user's phone. This gates the connect-time presence (and the periodic job) behind `Instance.AlwaysOnline`. When the flag is false we send `PresenceUnavailable` once so the phone keeps receiving push notifications; when true the previous always-online behavior is preserved. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reviewer's GuideWires the existing alwaysOnline instance flag into the WhatsApp presence handling so that presence updates and periodic presence scheduling are conditional on the flag, sending available only when alwaysOnline is true and explicitly sending unavailable when it is false, with additional logging around these behaviors. Sequence diagram for alwaysOnline-controlled presence handling on connectsequenceDiagram
participant WhatsAppServer
participant MyClient
participant WAClient
participant schedulePresenceUpdates
participant Logger
WhatsAppServer->>MyClient: events.Connected
MyClient->>MyClient: myEventHandler
alt Instance.AlwaysOnline == true
MyClient->>schedulePresenceUpdates: schedulePresenceUpdates(mycli)
MyClient->>WAClient: SendPresence(context, PresenceAvailable)
alt SendPresence succeeds
MyClient->>Logger: LogWarn(Marked self as available)
else SendPresence fails
MyClient->>Logger: LogWarn(Failed to send available presence)
end
else Instance.AlwaysOnline == false
MyClient->>WAClient: SendPresence(context, PresenceUnavailable)
alt SendPresence succeeds
MyClient->>Logger: LogInfo(Marked self as unavailable)
else SendPresence fails
MyClient->>Logger: LogWarn(Failed to send unavailable presence)
end
end
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 1 issue, and left some high level feedback:
- Consider using a non-Warn log level (e.g. Info/Debug) for the normal "Marked self as available" path so that only actual failures log as warnings, and align the log levels between the available/unavailable branches.
- Instead of calling SendPresence with context.Background(), consider threading through a request-scoped context or adding a timeout context to avoid potential hangs if the presence call blocks.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider using a non-Warn log level (e.g. Info/Debug) for the normal "Marked self as available" path so that only actual failures log as warnings, and align the log levels between the available/unavailable branches.
- Instead of calling SendPresence with context.Background(), consider threading through a request-scoped context or adding a timeout context to avoid potential hangs if the presence call blocks.
## Individual Comments
### Comment 1
<location path="pkg/whatsmeow/service/whatsmeow.go" line_range="917-930" />
<code_context>
+ if err != nil {
+ mycli.loggerWrapper.GetLogger(mycli.userID).LogWarn("[%s] Failed to send available presence %v", mycli.userID, err)
+ } else {
+ mycli.loggerWrapper.GetLogger(mycli.userID).LogWarn("[%s] Marked self as available", mycli.userID)
+ }
} else {
- mycli.loggerWrapper.GetLogger(mycli.userID).LogWarn("[%s] Marked self as available", mycli.userID)
+ err = mycli.WAClient.SendPresence(context.Background(), types.PresenceUnavailable)
+ if err != nil {
+ mycli.loggerWrapper.GetLogger(mycli.userID).LogWarn("[%s] Failed to send unavailable presence %v", mycli.userID, err)
+ } else {
+ mycli.loggerWrapper.GetLogger(mycli.userID).LogInfo("[%s] Marked self as unavailable (alwaysOnline=false)", mycli.userID)
+ }
}
</code_context>
<issue_to_address>
**suggestion:** Logging levels for successful presence updates are inconsistent and may be noisy.
In the `AlwaysOnline` branch, a successful `SendPresence` logs at `Warn`, while the `alwaysOnline=false` success logs at `Info`. Using `Warn` for an expected success will add noise and obscure real problems. Please use a non-warning level for the success case here, and keep both branches consistent unless there’s a strong reason not to.
```suggestion
err = mycli.WAClient.SendPresence(context.Background(), types.PresenceAvailable)
if err != nil {
mycli.loggerWrapper.GetLogger(mycli.userID).LogWarn("[%s] Failed to send available presence %v", mycli.userID, err)
} else {
mycli.loggerWrapper.GetLogger(mycli.userID).LogInfo("[%s] Marked self as available (alwaysOnline=true)", mycli.userID)
}
} else {
err = mycli.WAClient.SendPresence(context.Background(), types.PresenceUnavailable)
if err != nil {
mycli.loggerWrapper.GetLogger(mycli.userID).LogWarn("[%s] Failed to send unavailable presence %v", mycli.userID, err)
} else {
mycli.loggerWrapper.GetLogger(mycli.userID).LogInfo("[%s] Marked self as unavailable (alwaysOnline=false)", mycli.userID)
}
}
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| err = mycli.WAClient.SendPresence(context.Background(), types.PresenceAvailable) | ||
| if err != nil { | ||
| mycli.loggerWrapper.GetLogger(mycli.userID).LogWarn("[%s] Failed to send available presence %v", mycli.userID, err) | ||
| } else { | ||
| mycli.loggerWrapper.GetLogger(mycli.userID).LogWarn("[%s] Marked self as available", mycli.userID) | ||
| } | ||
| } else { | ||
| mycli.loggerWrapper.GetLogger(mycli.userID).LogWarn("[%s] Marked self as available", mycli.userID) | ||
| err = mycli.WAClient.SendPresence(context.Background(), types.PresenceUnavailable) | ||
| if err != nil { | ||
| mycli.loggerWrapper.GetLogger(mycli.userID).LogWarn("[%s] Failed to send unavailable presence %v", mycli.userID, err) | ||
| } else { | ||
| mycli.loggerWrapper.GetLogger(mycli.userID).LogInfo("[%s] Marked self as unavailable (alwaysOnline=false)", mycli.userID) | ||
| } | ||
| } |
There was a problem hiding this comment.
suggestion: Logging levels for successful presence updates are inconsistent and may be noisy.
In the AlwaysOnline branch, a successful SendPresence logs at Warn, while the alwaysOnline=false success logs at Info. Using Warn for an expected success will add noise and obscure real problems. Please use a non-warning level for the success case here, and keep both branches consistent unless there’s a strong reason not to.
| err = mycli.WAClient.SendPresence(context.Background(), types.PresenceAvailable) | |
| if err != nil { | |
| mycli.loggerWrapper.GetLogger(mycli.userID).LogWarn("[%s] Failed to send available presence %v", mycli.userID, err) | |
| } else { | |
| mycli.loggerWrapper.GetLogger(mycli.userID).LogWarn("[%s] Marked self as available", mycli.userID) | |
| } | |
| } else { | |
| mycli.loggerWrapper.GetLogger(mycli.userID).LogWarn("[%s] Marked self as available", mycli.userID) | |
| err = mycli.WAClient.SendPresence(context.Background(), types.PresenceUnavailable) | |
| if err != nil { | |
| mycli.loggerWrapper.GetLogger(mycli.userID).LogWarn("[%s] Failed to send unavailable presence %v", mycli.userID, err) | |
| } else { | |
| mycli.loggerWrapper.GetLogger(mycli.userID).LogInfo("[%s] Marked self as unavailable (alwaysOnline=false)", mycli.userID) | |
| } | |
| } | |
| err = mycli.WAClient.SendPresence(context.Background(), types.PresenceAvailable) | |
| if err != nil { | |
| mycli.loggerWrapper.GetLogger(mycli.userID).LogWarn("[%s] Failed to send available presence %v", mycli.userID, err) | |
| } else { | |
| mycli.loggerWrapper.GetLogger(mycli.userID).LogInfo("[%s] Marked self as available (alwaysOnline=true)", mycli.userID) | |
| } | |
| } else { | |
| err = mycli.WAClient.SendPresence(context.Background(), types.PresenceUnavailable) | |
| if err != nil { | |
| mycli.loggerWrapper.GetLogger(mycli.userID).LogWarn("[%s] Failed to send unavailable presence %v", mycli.userID, err) | |
| } else { | |
| mycli.loggerWrapper.GetLogger(mycli.userID).LogInfo("[%s] Marked self as unavailable (alwaysOnline=false)", mycli.userID) | |
| } | |
| } |
|
🔴 👎 A implementação do flag |
Problem
Numbers connected through Evolution Go stop receiving push notifications on the phone. This happens regardless of the
alwaysOnlineadvanced setting — even withalwaysOnline=falsefor every instance.Root cause
The
alwaysOnlinesetting exists on the instance model, the REST API and Swagger (the CHANGELOG even notesalwaysOnline (still to be implemented)), but it was never wired into the presence logic.In
pkg/whatsmeow/service/whatsmeow.go, on theevents.Connectedhandler, the client:SendPresence(types.PresenceAvailable)unconditionally on every (re)connect, andschedulePresenceUpdates, which every 1–3h togglesUnavailable→Available.Since whatsmeow reconnects frequently, the linked device is kept permanently "available". WhatsApp then delivers messages to that active companion session and suppresses push notifications on the primary phone.
Fix
Gate the connect-time presence (and the periodic
schedulePresenceUpdatesjob) behindInstance.AlwaysOnline:alwaysOnline == true→ previous behavior (mark available + start the periodic job).alwaysOnline == false→ sendPresenceUnavailableonce, so the phone keeps receiving push notifications.This makes the existing panel/API toggle actually work, per-instance, with no breaking change to current setups (the flag defaults to
falsein the model — operators who relied on always-online can simply enable it).Testing
Built and deployed on a production VPS (upgraded from 0.6.1 to 0.7.1 + this patch) across 16 instances. After the change:
Marked self as unavailable (alwaysOnline=false)on connect instead ofMarked self as available;🤖 Generated with Claude Code
Summary by Sourcery
Respect the instance alwaysOnline flag when setting WhatsApp presence on connect so linked devices are not kept permanently available by default.
Bug Fixes:
Enhancements: