Skip to content
Merged
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
67 changes: 17 additions & 50 deletions packages/provider-meta/__tests__/provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1074,87 +1074,54 @@ describe('#MetaProvider', () => {
})

describe('#sendPresenceUpdate', () => {
test('should send typing_on status by default', async () => {
test('should send typing_indicator with incoming message_id', async () => {
// Arrange
const fakeRecipient = '1234567890'
jest.spyOn(metaProvider, 'sendMessageToApi').mockResolvedValue({ success: true })

// Act
await metaProvider.sendPresenceUpdate(fakeRecipient)

// Assert
expect(metaProvider.sendMessageToApi).toHaveBeenCalledWith({
messaging_product: 'whatsapp',
recipient_type: 'individual',
to: fakeRecipient,
type: 'typing_on',
})
})

test('should send typing_on status when explicitly specified', async () => {
// Arrange
const fakeRecipient = '1234567890'
const fakeMessageId = 'wamid.HBgLMTIzNDU2Nzg5MA=='
jest.spyOn(metaProvider, 'sendMessageToApi').mockResolvedValue({ success: true })

// Act
await metaProvider.sendPresenceUpdate(fakeRecipient, 'typing_on')
await metaProvider.sendPresenceUpdate(fakeMessageId)

// Assert
expect(metaProvider.sendMessageToApi).toHaveBeenCalledWith({
messaging_product: 'whatsapp',
recipient_type: 'individual',
to: fakeRecipient,
type: 'typing_on',
})
})

test('should send typing_off status when specified', async () => {
// Arrange
const fakeRecipient = '1234567890'
jest.spyOn(metaProvider, 'sendMessageToApi').mockResolvedValue({ success: true })

// Act
await metaProvider.sendPresenceUpdate(fakeRecipient, 'typing_off')

// Assert
expect(metaProvider.sendMessageToApi).toHaveBeenCalledWith({
messaging_product: 'whatsapp',
recipient_type: 'individual',
to: fakeRecipient,
type: 'typing_off',
status: 'read',
message_id: fakeMessageId,
typing_indicator: {
type: 'text',
},
})
})
})

describe('#typing', () => {
test('should send typing_on when called without duration', async () => {
test('should call sendPresenceUpdate with messageId when called without duration', async () => {
// Arrange
const fakeRecipient = '1234567890'
const fakeMessageId = 'wamid.HBgLMTIzNDU2Nzg5MA=='
jest.spyOn(metaProvider, 'sendPresenceUpdate').mockResolvedValue({ success: true })

// Act
await metaProvider.typing(fakeRecipient)
await metaProvider.typing(fakeMessageId)

// Assert
expect(metaProvider.sendPresenceUpdate).toHaveBeenCalledTimes(1)
expect(metaProvider.sendPresenceUpdate).toHaveBeenCalledWith(fakeRecipient, 'typing_on')
expect(metaProvider.sendPresenceUpdate).toHaveBeenCalledWith(fakeMessageId)
})

test('should send typing_on then typing_off when called with duration', async () => {
test('should call sendPresenceUpdate then wait when called with duration', async () => {
// Arrange
const fakeRecipient = '1234567890'
const fakeMessageId = 'wamid.HBgLMTIzNDU2Nzg5MA=='
jest.useFakeTimers()
jest.spyOn(metaProvider, 'sendPresenceUpdate').mockResolvedValue({ success: true })

// Act
const typingPromise = metaProvider.typing(fakeRecipient, 1000)
const typingPromise = metaProvider.typing(fakeMessageId, 1000)
jest.runAllTimers()
await typingPromise

// Assert
expect(metaProvider.sendPresenceUpdate).toHaveBeenCalledTimes(2)
expect(metaProvider.sendPresenceUpdate).toHaveBeenNthCalledWith(1, fakeRecipient, 'typing_on')
expect(metaProvider.sendPresenceUpdate).toHaveBeenNthCalledWith(2, fakeRecipient, 'typing_off')
expect(metaProvider.sendPresenceUpdate).toHaveBeenCalledTimes(1)
expect(metaProvider.sendPresenceUpdate).toHaveBeenCalledWith(fakeMessageId)

jest.useRealTimers()
})
Expand Down
4 changes: 2 additions & 2 deletions packages/provider-meta/src/interface/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,6 @@ export interface MetaInterface {
sendFile: (to: string, mediaInput: string | null, caption: string, context: string | null) => Promise<any>
sendAudio: (to: string, fileOpus: string, context: string | null) => void
markAsRead: (wa_id: string) => Promise<any>
sendPresenceUpdate: (to: string, status?: 'typing_on' | 'typing_off') => Promise<any>
typing: (to: string, ms?: number) => Promise<void>
sendPresenceUpdate: (messageId: string) => Promise<any>
typing: (messageId: string, ms?: number) => Promise<void>
}
44 changes: 21 additions & 23 deletions packages/provider-meta/src/meta/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1027,44 +1027,42 @@ class MetaProvider extends ProviderClass<MetaInterface> implements MetaInterface
}

/**
* Send presence update to simulate typing indicator
* @param to - Recipient phone number
* @param status - Presence status: 'typing_on' to show typing indicator, 'typing_off' to hide it
* Send presence update to simulate typing indicator using Meta Cloud API format.
* Requires the message_id of the incoming message that triggered this response.
* The typing indicator lasts up to 25 seconds or until a message is sent.
* @param messageId - The wamid of the incoming message (from the user)
* @returns Promise with the API response
* @example
* // Show typing indicator
* await provider.sendPresenceUpdate('1234567890')
*
* // Hide typing indicator
* await provider.sendPresenceUpdate('1234567890', 'typing_off')
* await provider.sendPresenceUpdate('wamid.HBgLMTIzNDU2Nzg5MA==')
*/
sendPresenceUpdate = async (to: string, status: 'typing_on' | 'typing_off' = 'typing_on') => {
to = parseMetaNumber(to)
sendPresenceUpdate = async (messageId: string) => {
const body = {
messaging_product: 'whatsapp',
recipient_type: 'individual',
to,
type: status,
status: 'read',
message_id: messageId,
typing_indicator: {
type: 'text',
},
}
return this.sendMessageToApi(body)
}

/**
* Show a typing indicator to the recipient
* @param to - Recipient phone number
* @param ms - Optional duration in milliseconds after which the typing indicator is hidden automatically
* Show a typing indicator to the recipient.
* Requires the message_id of the incoming message that triggered this response.
* @param messageId - The wamid of the incoming message (from the user)
* @param ms - Optional duration in milliseconds to wait (typing indicator disappears automatically after 25s or when a message is sent)
* @example
* // Show typing indicator indefinitely
* await provider.typing('1234567890')
* // Show typing indicator
* await provider.typing('wamid.HBgLMTIzNDU2Nzg5MA==')
*
* // Show typing indicator for 3 seconds then hide it
* await provider.typing('1234567890', 3000)
* // Show typing indicator and wait 3 seconds before continuing
* await provider.typing('wamid.HBgLMTIzNDU2Nzg5MA==', 3000)
*/
typing = async (to: string, ms?: number): Promise<void> => {
await this.sendPresenceUpdate(to, 'typing_on')
typing = async (messageId: string, ms?: number): Promise<void> => {
await this.sendPresenceUpdate(messageId)
if (ms) {
await new Promise((resolve) => setTimeout(resolve, ms))
await this.sendPresenceUpdate(to, 'typing_off')
}
}

Expand Down
Loading