Skip to content

feat(group): member-add-mode, join-approval-mode, participant requests + message forward - #2595

Open
alpesdigital-adm wants to merge 16 commits into
evolution-foundation:mainfrom
alpesdigital-adm:feat/group-admin-controls-and-forward
Open

alpesdigital-adm wants to merge 16 commits into
evolution-foundation:mainfrom
alpesdigital-adm:feat/group-admin-controls-and-forward

Conversation

@alpesdigital-adm

@alpesdigital-adm alpesdigital-adm commented Jun 20, 2026 •

Copy link
Copy Markdown

What

Adds REST endpoints for group/message features that baileys (the underlying lib) already implements but Evolution doesn't expose yet:

Endpoint baileys function
POST /group/memberAddMode groupMemberAddMode — restrict who adds members (admin_add / all_member_add)
POST /group/joinApprovalMode groupJoinApprovalMode — turn membership approval on/off (on / off)
GET /group/participantRequests groupRequestParticipantsList — list pending join requests
POST /group/updateParticipantRequests groupRequestParticipantsUpdate — approve/reject requests
POST /message/forwardMessage sendMessage(jid, { forward }) — forward a stored message (keeps the forwarded tag)

Why

Common group-admin needs (membership approval, locking member-add) and message forwarding. All supported by baileys (7.0.0-rc.9 in this repo) but with no HTTP surface until now.

How

Each endpoint follows the existing updateSetting / toggleEphemeral pattern: DTO + JSONSchema + router + controller + baileys service call. forwardMessage reuses the private getMessage(key, full) to load the stored WAMessage and passes it to sendMessage(jid, { forward }).

No new dependencies. tsc --noEmit + tsup build pass; pre-commit lint clean.

🤖 Generated with Claude Code

Summary by Sourcery

Expand WhatsApp group and message APIs while strengthening instance safety, authentication state handling, reconnection, and chat read-state synchronization.

New Features:

  • Expose REST endpoints for group member-add restrictions, join approvals, pending participant requests, and request approval or rejection.
  • Add REST support for forwarding stored WhatsApp messages while preserving the forwarded message behavior.

Bug Fixes:

  • Preserve instance names during instance creation while preventing untrusted query parameters from overriding protected instance fields.
  • Correct app-state key restoration so persisted authentication keys are converted back to binary data.
  • Improve reconnection handling for WhatsApp stream error 515 events.
  • Improve read and unread chat state synchronization across phone JID and LID addressing.

Enhancements:

  • Enhance group metadata with administration modes and normalize read-state and chat addressing behavior for linked devices.
  • Throttle group metadata lookups to reduce WhatsApp rate-limit failures.

Build:

  • Upgrade the Baileys dependency and refresh locked dependencies.

CI:

  • Add a release-candidate workflow that tests, lints, builds, packages, and publishes a Docker image artifact.

Tests:

  • Add coverage for authentication key restoration and read-state validation, synchronization, LID mapping, and partial SDK failures.

…s + message forward

Exposes Baileys group/message capabilities that already exist in the underlying
library but had no REST endpoints:

- POST /group/memberAddMode      -> groupMemberAddMode (admin_add | all_member_add)
- POST /group/joinApprovalMode   -> groupJoinApprovalMode (on | off)
- GET  /group/participantRequests -> groupRequestParticipantsList (pending join requests)
- POST /group/updateParticipantRequests -> groupRequestParticipantsUpdate (approve | reject)
- POST /message/forwardMessage   -> sendMessage(jid, { forward }) (keeps isForwarded)

Each follows the existing pattern (DTO + JSONSchema + router + controller +
baileys service). No new dependencies — baileys 7.0.0-rc.9 already ships these.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Jun 20, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Adds REST endpoints and DTO/schema/controller/service plumbing to expose new WhatsApp group admin capabilities (member-add restrictions, join approval, participant request management) and a message-forwarding endpoint that reuses stored WAMessages and Baileys forwarding, following existing validation/router patterns.

Sequence diagram for forwarding a stored WhatsApp message

