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,
systemUserAccessToken: env.META_SYSTEM_USER_TOKEN,
},
state: newSessionState,
})
await saveStateToDatabase({
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:
- 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:
- Payment input block
- Chatwoot block
- Script block
- Google Analytics block
- Meta Pixel blocks
- Execute on client options
## Contact information

View File

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

View File

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

View File

@ -15,6 +15,7 @@ import { HTTPError } from 'got'
import { convertInputToWhatsAppMessages } from './convertInputToWhatsAppMessage'
import { isNotDefined } from '@typebot.io/lib/utils'
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.
const messageAfterMediaTimeout = 5000
@ -23,6 +24,7 @@ type Props = {
to: string
typingEmulation: SessionState['typingEmulation']
credentials: WhatsAppCredentials['data']
state: SessionState
} & Pick<ChatReply, 'messages' | 'input' | 'clientSideActions'>
export const sendChatReplyToWhatsApp = async ({
@ -32,13 +34,36 @@ export const sendChatReplyToWhatsApp = async ({
input,
clientSideActions,
credentials,
}: Props) => {
state,
}: Props): Promise<void> => {
const messagesBeforeInput = isLastMessageIncludedInInput(input)
? messages.slice(0, -1)
: messages
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) {
const whatsAppMessage = convertMessageToWhatsAppMessage(message)
if (isNotDefined(whatsAppMessage)) continue
@ -60,6 +85,28 @@ export const sendChatReplyToWhatsApp = async ({
credentials,
})
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) {
captureException(err, { extra: { message } })
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) {
const inputWhatsAppMessages = convertInputToWhatsAppMessages(
input,
@ -160,3 +179,40 @@ const isLastMessageIncludedInInput = (input: ChatReply['input']): boolean => {
if (isNotDefined(input)) return false
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)
}
}
}