From c45ea0072111b0cfbf9a8eba92d923db2ef4f4a9 Mon Sep 17 00:00:00 2001 From: Baptiste Arnaud Date: Sun, 7 Apr 2024 12:43:11 +0200 Subject: [PATCH] :construction_worker: Add vercel function to benefit from waitUntil method for WA webhook --- .gitignore | 2 + .../api/v1/whatsapp/preview/webhook.ts | 41 ++++++++++++++ apps/builder/package.json | 3 +- .../whatsapp/[credentialsId]/webhook.ts | 53 +++++++++++++++++++ apps/viewer/package.json | 9 ++-- pnpm-lock.yaml | 10 ++++ 6 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 apps/builder/api/v1/whatsapp/preview/webhook.ts create mode 100644 apps/viewer/api/v1/workspaces/[workspaceId]/whatsapp/[credentialsId]/webhook.ts diff --git a/.gitignore b/.gitignore index fa52735cf..ecd80620e 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ snapshots .env .typebot-build +.vercel +.env*.local diff --git a/apps/builder/api/v1/whatsapp/preview/webhook.ts b/apps/builder/api/v1/whatsapp/preview/webhook.ts new file mode 100644 index 000000000..e62dc8025 --- /dev/null +++ b/apps/builder/api/v1/whatsapp/preview/webhook.ts @@ -0,0 +1,41 @@ +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 type { RequestContext } from '@vercel/edge' + +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, + }, + }) +} + +export async function POST(request: Request, context: RequestContext) { + if (!env.WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID) + return new Response( + 'WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID is not defined', + { + status: 500, + } + ) + const body = await request.json() + const { entry } = whatsAppWebhookRequestBodySchema.parse(body) + context.waitUntil(processWhatsAppReply(entry)) + return new Response('Message is being processed.', { status: 200 }) +} diff --git a/apps/builder/package.json b/apps/builder/package.json index 56ce27422..6a8b87248 100644 --- a/apps/builder/package.json +++ b/apps/builder/package.json @@ -67,9 +67,9 @@ "framer-motion": "10.3.0", "google-auth-library": "8.9.0", "google-spreadsheet": "4.1.1", - "ky": "1.2.3", "immer": "10.0.2", "jsonwebtoken": "9.0.1", + "ky": "1.2.3", "libphonenumber-js": "1.10.37", "micro": "10.0.1", "micro-cors": "0.1.1", @@ -122,6 +122,7 @@ "@types/qs": "6.9.7", "@types/react": "18.2.15", "@types/tinycolor2": "1.4.3", + "@vercel/edge": "1.1.1", "dotenv-cli": "7.2.1", "eslint": "8.44.0", "eslint-config-custom": "workspace:*", diff --git a/apps/viewer/api/v1/workspaces/[workspaceId]/whatsapp/[credentialsId]/webhook.ts b/apps/viewer/api/v1/workspaces/[workspaceId]/whatsapp/[credentialsId]/webhook.ts new file mode 100644 index 000000000..b1e23e40a --- /dev/null +++ b/apps/viewer/api/v1/workspaces/[workspaceId]/whatsapp/[credentialsId]/webhook.ts @@ -0,0 +1,53 @@ +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 type { RequestContext } from '@vercel/edge' + +type Props = { + entry: WhatsAppWebhookRequestBody['entry'] + workspaceId: string + credentialsId: string +} + +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, + }, + }) +} + +export async function POST(request: Request, context: RequestContext) { + const workspaceId = request.url.match(/\/workspaces\/([^/]+)\//)?.[1] + const credentialsId = request.url.match(/\/whatsapp\/([^/]+)\//)?.[1] + if (!workspaceId || !credentialsId) { + console.error('No workspace or credentials id found') + return { message: 'No workspace or credentials id found' } + } + const body = await request.json() + const { entry } = whatsAppWebhookRequestBodySchema.parse(body) + context.waitUntil(processWhatsAppReply({ entry, workspaceId, credentialsId })) + return new Response('Message is being processed.', { status: 200 }) +} diff --git a/apps/viewer/package.json b/apps/viewer/package.json index fc04ff46a..145645e26 100644 --- a/apps/viewer/package.json +++ b/apps/viewer/package.json @@ -37,7 +37,6 @@ "stripe": "12.13.0" }, "devDependencies": { - "dotenv": "16.4.5", "@faire/mjml-react": "3.3.0", "@paralleldrive/cuid2": "2.2.1", "@playwright/test": "1.36.0", @@ -46,6 +45,8 @@ "@typebot.io/forge": "workspace:*", "@typebot.io/forge-repository": "workspace:*", "@typebot.io/lib": "workspace:*", + "@typebot.io/playwright": "workspace:*", + "@typebot.io/results": "workspace:*", "@typebot.io/schemas": "workspace:*", "@typebot.io/tsconfig": "workspace:*", "@typebot.io/variables": "workspace:*", @@ -55,6 +56,8 @@ "@types/papaparse": "5.3.7", "@types/qs": "6.9.7", "@types/react": "18.2.15", + "@vercel/edge": "1.1.1", + "dotenv": "16.4.5", "dotenv-cli": "7.2.1", "eslint": "8.44.0", "eslint-config-custom": "workspace:*", @@ -63,8 +66,6 @@ "papaparse": "5.4.1", "superjson": "1.12.4", "typescript": "5.3.2", - "zod": "3.22.4", - "@typebot.io/playwright": "workspace:*", - "@typebot.io/results": "workspace:*" + "zod": "3.22.4" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5a612ca09..c3640c86b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -351,6 +351,9 @@ importers: '@types/tinycolor2': specifier: 1.4.3 version: 1.4.3 + '@vercel/edge': + specifier: 1.1.1 + version: 1.1.1 dotenv-cli: specifier: 7.2.1 version: 7.2.1 @@ -678,6 +681,9 @@ importers: '@types/react': specifier: 18.2.15 version: 18.2.15 + '@vercel/edge': + specifier: 1.1.1 + version: 1.1.1 dotenv: specifier: 16.4.5 version: 16.4.5 @@ -10670,6 +10676,10 @@ packages: react: 18.2.0 dev: false + /@vercel/edge@1.1.1: + resolution: {integrity: sha512-NtKiIbn9Cq6HWGy+qRudz28mz5nxfOJWls5Pnckjw1yCfSX8rhXdvY/il3Sy3Zd5n/sKCM2h7VSCCpJF/oaDrQ==} + dev: true + /@vitejs/plugin-react@3.1.0(vite@4.5.2): resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==} engines: {node: ^14.18.0 || >=16.0.0}