🐛 (whatsapp) Fix preview failing to start and wait timeo…
This commit is contained in:
@ -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: [],
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -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({
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user