[core] Up WEBJS - fix _serialized id rename fix #2157 fix #2158 fix #2159 fix #2160 fix #2162

This commit is contained in:
devlikepro committed 2026-07-15 20:04:29 +07:00
1 parent b6ba3951da
commit 1496274a99
5 files changed
+280 -32

No files matched your search

+25 -12
View File
@@ -1,4 +1,5 @@
import { WebJSPresence } from '@waha/core/engines/webjs/types';
import { GetSerialized } from '@waha/core/utils/serialized';
import { GetChatMessagesFilter } from '@waha/structures/chats.dto';
import { Label } from '@waha/structures/labels.dto';
import { LidToPhoneNumber } from '@waha/structures/lids.dto';
@@ -75,7 +76,7 @@ function extractReactionsByMessageKey(
): Map<string, ChannelMessageReaction[]> {
const reactions = new Map();
for (const reaction of newsletterReactions) {
const key = reaction.parentMsgKey._serialized;
const key = GetSerialized(reaction.parentMsgKey);
const emojiCountMap = reaction.emojiCountMap;
const reactionList: ChannelMessageReaction[] = [];
for (const emoji in emojiCountMap) {
@@ -266,7 +267,8 @@ export class WebjsClientCore extends Client {
// Filter chats by IDs if filter is provided
if (filter && filter.ids && filter.ids.length > 0) {
chats = chats.filter((chat) =>
filter.ids.includes(chat.id._serialized),
// @ts-ignore
filter.ids.includes(window.WWebJS.GetSerialized(chat.id)),
);
}
@@ -382,7 +384,10 @@ export class WebjsClientCore extends Client {
// Construct the initial anchor the same way wa-js does:
// serialize to string then reconstruct via MsgKey.fromString so the
// object has the exact shape msgFindByDirection expects.
const lastReceivedSerialized = chat.lastReceivedKey?._serialized;
// @ts-ignore
const lastReceivedSerialized = window.WWebJS.GetSerialized(
chat.lastReceivedKey,
);
if (!lastReceivedSerialized) return [];
let currentAnchorKey = window
.require('WAWebMsgKey')
@@ -406,7 +411,8 @@ export class WebjsClientCore extends Client {
// @ts-ignore
const toModel = (m) => {
if (m && typeof m.serialize === 'function') return m;
const serializedId = m?.id?._serialized;
// @ts-ignore
const serializedId = window.WWebJS.GetSerialized(m?.id);
const Msg = window.require('WAWebCollections').Msg;
if (serializedId) {
const stored = Msg.get(serializedId);
@@ -437,7 +443,8 @@ export class WebjsClientCore extends Client {
// appear in multiple batches when anchors overlap
const seenIds = new Set();
msgs = msgs.filter((m) => {
const sid = m?.id?._serialized;
// @ts-ignore
const sid = window.WWebJS.GetSerialized(m?.id);
if (!sid || seenIds.has(sid)) return false;
seenIds.add(sid);
return true;
@@ -463,7 +470,10 @@ export class WebjsClientCore extends Client {
// the oldest message in this batch — use it as the next anchor to
// walk further back in history without overlap
const oldestInBatch = batchModels[batchModels.length - 1];
const oldestSerialized = oldestInBatch?.id?._serialized;
// @ts-ignore
const oldestSerialized = window.WWebJS.GetSerialized(
oldestInBatch?.id,
);
if (!oldestSerialized) break;
currentAnchorKey = window
.require('WAWebMsgKey')
@@ -532,9 +542,9 @@ export class WebjsClientCore extends Client {
const result = values.map((map) => {
return {
// @ts-ignore
lid: map.lid._serialized,
lid: window.WWebJS.GetSerialized(map.lid),
// @ts-ignore
pn: map.phoneNumber._serialized,
pn: window.WWebJS.GetSerialized(map.phoneNumber),
};
});
// @ts-ignore
@@ -565,7 +575,8 @@ export class WebjsClientCore extends Client {
const wid = WAWebWidFactory.createWid(lid);
const result = WAWebApiContact.getPhoneNumber(wid);
return result ? result._serialized : null;
// @ts-ignore
return window.WWebJS.GetSerialized(result);
}, lid);
return pn;
}
@@ -577,7 +588,8 @@ export class WebjsClientCore extends Client {
const wid = WAWebWidFactory.createWid(pn);
const result = WAWebApiContact.getCurrentLid(wid);
return result ? result._serialized : null;
// @ts-ignore
return window.WWebJS.GetSerialized(result);
}, phoneNumber)) as any;
return lid;
}
@@ -617,7 +629,8 @@ export class WebjsClientCore extends Client {
}
return chatstates.map((chatstate) => {
return {
participant: chatstate.id._serialized,
// @ts-ignore
participant: window.WWebJS.GetSerialized(chatstate.id),
lastSeen: chatstate.t,
state: chatstate.type,
};
@@ -681,7 +694,7 @@ export class WebjsClientCore extends Client {
const messages: WebjsChannelMessage[] = messageInstances.map((msg) => {
return {
message: msg,
reactions: reactions.get(msg.id._serialized) || [],
reactions: reactions.get(GetSerialized(msg.id)) || [],
viewCount: msg.rawData.viewCount,
};
});
+5 -5
View File
@@ -19,6 +19,7 @@ import {
GroupParticipant as WEBJSGroupParticipant,
} from 'whatsapp-web.js';
import { isPnUser } from '@waha/core/utils/jids';
import { GetSerialized } from '@waha/core/utils/serialized';
function ToGroupInfo(
group: GroupChat,
@@ -29,7 +30,7 @@ function ToGroupInfo(
const groupMetadata = group.groupMetadata;
const info: GroupInfo = {
// @ts-ignore
id: group.id._serialized,
id: GetSerialized(group.id),
subject: group.name,
description: group.description,
invite: invite,
@@ -72,11 +73,10 @@ export function getParticipants(
role = GroupParticipantRole.ADMIN;
}
const participantId = GetSerialized(participant.id);
return {
id: participant.id._serialized,
pn: isPnUser(participant.id._serialized)
? participant.id._serialized
: null,
id: participantId,
pn: isPnUser(participantId) ? participantId : null,
role: role,
};
});
+16 -15
View File
@@ -182,6 +182,7 @@ import {
Message as MessageInstance,
Call as CallInstance,
} from 'whatsapp-web.js/src/structures';
import { GetSerialized } from '@waha/core/utils/serialized';
import { WAJSPresenceChatStateType, WebJSPresence } from './types';
import { WebJSAuthFactory } from './WebJSAuthFactory';
@@ -531,7 +532,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
}
const wid = clientInfo.wid;
return {
id: wid?._serialized,
id: GetSerialized(wid),
pushName: clientInfo?.pushname,
};
}
@@ -738,7 +739,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
}
return {
numberExists: true,
chatId: result._serialized,
chatId: GetSerialized(result),
};
}
@@ -1135,14 +1136,14 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
protected async fetchChatSummary(chat: Chat): Promise<ChatSummary> {
const picture = await this.getContactProfilePicture(
chat.id._serialized,
GetSerialized(chat.id),
false,
);
const lastMessage = chat.lastMessage
? this.toWAMessage(chat.lastMessage)
: null;
return {
id: chat.id._serialized,
id: GetSerialized(chat.id),
name: chat.name || null,
picture: picture,
lastMessage: lastMessage,
@@ -1248,7 +1249,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
// @ts-ignore
message.rawData.receipts = await message.getInfo().catch((error) => {
this.logger.error(
{ error: error, msg: message.id._serialized },
{ error: error, msg: GetSerialized(message.id) },
'Failed to get receipts',
);
return null;
@@ -1713,7 +1714,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
role = ChannelRole.GUEST;
}
return {
id: chat.id._serialized,
id: GetSerialized(chat.id),
name: chat.name,
description: chat.description,
invite: getChannelInviteLink(metadata.inviteCode),
@@ -2212,10 +2213,10 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
},
);
const chatsArchived$ = chatArchived$.pipe(
filter((event: any) => this.jids.include(event?.chat?.id?._serialized)),
filter((event: any) => this.jids.include(GetSerialized(event?.chat?.id))),
map((event) => {
return {
id: event.chat.id._serialized,
id: GetSerialized(event.chat.id),
archived: event.archived,
timestamp: event.chat.timestamp,
};
@@ -2302,7 +2303,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
const source = this.getMessageSource(reaction.id.id);
return {
id: reaction.id._serialized,
id: GetSerialized(reaction.id),
from: normalizeJid(reaction.senderId),
fromMe: reaction.id.fromMe,
source: source,
@@ -2311,13 +2312,13 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
timestamp: reaction.timestamp,
reaction: {
text: reaction.reaction,
messageId: reaction.msgId._serialized,
messageId: GetSerialized(reaction.msgId),
},
};
}
private toPollVotePayload(vote: WebjsPollVote): PollVotePayload | null {
const pollMessageId = vote?.parentMessage?.id?._serialized;
const pollMessageId = GetSerialized(vote?.parentMessage?.id);
if (!pollMessageId) {
return null;
}
@@ -2397,10 +2398,10 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
protected toWAMessage(message: Message): WAMessage {
const replyTo = this.extractReplyTo(message);
const source = this.getMessageSource(message.id.id);
const key = parseMessageIdSerialized(message.id._serialized);
const key = parseMessageIdSerialized(GetSerialized(message.id));
// @ts-ignore
return {
id: message.id._serialized,
id: GetSerialized(message.id),
timestamp: message.timestamp,
from: message.from,
fromMe: message.fromMe,
@@ -2474,7 +2475,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
protected toWAContact(contact: Contact) {
// @ts-ignore
contact.id = contact.id._serialized;
contact.id = GetSerialized(contact.id);
return contact;
}
@@ -2552,7 +2553,7 @@ export class WEBJSEngineMediaProcessor
}
getMessageId(message: Message): string {
return message.id._serialized;
return GetSerialized(message.id);
}
getMimetype(message: Message): string {
+131
View File
@@ -0,0 +1,131 @@
import {
GetSerialized,
GetSerializedMsgKey,
GetSerializedWid,
} from './serialized';
describe('serialized helpers', () => {
describe('GetSerializedWid', () => {
it('returns strings and nullish values as-is', () => {
expect(GetSerializedWid('123@c.us')).toBe('123@c.us');
expect(GetSerializedWid(null)).toBeNull();
expect(GetSerializedWid(undefined)).toBeUndefined();
});
it('uses existing _serialized', () => {
expect(GetSerializedWid({ _serialized: '123@c.us' })).toBe('123@c.us');
});
it('ignores the minified $1 property and reconstructs from keys', () => {
const wid: any = { $1: 'ignored', user: '123', server: 'c.us' };
expect(GetSerializedWid(wid)).toBe('123@c.us');
expect(wid._serialized).toBe('123@c.us');
});
it('reconstructs from user/server and caches', () => {
const wid: any = { user: '123', server: 'c.us' };
expect(GetSerializedWid(wid)).toBe('123@c.us');
expect(wid._serialized).toBe('123@c.us');
});
it('reconstructs a group wid', () => {
expect(GetSerializedWid({ user: '123-456', server: 'g.us' })).toBe(
'123-456@g.us',
);
});
it('appends the device when present and truthy', () => {
expect(GetSerializedWid({ user: '123', server: 'c.us', device: 7 })).toBe(
'123:7@c.us',
);
expect(GetSerializedWid({ user: '123', server: 'c.us', device: 0 })).toBe(
'123@c.us',
);
});
it('handles the special "call" wid', () => {
expect(GetSerializedWid({ user: 'call', server: 'call' })).toBe('call');
});
it('returns null when it cannot reconstruct (no keys, only $1)', () => {
expect(GetSerializedWid({ foo: 'bar' })).toBeNull();
expect(GetSerializedWid({ $1: '123@c.us' })).toBeNull();
});
});
describe('GetSerializedMsgKey', () => {
it('reconstructs fromMe_remote_id and caches', () => {
const key: any = {
fromMe: true,
remote: { _serialized: '123@c.us' },
id: 'AAA',
};
expect(GetSerializedMsgKey(key)).toBe('true_123@c.us_AAA');
expect(key._serialized).toBe('true_123@c.us_AAA');
});
it('reconstructs a remote wid that only has component keys', () => {
const key: any = {
fromMe: false,
remote: { user: '123', server: 'c.us' },
id: 'AAA',
};
expect(GetSerializedMsgKey(key)).toBe('false_123@c.us_AAA');
});
it('appends the participant', () => {
const key: any = {
fromMe: false,
remote: { _serialized: '123-456@g.us' },
id: 'AAA',
participant: { _serialized: '789@c.us' },
};
expect(GetSerializedMsgKey(key)).toBe('false_123-456@g.us_AAA_789@c.us');
});
it('appends self before participant', () => {
const key: any = {
fromMe: true,
remote: { _serialized: '123@c.us' },
id: 'AAA',
self: 'out',
participant: { _serialized: '789@c.us' },
};
expect(GetSerializedMsgKey(key)).toBe('true_123@c.us_AAA_out_789@c.us');
});
it('ignores $1 and reconstructs from keys', () => {
const key: any = {
$1: 'ignored',
fromMe: true,
remote: { _serialized: '999@c.us' },
id: 'BBB',
};
expect(GetSerializedMsgKey(key)).toBe('true_999@c.us_BBB');
});
});
describe('GetSerialized (dispatcher)', () => {
it('dispatches wid shapes', () => {
expect(GetSerialized({ user: '123', server: 'c.us' })).toBe('123@c.us');
});
it('dispatches msgkey shapes', () => {
expect(
GetSerialized({
fromMe: true,
remote: { user: '123', server: 'c.us' },
id: 'AAA',
}),
).toBe('true_123@c.us_AAA');
});
it('honours existing _serialized before dispatching', () => {
expect(GetSerialized({ _serialized: 'x@c.us' })).toBe('x@c.us');
});
it('returns null for unknown shapes', () => {
expect(GetSerialized({ foo: 'bar' })).toBeNull();
});
});
});
+103
View File
@@ -0,0 +1,103 @@
/**
* Serialized-ID helpers for the WEBJS engine.
*
* COPIED from whatsapp-web.js `src/util/Serialized.js` (fork: ../whatsapp-web.js).
* Keep this file in sync with that upstream version whenever it changes.
*
* WhatsApp Web renamed the `_serialized` property on its ID objects (Wid /
* MsgKey) to a minified name (`$1`) in the 2026-07 update. Rather than depend on
* that unstable minified name, each helper reconstructs the serialized string
* deterministically from the object's own component keys - existing
* `_serialized` -> reconstruct from keys - and caches it back onto the object as
* `_serialized` so later reads keep working.
*/
interface CacheHit {
done: boolean;
value: string | null;
}
function cached(id: any): CacheHit {
if (id == null) {
return { done: true, value: id };
}
if (typeof id === 'string') {
return { done: true, value: id };
}
if (typeof id._serialized === 'string' && id._serialized !== '') {
return { done: true, value: id._serialized };
}
return { done: false, value: null };
}
/**
* Serializes a Wid (WhatsApp ID): `user[:device]@server`.
*/
export function GetSerializedWid(id: any): string | null {
const hit = cached(id);
if (hit.done) {
return hit.value;
}
let value: string | null;
if (id.user != null && id.server != null) {
value =
id.user === 'call'
? 'call'
: `${id.user}${id.device ? `:${id.device}` : ''}@${id.server}`;
} else {
value = null;
}
if (value != null) {
id._serialized = value;
}
return value;
}
/**
* Serializes a MsgKey (message ID): `fromMe_remote_id[_self][_participant]`.
*/
export function GetSerializedMsgKey(id: any): string | null {
const hit = cached(id);
if (hit.done) {
return hit.value;
}
let value: string | null;
if (id.remote != null && id.id != null) {
const remote = GetSerializedWid(id.remote);
const participant =
id.participant != null ? GetSerializedWid(id.participant) : null;
value =
`${id.fromMe ? 'true' : 'false'}_${remote}_${id.id}` +
(id.self ? `_${id.self}` : '') +
(participant ? `_${participant}` : '');
} else {
value = null;
}
if (value != null) {
id._serialized = value;
}
return value;
}
/**
* Generic entry point: resolves `_serialized`/`$1` then dispatches to the Wid
* or MsgKey helper based on the object's shape. Use the specific helpers when
* the id type is known.
*/
export function GetSerialized(id: any): string | null {
const hit = cached(id);
if (hit.done) {
return hit.value;
}
if (id.remote != null && id.id != null) {
return GetSerializedMsgKey(id);
}
if (id.user != null && id.server != null) {
return GetSerializedWid(id);
}
return null;
}