sequenceDiagram
  actor Client
  participant MessageRouter
  participant SendMessageController
  participant BaileysStartupService
  participant BaileysClient

  Client->>MessageRouter: POST /message/forwardMessage
  MessageRouter->>MessageRouter: dataValidate ForwardMessageDto
  MessageRouter->>SendMessageController: forwardMessage(instance, data)
  SendMessageController->>BaileysStartupService: forwardMessage(data)
  BaileysStartupService->>BaileysStartupService: getMessage({ id: messageId }, true)
  BaileysStartupService->>BaileysClient: sendMessage(jid, { forward: fullMsg })
  BaileysClient-->>BaileysStartupService: sendMessage result
  BaileysStartupService-->>SendMessageController: forward result
  SendMessageController-->>MessageRouter: response
  MessageRouter-->>Client: 201 Created
Loading

Sequence diagram for group participant request management endpoints

sequenceDiagram
  actor Admin
  participant GroupRouter
  participant GroupController
  participant BaileysStartupService
  participant BaileysClient

  rect rgb(235, 245, 255)
    Admin->>GroupRouter: GET /group/participantRequests
    GroupRouter->>GroupRouter: groupValidate GroupJid
    GroupRouter->>GroupController: findParticipantRequests(instance, groupJid)
    GroupController->>BaileysStartupService: findParticipantRequests(groupJid)
    BaileysStartupService->>BaileysClient: groupRequestParticipantsList(groupJid)
    BaileysClient-->>BaileysStartupService: requests
    BaileysStartupService-->>GroupController: { requests }
    GroupController-->>GroupRouter: { requests }
    GroupRouter-->>Admin: 200 OK
  end

  rect rgb(235, 255, 235)
    Admin->>GroupRouter: POST /group/updateParticipantRequests
    GroupRouter->>GroupRouter: groupValidate GroupUpdateParticipantRequestDto
    GroupRouter->>GroupController: updateParticipantRequests(instance, update)
    GroupController->>BaileysStartupService: updateParticipantRequests(update)
    BaileysStartupService->>BaileysClient: groupRequestParticipantsUpdate(groupJid, participants, action)
    BaileysClient-->>BaileysStartupService: update result
    BaileysStartupService-->>GroupController: { updateParticipantRequests: result }
    GroupController-->>GroupRouter: response
    GroupRouter-->>Admin: 201 Created
  end
Loading

File-Level Changes

Change Details Files
Expose group member-add restriction and join-approval toggles via new DTOs, schemas, controller methods, and service wrappers over Baileys client APIs.
  • Introduce GroupMemberAddModeDto and GroupJoinApprovalModeDto extending GroupJid with constrained mode fields
  • Define JSON Schemas memberAddModeSchema and joinApprovalModeSchema with enum validation and non-empty constraints
  • Add GroupController methods delegating updateMemberAddMode and updateJoinApprovalMode calls to per-instance BaileysStartupService
  • Implement BaileysStartupService.updateMemberAddMode and updateJoinApprovalMode to call client.groupMemberAddMode and client.groupJoinApprovalMode and wrap responses/errors
  • Register POST /group/memberAddMode and POST /group/joinApprovalMode routes in GroupRouter using existing groupValidate flow and new schemas
src/api/dto/group.dto.ts
src/validate/group.schema.ts
src/api/controllers/group.controller.ts
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
src/api/routes/group.router.ts
Add group participant-requests listing and approval/rejection endpoints wired through schema, DTO, controller, and Baileys client wrappers.
  • Introduce GroupUpdateParticipantRequestDto with action and participants fields extending GroupJid
  • Define updateParticipantRequestSchema with enum-constrained action and non-empty, unique participants array
  • Add GroupController methods findParticipantRequests and updateParticipantRequests delegating to service layer
  • Implement BaileysStartupService.findParticipantRequests and updateParticipantRequests using client.groupRequestParticipantsList and client.groupRequestParticipantsUpdate with JID normalization, returning structured result objects
  • Register GET /group/participantRequests and POST /group/updateParticipantRequests routes in GroupRouter using groupValidate and new schemas
