2
0

🐛 (whatsapp) Fix preview failing to start and wait timeo…

This commit is contained in:
Baptiste Arnaud
2023-09-27 16:45:14 +02:00
parent 99b0025a66
commit e10a506c96
5 changed files with 110 additions and 49 deletions

View File

@ -109,6 +109,7 @@ export const startWhatsAppPreview = authenticatedProcedure
phoneNumberId: env.WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID, phoneNumberId: env.WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID,
systemUserAccessToken: env.META_SYSTEM_USER_TOKEN, systemUserAccessToken: env.META_SYSTEM_USER_TOKEN,
}, },
state: newSessionState,
}) })
await saveStateToDatabase({ await saveStateToDatabase({
clientSideActions: [], clientSideActions: [],

View File

@ -21,14 +21,13 @@ Head over to the Share tab of your bot and click on the WhatsApp button to get t
WhatsApp environment have some limitations that you need to keep in mind when building the bot: WhatsApp environment have some limitations that you need to keep in mind when building the bot:
- GIF and SVG image files are not supported. They won't be displayed. - GIF and SVG image files are not supported. They won't be displayed.
- Buttons content can't be longer than 20 characters - Buttons content can't be longer than 20 characters. If the content is longer, it will be truncated.
- Incompatible blocks, if present, they will be skipped: - Incompatible blocks, if present, they will be skipped:
- Payment input block - Payment input block
- Chatwoot block - Chatwoot block
- Script block - Script block
- Google Analytics block - Google Analytics block
- Meta Pixel blocks - Meta Pixel blocks
- Execute on client options
## Contact information ## Contact information

View File

@ -7,18 +7,17 @@ export const executeWait = (
block: WaitBlock block: WaitBlock
): ExecuteLogicResponse => { ): ExecuteLogicResponse => {
const { variables } = state.typebotsQueue[0].typebot const { variables } = state.typebotsQueue[0].typebot
if (!block.options.secondsToWaitFor)
return { outgoingEdgeId: block.outgoingEdgeId }
const parsedSecondsToWaitFor = safeParseInt( const parsedSecondsToWaitFor = safeParseInt(
parseVariables(variables)(block.options.secondsToWaitFor) parseVariables(variables)(block.options.secondsToWaitFor)
) )
return { return {
outgoingEdgeId: block.outgoingEdgeId, outgoingEdgeId: block.outgoingEdgeId,
clientSideActions: parsedSecondsToWaitFor clientSideActions:
parsedSecondsToWaitFor || block.options.shouldPause
? [ ? [
{ {
wait: { secondsToWaitFor: parsedSecondsToWaitFor }, wait: { secondsToWaitFor: parsedSecondsToWaitFor ?? 0 },
expectsDedicatedReply: block.options.shouldPause, expectsDedicatedReply: block.options.shouldPause,
}, },
] ]

View File

@ -12,6 +12,15 @@ import { decrypt } from '@typebot.io/lib/api'
import { saveStateToDatabase } from '../saveStateToDatabase' import { saveStateToDatabase } from '../saveStateToDatabase'
import prisma from '@typebot.io/lib/prisma' import prisma from '@typebot.io/lib/prisma'
import { isDefined } from '@typebot.io/lib/utils' import { isDefined } from '@typebot.io/lib/utils'
import { startBotFlow } from '../startBotFlow'
type Props = {
receivedMessage: WhatsAppIncomingMessage
sessionId: string
credentialsId?: string
workspaceId?: string
contact: NonNullable<SessionState['whatsApp']>['contact']
}
export const resumeWhatsAppFlow = async ({ export const resumeWhatsAppFlow = async ({
receivedMessage, receivedMessage,
@ -19,13 +28,7 @@ export const resumeWhatsAppFlow = async ({
workspaceId, workspaceId,
credentialsId, credentialsId,
contact, contact,
}: { }: Props): Promise<{ message: string }> => {
receivedMessage: WhatsAppIncomingMessage
sessionId: string
credentialsId?: string
workspaceId?: string
contact: NonNullable<SessionState['whatsApp']>['contact']
}) => {
const messageSendDate = new Date(Number(receivedMessage.timestamp) * 1000) const messageSendDate = new Date(Number(receivedMessage.timestamp) * 1000)
const messageSentBefore3MinutesAgo = const messageSentBefore3MinutesAgo =
messageSendDate.getTime() < Date.now() - 180000 messageSendDate.getTime() < Date.now() - 180000
@ -62,7 +65,9 @@ export const resumeWhatsAppFlow = async ({
const resumeResponse = const resumeResponse =
session && !isSessionExpired session && !isSessionExpired
? session.state.currentBlock
? await continueBotFlow(session.state)(messageContent) ? await continueBotFlow(session.state)(messageContent)
: await startBotFlow(session.state)
: workspaceId : workspaceId
? await startWhatsAppSession({ ? await startWhatsAppSession({
incomingMessage: messageContent, incomingMessage: messageContent,
@ -90,6 +95,7 @@ export const resumeWhatsAppFlow = async ({
typingEmulation: newSessionState.typingEmulation, typingEmulation: newSessionState.typingEmulation,
clientSideActions, clientSideActions,
credentials, credentials,
state: newSessionState,
}) })
await saveStateToDatabase({ await saveStateToDatabase({

View File

@ -15,6 +15,7 @@ import { HTTPError } from 'got'
import { convertInputToWhatsAppMessages } from './convertInputToWhatsAppMessage' import { convertInputToWhatsAppMessages } from './convertInputToWhatsAppMessage'
import { isNotDefined } from '@typebot.io/lib/utils' import { isNotDefined } from '@typebot.io/lib/utils'
import { computeTypingDuration } from '../computeTypingDuration' import { computeTypingDuration } from '../computeTypingDuration'
import { continueBotFlow } from '../continueBotFlow'
// Media can take some time to be delivered. This make sure we don't send a message before the media is delivered. // Media can take some time to be delivered. This make sure we don't send a message before the media is delivered.
const messageAfterMediaTimeout = 5000 const messageAfterMediaTimeout = 5000
@ -23,6 +24,7 @@ type Props = {
to: string to: string
typingEmulation: SessionState['typingEmulation'] typingEmulation: SessionState['typingEmulation']
credentials: WhatsAppCredentials['data'] credentials: WhatsAppCredentials['data']
state: SessionState
} & Pick<ChatReply, 'messages' | 'input' | 'clientSideActions'> } & Pick<ChatReply, 'messages' | 'input' | 'clientSideActions'>
export const sendChatReplyToWhatsApp = async ({ export const sendChatReplyToWhatsApp = async ({
@ -32,13 +34,36 @@ export const sendChatReplyToWhatsApp = async ({
input, input,
clientSideActions, clientSideActions,
credentials, credentials,
}: Props) => { state,
}: Props): Promise<void> => {
const messagesBeforeInput = isLastMessageIncludedInInput(input) const messagesBeforeInput = isLastMessageIncludedInInput(input)
? messages.slice(0, -1) ? messages.slice(0, -1)
: messages : messages
const sentMessages: WhatsAppSendingMessage[] = [] const sentMessages: WhatsAppSendingMessage[] = []
const clientSideActionsBeforeMessages =
clientSideActions?.filter((action) =>
isNotDefined(action.lastBubbleBlockId)
) ?? []
for (const action of clientSideActionsBeforeMessages) {
const result = await executeClientSideAction({ to, credentials })(action)
if (!result) continue
const { input, newSessionState, messages, clientSideActions } =
await continueBotFlow(state)(result.replyToSend)
return sendChatReplyToWhatsApp({
to,
messages,
input,
typingEmulation: newSessionState.typingEmulation,
clientSideActions,
credentials,
state: newSessionState,
})
}
for (const message of messagesBeforeInput) { for (const message of messagesBeforeInput) {
const whatsAppMessage = convertMessageToWhatsAppMessage(message) const whatsAppMessage = convertMessageToWhatsAppMessage(message)
if (isNotDefined(whatsAppMessage)) continue if (isNotDefined(whatsAppMessage)) continue
@ -60,6 +85,28 @@ export const sendChatReplyToWhatsApp = async ({
credentials, credentials,
}) })
sentMessages.push(whatsAppMessage) sentMessages.push(whatsAppMessage)
const clientSideActionsAfterMessage =
clientSideActions?.filter(
(action) => action.lastBubbleBlockId === message.id
) ?? []
for (const action of clientSideActionsAfterMessage) {
const result = await executeClientSideAction({ to, credentials })(
action
)
if (!result) continue
const { input, newSessionState, messages, clientSideActions } =
await continueBotFlow(state)(result.replyToSend)
return sendChatReplyToWhatsApp({
to,
messages,
input,
typingEmulation: newSessionState.typingEmulation,
clientSideActions,
credentials,
state: newSessionState,
})
}
} catch (err) { } catch (err) {
captureException(err, { extra: { message } }) captureException(err, { extra: { message } })
console.log('Failed to send message:', JSON.stringify(message, null, 2)) console.log('Failed to send message:', JSON.stringify(message, null, 2))
@ -68,34 +115,6 @@ export const sendChatReplyToWhatsApp = async ({
} }
} }
if (clientSideActions)
for (const clientSideAction of clientSideActions) {
if ('redirect' in clientSideAction && clientSideAction.redirect.url) {
const message = {
type: 'text',
text: {
body: clientSideAction.redirect.url,
preview_url: true,
},
} satisfies WhatsAppSendingMessage
try {
await sendWhatsAppMessage({
to,
message,
credentials,
})
} catch (err) {
captureException(err, { extra: { message } })
console.log(
'Failed to send message:',
JSON.stringify(message, null, 2)
)
if (err instanceof HTTPError)
console.log('HTTPError', err.response.statusCode, err.response.body)
}
}
}
if (input) { if (input) {
const inputWhatsAppMessages = convertInputToWhatsAppMessages( const inputWhatsAppMessages = convertInputToWhatsAppMessages(
input, input,
@ -160,3 +179,40 @@ const isLastMessageIncludedInInput = (input: ChatReply['input']): boolean => {
if (isNotDefined(input)) return false if (isNotDefined(input)) return false
return input.type === InputBlockType.CHOICE return input.type === InputBlockType.CHOICE
} }
const executeClientSideAction =
(context: { to: string; credentials: WhatsAppCredentials['data'] }) =>
async (
clientSideAction: NonNullable<ChatReply['clientSideActions']>[number]
): Promise<{ replyToSend: string | undefined } | void> => {
if ('wait' in clientSideAction) {
await new Promise((resolve) =>
setTimeout(resolve, clientSideAction.wait.secondsToWaitFor * 1000)
)
if (!clientSideAction.expectsDedicatedReply) return
return {
replyToSend: undefined,
}
}
if ('redirect' in clientSideAction && clientSideAction.redirect.url) {
const message = {
type: 'text',
text: {
body: clientSideAction.redirect.url,
preview_url: true,
},
} satisfies WhatsAppSendingMessage
try {
await sendWhatsAppMessage({
to: context.to,
message,
credentials: context.credentials,
})
} catch (err) {
captureException(err, { extra: { message } })
console.log('Failed to send message:', JSON.stringify(message, null, 2))
if (err instanceof HTTPError)
console.log('HTTPError', err.response.statusCode, err.response.body)
}
}
}