Skip to content

Commit

Permalink
⚡ (whatsapp) Improve WhatsApp preview management
Browse files Browse the repository at this point in the history
Closes #800
  • Loading branch information
baptisteArno committed Sep 19, 2023
1 parent 2ce63f5 commit f626c98
Show file tree
Hide file tree
Showing 29 changed files with 796 additions and 647 deletions.
3 changes: 2 additions & 1 deletion apps/builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@
"tinycolor2": "1.6.0",
"trpc-openapi": "1.2.0",
"unsplash-js": "^7.0.18",
"use-debounce": "9.0.4"
"use-debounce": "9.0.4",
"@typebot.io/viewer": "workspace:*"
},
"devDependencies": {
"@chakra-ui/styled-system": "2.9.1",
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const WhatsAppPreviewInstructions = (props: StackProps) => {
const [hasMessageBeenSent, setHasMessageBeenSent] = useState(false)

const { showToast } = useToast()
const { mutate } = trpc.sendWhatsAppInitialMessage.useMutation({
const { mutate } = trpc.whatsApp.startWhatsAppPreview.useMutation({
onMutate: () => setIsSendingMessage(true),
onSettled: () => setIsSendingMessage(false),
onError: (error) => showToast({ description: error.message }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Typebot } from '@typebot.io/schemas'

export const isReadTypebotForbidden = async (
typebot: Pick<Typebot, 'workspaceId'> & {
collaborators: Pick<CollaboratorsOnTypebots, 'userId' | 'type'>[]
collaborators: Pick<CollaboratorsOnTypebots, 'userId'>[]
},
user: Pick<User, 'email' | 'id'>
) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { publicProcedure } from '@/helpers/server/trpc'
import { whatsAppWebhookRequestBodySchema } from '@typebot.io/schemas/features/whatsapp'
import { z } from 'zod'
import { resumeWhatsAppFlow } from '../helpers/resumeWhatsAppFlow'
import { resumeWhatsAppFlow } from '@typebot.io/viewer/src/features/whatsApp/helpers/resumeWhatsAppFlow'
import { isNotDefined } from '@typebot.io/lib'
import { TRPCError } from '@trpc/server'
import { env } from '@typebot.io/env'
Expand All @@ -11,7 +11,8 @@ export const receiveMessagePreview = publicProcedure
openapi: {
method: 'POST',
path: '/whatsapp/preview/webhook',
summary: 'WhatsApp',
summary: 'Message webhook',
tags: ['WhatsApp'],
},
})
.input(whatsAppWebhookRequestBodySchema)
Expand All @@ -30,8 +31,7 @@ export const receiveMessagePreview = publicProcedure
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?.metadata.display_phone_number ?? ''
const contactPhoneNumber = '+' + receivedMessage.from
return resumeWhatsAppFlow({
receivedMessage,
sessionId: `wa-${receivedMessage.from}-preview`,
Expand Down
6 changes: 6 additions & 0 deletions apps/builder/src/features/whatsapp/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ import { getPhoneNumber } from './getPhoneNumber'
import { getSystemTokenInfo } from './getSystemTokenInfo'
import { verifyIfPhoneNumberAvailable } from './verifyIfPhoneNumberAvailable'
import { generateVerificationToken } from './generateVerificationToken'
import { startWhatsAppPreview } from './startWhatsAppPreview'
import { subscribePreviewWebhook } from './subscribePreviewWebhook'
import { receiveMessagePreview } from './receiveMessagePreview'

export const whatsAppRouter = router({
getPhoneNumber,
getSystemTokenInfo,
verifyIfPhoneNumberAvailable,
generateVerificationToken,
startWhatsAppPreview,
subscribePreviewWebhook,
receiveMessagePreview,
})
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import { publicProcedure } from '@/helpers/server/trpc'
import { authenticatedProcedure } from '@/helpers/server/trpc'
import { z } from 'zod'
import { TRPCError } from '@trpc/server'
import { sendWhatsAppMessage } from '../helpers/sendWhatsAppMessage'
import { startSession } from '@/features/chat/helpers/startSession'
import { restartSession } from '@/features/chat/queries/restartSession'
import { sendWhatsAppMessage } from '@typebot.io/lib/whatsApp/sendWhatsAppMessage'
import { startSession } from '@typebot.io/viewer/src/features/chat/helpers/startSession'
import { env } from '@typebot.io/env'
import { HTTPError } from 'got'
import prisma from '@/lib/prisma'
import { sendChatReplyToWhatsApp } from '../helpers/sendChatReplyToWhatsApp'
import { saveStateToDatabase } from '@/features/chat/helpers/saveStateToDatabase'
import { sendChatReplyToWhatsApp } from '@typebot.io/lib/whatsApp/sendChatReplyToWhatsApp'
import { saveStateToDatabase } from '@typebot.io/viewer/src/features/chat/helpers/saveStateToDatabase'
import { restartSession } from '@typebot.io/viewer/src/features/chat/queries/restartSession'
import { isReadTypebotForbidden } from '../typebot/helpers/isReadTypebotForbidden'
import { SessionState } from '@typebot.io/schemas'

export const startWhatsAppPreview = publicProcedure
export const startWhatsAppPreview = authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/typebots/{typebotId}/whatsapp/start-preview',
summary: 'Start WhatsApp Preview',
summary: 'Start preview',
tags: ['WhatsApp'],
protect: true,
},
})
Expand All @@ -38,20 +41,35 @@ export const startWhatsAppPreview = publicProcedure
async ({ input: { to, typebotId, startGroupId }, ctx: { user } }) => {
if (
!env.WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID ||
!env.META_SYSTEM_USER_TOKEN
!env.META_SYSTEM_USER_TOKEN ||
!env.WHATSAPP_PREVIEW_TEMPLATE_NAME
)
throw new TRPCError({
code: 'BAD_REQUEST',
message:
'Missing WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID and/or META_SYSTEM_USER_TOKEN env variables',
})
if (!user)
throw new TRPCError({
code: 'UNAUTHORIZED',
message:
'You need to authenticate your request in order to start a preview',
'Missing WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID or META_SYSTEM_USER_TOKEN or WHATSAPP_PREVIEW_TEMPLATE_NAME env variables',
})

const existingTypebot = await prisma.typebot.findFirst({
where: {
id: typebotId,
},
select: {
id: true,
workspaceId: true,
collaborators: {
select: {
userId: true,
},
},
},
})
if (
!existingTypebot?.id ||
(await isReadTypebotForbidden(existingTypebot, user))
)
throw new TRPCError({ code: 'NOT_FOUND', message: 'Typebot not found' })

const sessionId = `wa-${to}-preview`

const existingSession = await prisma.chatSession.findFirst({
Expand All @@ -60,6 +78,7 @@ export const startWhatsAppPreview = publicProcedure
},
select: {
updatedAt: true,
state: true,
},
})

Expand Down Expand Up @@ -105,7 +124,11 @@ export const startWhatsAppPreview = publicProcedure
})
} else {
await restartSession({
state: newSessionState,
state: {
...newSessionState,
whatsApp: (existingSession?.state as SessionState | undefined)
?.whatsApp,
},
id: `wa-${to}-preview`,
})
try {
Expand All @@ -115,9 +138,9 @@ export const startWhatsAppPreview = publicProcedure
type: 'template',
template: {
language: {
code: 'en',
code: env.WHATSAPP_PREVIEW_TEMPLATE_LANG,
},
name: 'preview_initial_message',
name: env.WHATSAPP_PREVIEW_TEMPLATE_NAME,
},
},
credentials: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ export const subscribePreviewWebhook = publicProcedure
openapi: {
method: 'GET',
path: '/whatsapp/preview/webhook',
summary: 'WhatsApp',
summary: 'Subscribe webhook',
tags: ['WhatsApp'],
},
})
.input(
Expand Down
2 changes: 0 additions & 2 deletions apps/builder/src/helpers/server/routers/v1/trpcRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { webhookRouter } from '@/features/blocks/integrations/webhook/api/router
import { getLinkedTypebots } from '@/features/blocks/logic/typebotLink/api/getLinkedTypebots'
import { credentialsRouter } from '@/features/credentials/api/router'
import { getAppVersionProcedure } from '@/features/dashboard/api/getAppVersionProcedure'
import { sendWhatsAppInitialMessage } from '@/features/preview/api/sendWhatsAppInitialMessage'
import { resultsRouter } from '@/features/results/api/router'
import { processTelemetryEvent } from '@/features/telemetry/api/processTelemetryEvent'
import { themeRouter } from '@/features/theme/api/router'
Expand All @@ -23,7 +22,6 @@ export const trpcRouter = router({
processTelemetryEvent,
getLinkedTypebots,
analytics: analyticsRouter,
sendWhatsAppInitialMessage,
workspace: workspaceRouter,
typebot: typebotRouter,
webhook: webhookRouter,
Expand Down
2 changes: 1 addition & 1 deletion apps/builder/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
"@/*": ["src/*", "../viewer/src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
Expand Down
52 changes: 43 additions & 9 deletions apps/docs/docs/self-hosting/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,29 +200,63 @@ In order to be able to test your bot on WhatsApp from the Preview drawer, you ne
<details><summary><h4>Requirements</h4></summary>
<p>

1. Make sure you have [created a WhatsApp Business Account](https://developers.facebook.com/docs/whatsapp/cloud-api/get-started#set-up-developer-assets).
2. Go to your [System users page](https://business.facebook.com/settings/system-users) and create a new system user that has access to the related.
### Create a Facebook Business account

1. Head over to https://business.facebook.com and log in
2. Create a new business account on the left side bar

:::note
It is possible that Meta directly restricts your newly created Business account. In that case, make sure to verify your identity to proceed.
:::

### Create a Meta app

1. Head over to https://developers.facebook.com/apps
2. Click on Create App
3. Give it any name and select `Business` type
4. Select your newly created Business Account
5. On the app page, set up the `WhatsApp` product

### Get the System User token

1. Go to your [System users page](https://business.facebook.com/settings/system-users) and create a new system user that has access to the related.

- Token expiration: `Never`
- Available Permissions: `whatsapp_business_messaging`, `whatsapp_business_management`

3. The generated token will be used as `META_SYSTEM_USER_TOKEN` in your viewer configuration.
4. Click on `Add assets`. Under `Apps`, look for your app, select it and check `Manage app`
5. Go to your WhatsApp Dev Console
2. The generated token will be used as `META_SYSTEM_USER_TOKEN` in your viewer configuration.
3. Click on `Add assets`. Under `Apps`, look for your app, select it and check `Manage app`

### Get the phone number ID

1. Go to your WhatsApp Dev Console

<img src="/img/whatsapp/dev-console.png" alt="WhatsApp dev console" />

6. Add your phone number by clicking on the `Add phone number` button.
7. Select the newly created phone number in the `From` dropdown list. This will be used as `WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID` in your viewer configuration.
8. Head over to `Quickstart > Configuration`. Edit the webhook URL to `$NEXT_PUBLIC_VIEWER_URL/api/v1/whatsapp/preview/webhook`. Set the Verify token to `$ENCRYPTION_SECRET` and click on `Verify and save`.
9. Add the `messages` webhook field.
2. Add your phone number by clicking on the `Add phone number` button.
3. Select the newly created phone number in the `From` dropdown list and you will see right below the associated `Phone number ID` This will be used as `WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID` in your viewer configuration.

### Set up the webhook

1. Head over to `Quickstart > Configuration`. Edit the webhook URL to `$NEXTAUTH_URL/api/v1/whatsapp/preview/webhook`. Set the Verify token to `$ENCRYPTION_SECRET` and click on `Verify and save`.
2. Add the `messages` webhook field.

### Set up the message template

1. Head over to `Messaging > Message Templates` and click on `Create Template`
2. Select the `Utility` category
3. Give it a name that corresponds to your `WHATSAPP_PREVIEW_TEMPLATE_NAME` configuration.
4. Select the language that corresponds to your `WHATSAPP_PREVIEW_TEMPLATE_LANG` configuration.
5. You can format it as you'd like. The user will just have to send a message to start the preview.

</p></details>

| Parameter | Default | Description |
| ------------------------------------- | ------- | ------------------------------------------------------- |
| META_SYSTEM_USER_TOKEN | | The system user token used to send WhatsApp messages |
| WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID | | The phone number ID from which the message will be sent |
| WHATSAPP_PREVIEW_TEMPLATE_NAME | | The preview start template message name |
| WHATSAPP_PREVIEW_TEMPLATE_LANG | en | The preview start template message name |

## Others

Expand Down
Loading

4 comments on commit f626c98

@vercel
Copy link

@vercel vercel bot commented on f626c98 Sep 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

docs – ./apps/docs

docs-git-main-typebot-io.vercel.app
docs.typebot.io
docs-typebot-io.vercel.app

@vercel
Copy link

@vercel vercel bot commented on f626c98 Sep 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on f626c98 Sep 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

builder-v2 – ./apps/builder

builder-v2-typebot-io.vercel.app
app.typebot.io
builder-v2-git-main-typebot-io.vercel.app

@vercel
Copy link

@vercel vercel bot commented on f626c98 Sep 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

viewer-v2 – ./apps/viewer

cares.urlabout.me
chat.ezbooking.ai
chat.gaswadern.de
chat.gniorder.com
chat.leadmagic.io
chat.onrentme.com
chat.rojie.online
chatdocidadao.com
chatwebonline.com
fmm.wpwakanda.com
footballmeetup.ie
gentleman-shop.fr
island.wakanda.is
k1.kandabrand.com
kp.pedroknoll.com
lb.ticketfute.com
mariwelash.com.br
metodoelev.com.br
nutriandreia.shop
order.chatjer.com
ov1.wpwakanda.com
ov2.wpwakanda.com
ov3.wpwakanda.com
pcb.drapamela.com
softwarelucra.com
support.triplo.ai
survey.collab.day
test.eqfeqfeq.com
viewer.typebot.io
welcome.triplo.ai
www.thegymgame.it
zeropendencia.com
1988.bouclidom.com
a.onewebcenter.com
amancarseat.online
amostra-safe.click
andreimayer.com.br
bebesemcolicas.com
bot.innovacion.fun
bot.jogodospix.com
bot.jogomilion.com
bot.lucide.contact
bot.neferlopez.com
bot.photonative.de
bot.rajatanjak.com
bot.samplehunt.com
bot.sinalcerto.com
bot.wphelpchat.com
bots.robomotion.io
brandingmkt.com.br
chat.marius.digital
chat.mosdent.com.tr
chat.sr7digital.com
chatbot.matthesv.de
chatbot.repplai.com
chatwebandreia.site
co.onewebcenter.com
viewer-v2-typebot-io.vercel.app
mdb.assessoria.marinho.progenbr.com
mdb.assessoria.rodrigo.progenbr.com
register.thailandmicespecialist.com
mdb.assessoria.desideri.progenbr.com
mdb.assessoria.fernanda.progenbr.com
mdb.assessoria.jbatista.progenbr.com
mdb.assessoria.mauricio.progenbr.com
mdb.evento.autocadastro.progenbr.com
form.shopmercedesbenzsouthorlando.com
mdb.evento.equipeinterna.progenbr.com
bot.studiotecnicoimmobiliaremerelli.it
mdb.assessoria.boaventura.progenbr.com
mdb.assessoria.jtrebesqui.progenbr.com
pesquisa.escolamodacomproposito.com.br
anamnese.clinicaramosodontologia.com.br
gabinete.baleia.formulario.progenbr.com
mdb.assessoria.carreirinha.progenbr.com
chrome-os-inquiry-system.itschromeos.com
mdb.assessoria.paulomarques.progenbr.com
viewer-v2-git-main-typebot-io.vercel.app
main-menu-for-itschromeos.itschromeos.com
mdb.assessoria.qrcode.ademir.progenbr.com
mdb.assessoria.qrcode.arthur.progenbr.com
mdb.assessoria.qrcode.danilo.progenbr.com
mdb.assessoria.qrcode.marcao.progenbr.com
mdb.assessoria.qrcode.marcio.progenbr.com
mdb.assessoria.qrcode.aloisio.progenbr.com
mdb.assessoria.qrcode.girotto.progenbr.com
mdb.assessoria.qrcode.marinho.progenbr.com
mdb.assessoria.qrcode.rodrigo.progenbr.com
mdb.assessoria.carlosalexandre.progenbr.com
mdb.assessoria.qrcode.desideri.progenbr.com
mdb.assessoria.qrcode.fernanda.progenbr.com
mdb.assessoria.qrcode.jbatista.progenbr.com
mdb.assessoria.qrcode.mauricio.progenbr.com
mdb.assessoria.fernanda.regional.progenbr.com
mdb.assessoria.qrcode.boaventura.progenbr.com
mdb.assessoria.qrcode.jtrebesqui.progenbr.com
mdb.assessoria.qrcode.carreirinha.progenbr.com
mdb.assessoria.qrcode.paulomarques.progenbr.com
mdb.assessoria.qrcode.carlosalexandre.progenbr.com
mdb.assessoria.qrcode.fernanda.regional.progenbr.com

Please sign in to comment.