Skip to content

Commit 1d38d4f

Browse files
octo-patchocto-patch
andauthored
fix(baileys): prevent healthy instances from being killed after stream:error 515 (#2509)
* fix(baileys): prevent healthy instances from being killed after stream:error 515 When WhatsApp sends stream:error code=515 (Connection Replaced), Baileys handles the reconnect correctly and fires connection.update with state='open'. However, WhatsApp then sends a 401 (loggedOut) to clean up the old session slot, which Evolution API incorrectly treated as a real logout, killing the newly-connected healthy instance. The fix tracks when a stream:error 515 node arrives via the CB:stream:error WebSocket event. If a loggedOut (401) close event fires within 30 seconds of a 515, it is treated as a transient reconnect rather than a real logout. Fixes #2498 * fix(baileys): name the 515 reconnect grace + tighten stream-error types Addresses sourcery-ai review feedback on the previous commit: - Extract the 30 000ms reconnect grace window into a named class constant STREAM_515_RECONNECT_GRACE_MS so future tuning is self-documenting rather than a literal scattered through the close handler. - Extract the magic '515' string into STREAM_ERROR_CODE_RECONNECT. - Replace the loose 'node: any' on the 'CB:stream:error' handler with a minimal structural type ({ attrs?: { code?: string | number } }) so the payload shape is documented and type-checked. - Compare the code via String(...) so a numeric 515 from the underlying socket library still triggers the grace window — the original literal '515' check would have silently broken on a type change. * fix(baileys): satisfy prettier — collapse 515 reconnect guard onto one line Check Code Quality lint failed on prettier/prettier (the recentStream515 expression fits within the project's 120-col printWidth on a single line, while shouldReconnect still needs to break across two). Co-authored-by: Octopus <liyuan851277048@icloud.com> --------- Co-authored-by: octo-patch <octo-patch@github.com> Co-authored-by: octo-patch <octo-patch@users.noreply.github.com>
1 parent 5624bda commit 1d38d4f

1 file changed

Lines changed: 21 additions & 1 deletion

File tree

src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ export class BaileysStartupService extends ChannelStartupService {
264264
private isDeleting = false; // Flag to prevent reconnection during deletion
265265
private logBaileys = this.configService.get<Log>('LOG').BAILEYS;
266266
private eventProcessingQueue: Promise<void> = Promise.resolve();
267+
private _lastStream515At = 0;
267268

268269
// Cumulative history sync counters (reset on new sync or completion)
269270
private historySyncMessageCount = 0;
@@ -275,6 +276,13 @@ export class BaileysStartupService extends ChannelStartupService {
275276
private readonly MESSAGE_CACHE_TTL_SECONDS = 5 * 60; // 5 minutes - avoid duplicate message processing
276277
private readonly UPDATE_CACHE_TTL_SECONDS = 30 * 60; // 30 minutes - avoid duplicate status updates
277278

279+
// Reconnect behaviour for the Baileys "stream:error 515" sequence.
280+
// After WhatsApp emits 515 it usually closes with `loggedOut`; that close is *not* a real logout
281+
// and we should reconnect. We treat any close arriving within this grace window as 515-driven.
282+
private static readonly STREAM_515_RECONNECT_GRACE_MS = 30_000;
283+
// The numeric WhatsApp stream-error code that triggers the grace-period reconnect above.
284+
private static readonly STREAM_ERROR_CODE_RECONNECT = '515';
285+
278286
public stateConnection: wa.StateConnection = { state: 'close' };
279287

280288
public phoneNumber: string;
@@ -506,7 +514,12 @@ export class BaileysStartupService extends ChannelStartupService {
506514
return;
507515
}
508516

509-
const shouldReconnect = !codesToNotReconnect.includes(statusCode);
517+
// If a stream:error 515 (Baileys' "restart needed" handshake) just fired,
518+
// a follow-up loggedOut is the expected restart signal — not an actual
519+
// logout — so reconnect anyway.
520+
const recentStream515 = Date.now() - this._lastStream515At < BaileysStartupService.STREAM_515_RECONNECT_GRACE_MS;
521+
const shouldReconnect =
522+
!codesToNotReconnect.includes(statusCode) || (statusCode === DisconnectReason.loggedOut && recentStream515);
510523

511524
this.logger.info({
512525
message: 'Connection closed, evaluating reconnection',
@@ -515,6 +528,7 @@ export class BaileysStartupService extends ChannelStartupService {
515528
instanceName: this.instance.name,
516529
});
517530

531+
518532
if (shouldReconnect) {
519533
// Add 3 second delay before reconnection to prevent rapid reconnection loops
520534
this.logger.info('Reconnecting in 3 seconds...');
@@ -813,6 +827,12 @@ export class BaileysStartupService extends ChannelStartupService {
813827
this.sendDataWebhook(Events.CALL, payload, true, ['websocket']);
814828
});
815829

830+
this.client.ws.on('CB:stream:error', (node: { attrs?: { code?: string | number } }) => {
831+
if (String(node?.attrs?.code) === BaileysStartupService.STREAM_ERROR_CODE_RECONNECT) {
832+
this._lastStream515At = Date.now();
833+
}
834+
});
835+
816836
this.phoneNumber = number;
817837

818838
return this.client;

0 commit comments

Comments
 (0)