diff --git a/apps/builder/src/features/whatsapp/receiveMessagePreview.ts b/apps/builder/src/features/whatsapp/receiveMessagePreview.ts index 97d03c52f..2342819cb 100644 --- a/apps/builder/src/features/whatsapp/receiveMessagePreview.ts +++ b/apps/builder/src/features/whatsapp/receiveMessagePreview.ts @@ -6,6 +6,7 @@ import { isNotDefined } from '@typebot.io/lib' import { TRPCError } from '@trpc/server' import { env } from '@typebot.io/env' +// TODO: Delete file, replaced by /api/v1/whatsapp/preview/webhook.ts export const receiveMessagePreview = publicProcedure .meta({ openapi: { diff --git a/apps/builder/src/pages/api/v1/whatsapp/preview/webhook.ts b/apps/builder/src/pages/api/v1/whatsapp/preview/webhook.ts new file mode 100644 index 000000000..2daaf6020 --- /dev/null +++ b/apps/builder/src/pages/api/v1/whatsapp/preview/webhook.ts @@ -0,0 +1,49 @@ +import { + WhatsAppWebhookRequestBody, + whatsAppWebhookRequestBodySchema, +} from '@typebot.io/schemas/features/whatsapp' +import { resumeWhatsAppFlow } from '@typebot.io/bot-engine/whatsapp/resumeWhatsAppFlow' +import { isNotDefined } from '@typebot.io/lib' +import { env } from '@typebot.io/env' +import { NextApiRequest, NextApiResponse } from 'next' + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (!env.WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID) + return res + .status(500) + .send('WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID is not defined') + const body = typeof req.body === 'string' ? JSON.parse(req.body) : req.body + const { entry } = whatsAppWebhookRequestBodySchema.parse(body) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const ctx = globalThis[Symbol.for('@vercel/request-context')] + if (ctx?.get?.().waitUntil) { + ctx.get().waitUntil(() => processWhatsAppReply(entry)) + return res.status(200).json({ message: 'Message is being processed.' }) + } + console.log('Processing message') + const { message } = await processWhatsAppReply(entry) + return res.status(200).json({ message }) +} + +const processWhatsAppReply = async ( + entry: WhatsAppWebhookRequestBody['entry'] +) => { + const receivedMessage = entry.at(0)?.changes.at(0)?.value.messages?.at(0) + if (isNotDefined(receivedMessage)) return { message: 'No message found' } + const contactName = + entry.at(0)?.changes.at(0)?.value?.contacts?.at(0)?.profile?.name ?? '' + const contactPhoneNumber = + entry.at(0)?.changes.at(0)?.value?.messages?.at(0)?.from ?? '' + return resumeWhatsAppFlow({ + receivedMessage, + sessionId: `wa-preview-${receivedMessage.from}`, + contact: { + name: contactName, + phoneNumber: contactPhoneNumber, + }, + }) +} diff --git a/apps/viewer/src/features/whatsapp/api/receiveMessage.ts b/apps/viewer/src/features/whatsapp/api/receiveMessage.ts index b580e9f15..9d5c7275f 100644 --- a/apps/viewer/src/features/whatsapp/api/receiveMessage.ts +++ b/apps/viewer/src/features/whatsapp/api/receiveMessage.ts @@ -4,6 +4,7 @@ import { z } from 'zod' import { isNotDefined } from '@typebot.io/lib' import { resumeWhatsAppFlow } from '@typebot.io/bot-engine/whatsapp/resumeWhatsAppFlow' +// TODO: Delete file, replaced by /api/v1/workspaces/[workspaceId]/whatsapp/[credentialsId]/webhook.ts export const receiveMessage = publicProcedure .meta({ openapi: { diff --git a/apps/viewer/src/pages/api/v1/workspaces/[workspaceId]/whatsapp/[credentialsId]/webhook.ts b/apps/viewer/src/pages/api/v1/workspaces/[workspaceId]/whatsapp/[credentialsId]/webhook.ts new file mode 100644 index 000000000..3734a5b2e --- /dev/null +++ b/apps/viewer/src/pages/api/v1/workspaces/[workspaceId]/whatsapp/[credentialsId]/webhook.ts @@ -0,0 +1,71 @@ +import { + WhatsAppWebhookRequestBody, + whatsAppWebhookRequestBodySchema, +} from '@typebot.io/schemas/features/whatsapp' +import { resumeWhatsAppFlow } from '@typebot.io/bot-engine/whatsapp/resumeWhatsAppFlow' +import { isNotDefined } from '@typebot.io/lib' +import { NextApiRequest, NextApiResponse } from 'next' + +type Props = { + entry: WhatsAppWebhookRequestBody['entry'] + workspaceId: string + credentialsId: string +} + +export const config = { + supportsResponseStreaming: true, +} + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const workspaceId = req.query.workspaceId as string + const credentialsId = req.query.credentialsId as string + const body = typeof req.body === 'string' ? JSON.parse(req.body) : req.body + const { entry } = whatsAppWebhookRequestBodySchema.parse(body) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const ctx = globalThis[Symbol.for('@vercel/request-context')] + if (ctx?.get?.().waitUntil) { + ctx + .get() + .waitUntil(() => + processWhatsAppReply({ entry, workspaceId, credentialsId }) + ) + return res.status(200).json({ message: 'Message is being processed.' }) + } + const { message } = await processWhatsAppReply({ + entry, + workspaceId, + credentialsId, + }) + return res.status(200).json({ message }) +} + +const processWhatsAppReply = async ({ + entry, + workspaceId, + credentialsId, +}: Props) => { + const receivedMessage = entry.at(0)?.changes.at(0)?.value.messages?.at(0) + if (isNotDefined(receivedMessage)) return { message: 'No message found' } + const contactName = + entry.at(0)?.changes.at(0)?.value?.contacts?.at(0)?.profile?.name ?? '' + const contactPhoneNumber = + entry.at(0)?.changes.at(0)?.value?.messages?.at(0)?.from ?? '' + const phoneNumberId = entry.at(0)?.changes.at(0)?.value + .metadata.phone_number_id + if (!phoneNumberId) return { message: 'No phone number id found' } + return resumeWhatsAppFlow({ + receivedMessage, + sessionId: `wa-${phoneNumberId}-${receivedMessage.from}`, + phoneNumberId, + credentialsId, + workspaceId, + contact: { + name: contactName, + phoneNumber: contactPhoneNumber, + }, + }) +}