src/api/dto/group.dto.ts
src/validate/group.schema.ts
src/api/controllers/group.controller.ts
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
src/api/routes/group.router.ts
Introduce a message forwarding endpoint that loads a stored message and forwards it via Baileys while preserving the forwarded tag.
  • Add ForwardMessageDto carrying number and messageId
  • Define forwardMessageSchema with number and messageId validation, reusing numberDefinition and isNotEmpty
  • Register POST /message/forwardMessage in MessageRouter using dataValidate and ForwardMessageDto
  • Add SendMessageController.forwardMessage to delegate to the per-instance service
  • Implement BaileysStartupService.forwardMessage to resolve the stored WAMessage via getMessage, validate presence of message payload, normalize number to WhatsApp JID, and call client.sendMessage with a forward payload, wrapping errors as BadRequestException
src/api/dto/sendMessage.dto.ts
src/validate/message.schema.ts
src/api/controllers/sendMessage.controller.ts
src/api/routes/sendMessage.router.ts
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts

Possibly linked issues

  • #member_add_mode.: PR implements groupMemberAddMode via POST /group/memberAddMode, fulfilling the issue’s request for member_add_mode control.
  • #unknown: The PR adds /message/forwardMessage, implementing the requested native endpoint to forward existing WhatsApp messages.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • In forwardMessage, the inner throw new BadRequestException('Message not found') is immediately caught and wrapped as a generic 'Error forwarding message', so callers lose the specific error; consider either not catching that case or rethrowing it unchanged when fullMsg?.message is missing.
  • The logic to normalize phone numbers/JIDs (e.g., number.replace(/\D/g, '') and appending @s.whatsapp.net in forwardMessage and updateParticipantRequests) appears ad hoc; consider extracting and reusing a shared helper to keep JID handling consistent across endpoints.
  • In updateParticipantRequestSchema, participants is required but not included in the isNotEmpty call, so empty strings would pass validation; it may be worth adding participants to isNotEmpty or tightening the item schema if you want to reject blank participant entries.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `forwardMessage`, the inner `throw new BadRequestException('Message not found')` is immediately caught and wrapped as a generic `'Error forwarding message'`, so callers lose the specific error; consider either not catching that case or rethrowing it unchanged when `fullMsg?.message` is missing.
- The logic to normalize phone numbers/JIDs (e.g., `number.replace(/\D/g, '')` and appending `@s.whatsapp.net` in `forwardMessage` and `updateParticipantRequests`) appears ad hoc; consider extracting and reusing a shared helper to keep JID handling consistent across endpoints.
- In `updateParticipantRequestSchema`, `participants` is required but not included in the `isNotEmpty` call, so empty strings would pass validation; it may be worth adding `participants` to `isNotEmpty` or tightening the item schema if you want to reject blank participant entries.

## Individual Comments

