mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
feat: implement message retry handling
so if a message fails to decrypt on the other user's end -- it can be retried
This commit is contained in:
@@ -112,6 +112,11 @@ type SocketConfig = {
|
|||||||
fetchAgent?: Agent
|
fetchAgent?: Agent
|
||||||
/** should the QR be printed in the terminal */
|
/** should the QR be printed in the terminal */
|
||||||
printQRInTerminal: boolean
|
printQRInTerminal: boolean
|
||||||
|
/**
|
||||||
|
* fetch a message from your store
|
||||||
|
* implement this so that messages failed to send (solves the "this message can take a while" issue) can be retried
|
||||||
|
* */
|
||||||
|
getMessage: (key: proto.IMessageKey) => Promise<proto.IMessage | undefined>
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ export const DEFAULT_CONNECTION_CONFIG: SocketConfig = {
|
|||||||
logger: P().child({ class: 'baileys' }),
|
logger: P().child({ class: 'baileys' }),
|
||||||
printQRInTerminal: false,
|
printQRInTerminal: false,
|
||||||
emitOwnEvents: true,
|
emitOwnEvents: true,
|
||||||
defaultQueryTimeoutMs: 60_000
|
defaultQueryTimeoutMs: 60_000,
|
||||||
|
getMessage: async() => undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MEDIA_PATH_MAP: { [T in MediaType]: string } = {
|
export const MEDIA_PATH_MAP: { [T in MediaType]: string } = {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { proto } from "../../WAProto"
|
|||||||
import { KEY_BUNDLE_TYPE } from "../Defaults"
|
import { KEY_BUNDLE_TYPE } from "../Defaults"
|
||||||
import { makeChatsSocket } from "./chats"
|
import { makeChatsSocket } from "./chats"
|
||||||
import { extractGroupMetadata } from "./groups"
|
import { extractGroupMetadata } from "./groups"
|
||||||
|
import { Boom } from "@hapi/boom"
|
||||||
|
|
||||||
const getStatusFromReceiptType = (type: string | undefined) => {
|
const getStatusFromReceiptType = (type: string | undefined) => {
|
||||||
if(type === 'read' || type === 'read-self') {
|
if(type === 'read' || type === 'read-self') {
|
||||||
@@ -24,6 +25,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
ev,
|
ev,
|
||||||
authState,
|
authState,
|
||||||
ws,
|
ws,
|
||||||
|
assertSession,
|
||||||
assertingPreKeys,
|
assertingPreKeys,
|
||||||
sendNode,
|
sendNode,
|
||||||
relayMessage,
|
relayMessage,
|
||||||
@@ -464,30 +466,64 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const sendMessagesAgain = async(key: proto.IMessageKey, ids: string[]) => {
|
||||||
|
const participant = key.participant || key.remoteJid
|
||||||
|
await assertSession(participant, true)
|
||||||
|
|
||||||
|
logger.debug({ key, ids }, 'recv retry request, forced new session')
|
||||||
|
|
||||||
|
const msgs = await Promise.all(
|
||||||
|
ids.map(id => (
|
||||||
|
config.getMessage({ ...key, id })
|
||||||
|
))
|
||||||
|
)
|
||||||
|
const missingMsgIdx = msgs.findIndex(m => !m)
|
||||||
|
if(missingMsgIdx >= 0) {
|
||||||
|
throw new Boom(
|
||||||
|
`recv request to retry message, but message "${ids[missingMsgIdx]}" not available`,
|
||||||
|
{ statusCode: 404, data: { key } }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let i = 0; i < msgs.length;i++) {
|
||||||
|
await relayMessage(key.remoteJid, msgs[i], {
|
||||||
|
messageId: ids[i],
|
||||||
|
participant
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
const handleReceipt = async(node: BinaryNode) => {
|
const handleReceipt = async(node: BinaryNode) => {
|
||||||
const { attrs, content } = node
|
const { attrs, content } = node
|
||||||
const status = getStatusFromReceiptType(attrs.type)
|
const remoteJid = attrs.recipient || attrs.from
|
||||||
|
const fromMe = attrs.recipient ? false : true
|
||||||
|
|
||||||
|
const ids = [attrs.id]
|
||||||
|
if(Array.isArray(content)) {
|
||||||
|
const items = getBinaryNodeChildren(content[0], 'item')
|
||||||
|
ids.push(...items.map(i => i.attrs.id))
|
||||||
|
}
|
||||||
|
|
||||||
|
const key: proto.IMessageKey = {
|
||||||
|
remoteJid,
|
||||||
|
id: '',
|
||||||
|
fromMe,
|
||||||
|
participant: attrs.participant
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = getStatusFromReceiptType(attrs.type)
|
||||||
if(typeof status !== 'undefined' && !areJidsSameUser(attrs.from, authState.creds.me?.id)) {
|
if(typeof status !== 'undefined' && !areJidsSameUser(attrs.from, authState.creds.me?.id)) {
|
||||||
const ids = [attrs.id]
|
|
||||||
if(Array.isArray(content)) {
|
|
||||||
const items = getBinaryNodeChildren(content[0], 'item')
|
|
||||||
ids.push(...items.map(i => i.attrs.id))
|
|
||||||
}
|
|
||||||
|
|
||||||
const remoteJid = attrs.recipient || attrs.from
|
|
||||||
const fromMe = attrs.recipient ? false : true
|
|
||||||
ev.emit('messages.update', ids.map(id => ({
|
ev.emit('messages.update', ids.map(id => ({
|
||||||
key: {
|
key: { ...key, id },
|
||||||
remoteJid,
|
|
||||||
id: id,
|
|
||||||
fromMe,
|
|
||||||
participant: attrs.participant
|
|
||||||
},
|
|
||||||
update: { status }
|
update: { status }
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(attrs.type === 'retry') {
|
||||||
|
await sendMessagesAgain(key, ids)
|
||||||
|
}
|
||||||
|
|
||||||
await sendMessageAck(node, { class: 'receipt', type: attrs.type })
|
await sendMessageAck(node, { class: 'receipt', type: attrs.type })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -238,8 +238,10 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
const relayMessage = async(
|
const relayMessage = async(
|
||||||
jid: string,
|
jid: string,
|
||||||
message: proto.IMessage,
|
message: proto.IMessage,
|
||||||
{ messageId: msgId, additionalAttributes, cachedGroupMetadata }: MessageRelayOptions
|
{ messageId: msgId, participant, additionalAttributes, cachedGroupMetadata }: MessageRelayOptions
|
||||||
) => {
|
) => {
|
||||||
|
const meId = authState.creds.me!.id
|
||||||
|
|
||||||
const { user, server } = jidDecode(jid)
|
const { user, server } = jidDecode(jid)
|
||||||
const isGroup = server === 'g.us'
|
const isGroup = server === 'g.us'
|
||||||
msgId = msgId || generateMessageID()
|
msgId = msgId || generateMessageID()
|
||||||
@@ -250,16 +252,23 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
|
|
||||||
const destinationJid = jidEncode(user, isGroup ? 'g.us' : 's.whatsapp.net')
|
const destinationJid = jidEncode(user, isGroup ? 'g.us' : 's.whatsapp.net')
|
||||||
|
|
||||||
|
const devices: JidWithDevice[] = []
|
||||||
|
if(participant) {
|
||||||
|
const { user, device } = jidDecode(participant)
|
||||||
|
devices.push({ user, device })
|
||||||
|
}
|
||||||
|
|
||||||
if(isGroup) {
|
if(isGroup) {
|
||||||
const { ciphertext, senderKeyDistributionMessageKey } = await encryptSenderKeyMsgSignalProto(destinationJid, encodedMsg, authState.creds.me!.id, authState)
|
const { ciphertext, senderKeyDistributionMessageKey } = await encryptSenderKeyMsgSignalProto(destinationJid, encodedMsg, meId, authState)
|
||||||
|
|
||||||
let groupData = cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined
|
let groupData = cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined
|
||||||
if(!groupData) groupData = await groupMetadata(jid)
|
if(!groupData) groupData = await groupMetadata(jid)
|
||||||
|
|
||||||
const participantsList = groupData.participants.map(p => p.id)
|
if(!participant) {
|
||||||
const devices = await getUSyncDevices(participantsList, false)
|
const participantsList = groupData.participants.map(p => p.id)
|
||||||
|
const devices = await getUSyncDevices(participantsList, false)
|
||||||
logger.debug(`got ${devices.length} additional devices`)
|
devices.push(...devices)
|
||||||
|
}
|
||||||
|
|
||||||
const encSenderKeyMsg = encodeWAMessage({
|
const encSenderKeyMsg = encodeWAMessage({
|
||||||
senderKeyDistributionMessage: {
|
senderKeyDistributionMessage: {
|
||||||
@@ -304,7 +313,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
content: binaryNodeContent
|
content: binaryNodeContent
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const { user: meUser } = jidDecode(authState.creds.me!.id!)
|
const { user: meUser } = jidDecode(meId)
|
||||||
|
|
||||||
const messageToMyself: proto.IMessage = {
|
const messageToMyself: proto.IMessage = {
|
||||||
deviceSentMessage: {
|
deviceSentMessage: {
|
||||||
@@ -314,15 +323,13 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
const encodedMeMsg = encodeWAMessage(messageToMyself)
|
const encodedMeMsg = encodeWAMessage(messageToMyself)
|
||||||
|
|
||||||
participants.push(
|
if(!participant) {
|
||||||
await createParticipantNode(jidEncode(user, 's.whatsapp.net'), encodedMsg)
|
devices.push({ user })
|
||||||
)
|
devices.push({ user: meUser })
|
||||||
participants.push(
|
|
||||||
await createParticipantNode(jidEncode(meUser, 's.whatsapp.net'), encodedMeMsg)
|
const additionalDevices = await getUSyncDevices([ meId, jid ], true)
|
||||||
)
|
devices.push(...additionalDevices)
|
||||||
const devices = await getUSyncDevices([ authState.creds.me!.id!, jid ], true)
|
}
|
||||||
|
|
||||||
logger.debug(`got ${devices.length} additional devices`)
|
|
||||||
|
|
||||||
for(const { user, device } of devices) {
|
for(const { user, device } of devices) {
|
||||||
const isMe = user === meUser
|
const isMe = user === meUser
|
||||||
@@ -363,7 +370,8 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
content: proto.ADVSignedDeviceIdentity.encode(authState.creds.account).finish()
|
content: proto.ADVSignedDeviceIdentity.encode(authState.creds.account).finish()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
logger.debug({ msgId }, 'sending message')
|
|
||||||
|
logger.debug({ msgId }, `sending message to ${devices.length} devices`)
|
||||||
|
|
||||||
await sendNode(stanza)
|
await sendNode(stanza)
|
||||||
|
|
||||||
|
|||||||
@@ -117,6 +117,8 @@ export type AnyMessageContent = AnyRegularMessageContent | {
|
|||||||
|
|
||||||
export type MessageRelayOptions = {
|
export type MessageRelayOptions = {
|
||||||
messageId?: string
|
messageId?: string
|
||||||
|
/** only send to a specific participant */
|
||||||
|
participant?: string
|
||||||
additionalAttributes?: { [_: string]: string }
|
additionalAttributes?: { [_: string]: string }
|
||||||
cachedGroupMetadata?: (jid: string) => Promise<GroupMetadata | undefined>
|
cachedGroupMetadata?: (jid: string) => Promise<GroupMetadata | undefined>
|
||||||
//cachedDevices?: (jid: string) => Promise<string[] | undefined>
|
//cachedDevices?: (jid: string) => Promise<string[] | undefined>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { ConnectionState } from './State'
|
|||||||
|
|
||||||
import { GroupMetadata, ParticipantAction } from './GroupMetadata'
|
import { GroupMetadata, ParticipantAction } from './GroupMetadata'
|
||||||
import { MessageInfoUpdate, MessageUpdateType, WAMessage, WAMessageUpdate, WAMessageKey } from './Message'
|
import { MessageInfoUpdate, MessageUpdateType, WAMessage, WAMessageUpdate, WAMessageKey } from './Message'
|
||||||
|
import { proto } from '../../WAProto'
|
||||||
|
|
||||||
export type WAVersion = [number, number, number]
|
export type WAVersion = [number, number, number]
|
||||||
export type WABrowserDescription = [string, string, string]
|
export type WABrowserDescription = [string, string, string]
|
||||||
@@ -54,6 +55,11 @@ export type SocketConfig = {
|
|||||||
mediaCache?: NodeCache
|
mediaCache?: NodeCache
|
||||||
/** map to store the retry counts for failed messages */
|
/** map to store the retry counts for failed messages */
|
||||||
msgRetryCounterMap?: { [msgId: string]: number }
|
msgRetryCounterMap?: { [msgId: string]: number }
|
||||||
|
/**
|
||||||
|
* fetch a message from your store
|
||||||
|
* implement this so that messages failed to send (solves the "this message can take a while" issue) can be retried
|
||||||
|
* */
|
||||||
|
getMessage: (key: proto.IMessageKey) => Promise<proto.IMessage | undefined>
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DisconnectReason {
|
export enum DisconnectReason {
|
||||||
|
|||||||
Reference in New Issue
Block a user