Add WhatsApp typing indicator

This commit is contained in:
Baptiste Arnaud 2025-12-15 10:02:01 +01:00
parent 506435ffa6
commit ea2a87dd50
No known key found for this signature in database
4 changed files with 67 additions and 0 deletions

View File

@ -8,6 +8,7 @@ import {
methodNotAllowed,
notFound,
} from "@typebot.io/lib/api/utils";
import { createId } from "@typebot.io/lib/createId";
import { byId } from "@typebot.io/lib/utils";
import prisma from "@typebot.io/prisma";
import { isTypebotVersionAtLeastV6 } from "@typebot.io/schemas/helpers/isTypebotVersionAtLeastV6";

View File

@ -26,6 +26,7 @@ import type {
WhatsAppMessageReferral,
} from "./schemas";
import { sendChatReplyToWhatsApp } from "./sendChatReplyToWhatsApp";
import { sendWhatsAppTypingIndicator } from "./sendWhatsAppTypingIndicator";
import { startWhatsAppSession } from "./startWhatsAppSession";
import { WhatsAppError } from "./WhatsAppError";
@ -137,6 +138,7 @@ export const resumeWhatsAppFlow = async ({
isWaitingForWebhook,
} = await resumeFlowAndSendWhatsAppMessages({
to: receivedMessages[0].from,
messageId: receivedMessages[0].id,
credentials,
isSessionExpired,
reply,
@ -430,6 +432,7 @@ const aggregateParallelMediaMessagesIfRedisEnabled = async ({
const resumeFlowAndSendWhatsAppMessages = async (props: {
to: string;
messageId: string | undefined;
state: SessionState | null | undefined;
sessionStore: SessionStore;
reply: Message | undefined;
@ -440,6 +443,13 @@ const resumeFlowAndSendWhatsAppMessages = async (props: {
credentialsId?: string;
workspaceId?: string;
}) => {
if (props.messageId) {
sendWhatsAppTypingIndicator({
messageId: props.messageId,
credentials: props.credentials,
});
}
const resumeResponse = await resumeFlow(props);
const {

View File

@ -103,6 +103,7 @@ const incomingMessageReferral = z.object({
export type WhatsAppMessageReferral = z.infer<typeof incomingMessageReferral>;
const sharedIncomingMessageFieldsSchema = z.object({
id: z.string().optional(),
from: z.string(),
timestamp: z.string(),
referral: incomingMessageReferral.optional(),

View File

@ -0,0 +1,55 @@
import * as Sentry from "@sentry/nextjs";
import type { WhatsAppCredentials } from "@typebot.io/credentials/schemas";
import { env } from "@typebot.io/env";
import ky from "ky";
import { dialog360AuthHeaderName, dialog360BaseUrl } from "./constants";
type Props = {
messageId: string;
credentials: WhatsAppCredentials["data"];
};
export const sendWhatsAppTypingIndicator = async ({
messageId,
credentials,
}: Props) => {
try {
const json = {
messaging_product: "whatsapp",
status: "read",
message_id: messageId,
typing_indicator: {
type: "text",
},
};
if (credentials.provider === "360dialog") {
await ky.post(`${dialog360BaseUrl}/messages`, {
headers: {
[dialog360AuthHeaderName]: credentials.apiKey,
},
json,
});
} else {
await ky.post(
`${env.WHATSAPP_CLOUD_API_URL}/v21.0/${credentials.phoneNumberId}/messages`,
{
headers: {
Authorization: `Bearer ${credentials.systemUserAccessToken}`,
},
json,
},
);
}
} catch (err) {
// Typing indicators are non-critical, log the error but don't throw
Sentry.captureException(err, {
tags: {
context: "whatsapp-typing-indicator",
},
extra: {
messageId,
},
});
}
};