### Comment 1
<location path="src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts" line_range="4647" />
<code_context>
+
+  public async updateParticipantRequests(update: GroupUpdateParticipantRequestDto) {
+    try {
+      const participants = update.participants.map((p) => (p.includes('@') ? p : `${p}@s.whatsapp.net`));
+      const result = await this.client.groupRequestParticipantsUpdate(update.groupJid, participants, update.action);
+      return { updateParticipantRequests: result };
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Participant JID normalization is inconsistent with number normalization elsewhere and may accept malformed identifiers.

Participants are treated as JIDs whenever they contain `@`, and otherwise `@s.whatsapp.net` is appended, but there’s no stripping/validation of the local part. If callers pass phone numbers with spaces, dashes, or other formatting, this will produce invalid JIDs. Please either reuse the same normalization helper used in `forwardMessage` (or similar) or enforce that inputs are already-normalized JIDs or strictly numeric IDs before appending the domain.

Suggested implementation:

```typescript
  public async updateParticipantRequests(update: GroupUpdateParticipantRequestDto) {
    try {
      const normalizeParticipant = (raw: string): string => {
        const value = raw.trim();

        if (!value) {
          throw new BadRequestException('Participant identifier cannot be empty');
        }

        // If it's already a JID, return as-is (after trimming)
        if (value.includes('@')) {
          return value;
        }

        // Treat as phone-like input: strip non-digits and validate
        const digitsOnly = value.replace(/\D/g, '');

        if (!digitsOnly) {
          throw new BadRequestException(
            `Invalid participant identifier "${raw}". Expected a JID or numeric phone number.`,
          );
        }

        return `${digitsOnly}@s.whatsapp.net`;
      };

      const participants = update.participants.map(normalizeParticipant);
      const result = await this.client.groupRequestParticipantsUpdate(update.groupJid, participants, update.action);
      return { updateParticipantRequests: result };
    } catch (error) {
      throw new BadRequestException('Error updating participant requests', error.toString());
    }
  }

```

If your codebase already has a shared normalization helper (e.g. used in `forwardMessage`), consider:
1. Replacing the in-method `normalizeParticipant` function with a call to that shared helper for consistency.
2. If that helper returns bare numbers instead of full JIDs, adjust the logic to append `@s.whatsapp.net` only when necessary, keeping the validation/stripping behavior consistent.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.


public async updateParticipantRequests(update: GroupUpdateParticipantRequestDto) {
try {
const participants = update.participants.map((p) => (p.includes('@') ? p : `${p}@s.whatsapp.net`));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Participant JID normalization is inconsistent with number normalization elsewhere and may accept malformed identifiers.

Participants are treated as JIDs whenever they contain @, and otherwise @s.whatsapp.net is appended, but there’s no stripping/validation of the local part. If callers pass phone numbers with spaces, dashes, or other formatting, this will produce invalid JIDs. Please either reuse the same normalization helper used in forwardMessage (or similar) or enforce that inputs are already-normalized JIDs or strictly numeric IDs before appending the domain.

Suggested implementation:

  public async updateParticipantRequests(update: GroupUpdateParticipantRequestDto) {
    try {
      const normalizeParticipant = (raw: string): string => {
        const value = raw.trim();

        if (!value) {
          throw new BadRequestException('Participant identifier cannot be empty');
        }

        // If it's already a JID, return as-is (after trimming)
        if (value.includes('@')) {
          return value;
        }

        // Treat as phone-like input: strip non-digits and validate
        const digitsOnly = value.replace(/\D/g, '');

        if (!digitsOnly) {
          throw new BadRequestException(
            `Invalid participant identifier "${raw}". Expected a JID or numeric phone number.`,
          );
        }

        return `${digitsOnly}@s.whatsapp.net`;
      };

      const participants = update.participants.map(normalizeParticipant);
      const result = await this.client.groupRequestParticipantsUpdate(update.groupJid, participants, update.action);
      return { updateParticipantRequests: result };
    } catch (error) {
      throw new BadRequestException('Error updating participant requests', error.toString());
    }
  }

If your codebase already has a shared normalization helper (e.g. used in forwardMessage), consider:

  1. Replacing the in-method normalizeParticipant function with a call to that shared helper for consistency.
  2. If that helper returns bare numbers instead of full JIDs, adjust the logic to append @s.whatsapp.net only when necessary, keeping the validation/stripping behavior consistent.

@NeritonDias

Copy link
Copy Markdown

🔴 👎

A feature de grupo (member-add-mode, join-approval, participant requests) parece útil e aditiva, mas o PR aponta para main. O fluxo é via develop — reabre contra develop que eu reavalio.

@dpaes

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New security issues found

Comment thread package-lock.json
Comment on lines 10565 to 10574
"node_modules/libsignal": {
"name": "@whiskeysockets/libsignal-node",
"version": "2.0.1",
"resolved": "git+ssh://git@github.com/whiskeysockets/libsignal-node.git#1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67",
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/libsignal/-/libsignal-6.0.0.tgz",
"integrity": "sha512-d/5V3YFtDljbFMufz4ncyUYGYhJl+vzAe+c2EFFBQ6bz1h8Q3IOMEGXYMzlibU60I+e8GagMMpji18iez3P1hA==",
"license": "GPL-3.0",
"dependencies": {
"curve25519-js": "^0.0.4",
"protobufjs": "6.8.8"
}
},
"node_modules/libsignal/node_modules/@types/node": {
"version": "10.17.60",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz",
"integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==",
"license": "MIT"
},
"node_modules/libsignal/node_modules/long": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==",
"license": "Apache-2.0"
},
"node_modules/libsignal/node_modules/protobufjs": {
"version": "6.8.8",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz",
"integrity": "sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==",
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
"@protobufjs/codegen": "^2.0.4",
"@protobufjs/eventemitter": "^1.1.0",
"@protobufjs/fetch": "^1.1.0",
"@protobufjs/float": "^1.0.2",
"@protobufjs/inquire": "^1.1.0",
"@protobufjs/path": "^1.1.2",
"@protobufjs/pool": "^1.1.0",
"@protobufjs/utf8": "^1.1.0",
"@types/long": "^4.0.0",
"@types/node": "^10.1.0",
"long": "^4.0.0"
},
"bin": {
"pbjs": "bin/pbjs",
"pbts": "bin/pbts"
"protobufjs": "^7.5.5"
}
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (license/libsignal): GPL-3.0-only: Open-source license can require releasing the entire application source

This GPL-3.0-only open-source license can impose strong copyleft or non-commercial obligations that may require releasing your full application source code or restrict commercial use, depending on how the code is used or distributed

Source: trivy

@dpaes

dpaes commented Jul 13, 2026

Copy link
Copy Markdown

need to appoint to develop (not to main) @alpesdigital-adm
so we can review that

alpesdigital-adm and others added 13 commits September 9, 2026 09:21
Co-authored-by: root <root@srv1512423.hstgr.cloud>
chats.update arrives keyed by LID on accounts migrated to LID addressing,
while inbound messages are already normalized to the phone JID. Webhook
consumers could not match the read state to the conversation, so reading
on the phone never cleared unread counts downstream.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Webhook consumers only see the phone JID, but LID-addressed chats are
indexed by LID on the primary device. Receipts and app-state patches sent
to the phone JID target a chat the phone does not know.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The auth-state adapters rebuilt AppStateSyncKeyData with create(), which
keeps keyData as the stored base64 string. Baileys then derived the
app-state keys from the 44-char text instead of the 32 key bytes: every
incoming patch failed to decrypt and was skipped silently (MAC checks are
off by default), and every outgoing chatModify patch was encrypted with
the wrong keys, which the server answers with 401 device_removed.
fromObject decodes base64 into bytes, as Baileys useMultiFileAuthState.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…(CVE pending, evolution-foundation#2435) (evolution-foundation#2549)

* security: prevent cross-instance auth bypass via query/body override (evolution-foundation#2435)

The auth guard in src/api/guards/auth.guard.ts validates instance
ownership using req.params.instanceName. The abstract router then
merged req.query (and, on /instance/create, req.body) into the
instance object via Object.assign — which silently overwrote the
already-authenticated instanceName.

An attacker with one valid token could send:

  GET /chat/findMessages/MY_INSTANCE?instanceName=VICTIM_INSTANCE

Auth passed for MY_INSTANCE, but dataValidate() then replaced the
instance with VICTIM_INSTANCE before execute() ran — giving the
caller full access to read/send messages, modify settings, and
delete other tenants' instances.

CWE-639: Authorization Bypass Through User-Controlled Key

Fix: introduce sanitizeUntrustedInput() that strips
PROTECTED_INSTANCE_FIELDS (instanceName, instanceId) from any
untrusted source before merging into the instance object. Logs a
warning on attempts so abuse is auditable.

Closes evolution-foundation#2435
Reported by @lighthousekeeper1212 via static analysis.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(lint): remove extra blank line introduced by recent merge

Pre-push lint flagged whatsapp.baileys.service.ts:531 (double blank
line after the stream:error 515 fix merged via evolution-foundation#2509). Trivial
prettier fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
(cherry picked from commit 7a55a2b)
…m:error 515 (evolution-foundation#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 evolution-foundation#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>
(cherry picked from commit 1d38d4f)
sanitizeUntrustedInput on /instance/create removed instanceName from the
body, which is the only source of the new instance name. Create has no
authenticated instance in the path, so there is nothing to override there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nests the tc token under the picture query in profilePictureUrl, so
profile pictures of privacy-restricted contacts resolve. The Android
browser mode is opt-in by browser name and stays off here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
History sync after a fresh pairing (and some live messages) deliver
LID-only keys. Without the phone JID, webhook consumers cannot identify
the sender and drop the message. Resolve it with getPNForLID, live and in
messaging-history.set; unknown mappings leave the key untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
History messages without a known contact got pushName = JID user part
(LID digits included), which CRMs then stored as the contact name. Look
the contact up by the phone JID as well and otherwise leave it empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants