@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/js",
|
||||
"version": "0.1.19",
|
||||
"version": "0.1.20",
|
||||
"description": "Javascript library to display typebots on your website",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
createSignal,
|
||||
createUniqueId,
|
||||
For,
|
||||
onCleanup,
|
||||
onMount,
|
||||
Show,
|
||||
} from 'solid-js'
|
||||
@@ -268,6 +269,11 @@ export const ConversationContainer = (props: Props) => {
|
||||
}
|
||||
}
|
||||
|
||||
onCleanup(() => {
|
||||
setStreamingMessage(undefined)
|
||||
setFormattedMessages([])
|
||||
})
|
||||
|
||||
const handleSkip = () => sendMessage(undefined)
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { TypingBubble } from '@/components'
|
||||
import type { TextBubbleContent, TypingEmulation } from '@typebot.io/schemas'
|
||||
import { For, createSignal, onCleanup, onMount } from 'solid-js'
|
||||
import { computeTypingDuration } from '../helpers/computeTypingDuration'
|
||||
import { PlateBlock } from './plate/PlateBlock'
|
||||
import { computePlainText } from '../helpers/convertRichTextToPlainText'
|
||||
import { clsx } from 'clsx'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
import { computeTypingDuration } from '@typebot.io/lib/computeTypingDuration'
|
||||
|
||||
type Props = {
|
||||
content: TextBubbleContent
|
||||
@@ -15,12 +15,6 @@ type Props = {
|
||||
|
||||
export const showAnimationDuration = 400
|
||||
|
||||
const defaultTypingEmulation = {
|
||||
enabled: true,
|
||||
speed: 300,
|
||||
maxDelay: 1.5,
|
||||
}
|
||||
|
||||
let typingTimeout: NodeJS.Timeout
|
||||
|
||||
export const TextBubble = (props: Props) => {
|
||||
@@ -41,10 +35,10 @@ export const TextBubble = (props: Props) => {
|
||||
const typingDuration =
|
||||
props.typingEmulation?.enabled === false
|
||||
? 0
|
||||
: computeTypingDuration(
|
||||
plainText,
|
||||
props.typingEmulation ?? defaultTypingEmulation
|
||||
)
|
||||
: computeTypingDuration({
|
||||
bubbleContent: plainText,
|
||||
typingSettings: props.typingEmulation,
|
||||
})
|
||||
typingTimeout = setTimeout(onTypingEnd, typingDuration)
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/nextjs",
|
||||
"version": "0.1.19",
|
||||
"version": "0.1.20",
|
||||
"description": "Convenient library to display typebots on your Next.js website",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/react",
|
||||
"version": "0.1.19",
|
||||
"version": "0.1.20",
|
||||
"description": "Convenient library to display typebots on your React app",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
26
packages/env/env.ts
vendored
26
packages/env/env.ts
vendored
@@ -258,8 +258,17 @@ const telemetryEnv = {
|
||||
}
|
||||
|
||||
const posthogEnv = {
|
||||
server: {
|
||||
POSTHOG_API_KEY: z.string().min(1).optional(),
|
||||
client: {
|
||||
NEXT_PUBLIC_POSTHOG_KEY: z.string().min(1).optional(),
|
||||
NEXT_PUBLIC_POSTHOG_HOST: z
|
||||
.string()
|
||||
.min(1)
|
||||
.optional()
|
||||
.default('https://app.posthog.com'),
|
||||
},
|
||||
runtimeEnv: {
|
||||
NEXT_PUBLIC_POSTHOG_KEY: getRuntimeVariable('NEXT_PUBLIC_POSTHOG_KEY'),
|
||||
NEXT_PUBLIC_POSTHOG_HOST: getRuntimeVariable('NEXT_PUBLIC_POSTHOG_HOST'),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -281,7 +290,6 @@ export const env = createEnv({
|
||||
...customOAuthEnv.server,
|
||||
...sentryEnv.server,
|
||||
...telemetryEnv.server,
|
||||
...posthogEnv.server,
|
||||
},
|
||||
client: {
|
||||
...baseEnv.client,
|
||||
@@ -292,6 +300,7 @@ export const env = createEnv({
|
||||
...vercelEnv.client,
|
||||
...unsplashEnv.client,
|
||||
...sentryEnv.client,
|
||||
...posthogEnv.client,
|
||||
},
|
||||
experimental__runtimeEnv: {
|
||||
...baseEnv.runtimeEnv,
|
||||
@@ -302,10 +311,11 @@ export const env = createEnv({
|
||||
...vercelEnv.runtimeEnv,
|
||||
...unsplashEnv.runtimeEnv,
|
||||
...sentryEnv.runtimeEnv,
|
||||
...posthogEnv.runtimeEnv,
|
||||
},
|
||||
onInvalidAccess: (variable: string) => {
|
||||
throw new Error(
|
||||
`❌ Attempted to access a server-side environment variable on the client: ${variable}`
|
||||
)
|
||||
},
|
||||
// onInvalidAccess: (variable: string) => {
|
||||
// throw new Error(
|
||||
// `❌ Attempted to access a server-side environment variable on the client: ${variable}`
|
||||
// )
|
||||
// },
|
||||
})
|
||||
|
||||
31
packages/lib/api/deleteFilesFromBucket.ts
Normal file
31
packages/lib/api/deleteFilesFromBucket.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { env } from '@typebot.io/env'
|
||||
import { Client } from 'minio'
|
||||
|
||||
export const deleteFilesFromBucket = async ({
|
||||
urls,
|
||||
}: {
|
||||
urls: string[]
|
||||
}): Promise<void> => {
|
||||
if (!env.S3_ENDPOINT || !env.S3_ACCESS_KEY || !env.S3_SECRET_KEY)
|
||||
throw new Error(
|
||||
'S3 not properly configured. Missing one of those variables: S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY'
|
||||
)
|
||||
|
||||
const minioClient = new Client({
|
||||
endPoint: env.S3_ENDPOINT,
|
||||
port: env.S3_PORT,
|
||||
useSSL: env.S3_SSL ?? true,
|
||||
accessKey: env.S3_ACCESS_KEY,
|
||||
secretKey: env.S3_SECRET_KEY,
|
||||
region: env.S3_REGION,
|
||||
})
|
||||
|
||||
const bucket = env.S3_BUCKET ?? 'typebot'
|
||||
|
||||
return minioClient.removeObjects(
|
||||
bucket,
|
||||
urls
|
||||
.filter((url) => url.includes(env.S3_ENDPOINT as string))
|
||||
.map((url) => url.split(`/${bucket}/`)[1])
|
||||
)
|
||||
}
|
||||
@@ -24,9 +24,9 @@ export const generatePresignedUrl = ({
|
||||
accessKeyId: env.S3_ACCESS_KEY,
|
||||
secretAccessKey: env.S3_SECRET_KEY,
|
||||
region: env.S3_REGION,
|
||||
sslEnabled: env.S3_SSL,
|
||||
sslEnabled: env.S3_SSL ?? true,
|
||||
})
|
||||
const protocol = env.S3_SSL ? 'https' : 'http'
|
||||
const protocol = env.S3_SSL ?? true ? 'https' : 'http'
|
||||
const s3 = new S3({
|
||||
endpoint: new Endpoint(
|
||||
`${protocol}://${env.S3_ENDPOINT}${env.S3_PORT ? `:${env.S3_PORT}` : ''}`
|
||||
@@ -104,7 +104,7 @@ const deleteFilesFromBucket = async ({
|
||||
const minioClient = new Client({
|
||||
endPoint: env.S3_ENDPOINT,
|
||||
port: env.S3_PORT,
|
||||
useSSL: env.S3_SSL,
|
||||
useSSL: env.S3_SSL ?? true,
|
||||
accessKey: env.S3_ACCESS_KEY,
|
||||
secretKey: env.S3_SECRET_KEY,
|
||||
region: env.S3_REGION,
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export * from './utils'
|
||||
export * from './storage'
|
||||
export * from './generatePresignedUrl'
|
||||
export * from './encryption'
|
||||
|
||||
36
packages/lib/api/uploadFileToBucket.ts
Normal file
36
packages/lib/api/uploadFileToBucket.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { env } from '@typebot.io/env'
|
||||
import { Client } from 'minio'
|
||||
|
||||
type Props = {
|
||||
fileName: string
|
||||
file: Buffer
|
||||
mimeType: string
|
||||
}
|
||||
|
||||
export const uploadFileToBucket = async ({
|
||||
fileName,
|
||||
file,
|
||||
mimeType,
|
||||
}: Props): Promise<string> => {
|
||||
if (!env.S3_ENDPOINT || !env.S3_ACCESS_KEY || !env.S3_SECRET_KEY)
|
||||
throw new Error(
|
||||
'S3 not properly configured. Missing one of those variables: S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY'
|
||||
)
|
||||
|
||||
const minioClient = new Client({
|
||||
endPoint: env.S3_ENDPOINT,
|
||||
port: env.S3_PORT,
|
||||
useSSL: env.S3_SSL,
|
||||
accessKey: env.S3_ACCESS_KEY,
|
||||
secretKey: env.S3_SECRET_KEY,
|
||||
region: env.S3_REGION,
|
||||
})
|
||||
|
||||
await minioClient.putObject(env.S3_BUCKET, fileName, file, {
|
||||
'Content-Type': mimeType,
|
||||
})
|
||||
|
||||
return `http${env.S3_SSL ? 's' : ''}://${env.S3_ENDPOINT}${
|
||||
env.S3_PORT ? `:${env.S3_PORT}` : ''
|
||||
}/${env.S3_BUCKET}/${fileName}`
|
||||
}
|
||||
@@ -1,9 +1,18 @@
|
||||
import type { TypingEmulation } from '@typebot.io/schemas'
|
||||
import {
|
||||
TypingEmulation,
|
||||
defaultSettings,
|
||||
} from '@typebot.io/schemas/features/typebot/settings'
|
||||
|
||||
export const computeTypingDuration = (
|
||||
bubbleContent: string,
|
||||
typingSettings: TypingEmulation
|
||||
) => {
|
||||
type Props = {
|
||||
bubbleContent: string
|
||||
typingSettings?: TypingEmulation
|
||||
}
|
||||
|
||||
export const computeTypingDuration = ({
|
||||
bubbleContent,
|
||||
typingSettings = defaultSettings({ isBrandingEnabled: false })
|
||||
.typingEmulation,
|
||||
}: Props) => {
|
||||
let wordCount = bubbleContent.match(/(\w+)/g)?.length ?? 0
|
||||
if (wordCount === 0) wordCount = bubbleContent.length
|
||||
const typedWordsPerMinute = typingSettings.speed
|
||||
@@ -31,6 +31,7 @@ export const parseTestTypebot = (
|
||||
isArchived: false,
|
||||
isClosed: false,
|
||||
resultsTablePreferences: null,
|
||||
whatsAppPhoneNumberId: null,
|
||||
variables: [{ id: 'var1', name: 'var1' }],
|
||||
...partialTypebot,
|
||||
edges: [
|
||||
|
||||
@@ -248,8 +248,6 @@ export const uploadFiles = async ({
|
||||
return urls
|
||||
}
|
||||
|
||||
declare const window: any
|
||||
|
||||
export const hasValue = (
|
||||
value: string | undefined | null
|
||||
): value is NonNullable<string> =>
|
||||
|
||||
@@ -198,6 +198,7 @@ model Typebot {
|
||||
webhooks Webhook[]
|
||||
isArchived Boolean @default(false)
|
||||
isClosed Boolean @default(false)
|
||||
whatsAppPhoneNumberId String?
|
||||
|
||||
@@index([workspaceId])
|
||||
@@index([folderId])
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Typebot" ADD COLUMN "whatsAppPhoneNumberId" TEXT;
|
||||
@@ -182,6 +182,7 @@ model Typebot {
|
||||
webhooks Webhook[]
|
||||
isArchived Boolean @default(false)
|
||||
isClosed Boolean @default(false)
|
||||
whatsAppPhoneNumberId String?
|
||||
|
||||
@@index([workspaceId])
|
||||
@@index([isArchived, createdAt(sort: Desc)])
|
||||
|
||||
@@ -13,6 +13,8 @@ export const valueTypes = [
|
||||
'Random ID',
|
||||
'Moment of the day',
|
||||
'Map item with same index',
|
||||
'Phone number',
|
||||
'Contact name',
|
||||
] as const
|
||||
|
||||
export const hiddenTypes = ['Today']
|
||||
|
||||
@@ -130,6 +130,12 @@ const startParamsSchema = z.object({
|
||||
.describe(
|
||||
'Set this to `true` if you intend to stream OpenAI completions on a client.'
|
||||
),
|
||||
isOnlyRegistering: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
'If set to `true`, it will only register the session and not start the chat. This is used for other chat platform integration as it can require a session to be registered before sending the first message.'
|
||||
),
|
||||
})
|
||||
|
||||
const replyLogSchema = logSchema
|
||||
|
||||
@@ -2,6 +2,7 @@ import { z } from 'zod'
|
||||
import { answerSchema } from '../answer'
|
||||
import { resultSchema } from '../result'
|
||||
import { typebotInSessionStateSchema, dynamicThemeSchema } from './shared'
|
||||
import { settingsSchema } from '../typebot/settings'
|
||||
|
||||
const answerInSessionStateSchema = answerSchema.pick({
|
||||
content: true,
|
||||
@@ -64,6 +65,16 @@ const sessionStateSchemaV2 = z.object({
|
||||
})
|
||||
.optional(),
|
||||
isStreamEnabled: z.boolean().optional(),
|
||||
whatsApp: z
|
||||
.object({
|
||||
contact: z.object({
|
||||
name: z.string(),
|
||||
phoneNumber: z.string(),
|
||||
}),
|
||||
credentialsId: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
typingEmulation: settingsSchema.shape.typingEmulation.optional(),
|
||||
})
|
||||
|
||||
export type SessionState = z.infer<typeof sessionStateSchemaV2>
|
||||
|
||||
@@ -3,12 +3,14 @@ import { stripeCredentialsSchema } from './blocks/inputs/payment/schemas'
|
||||
import { googleSheetsCredentialsSchema } from './blocks/integrations/googleSheets/schemas'
|
||||
import { openAICredentialsSchema } from './blocks/integrations/openai'
|
||||
import { smtpCredentialsSchema } from './blocks/integrations/sendEmail'
|
||||
import { whatsAppCredentialsSchema } from './whatsapp'
|
||||
|
||||
export const credentialsSchema = z.discriminatedUnion('type', [
|
||||
smtpCredentialsSchema,
|
||||
googleSheetsCredentialsSchema,
|
||||
stripeCredentialsSchema,
|
||||
openAICredentialsSchema,
|
||||
whatsAppCredentialsSchema,
|
||||
])
|
||||
|
||||
export type Credentials = z.infer<typeof credentialsSchema>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { PublicTypebot as PrismaPublicTypebot } from '@typebot.io/prisma'
|
||||
import {
|
||||
groupSchema,
|
||||
edgeSchema,
|
||||
variableSchema,
|
||||
themeSchema,
|
||||
settingsSchema,
|
||||
} from './typebot'
|
||||
import { z } from 'zod'
|
||||
import { preprocessTypebot } from './typebot/helpers/preprocessTypebot'
|
||||
import { edgeSchema } from './typebot/edge'
|
||||
|
||||
export const publicTypebotSchema = z.preprocess(
|
||||
preprocessTypebot,
|
||||
|
||||
21
packages/schemas/features/typebot/edge.ts
Normal file
21
packages/schemas/features/typebot/edge.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
const sourceSchema = z.object({
|
||||
groupId: z.string(),
|
||||
blockId: z.string(),
|
||||
itemId: z.string().optional(),
|
||||
})
|
||||
export type Source = z.infer<typeof sourceSchema>
|
||||
|
||||
const targetSchema = z.object({
|
||||
groupId: z.string(),
|
||||
blockId: z.string().optional(),
|
||||
})
|
||||
export type Target = z.infer<typeof targetSchema>
|
||||
|
||||
export const edgeSchema = z.object({
|
||||
id: z.string(),
|
||||
from: sourceSchema,
|
||||
to: targetSchema,
|
||||
})
|
||||
export type Edge = z.infer<typeof edgeSchema>
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Block } from '../../blocks'
|
||||
import { Group, edgeSchema } from '../typebot'
|
||||
import { edgeSchema } from '../edge'
|
||||
import type { Group } from '../typebot'
|
||||
|
||||
export const preprocessTypebot = (typebot: any) => {
|
||||
if (!typebot || typebot.version === '5') return typebot
|
||||
|
||||
@@ -2,3 +2,4 @@ export * from './typebot'
|
||||
export * from './theme'
|
||||
export * from './settings'
|
||||
export * from './variable'
|
||||
export * from './edge'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { z } from 'zod'
|
||||
import { whatsAppSettingsSchema } from '../whatsapp'
|
||||
|
||||
export const rememberUserStorages = ['session', 'local'] as const
|
||||
|
||||
@@ -35,6 +36,7 @@ export const settingsSchema = z.object({
|
||||
general: generalSettings,
|
||||
typingEmulation: typingEmulation,
|
||||
metadata: metadataSchema,
|
||||
whatsApp: whatsAppSettingsSchema.optional(),
|
||||
})
|
||||
|
||||
export const defaultSettings = ({
|
||||
|
||||
@@ -5,6 +5,7 @@ import { variableSchema } from './variable'
|
||||
import { Typebot as TypebotPrisma } from '@typebot.io/prisma'
|
||||
import { blockSchema } from '../blocks/schemas'
|
||||
import { preprocessTypebot } from './helpers/preprocessTypebot'
|
||||
import { edgeSchema } from './edge'
|
||||
|
||||
export const groupSchema = z.object({
|
||||
id: z.string(),
|
||||
@@ -16,23 +17,6 @@ export const groupSchema = z.object({
|
||||
blocks: z.array(blockSchema),
|
||||
})
|
||||
|
||||
const sourceSchema = z.object({
|
||||
groupId: z.string(),
|
||||
blockId: z.string(),
|
||||
itemId: z.string().optional(),
|
||||
})
|
||||
|
||||
const targetSchema = z.object({
|
||||
groupId: z.string(),
|
||||
blockId: z.string().optional(),
|
||||
})
|
||||
|
||||
export const edgeSchema = z.object({
|
||||
id: z.string(),
|
||||
from: sourceSchema,
|
||||
to: targetSchema,
|
||||
})
|
||||
|
||||
const resultsTablePreferencesSchema = z.object({
|
||||
columnsOrder: z.array(z.string()),
|
||||
columnsVisibility: z.record(z.string(), z.boolean()),
|
||||
@@ -72,6 +56,7 @@ export const typebotSchema = z.preprocess(
|
||||
resultsTablePreferences: resultsTablePreferencesSchema.nullable(),
|
||||
isArchived: z.boolean(),
|
||||
isClosed: z.boolean(),
|
||||
whatsAppPhoneNumberId: z.string().nullable(),
|
||||
}) satisfies z.ZodType<TypebotPrisma, z.ZodTypeDef, unknown>
|
||||
)
|
||||
|
||||
@@ -93,9 +78,7 @@ export const typebotCreateSchema = typebotSchema._def.schema
|
||||
.partial()
|
||||
|
||||
export type Typebot = z.infer<typeof typebotSchema>
|
||||
export type Target = z.infer<typeof targetSchema>
|
||||
export type Source = z.infer<typeof sourceSchema>
|
||||
export type Edge = z.infer<typeof edgeSchema>
|
||||
|
||||
export type Group = z.infer<typeof groupSchema>
|
||||
export type ResultsTablePreferences = z.infer<
|
||||
typeof resultsTablePreferencesSchema
|
||||
|
||||
200
packages/schemas/features/whatsapp.ts
Normal file
200
packages/schemas/features/whatsapp.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
import { z } from 'zod'
|
||||
import { credentialsBaseSchema } from './blocks/baseSchemas'
|
||||
import { ComparisonOperators, LogicalOperator } from './blocks/logic/condition'
|
||||
|
||||
const mediaSchema = z.object({ link: z.string() })
|
||||
|
||||
const headerSchema = z
|
||||
.object({
|
||||
type: z.literal('image'),
|
||||
image: mediaSchema,
|
||||
})
|
||||
.or(
|
||||
z.object({
|
||||
type: z.literal('video'),
|
||||
video: mediaSchema,
|
||||
})
|
||||
)
|
||||
.or(
|
||||
z.object({
|
||||
type: z.literal('text'),
|
||||
text: z.string(),
|
||||
})
|
||||
)
|
||||
|
||||
const bodySchema = z.object({
|
||||
text: z.string(),
|
||||
})
|
||||
|
||||
const actionSchema = z.object({
|
||||
buttons: z.array(
|
||||
z.object({
|
||||
type: z.literal('reply'),
|
||||
reply: z.object({ id: z.string(), title: z.string() }),
|
||||
})
|
||||
),
|
||||
})
|
||||
|
||||
const templateSchema = z.object({
|
||||
name: z.literal('preview_initial_message'),
|
||||
language: z.object({
|
||||
code: z.literal('en'),
|
||||
}),
|
||||
})
|
||||
|
||||
const interactiveSchema = z.object({
|
||||
type: z.literal('button'),
|
||||
header: headerSchema.optional(),
|
||||
body: bodySchema.optional(),
|
||||
action: actionSchema,
|
||||
})
|
||||
|
||||
// https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages#message-object
|
||||
const sendingMessageSchema = z.discriminatedUnion('type', [
|
||||
z.object({
|
||||
type: z.literal('text'),
|
||||
text: z.object({
|
||||
body: z.string(),
|
||||
preview_url: z.boolean().optional(),
|
||||
}),
|
||||
preview_url: z.boolean().optional(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('image'),
|
||||
image: mediaSchema,
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('audio'),
|
||||
audio: mediaSchema,
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('video'),
|
||||
video: mediaSchema,
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('interactive'),
|
||||
interactive: interactiveSchema,
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('template'),
|
||||
template: templateSchema,
|
||||
}),
|
||||
])
|
||||
|
||||
export const incomingMessageSchema = z.discriminatedUnion('type', [
|
||||
z.object({
|
||||
from: z.string(),
|
||||
type: z.literal('text'),
|
||||
text: z.object({
|
||||
body: z.string(),
|
||||
}),
|
||||
timestamp: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
from: z.string(),
|
||||
type: z.literal('button'),
|
||||
button: z.object({
|
||||
text: z.string(),
|
||||
payload: z.string(),
|
||||
}),
|
||||
timestamp: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
from: z.string(),
|
||||
type: z.literal('interactive'),
|
||||
interactive: z.object({
|
||||
button_reply: z.object({
|
||||
id: z.string(),
|
||||
title: z.string(),
|
||||
}),
|
||||
}),
|
||||
timestamp: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
from: z.string(),
|
||||
type: z.literal('image'),
|
||||
image: z.object({ id: z.string() }),
|
||||
timestamp: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
from: z.string(),
|
||||
type: z.literal('video'),
|
||||
video: z.object({ id: z.string() }),
|
||||
timestamp: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
from: z.string(),
|
||||
type: z.literal('audio'),
|
||||
audio: z.object({ id: z.string() }),
|
||||
timestamp: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
from: z.string(),
|
||||
type: z.literal('document'),
|
||||
document: z.object({ id: z.string() }),
|
||||
timestamp: z.string(),
|
||||
}),
|
||||
])
|
||||
|
||||
export const whatsAppWebhookRequestBodySchema = z.object({
|
||||
entry: z.array(
|
||||
z.object({
|
||||
changes: z.array(
|
||||
z.object({
|
||||
value: z.object({
|
||||
contacts: z
|
||||
.array(
|
||||
z.object({
|
||||
profile: z.object({
|
||||
name: z.string(),
|
||||
}),
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
metadata: z.object({
|
||||
display_phone_number: z.string(),
|
||||
}),
|
||||
messages: z.array(incomingMessageSchema).optional(),
|
||||
}),
|
||||
})
|
||||
),
|
||||
})
|
||||
),
|
||||
})
|
||||
|
||||
export const whatsAppCredentialsSchema = z
|
||||
.object({
|
||||
type: z.literal('whatsApp'),
|
||||
data: z.object({
|
||||
systemUserAccessToken: z.string(),
|
||||
phoneNumberId: z.string(),
|
||||
}),
|
||||
})
|
||||
.merge(credentialsBaseSchema)
|
||||
|
||||
const whatsAppComparisonSchema = z.object({
|
||||
id: z.string(),
|
||||
comparisonOperator: z.nativeEnum(ComparisonOperators).optional(),
|
||||
value: z.string().optional(),
|
||||
})
|
||||
export type WhatsAppComparison = z.infer<typeof whatsAppComparisonSchema>
|
||||
|
||||
const startConditionSchema = z.object({
|
||||
logicalOperator: z.nativeEnum(LogicalOperator),
|
||||
comparisons: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
comparisonOperator: z.nativeEnum(ComparisonOperators).optional(),
|
||||
value: z.string().optional(),
|
||||
})
|
||||
),
|
||||
})
|
||||
|
||||
export const whatsAppSettingsSchema = z.object({
|
||||
credentialsId: z.string().optional(),
|
||||
startCondition: startConditionSchema.optional(),
|
||||
})
|
||||
|
||||
export type WhatsAppIncomingMessage = z.infer<typeof incomingMessageSchema>
|
||||
export type WhatsAppSendingMessage = z.infer<typeof sendingMessageSchema>
|
||||
export type WhatsAppCredentials = z.infer<typeof whatsAppCredentialsSchema>
|
||||
Reference in New Issue
Block a user