Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 2 additions & 9 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ services:
- redis
- evolution-postgres
ports:
- "127.0.0.1:8080:8080"
- "127.0.0.1:8081:8080"
volumes:
- evolution_instances:/evolution/instances
networks:
- evolution-net
- dokploy-network
env_file:
- .env
expose:
Expand All @@ -41,9 +40,6 @@ services:
evolution-net:
aliases:
- evolution-redis
dokploy-network:
aliases:
- evolution-redis
expose:
- "6379"

Expand All @@ -67,7 +63,6 @@ services:
- postgres_data:/var/lib/postgresql/data
networks:
- evolution-net
- dokploy-network
expose:
- "5432"

Expand All @@ -79,6 +74,4 @@ volumes:
networks:
evolution-net:
name: evolution-net
driver: bridge
dokploy-network:
external: true
driver: bridge
26 changes: 0 additions & 26 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 33 additions & 3 deletions src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1201,6 +1201,20 @@ export class BaileysStartupService extends ChannelStartupService {
}
}

// FIX: Fallback pushName if not present in received payload
if (!received.pushName && !received.key.fromMe) {
const participantJid = received.participant || received.key.participant || received.key.remoteJid;
if (participantJid) {
const contact = await this.prismaRepository.contact.findFirst({
where: { instanceId: this.instanceId, remoteJid: participantJid },
select: { pushName: true }
});
if (contact && contact.pushName) {
Comment on lines +1205 to +1212

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 (performance): Avoid per-message lookup for pushName where possible to reduce database load.

This path triggers a contact.findFirst for every message without pushName, which can become an N+1 query pattern under high throughput. To mitigate this, you could restrict the fallback (e.g. only for group messages where participant is set) and/or add a short-lived in-memory cache keyed by participantJid and instanceId so repeated messages from the same JID don’t repeatedly hit the DB. Alternatively, consider batching contact lookups earlier in the pipeline if you can determine the relevant JIDs in advance.

Suggested implementation:

          // FIX: Fallback pushName if not present in received payload.
          // Restrict to group messages (participant present) and use a small in-memory cache
          // to avoid per-message DB lookups for the same JID.
          if (
            !received.pushName &&
            !received.key.fromMe &&
            (received.participant || received.key?.participant)
          ) {
            const participantJid =
              received.participant || received.key.participant;

            if (participantJid) {
              const cacheKey = `${this.instanceId}:${participantJid}`;

              // In-memory cache for pushName lookups, keyed by instance + JID
              let cachedPushName =
                this.pushNameCache && this.pushNameCache.get(cacheKey);

              if (!cachedPushName) {
                const contact = await this.prismaRepository.contact.findFirst({
                  where: {
                    instanceId: this.instanceId,
                    remoteJid: participantJid,
                  },
                  select: { pushName: true },
                });

                if (contact?.pushName) {
                  cachedPushName = contact.pushName;

                  if (this.pushNameCache) {
                    this.pushNameCache.set(cacheKey, cachedPushName);
                  }
                }
              }

              if (cachedPushName) {
                received.pushName = cachedPushName;
              }
            }
  1. Add an in-memory cache field to the WhatsApp Baileys service class, for example:
    private readonly pushNameCache = new Map<string, string>();
    Place this alongside other private fields on the service.
  2. If you want cache eviction, you can later wrap this Map with a TTL mechanism or use an LRU cache implementation already present in your codebase (if any).

received.pushName = contact.pushName;
}
}
}

const messageRaw = this.prepareMessage(received);

if (messageRaw.messageType === 'pollUpdateMessage') {
Expand Down Expand Up @@ -1355,7 +1369,20 @@ export class BaileysStartupService extends ChannelStartupService {
if (this.configService.get<Database>('DATABASE').SAVE_DATA.NEW_MESSAGE) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { pollUpdates, ...messageData } = messageRaw;
const msg = await this.prismaRepository.message.create({ data: messageData });

const existingMessage = await this.prismaRepository.$queryRaw`
SELECT id, status, "messageTimestamp" FROM "Message"
WHERE "instanceId" = ${this.instanceId}
AND "key"->>'id' = ${received.key.id}
` as any[];

let msg;
if (existingMessage && existingMessage.length > 0) {
Comment on lines +1373 to +1380

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.

issue (bug_risk): Use a uniqueness constraint or upsert instead of a raw pre-check query to prevent duplicates.

Because the SELECT + conditional create flow is not atomic, concurrent deliveries of the same message ID can still result in duplicate rows. It would be safer to enforce a unique index on (instanceId, key->>'id') and rely on the DB to handle conflicts (e.g., via an upsert or create with ON CONFLICT DO NOTHING using $executeRaw).

msg = existingMessage[0];
this.logger.info(`Message already exists, ignoring create: ${received.key.id}`);
} else {
msg = await this.prismaRepository.message.create({ data: messageData });
}

const { remoteJid } = received.key;
const timestamp = msg.messageTimestamp;
Expand Down Expand Up @@ -1495,16 +1522,19 @@ export class BaileysStartupService extends ChannelStartupService {

const contactRaw: {
remoteJid: string;
pushName: string;
pushName?: string;
profilePicUrl?: string;
instanceId: string;
} = {
remoteJid: received.key.remoteJid,
pushName: received.key.fromMe ? '' : received.key.fromMe == null ? '' : received.pushName,
profilePicUrl: (await this.profilePicture(received.key.remoteJid)).profilePictureUrl,
instanceId: this.instanceId,
};

if (!received.key.fromMe && received.pushName) {
contactRaw.pushName = received.pushName;
}

if (contactRaw.remoteJid === 'status@broadcast') {
continue;
}
Expand Down
16 changes: 16 additions & 0 deletions src/api/services/monitor.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,22 @@ export class WAMonitoringService {
}

private async setInstance(instanceData: InstanceDto) {
if (this.waInstances[instanceData.instanceName]) {
this.logger.warn(`Forcefully closing existing instance ${instanceData.instanceName} to prevent zombies`);
try {
const existing = this.waInstances[instanceData.instanceName];
if (existing?.client?.ws) {
existing.client.ws.close();
}
if (existing?.client?.end) {
existing.client.end(undefined);
}
} catch (err) {
this.logger.error(`Error closing zombie instance ${instanceData.instanceName}: ${err}`);
}
delete this.waInstances[instanceData.instanceName];
}

const instance = channelController.init(instanceData, {
configService: this.configService,
eventEmitter: this.eventEmitter,
Expand Down