2
0

♻️ Migrate from got to ky (#1416)

Closes #1415
This commit is contained in:
Baptiste Arnaud
2024-04-05 09:01:16 +02:00
committed by GitHub
parent ccc7101dd3
commit d96f384e02
59 changed files with 990 additions and 628 deletions

1
.eslintignore Normal file
View File

@ -0,0 +1 @@
node_modules

View File

@ -21,7 +21,7 @@ const injectViewerUrlIfVercelPreview = (val) => {
process.env.VERCEL_BUILDER_PROJECT_NAME, process.env.VERCEL_BUILDER_PROJECT_NAME,
process.env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME process.env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME
) )
if (process.env.NEXT_PUBLIC_CHAT_API_URL.includes('{{pr_id}}')) if (process.env.NEXT_PUBLIC_CHAT_API_URL?.includes('{{pr_id}}'))
process.env.NEXT_PUBLIC_CHAT_API_URL = process.env.NEXT_PUBLIC_CHAT_API_URL =
process.env.NEXT_PUBLIC_CHAT_API_URL.replace( process.env.NEXT_PUBLIC_CHAT_API_URL.replace(
'{{pr_id}}', '{{pr_id}}',
@ -56,12 +56,14 @@ const nextConfig = {
if (nextRuntime === 'edge') { if (nextRuntime === 'edge') {
config.resolve.alias['minio'] = false config.resolve.alias['minio'] = false
config.resolve.alias['got'] = false config.resolve.alias['got'] = false
config.resolve.alias['qrcode'] = false
return config return config
} }
// These packages are imports from the integrations definition files that can be ignored for the client. // These packages are imports from the integrations definition files that can be ignored for the client.
config.resolve.alias['minio'] = false config.resolve.alias['minio'] = false
config.resolve.alias['got'] = false config.resolve.alias['got'] = false
config.resolve.alias['openai'] = false config.resolve.alias['openai'] = false
config.resolve.alias['qrcode'] = false
return config return config
}, },
headers: async () => { headers: async () => {

View File

@ -67,7 +67,7 @@
"framer-motion": "10.3.0", "framer-motion": "10.3.0",
"google-auth-library": "8.9.0", "google-auth-library": "8.9.0",
"google-spreadsheet": "4.1.1", "google-spreadsheet": "4.1.1",
"got": "12.6.0", "ky": "1.2.3",
"immer": "10.0.2", "immer": "10.0.2",
"jsonwebtoken": "9.0.1", "jsonwebtoken": "9.0.1",
"libphonenumber-js": "1.10.37", "libphonenumber-js": "1.10.37",

View File

@ -32,7 +32,6 @@ export const getResultExample = authenticatedProcedure
}) })
) )
.query(async ({ input: { typebotId, blockId }, ctx: { user } }) => { .query(async ({ input: { typebotId, blockId }, ctx: { user } }) => {
console.log('user', user)
const typebot = (await prisma.typebot.findFirst({ const typebot = (await prisma.typebot.findFirst({
where: canReadTypebots(typebotId, user), where: canReadTypebots(typebotId, user),
select: { select: {

View File

@ -17,7 +17,7 @@ export const convertVariablesForTestToVariables = (
) as Variable ) as Variable
return { ...variable, value: parseVariableValue(variableForTest.value) } return { ...variable, value: parseVariableValue(variableForTest.value) }
}, {}), }, {}),
] ].filter((v) => v.value)
} }
const parseVariableValue = (value: string | undefined): string | string[] => { const parseVariableValue = (value: string | undefined): string | string[] => {

View File

@ -1,6 +1,5 @@
import { Variable, HttpResponse } from '@typebot.io/schemas' import { Variable, HttpResponse } from '@typebot.io/schemas'
import { sendRequest } from '@typebot.io/lib' import { sendRequest } from '@typebot.io/lib'
import { env } from '@typebot.io/env'
export const executeWebhook = ( export const executeWebhook = (
typebotId: string, typebotId: string,
@ -8,7 +7,7 @@ export const executeWebhook = (
{ blockId }: { blockId: string } { blockId }: { blockId: string }
) => ) =>
sendRequest<HttpResponse>({ sendRequest<HttpResponse>({
url: `${env.NEXT_PUBLIC_VIEWER_URL[0]}/api/typebots/${typebotId}/blocks/${blockId}/executeWebhook`, url: `/api/typebots/${typebotId}/blocks/${blockId}/testWebhook`,
method: 'POST', method: 'POST',
body: { body: {
variables, variables,

View File

@ -5,7 +5,7 @@ import { z } from 'zod'
import { isReadWorkspaceFobidden } from '@/features/workspace/helpers/isReadWorkspaceFobidden' import { isReadWorkspaceFobidden } from '@/features/workspace/helpers/isReadWorkspaceFobidden'
import { decrypt } from '@typebot.io/lib/api/encryption/decrypt' import { decrypt } from '@typebot.io/lib/api/encryption/decrypt'
import { ZemanticAiCredentials } from '@typebot.io/schemas/features/blocks/integrations/zemanticAi' import { ZemanticAiCredentials } from '@typebot.io/schemas/features/blocks/integrations/zemanticAi'
import got from 'got' import ky from 'ky'
export const listProjects = authenticatedProcedure export const listProjects = authenticatedProcedure
.input( .input(
@ -58,7 +58,7 @@ export const listProjects = authenticatedProcedure
const url = 'https://api.zemantic.ai/v1/projects' const url = 'https://api.zemantic.ai/v1/projects'
try { try {
const response = await got const response = await ky
.get(url, { .get(url, {
headers: { headers: {
Authorization: `Bearer ${data.apiKey}`, Authorization: `Bearer ${data.apiKey}`,

View File

@ -3,7 +3,7 @@ import { authenticatedProcedure } from '@/helpers/server/trpc'
import { TRPCError } from '@trpc/server' import { TRPCError } from '@trpc/server'
import { z } from 'zod' import { z } from 'zod'
import { customDomainSchema } from '@typebot.io/schemas/features/customDomains' import { customDomainSchema } from '@typebot.io/schemas/features/customDomains'
import got, { HTTPError } from 'got' import ky, { HTTPError } from 'ky'
import { env } from '@typebot.io/env' import { env } from '@typebot.io/env'
import { isWriteWorkspaceForbidden } from '@/features/workspace/helpers/isWriteWorkspaceForbidden' import { isWriteWorkspaceForbidden } from '@/features/workspace/helpers/isWriteWorkspaceForbidden'
import { trackEvents } from '@typebot.io/telemetry/trackEvents' import { trackEvents } from '@typebot.io/telemetry/trackEvents'
@ -61,12 +61,12 @@ export const createCustomDomain = authenticatedProcedure
try { try {
await createDomainOnVercel(name) await createDomainOnVercel(name)
} catch (err) { } catch (err) {
console.log(err) if (err instanceof HTTPError && err.response.status !== 409) {
if (err instanceof HTTPError && err.response.statusCode !== 409)
throw new TRPCError({ throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR', code: 'INTERNAL_SERVER_ERROR',
message: 'Failed to create custom domain on Vercel', message: 'Failed to create custom domain on Vercel',
}) })
}
} }
const customDomain = await prisma.customDomain.create({ const customDomain = await prisma.customDomain.create({
@ -91,8 +91,12 @@ export const createCustomDomain = authenticatedProcedure
}) })
const createDomainOnVercel = (name: string) => const createDomainOnVercel = (name: string) =>
got.post({ ky.post(
url: `https://api.vercel.com/v10/projects/${env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME}/domains?teamId=${env.VERCEL_TEAM_ID}`, `https://api.vercel.com/v10/projects/${env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME}/domains?teamId=${env.VERCEL_TEAM_ID}`,
headers: { Authorization: `Bearer ${env.VERCEL_TOKEN}` }, {
json: { name }, headers: {
}) authorization: `Bearer ${env.VERCEL_TOKEN}`,
},
json: { name },
}
)

View File

@ -2,7 +2,7 @@ import prisma from '@typebot.io/lib/prisma'
import { authenticatedProcedure } from '@/helpers/server/trpc' import { authenticatedProcedure } from '@/helpers/server/trpc'
import { TRPCError } from '@trpc/server' import { TRPCError } from '@trpc/server'
import { z } from 'zod' import { z } from 'zod'
import got from 'got' import ky from 'ky'
import { env } from '@typebot.io/env' import { env } from '@typebot.io/env'
import { isWriteWorkspaceForbidden } from '@/features/workspace/helpers/isWriteWorkspaceForbidden' import { isWriteWorkspaceForbidden } from '@/features/workspace/helpers/isWriteWorkspaceForbidden'
@ -63,7 +63,9 @@ export const deleteCustomDomain = authenticatedProcedure
}) })
const deleteDomainOnVercel = (name: string) => const deleteDomainOnVercel = (name: string) =>
got.delete({ ky.delete(
url: `https://api.vercel.com/v9/projects/${env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME}/domains/${name}?teamId=${env.VERCEL_TEAM_ID}`, `https://api.vercel.com/v9/projects/${env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME}/domains/${name}?teamId=${env.VERCEL_TEAM_ID}`,
headers: { Authorization: `Bearer ${env.VERCEL_TOKEN}` }, {
}) headers: { Authorization: `Bearer ${env.VERCEL_TOKEN}` },
}
)

View File

@ -377,6 +377,7 @@ const SystemUserToken = ({
<ListItem>Copy and paste the generated token:</ListItem> <ListItem>Copy and paste the generated token:</ListItem>
<TextInput <TextInput
isRequired isRequired
type="password"
label="System User Token" label="System User Token"
defaultValue={initialToken} defaultValue={initialToken}
onChange={(val) => setToken(val.trim())} onChange={(val) => setToken(val.trim())}

View File

@ -1,6 +1,6 @@
import { authenticatedProcedure } from '@/helpers/server/trpc' import { authenticatedProcedure } from '@/helpers/server/trpc'
import { z } from 'zod' import { z } from 'zod'
import got from 'got' import ky from 'ky'
import prisma from '@typebot.io/lib/prisma' import prisma from '@typebot.io/lib/prisma'
import { decrypt } from '@typebot.io/lib/api/encryption/decrypt' import { decrypt } from '@typebot.io/lib/api/encryption/decrypt'
import { TRPCError } from '@trpc/server' import { TRPCError } from '@trpc/server'
@ -22,16 +22,13 @@ export const getPhoneNumber = authenticatedProcedure
code: 'NOT_FOUND', code: 'NOT_FOUND',
message: 'Credentials not found', message: 'Credentials not found',
}) })
const { display_phone_number } = (await got( const { display_phone_number } = await ky
`${env.WHATSAPP_CLOUD_API_URL}/v17.0/${credentials.phoneNumberId}`, .get(`${env.WHATSAPP_CLOUD_API_URL}/v17.0/${credentials.phoneNumberId}`, {
{
headers: { headers: {
Authorization: `Bearer ${credentials.systemUserAccessToken}`, Authorization: `Bearer ${credentials.systemUserAccessToken}`,
}, },
} })
).json()) as { .json<{ display_phone_number: string }>()
display_phone_number: string
}
const formattedPhoneNumber = `${ const formattedPhoneNumber = `${
display_phone_number.startsWith('+') ? '' : '+' display_phone_number.startsWith('+') ? '' : '+'

View File

@ -1,6 +1,6 @@
import { authenticatedProcedure } from '@/helpers/server/trpc' import { authenticatedProcedure } from '@/helpers/server/trpc'
import { z } from 'zod' import { z } from 'zod'
import got from 'got' import ky from 'ky'
import { TRPCError } from '@trpc/server' import { TRPCError } from '@trpc/server'
import { WhatsAppCredentials } from '@typebot.io/schemas/features/whatsapp' import { WhatsAppCredentials } from '@typebot.io/schemas/features/whatsapp'
import prisma from '@typebot.io/lib/prisma' import prisma from '@typebot.io/lib/prisma'
@ -28,21 +28,23 @@ export const getSystemTokenInfo = authenticatedProcedure
}) })
const { const {
data: { expires_at, scopes, app_id, application }, data: { expires_at, scopes, app_id, application },
} = (await got( } = await ky
`${env.WHATSAPP_CLOUD_API_URL}/v17.0/debug_token?input_token=${credentials.systemUserAccessToken}`, .get(
{ `${env.WHATSAPP_CLOUD_API_URL}/v17.0/debug_token?input_token=${credentials.systemUserAccessToken}`,
headers: { {
Authorization: `Bearer ${credentials.systemUserAccessToken}`, headers: {
}, Authorization: `Bearer ${credentials.systemUserAccessToken}`,
} },
).json()) as { }
data: { )
app_id: string .json<{
application: string data: {
expires_at: number app_id: string
scopes: string[] application: string
} expires_at: number
} scopes: string[]
}
}>()
return { return {
appId: app_id, appId: app_id,

View File

@ -3,7 +3,7 @@ import { z } from 'zod'
import { TRPCError } from '@trpc/server' import { TRPCError } from '@trpc/server'
import { startSession } from '@typebot.io/bot-engine/startSession' import { startSession } from '@typebot.io/bot-engine/startSession'
import { env } from '@typebot.io/env' import { env } from '@typebot.io/env'
import { HTTPError } from 'got' import { HTTPError } from 'ky'
import prisma from '@typebot.io/lib/prisma' import prisma from '@typebot.io/lib/prisma'
import { saveStateToDatabase } from '@typebot.io/bot-engine/saveStateToDatabase' import { saveStateToDatabase } from '@typebot.io/bot-engine/saveStateToDatabase'
import { restartSession } from '@typebot.io/bot-engine/queries/restartSession' import { restartSession } from '@typebot.io/bot-engine/queries/restartSession'
@ -169,7 +169,7 @@ export const startWhatsAppPreview = authenticatedProcedure
}, },
}) })
} catch (err) { } catch (err) {
if (err instanceof HTTPError) console.log(err.response.body) if (err instanceof HTTPError) console.log(await err.response.text())
throw new TRPCError({ throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR', code: 'INTERNAL_SERVER_ERROR',
message: 'Request to Meta to send preview message failed', message: 'Request to Meta to send preview message failed',

View File

@ -16,7 +16,7 @@ import { getNewUserInvitations } from '@/features/auth/helpers/getNewUserInvitat
import { sendVerificationRequest } from '@/features/auth/helpers/sendVerificationRequest' import { sendVerificationRequest } from '@/features/auth/helpers/sendVerificationRequest'
import { Ratelimit } from '@upstash/ratelimit' import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis/nodejs' import { Redis } from '@upstash/redis/nodejs'
import got from 'got' import ky from 'ky'
import { env } from '@typebot.io/env' import { env } from '@typebot.io/env'
import * as Sentry from '@sentry/nextjs' import * as Sentry from '@sentry/nextjs'
import { getIp } from '@typebot.io/lib/getIp' import { getIp } from '@typebot.io/lib/getIp'
@ -164,10 +164,12 @@ export const getAuthOptions = ({
if (!account) return false if (!account) return false
const isNewUser = !('createdAt' in user && isDefined(user.createdAt)) const isNewUser = !('createdAt' in user && isDefined(user.createdAt))
if (isNewUser && user.email) { if (isNewUser && user.email) {
const { body } = await got.get( const data = await ky
'https://raw.githubusercontent.com/disposable-email-domains/disposable-email-domains/master/disposable_email_blocklist.conf' .get(
) 'https://raw.githubusercontent.com/disposable-email-domains/disposable-email-domains/master/disposable_email_blocklist.conf'
const disposableEmailDomains = body.split('\n') )
.text()
const disposableEmailDomains = data.split('\n')
if (disposableEmailDomains.includes(user.email.split('@')[1])) if (disposableEmailDomains.includes(user.email.split('@')[1]))
return false return false
} }

View File

@ -5,7 +5,7 @@ import {
methodNotAllowed, methodNotAllowed,
notAuthenticated, notAuthenticated,
} from '@typebot.io/lib/api' } from '@typebot.io/lib/api'
import { got } from 'got' import ky from 'ky'
import { getAuthenticatedUser } from '@/features/auth/helpers/getAuthenticatedUser' import { getAuthenticatedUser } from '@/features/auth/helpers/getAuthenticatedUser'
import { env } from '@typebot.io/env' import { env } from '@typebot.io/env'
@ -31,9 +31,11 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
} }
const deleteDomainOnVercel = (name: string) => const deleteDomainOnVercel = (name: string) =>
got.delete({ ky.delete(
url: `https://api.vercel.com/v8/projects/${env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME}/domains/${name}?teamId=${env.VERCEL_TEAM_ID}`, `https://api.vercel.com/v8/projects/${env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME}/domains/${name}?teamId=${env.VERCEL_TEAM_ID}`,
headers: { Authorization: `Bearer ${env.VERCEL_TOKEN}` }, {
}) headers: { Authorization: `Bearer ${env.VERCEL_TOKEN}` },
}
)
export default handler export default handler

View File

@ -0,0 +1,114 @@
import {
Typebot,
Variable,
HttpRequest,
Block,
AnswerInSessionState,
} from '@typebot.io/schemas'
import { NextApiRequest, NextApiResponse } from 'next'
import { byId } from '@typebot.io/lib'
import { isWebhookBlock } from '@typebot.io/schemas/helpers'
import { methodNotAllowed, notFound } from '@typebot.io/lib/api'
import prisma from '@typebot.io/lib/prisma'
import { getBlockById } from '@typebot.io/schemas/helpers'
import {
executeWebhook,
parseWebhookAttributes,
} from '@typebot.io/bot-engine/blocks/integrations/webhook/executeWebhookBlock'
import { fetchLinkedChildTypebots } from '@typebot.io/bot-engine/blocks/logic/typebotLink/fetchLinkedChildTypebots'
import { parseSampleResult } from '@typebot.io/bot-engine/blocks/integrations/webhook/parseSampleResult'
import { saveLog } from '@typebot.io/bot-engine/logs/saveLog'
import { getAuthenticatedUser } from '@/features/auth/helpers/getAuthenticatedUser'
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'POST') {
const user = await getAuthenticatedUser(req, res)
const typebotId = req.query.typebotId as string
const blockId = req.query.blockId as string
const resultId = req.query.resultId as string | undefined
const { variables } = (
typeof req.body === 'string' ? JSON.parse(req.body) : req.body
) as {
variables: Variable[]
}
const typebot = (await prisma.typebot.findUnique({
where: { id: typebotId },
include: { webhooks: true },
})) as unknown as (Typebot & { webhooks: HttpRequest[] }) | null
if (!typebot) return notFound(res)
const block = typebot.groups
.flatMap<Block>((g) => g.blocks)
.find(byId(blockId))
if (!block || !isWebhookBlock(block))
return notFound(res, 'Webhook block not found')
const webhookId = 'webhookId' in block ? block.webhookId : undefined
const webhook =
block.options?.webhook ??
typebot.webhooks.find((w) => {
if ('id' in w) return w.id === webhookId
return false
})
if (!webhook)
return res
.status(404)
.send({ statusCode: 404, data: { message: `Couldn't find webhook` } })
const { group } = getBlockById(blockId, typebot.groups)
const linkedTypebots = await fetchLinkedChildTypebots({
isPreview: !('typebotId' in typebot),
typebots: [typebot],
userId: user?.id,
})([])
const answers = arrayify(
await parseSampleResult(typebot, linkedTypebots)(group.id, variables)
)
const parsedWebhook = await parseWebhookAttributes({
webhook,
isCustomBody: block.options?.isCustomBody,
typebot: {
...typebot,
variables: typebot.variables.map((v) => {
const matchingVariable = variables.find(byId(v.id))
if (!matchingVariable) return v
return { ...v, value: matchingVariable.value }
}),
},
answers,
})
if (!parsedWebhook)
return res.status(500).send({
statusCode: 500,
data: { message: `Couldn't parse webhook attributes` },
})
const { response, logs } = await executeWebhook(parsedWebhook, {
timeout: block.options?.timeout,
})
if (resultId)
await Promise.all(
logs?.map((log) =>
saveLog({
message: log.description,
details: log.details,
status: log.status as 'error' | 'success' | 'info',
resultId,
})
) ?? []
)
return res.status(200).send(response)
}
return methodNotAllowed(res)
}
const arrayify = (
obj: Record<string, string | boolean | undefined>
): AnswerInSessionState[] =>
Object.entries(obj)
.map(([key, value]) => ({ key, value: value?.toString() }))
.filter((a) => a.value) as AnswerInSessionState[]
export default handler

View File

@ -9,7 +9,7 @@ Here is the `sendMessage` action of the Telegram block:
```ts ```ts
import { createAction, option } from '@typebot.io/forge' import { createAction, option } from '@typebot.io/forge'
import { auth } from '../auth' import { auth } from '../auth'
import { got } from 'got' import ky from 'ky'
export const sendMessage = createAction({ export const sendMessage = createAction({
auth, auth,
@ -27,14 +27,14 @@ export const sendMessage = createAction({
run: { run: {
server: async ({ credentials: { token }, options: { chatId, text } }) => { server: async ({ credentials: { token }, options: { chatId, text } }) => {
try { try {
await got.post(`https://api.telegram.org/bot${token}/sendMessage`, { await ky.post(`https://api.telegram.org/bot${token}/sendMessage`, {
json: { json: {
chat_id: chatId, chat_id: chatId,
text, text,
}, },
}) })
} catch (error) { } catch (error) {
console.log('ERROR', error.response.body) console.log('ERROR', await error.response.text())
} }
}, },
}, },

View File

@ -13,8 +13,6 @@ An action can do one of the following things:
The most common action is to execute a function on the server. This is done by simply declaring that function in the action block. The most common action is to execute a function on the server. This is done by simply declaring that function in the action block.
If you need to use an external package that is not compatible with web browser environment, you will have to dynamically import it in the server function. You can find an example of this in the [Anthropic's Create Chat Message action](https://github.com/baptisteArno/typebot.io/blob/main/packages/forge/blocks/anthropic/actions/createChatMessage.tsx)
Example: Example:
```ts ```ts

View File

@ -17,7 +17,7 @@ const injectViewerUrlIfVercelPreview = (val) => {
) )
return return
process.env.NEXT_PUBLIC_VIEWER_URL = `https://${process.env.VERCEL_BRANCH_URL}` process.env.NEXT_PUBLIC_VIEWER_URL = `https://${process.env.VERCEL_BRANCH_URL}`
if (process.env.NEXT_PUBLIC_CHAT_API_URL.includes('{{pr_id}}')) if (process.env.NEXT_PUBLIC_CHAT_API_URL?.includes('{{pr_id}}'))
process.env.NEXT_PUBLIC_CHAT_API_URL = process.env.NEXT_PUBLIC_CHAT_API_URL =
process.env.NEXT_PUBLIC_CHAT_API_URL.replace( process.env.NEXT_PUBLIC_CHAT_API_URL.replace(
'{{pr_id}}', '{{pr_id}}',
@ -56,12 +56,14 @@ const nextConfig = {
if (nextRuntime === 'edge') { if (nextRuntime === 'edge') {
config.resolve.alias['minio'] = false config.resolve.alias['minio'] = false
config.resolve.alias['got'] = false config.resolve.alias['got'] = false
config.resolve.alias['qrcode'] = false
return config return config
} }
// These packages are imports from the integrations definition files that can be ignored for the client. // These packages are imports from the integrations definition files that can be ignored for the client.
config.resolve.alias['minio'] = false config.resolve.alias['minio'] = false
config.resolve.alias['got'] = false config.resolve.alias['got'] = false
config.resolve.alias['openai'] = false config.resolve.alias['openai'] = false
config.resolve.alias['qrcode'] = false
return config return config
}, },
async redirects() { async redirects() {

View File

@ -26,6 +26,7 @@
"cors": "2.8.5", "cors": "2.8.5",
"google-spreadsheet": "4.1.1", "google-spreadsheet": "4.1.1",
"got": "12.6.0", "got": "12.6.0",
"ky": "1.2.3",
"next": "14.1.0", "next": "14.1.0",
"nextjs-cors": "2.1.2", "nextjs-cors": "2.1.2",
"nodemailer": "6.9.8", "nodemailer": "6.9.8",

View File

@ -1,45 +1,35 @@
import { import {
PublicTypebot,
ResultValues, ResultValues,
Typebot, Typebot,
Variable, Variable,
HttpRequest, HttpRequest,
HttpResponse,
Block, Block,
PublicTypebot,
AnswerInSessionState,
} from '@typebot.io/schemas' } from '@typebot.io/schemas'
import { NextApiRequest, NextApiResponse } from 'next' import { NextApiRequest, NextApiResponse } from 'next'
import got, { Method, Headers, HTTPError } from 'got' import { byId } from '@typebot.io/lib'
import { byId, isEmpty, isNotDefined, omit } from '@typebot.io/lib'
import { isWebhookBlock } from '@typebot.io/schemas/helpers' import { isWebhookBlock } from '@typebot.io/schemas/helpers'
import { parseAnswers } from '@typebot.io/results/parseAnswers'
import { initMiddleware, methodNotAllowed, notFound } from '@typebot.io/lib/api' import { initMiddleware, methodNotAllowed, notFound } from '@typebot.io/lib/api'
import { stringify } from 'qs'
import Cors from 'cors' import Cors from 'cors'
import prisma from '@typebot.io/lib/prisma' import prisma from '@typebot.io/lib/prisma'
import { fetchLinkedTypebots } from '@typebot.io/bot-engine/blocks/logic/typebotLink/fetchLinkedTypebots'
import { getPreviouslyLinkedTypebots } from '@typebot.io/bot-engine/blocks/logic/typebotLink/getPreviouslyLinkedTypebots'
import { parseVariables } from '@typebot.io/variables/parseVariables'
import { saveErrorLog } from '@typebot.io/bot-engine/logs/saveErrorLog'
import { saveSuccessLog } from '@typebot.io/bot-engine/logs/saveSuccessLog'
import { parseSampleResult } from '@typebot.io/bot-engine/blocks/integrations/webhook/parseSampleResult'
import {
HttpMethod,
defaultTimeout,
defaultWebhookAttributes,
maxTimeout,
} from '@typebot.io/schemas/features/blocks/integrations/webhook/constants'
import { getBlockById } from '@typebot.io/schemas/helpers' import { getBlockById } from '@typebot.io/schemas/helpers'
import { import {
convertKeyValueTableToObject, executeWebhook,
longReqTimeoutWhitelist, parseWebhookAttributes,
} from '@typebot.io/bot-engine/blocks/integrations/webhook/executeWebhookBlock' } from '@typebot.io/bot-engine/blocks/integrations/webhook/executeWebhookBlock'
import { env } from '@typebot.io/env' import { fetchLinkedParentTypebots } from '@typebot.io/bot-engine/blocks/logic/typebotLink/fetchLinkedParentTypebots'
import { fetchLinkedChildTypebots } from '@typebot.io/bot-engine/blocks/logic/typebotLink/fetchLinkedChildTypebots'
import { parseSampleResult } from '@typebot.io/bot-engine/blocks/integrations/webhook/parseSampleResult'
import { saveLog } from '@typebot.io/bot-engine/logs/saveLog'
import { authenticateUser } from '@/helpers/authenticateUser'
const cors = initMiddleware(Cors()) const cors = initMiddleware(Cors())
const handler = async (req: NextApiRequest, res: NextApiResponse) => { const handler = async (req: NextApiRequest, res: NextApiResponse) => {
await cors(req, res) await cors(req, res)
if (req.method === 'POST') { if (req.method === 'POST') {
const user = await authenticateUser(req)
const typebotId = req.query.typebotId as string const typebotId = req.query.typebotId as string
const blockId = req.query.blockId as string const blockId = req.query.blockId as string
const resultId = req.query.resultId as string | undefined const resultId = req.query.resultId as string | undefined
@ -72,232 +62,81 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
.status(404) .status(404)
.send({ statusCode: 404, data: { message: `Couldn't find webhook` } }) .send({ statusCode: 404, data: { message: `Couldn't find webhook` } })
const { group } = getBlockById(blockId, typebot.groups) const { group } = getBlockById(blockId, typebot.groups)
const result = await executeWebhook(typebot)({ const linkedTypebotsParents = (await fetchLinkedParentTypebots({
webhook, isPreview: !('typebotId' in typebot),
variables,
groupId: group.id,
resultValues,
resultId,
parentTypebotIds, parentTypebotIds,
userId: user?.id,
})) as (Typebot | PublicTypebot)[]
const linkedTypebotsChildren = await fetchLinkedChildTypebots({
isPreview: !('typebotId' in typebot),
typebots: [typebot],
userId: user?.id,
})([])
const linkedTypebots = [...linkedTypebotsParents, ...linkedTypebotsChildren]
const answers = resultValues
? resultValues.answers.map((answer) => ({
key:
(answer.variableId
? typebot.variables.find(
(variable) => variable.id === answer.variableId
)?.name
: typebot.groups.find((group) =>
group.blocks.find((block) => block.id === answer.blockId)
)?.title) ?? '',
value: answer.content,
}))
: arrayify(
await parseSampleResult(typebot, linkedTypebots)(group.id, variables)
)
const parsedWebhook = await parseWebhookAttributes({
webhook,
isCustomBody: block.options?.isCustomBody, isCustomBody: block.options?.isCustomBody,
typebot: {
...typebot,
variables: typebot.variables.map((v) => {
const matchingVariable = variables.find(byId(v.id))
if (!matchingVariable) return v
return { ...v, value: matchingVariable.value }
}),
},
answers,
})
if (!parsedWebhook)
return res.status(500).send({
statusCode: 500,
data: { message: `Couldn't parse webhook attributes` },
})
const { response, logs } = await executeWebhook(parsedWebhook, {
timeout: block.options?.timeout, timeout: block.options?.timeout,
}) })
return res.status(200).send(result)
if (resultId)
await Promise.all(
logs?.map((log) =>
saveLog({
message: log.description,
details: log.details,
status: log.status as 'error' | 'success' | 'info',
resultId,
})
) ?? []
)
return res.status(200).send(response)
} }
return methodNotAllowed(res) return methodNotAllowed(res)
} }
const checkIfBodyIsAVariable = (body: string) => /^{{.+}}$/.test(body) const arrayify = (
obj: Record<string, string | boolean | undefined>
export const executeWebhook = ): AnswerInSessionState[] =>
(typebot: Typebot) => Object.entries(obj)
async ({ .map(([key, value]) => ({ key, value: value?.toString() }))
webhook, .filter((a) => a.value) as AnswerInSessionState[]
variables,
groupId,
resultValues,
resultId,
parentTypebotIds = [],
isCustomBody,
timeout,
}: {
webhook: HttpRequest
variables: Variable[]
groupId: string
resultValues?: ResultValues
resultId?: string
parentTypebotIds: string[]
isCustomBody?: boolean
timeout?: number
}): Promise<HttpResponse> => {
if (!webhook.url)
return {
statusCode: 400,
data: { message: `Webhook doesn't have url or method` },
}
const basicAuth: { username?: string; password?: string } = {}
const basicAuthHeaderIdx =
webhook.headers?.findIndex(
(h) =>
h.key?.toLowerCase() === 'authorization' &&
h.value?.toLowerCase()?.includes('basic')
) ?? -1
const isUsernamePasswordBasicAuth =
basicAuthHeaderIdx !== -1 &&
webhook.headers?.[basicAuthHeaderIdx].value?.includes(':')
if (isUsernamePasswordBasicAuth) {
const [username, password] =
webhook.headers?.[basicAuthHeaderIdx].value?.slice(6).split(':') ?? []
basicAuth.username = username
basicAuth.password = password
webhook.headers?.splice(basicAuthHeaderIdx, 1)
}
const headers = convertKeyValueTableToObject(webhook.headers, variables) as
| Headers
| undefined
const queryParams = stringify(
convertKeyValueTableToObject(webhook.queryParams, variables)
)
const contentType = headers ? headers['Content-Type'] : undefined
const linkedTypebotsParents = (await fetchLinkedTypebots({
isPreview: !('typebotId' in typebot),
typebotIds: parentTypebotIds,
})) as (Typebot | PublicTypebot)[]
const linkedTypebotsChildren = await getPreviouslyLinkedTypebots({
isPreview: !('typebotId' in typebot),
typebots: [typebot],
})([])
const bodyContent = await getBodyContent(typebot, [
...linkedTypebotsParents,
...linkedTypebotsChildren,
])({
body: webhook.body,
isCustomBody,
resultValues,
groupId,
variables,
})
const { data: body, isJson } =
bodyContent && webhook.method !== HttpMethod.GET
? safeJsonParse(
parseVariables(variables, {
isInsideJson: !checkIfBodyIsAVariable(bodyContent),
})(bodyContent)
)
: { data: undefined, isJson: false }
const url = parseVariables(variables)(
webhook.url + (queryParams !== '' ? `?${queryParams}` : '')
)
const isLongRequest = longReqTimeoutWhitelist.some((whiteListedUrl) =>
url?.includes(whiteListedUrl)
)
const request = {
url,
method: (webhook.method ?? defaultWebhookAttributes.method) as Method,
headers: headers ?? {},
...basicAuth,
json:
!contentType?.includes('x-www-form-urlencoded') && body && isJson
? body
: undefined,
form:
contentType?.includes('x-www-form-urlencoded') && body
? body
: undefined,
body: body && !isJson ? body : undefined,
timeout: {
response: isNotDefined(env.CHAT_API_TIMEOUT)
? undefined
: timeout && timeout !== defaultTimeout
? Math.min(timeout, maxTimeout) * 1000
: isLongRequest
? maxTimeout * 1000
: defaultTimeout * 1000,
},
}
try {
const response = await got(request.url, omit(request, 'url'))
await saveSuccessLog({
resultId,
message: 'Webhook successfuly executed.',
details: {
statusCode: response.statusCode,
request,
response: safeJsonParse(response.body).data,
},
})
return {
statusCode: response.statusCode,
data: safeJsonParse(response.body).data,
}
} catch (error) {
if (error instanceof HTTPError) {
const response = {
statusCode: error.response.statusCode,
data: safeJsonParse(error.response.body as string).data,
}
await saveErrorLog({
resultId,
message: 'Webhook returned an error',
details: {
request,
response,
},
})
return response
}
const response = {
statusCode: 500,
data: { message: `Error from Typebot server: ${error}` },
}
console.error(error)
await saveErrorLog({
resultId,
message: 'Webhook failed to execute',
details: {
request,
response,
},
})
return response
}
}
const getBodyContent =
(
typebot: Pick<Typebot | PublicTypebot, 'groups' | 'variables' | 'edges'>,
linkedTypebots: (Typebot | PublicTypebot)[]
) =>
async ({
body,
resultValues,
groupId,
variables,
isCustomBody,
}: {
body?: string | null
resultValues?: ResultValues
groupId: string
variables: Variable[]
isCustomBody?: boolean
}): Promise<string | undefined> => {
return body === '{{state}}' || isEmpty(body) || isCustomBody !== true
? JSON.stringify(
resultValues
? parseAnswers({
answers: resultValues.answers.map((answer) => ({
key:
(answer.variableId
? typebot.variables.find(
(variable) => variable.id === answer.variableId
)?.name
: typebot.groups.find((group) =>
group.blocks.find(
(block) => block.id === answer.blockId
)
)?.title) ?? '',
value: answer.content,
})),
variables: resultValues.variables,
})
: await parseSampleResult(typebot, linkedTypebots)(
groupId,
variables
)
)
: body ?? undefined
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const safeJsonParse = (json: string): { data: any; isJson: boolean } => {
try {
return { data: JSON.parse(json), isJson: true }
} catch (err) {
return { data: json, isJson: false }
}
}
export default handler export default handler

View File

@ -13,7 +13,6 @@ type Props = {
sessionId: string sessionId: string
} }
export const continueChat = async ({ origin, sessionId, message }: Props) => { export const continueChat = async ({ origin, sessionId, message }: Props) => {
console.log('test')
const session = await getSession(sessionId) const session = await getSession(sessionId)
if (!session) { if (!session) {

View File

@ -1,7 +1,7 @@
import { isNotEmpty } from '@typebot.io/lib/utils' import { isNotEmpty } from '@typebot.io/lib/utils'
import { ContinueChatResponse } from '@typebot.io/schemas' import { ContinueChatResponse } from '@typebot.io/schemas'
import { OpenAIBlock } from '@typebot.io/schemas/features/blocks/integrations/openai' import { OpenAIBlock } from '@typebot.io/schemas/features/blocks/integrations/openai'
import { HTTPError } from 'got' import { HTTPError } from 'ky'
import { ClientOptions, OpenAI } from 'openai' import { ClientOptions, OpenAI } from 'openai'
type Props = Pick< type Props = Pick<
@ -55,9 +55,9 @@ export const executeChatCompletionOpenAIRequest = async ({
} catch (error) { } catch (error) {
if (error instanceof HTTPError) { if (error instanceof HTTPError) {
if ( if (
(error.response.statusCode === 503 || (error.response.status === 503 ||
error.response.statusCode === 500 || error.response.status === 500 ||
error.response.statusCode === 403) && error.response.status === 403) &&
!isRetrying !isRetrying
) { ) {
console.log('OpenAI API error - 503, retrying in 3 seconds') console.log('OpenAI API error - 503, retrying in 3 seconds')
@ -73,7 +73,7 @@ export const executeChatCompletionOpenAIRequest = async ({
isRetrying: true, isRetrying: true,
}) })
} }
if (error.response.statusCode === 400) { if (error.response.status === 400) {
const log = { const log = {
status: 'info', status: 'info',
description: description:
@ -93,8 +93,8 @@ export const executeChatCompletionOpenAIRequest = async ({
} }
logs.push({ logs.push({
status: 'error', status: 'error',
description: `OpenAI API error - ${error.response.statusCode}`, description: `OpenAI API error - ${error.response.status}`,
details: error.response.body, details: await error.response.text(),
}) })
return { logs } return { logs }
} }

View File

@ -11,10 +11,11 @@ import {
ChatLog, ChatLog,
ExecutableHttpRequest, ExecutableHttpRequest,
AnswerInSessionState, AnswerInSessionState,
TypebotInSession,
} from '@typebot.io/schemas' } from '@typebot.io/schemas'
import { stringify } from 'qs' import { stringify } from 'qs'
import { isDefined, isEmpty, isNotDefined, omit } from '@typebot.io/lib' import { isDefined, isEmpty, isNotDefined, omit } from '@typebot.io/lib'
import got, { Method, HTTPError, OptionsInit } from 'got' import ky, { HTTPError, Options } from 'ky'
import { resumeWebhookExecution } from './resumeWebhookExecution' import { resumeWebhookExecution } from './resumeWebhookExecution'
import { ExecuteIntegrationResponse } from '../../../types' import { ExecuteIntegrationResponse } from '../../../types'
import { parseVariables } from '@typebot.io/variables/parseVariables' import { parseVariables } from '@typebot.io/variables/parseVariables'
@ -60,9 +61,11 @@ export const executeWebhookBlock = async (
})) as HttpRequest | null) })) as HttpRequest | null)
: null) : null)
if (!webhook) return { outgoingEdgeId: block.outgoingEdgeId } if (!webhook) return { outgoingEdgeId: block.outgoingEdgeId }
const parsedWebhook = await parseWebhookAttributes(state)({ const parsedWebhook = await parseWebhookAttributes({
webhook, webhook,
isCustomBody: block.options?.isCustomBody, isCustomBody: block.options?.isCustomBody,
typebot: state.typebotsQueue[0].typebot,
answers: state.typebotsQueue[0].answers,
}) })
if (!parsedWebhook) { if (!parsedWebhook) {
logs.push({ logs.push({
@ -104,69 +107,69 @@ export const executeWebhookBlock = async (
const checkIfBodyIsAVariable = (body: string) => /^{{.+}}$/.test(body) const checkIfBodyIsAVariable = (body: string) => /^{{.+}}$/.test(body)
const parseWebhookAttributes = export const parseWebhookAttributes = async ({
(state: SessionState) => webhook,
async ({ isCustomBody,
webhook, typebot,
isCustomBody, answers,
}: { }: {
webhook: HttpRequest webhook: HttpRequest
isCustomBody?: boolean isCustomBody?: boolean
}): Promise<ParsedWebhook | undefined> => { typebot: TypebotInSession
if (!webhook.url) return answers: AnswerInSessionState[]
const { typebot } = state.typebotsQueue[0] }): Promise<ParsedWebhook | undefined> => {
const basicAuth: { username?: string; password?: string } = {} if (!webhook.url) return
const basicAuthHeaderIdx = webhook.headers?.findIndex( const basicAuth: { username?: string; password?: string } = {}
(h) => const basicAuthHeaderIdx = webhook.headers?.findIndex(
h.key?.toLowerCase() === 'authorization' && (h) =>
h.value?.toLowerCase()?.includes('basic') h.key?.toLowerCase() === 'authorization' &&
) h.value?.toLowerCase()?.includes('basic')
const isUsernamePasswordBasicAuth = )
basicAuthHeaderIdx !== -1 && const isUsernamePasswordBasicAuth =
isDefined(basicAuthHeaderIdx) && basicAuthHeaderIdx !== -1 &&
webhook.headers?.at(basicAuthHeaderIdx)?.value?.includes(':') isDefined(basicAuthHeaderIdx) &&
if (isUsernamePasswordBasicAuth) { webhook.headers?.at(basicAuthHeaderIdx)?.value?.includes(':')
const [username, password] = if (isUsernamePasswordBasicAuth) {
webhook.headers?.at(basicAuthHeaderIdx)?.value?.slice(6).split(':') ?? const [username, password] =
[] webhook.headers?.at(basicAuthHeaderIdx)?.value?.slice(6).split(':') ?? []
basicAuth.username = username basicAuth.username = username
basicAuth.password = password basicAuth.password = password
webhook.headers?.splice(basicAuthHeaderIdx, 1) webhook.headers?.splice(basicAuthHeaderIdx, 1)
}
const headers = convertKeyValueTableToObject(
webhook.headers,
typebot.variables
) as ExecutableHttpRequest['headers'] | undefined
const queryParams = stringify(
convertKeyValueTableToObject(webhook.queryParams, typebot.variables)
)
const bodyContent = await getBodyContent({
body: webhook.body,
answers: state.typebotsQueue[0].answers,
variables: typebot.variables,
isCustomBody,
})
const method = webhook.method ?? defaultWebhookAttributes.method
const { data: body, isJson } =
bodyContent && method !== HttpMethod.GET
? safeJsonParse(
parseVariables(typebot.variables, {
isInsideJson: !checkIfBodyIsAVariable(bodyContent),
})(bodyContent)
)
: { data: undefined, isJson: false }
return {
url: parseVariables(typebot.variables)(
webhook.url + (queryParams !== '' ? `?${queryParams}` : '')
),
basicAuth,
method,
headers,
body,
isJson,
}
} }
const headers = convertKeyValueTableToObject(
webhook.headers,
typebot.variables
) as ExecutableHttpRequest['headers'] | undefined
const queryParams = stringify(
convertKeyValueTableToObject(webhook.queryParams, typebot.variables)
)
const bodyContent = await getBodyContent({
body: webhook.body,
answers,
variables: typebot.variables,
isCustomBody,
})
const method = webhook.method ?? defaultWebhookAttributes.method
const { data: body, isJson } =
bodyContent && method !== HttpMethod.GET
? safeJsonParse(
parseVariables(typebot.variables, {
isInsideJson: !checkIfBodyIsAVariable(bodyContent),
})(bodyContent)
)
: { data: undefined, isJson: false }
return {
url: parseVariables(typebot.variables)(
webhook.url + (queryParams !== '' ? `?${queryParams}` : '')
),
basicAuth,
method,
headers,
body,
isJson,
}
}
export const executeWebhook = async ( export const executeWebhook = async (
webhook: ParsedWebhook, webhook: ParsedWebhook,
@ -177,7 +180,8 @@ export const executeWebhook = async (
startTimeShouldBeUpdated?: boolean startTimeShouldBeUpdated?: boolean
}> => { }> => {
const logs: ChatLog[] = [] const logs: ChatLog[] = []
const { headers, url, method, basicAuth, body, isJson } = webhook
const { headers, url, method, basicAuth, isJson } = webhook
const contentType = headers ? headers['Content-Type'] : undefined const contentType = headers ? headers['Content-Type'] : undefined
const isLongRequest = params.disableRequestTimeout const isLongRequest = params.disableRequestTimeout
@ -186,59 +190,60 @@ export const executeWebhook = async (
url?.includes(whiteListedUrl) url?.includes(whiteListedUrl)
) )
const isFormData = contentType?.includes('x-www-form-urlencoded')
let body = webhook.body
if (isFormData && isJson) body = parseFormDataBody(body as object)
const request = { const request = {
url, url,
method: method as Method, method,
headers: headers ?? {}, headers: headers ?? {},
...(basicAuth ?? {}), ...(basicAuth ?? {}),
json: json: !isFormData && body && isJson ? body : undefined,
!contentType?.includes('x-www-form-urlencoded') && body && isJson body: (isFormData && body ? body : undefined) as any,
? body timeout: isNotDefined(env.CHAT_API_TIMEOUT)
: undefined, ? undefined
form: : params.timeout && params.timeout !== defaultTimeout
contentType?.includes('x-www-form-urlencoded') && body ? body : undefined, ? Math.min(params.timeout, maxTimeout) * 1000
body: body && !isJson ? (body as string) : undefined, : isLongRequest
timeout: { ? maxTimeout * 1000
response: isNotDefined(env.CHAT_API_TIMEOUT) : defaultTimeout * 1000,
? undefined } satisfies Options & { url: string; body: any }
: params.timeout && params.timeout !== defaultTimeout
? Math.min(params.timeout, maxTimeout) * 1000
: isLongRequest
? maxTimeout * 1000
: defaultTimeout * 1000,
},
} satisfies OptionsInit
try { try {
const response = await got(request.url, omit(request, 'url')) const response = await ky(request.url, omit(request, 'url'))
const body = await response.text()
logs.push({ logs.push({
status: 'success', status: 'success',
description: webhookSuccessDescription, description: webhookSuccessDescription,
details: { details: {
statusCode: response.statusCode, statusCode: response.status,
response: safeJsonParse(response.body).data, response: safeJsonParse(body).data,
request, request,
}, },
}) })
return { return {
response: { response: {
statusCode: response.statusCode, statusCode: response.status,
data: safeJsonParse(response.body).data, data: safeJsonParse(body).data,
}, },
logs, logs,
startTimeShouldBeUpdated: true, startTimeShouldBeUpdated: true,
} }
} catch (error) { } catch (error) {
if (error instanceof HTTPError) { if (error instanceof HTTPError) {
const responseBody = await error.response.text()
const response = { const response = {
statusCode: error.response.statusCode, statusCode: error.response.status,
data: safeJsonParse(error.response.body as string).data, data: safeJsonParse(responseBody).data,
} }
logs.push({ logs.push({
status: 'error', status: 'error',
description: webhookErrorDescription, description: webhookErrorDescription,
details: { details: {
statusCode: error.response.statusCode, statusCode: error.response.status,
request, request,
response, response,
}, },
@ -257,7 +262,7 @@ export const executeWebhook = async (
} }
logs.push({ logs.push({
status: 'error', status: 'error',
description: `Webhook request timed out. (${request.timeout.response}ms)`, description: `Webhook request timed out. (${request.timeout}ms)`,
details: { details: {
response, response,
request, request,
@ -320,10 +325,18 @@ export const convertKeyValueTableToObject = (
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const safeJsonParse = (json: string): { data: any; isJson: boolean } => { const safeJsonParse = (json: unknown): { data: any; isJson: boolean } => {
try { try {
return { data: JSON.parse(json), isJson: true } return { data: JSON.parse(json as string), isJson: true }
} catch (err) { } catch (err) {
return { data: json, isJson: false } return { data: json, isJson: false }
} }
} }
const parseFormDataBody = (body: object) => {
const searchParams = new URLSearchParams()
Object.entries(body as object).forEach(([key, value]) => {
searchParams.set(key, value)
})
return searchParams
}

View File

@ -4,7 +4,7 @@ import {
ZemanticAiCredentials, ZemanticAiCredentials,
ZemanticAiResponse, ZemanticAiResponse,
} from '@typebot.io/schemas/features/blocks/integrations/zemanticAi' } from '@typebot.io/schemas/features/blocks/integrations/zemanticAi'
import got from 'got' import ky from 'ky'
import { decrypt } from '@typebot.io/lib/api/encryption/decrypt' import { decrypt } from '@typebot.io/lib/api/encryption/decrypt'
import { byId, isDefined, isEmpty } from '@typebot.io/lib' import { byId, isDefined, isEmpty } from '@typebot.io/lib'
import { ExecuteIntegrationResponse } from '../../../types' import { ExecuteIntegrationResponse } from '../../../types'
@ -51,7 +51,7 @@ export const executeZemanticAiBlock = async (
}) })
try { try {
const res: ZemanticAiResponse = await got const res: ZemanticAiResponse = await ky
.post(URL, { .post(URL, {
headers: { headers: {
Authorization: `Bearer ${apiKey}`, Authorization: `Bearer ${apiKey}`,

View File

@ -11,12 +11,12 @@ import { LogicBlockType } from '@typebot.io/schemas/features/blocks/logic/consta
type Props = { type Props = {
typebots: Pick<PublicTypebot, 'groups'>[] typebots: Pick<PublicTypebot, 'groups'>[]
user?: User userId: string | undefined
isPreview?: boolean isPreview?: boolean
} }
export const getPreviouslyLinkedTypebots = export const fetchLinkedChildTypebots =
({ typebots, user, isPreview }: Props) => ({ typebots, userId, isPreview }: Props) =>
async ( async (
capturedLinkedBots: (Typebot | PublicTypebot)[] capturedLinkedBots: (Typebot | PublicTypebot)[]
): Promise<(Typebot | PublicTypebot)[]> => { ): Promise<(Typebot | PublicTypebot)[]> => {
@ -40,13 +40,13 @@ export const getPreviouslyLinkedTypebots =
.filter(isDefined) .filter(isDefined)
if (linkedTypebotIds.length === 0) return capturedLinkedBots if (linkedTypebotIds.length === 0) return capturedLinkedBots
const linkedTypebots = (await fetchLinkedTypebots({ const linkedTypebots = (await fetchLinkedTypebots({
user, userId,
typebotIds: linkedTypebotIds, typebotIds: linkedTypebotIds,
isPreview, isPreview,
})) as (Typebot | PublicTypebot)[] })) as (Typebot | PublicTypebot)[]
return getPreviouslyLinkedTypebots({ return fetchLinkedChildTypebots({
typebots: linkedTypebots, typebots: linkedTypebots,
user, userId,
isPreview, isPreview,
})([...capturedLinkedBots, ...linkedTypebots]) })([...capturedLinkedBots, ...linkedTypebots])
} }

View File

@ -0,0 +1,20 @@
import { fetchLinkedTypebots } from './fetchLinkedTypebots'
type Props = {
parentTypebotIds: string[]
userId: string | undefined
isPreview?: boolean
}
export const fetchLinkedParentTypebots = ({
parentTypebotIds,
isPreview,
userId,
}: Props) =>
parentTypebotIds.length > 0
? fetchLinkedTypebots({
typebotIds: parentTypebotIds,
isPreview,
userId,
})
: []

View File

@ -1,20 +1,19 @@
import prisma from '@typebot.io/lib/prisma' import prisma from '@typebot.io/lib/prisma'
import { User } from '@typebot.io/prisma'
type Props = { type Props = {
isPreview?: boolean isPreview?: boolean
typebotIds: string[] typebotIds: string[]
user?: User userId: string | undefined
} }
export const fetchLinkedTypebots = async ({ export const fetchLinkedTypebots = async ({
user, userId,
isPreview, isPreview,
typebotIds, typebotIds,
}: Props) => { }: Props) => {
if (!user || !isPreview) if (!userId || !isPreview)
return prisma.publicTypebot.findMany({ return prisma.publicTypebot.findMany({
where: { id: { in: typebotIds } }, where: { typebotId: { in: typebotIds } },
}) })
const linkedTypebots = await prisma.typebot.findMany({ const linkedTypebots = await prisma.typebot.findMany({
where: { id: { in: typebotIds } }, where: { id: { in: typebotIds } },
@ -39,7 +38,7 @@ export const fetchLinkedTypebots = async ({
return linkedTypebots.filter( return linkedTypebots.filter(
(typebot) => (typebot) =>
typebot.collaborators.some( typebot.collaborators.some(
(collaborator) => collaborator.userId === user.id (collaborator) => collaborator.userId === userId
) || typebot.workspace.members.some((member) => member.userId === user.id) ) || typebot.workspace.members.some((member) => member.userId === userId)
) )
} }

View File

@ -111,7 +111,6 @@ export const executeGroup = async (
logs, logs,
visitedEdges, visitedEdges,
} }
console.log('yes')
const executionResponse = ( const executionResponse = (
isLogicBlock(block) isLogicBlock(block)
? await executeLogic(newSessionState)(block) ? await executeLogic(newSessionState)(block)

View File

@ -16,7 +16,6 @@ import { env } from '@typebot.io/env'
export const executeIntegration = export const executeIntegration =
(state: SessionState) => (state: SessionState) =>
async (block: IntegrationBlock): Promise<ExecuteIntegrationResponse> => { async (block: IntegrationBlock): Promise<ExecuteIntegrationResponse> => {
console.log('HI')
switch (block.type) { switch (block.type) {
case IntegrationBlockType.GOOGLE_SHEETS: case IntegrationBlockType.GOOGLE_SHEETS:
return { return {

View File

@ -27,7 +27,6 @@ export const executeForgedBlock = async (
const blockDef = forgedBlocks[block.type] const blockDef = forgedBlocks[block.type]
if (!blockDef) return { outgoingEdgeId: block.outgoingEdgeId } if (!blockDef) return { outgoingEdgeId: block.outgoingEdgeId }
const action = blockDef.actions.find((a) => a.name === block.options.action) const action = blockDef.actions.find((a) => a.name === block.options.action)
console.log('test', action)
const noCredentialsError = { const noCredentialsError = {
status: 'error', status: 'error',
description: 'Credentials not provided for integration', description: 'Credentials not provided for integration',

View File

@ -14,6 +14,7 @@
"@typebot.io/env": "workspace:*", "@typebot.io/env": "workspace:*",
"@typebot.io/lib": "workspace:*", "@typebot.io/lib": "workspace:*",
"@typebot.io/prisma": "workspace:*", "@typebot.io/prisma": "workspace:*",
"@typebot.io/results": "workspace:*",
"@typebot.io/schemas": "workspace:*", "@typebot.io/schemas": "workspace:*",
"@typebot.io/tsconfig": "workspace:*", "@typebot.io/tsconfig": "workspace:*",
"@typebot.io/variables": "workspace:*", "@typebot.io/variables": "workspace:*",
@ -24,19 +25,18 @@
"date-fns-tz": "2.0.0", "date-fns-tz": "2.0.0",
"google-auth-library": "8.9.0", "google-auth-library": "8.9.0",
"google-spreadsheet": "4.1.1", "google-spreadsheet": "4.1.1",
"got": "12.6.0", "ky": "1.2.3",
"ky": "^1.1.3",
"libphonenumber-js": "1.10.37", "libphonenumber-js": "1.10.37",
"node-html-parser": "6.1.5", "node-html-parser": "6.1.5",
"nodemailer": "6.9.8", "nodemailer": "6.9.8",
"openai": "4.28.4", "openai": "4.28.4",
"qs": "6.11.2", "qs": "6.11.2",
"stripe": "12.13.0", "stripe": "12.13.0"
"@typebot.io/results": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"@typebot.io/forge": "workspace:*", "@typebot.io/forge": "workspace:*",
"@typebot.io/forge-repository": "workspace:*", "@typebot.io/forge-repository": "workspace:*",
"@types/node": "^20.12.3",
"@types/nodemailer": "6.4.14", "@types/nodemailer": "6.4.14",
"@types/qs": "6.9.7" "@types/qs": "6.9.7"
} }

View File

@ -1,5 +1,5 @@
import { env } from '@typebot.io/env' import { env } from '@typebot.io/env'
import got from 'got' import ky from 'ky'
type Props = { type Props = {
mediaId: string mediaId: string
@ -10,21 +10,24 @@ export const downloadMedia = async ({
mediaId, mediaId,
systemUserAccessToken, systemUserAccessToken,
}: Props): Promise<{ file: Buffer; mimeType: string }> => { }: Props): Promise<{ file: Buffer; mimeType: string }> => {
const { body } = await got.get({ const { url, mime_type } = await ky
url: `${env.WHATSAPP_CLOUD_API_URL}/v17.0/${mediaId}`, .get(`${env.WHATSAPP_CLOUD_API_URL}/v17.0/${mediaId}`, {
headers: {
Authorization: `Bearer ${systemUserAccessToken}`,
},
})
const parsedBody = JSON.parse(body) as { url: string; mime_type: string }
return {
file: await got(parsedBody.url, {
headers: { headers: {
Authorization: `Bearer ${systemUserAccessToken}`, Authorization: `Bearer ${systemUserAccessToken}`,
}, },
}).buffer(), })
mimeType: parsedBody.mime_type, .json<{ url: string; mime_type: string }>()
return {
file: Buffer.from(
await ky
.get(url, {
headers: {
Authorization: `Bearer ${systemUserAccessToken}`,
},
})
.arrayBuffer()
),
mimeType: mime_type,
} }
} }

View File

@ -10,7 +10,7 @@ import {
import { convertMessageToWhatsAppMessage } from './convertMessageToWhatsAppMessage' import { convertMessageToWhatsAppMessage } from './convertMessageToWhatsAppMessage'
import { sendWhatsAppMessage } from './sendWhatsAppMessage' import { sendWhatsAppMessage } from './sendWhatsAppMessage'
import * as Sentry from '@sentry/nextjs' import * as Sentry from '@sentry/nextjs'
import { HTTPError } from 'got' import { HTTPError } from 'ky'
import { convertInputToWhatsAppMessages } from './convertInputToWhatsAppMessage' import { convertInputToWhatsAppMessages } from './convertInputToWhatsAppMessage'
import { isNotDefined } from '@typebot.io/lib/utils' import { isNotDefined } from '@typebot.io/lib/utils'
import { computeTypingDuration } from '../computeTypingDuration' import { computeTypingDuration } from '../computeTypingDuration'
@ -141,7 +141,7 @@ export const sendChatReplyToWhatsApp = async ({
Sentry.captureException(err, { extra: { message } }) Sentry.captureException(err, { extra: { message } })
console.log('Failed to send message:', JSON.stringify(message, null, 2)) console.log('Failed to send message:', JSON.stringify(message, null, 2))
if (err instanceof HTTPError) if (err instanceof HTTPError)
console.log('HTTPError', err.response.statusCode, err.response.body) console.log('HTTPError', err.response.status, await err.response.text())
} }
} }
@ -172,7 +172,11 @@ export const sendChatReplyToWhatsApp = async ({
Sentry.captureException(err, { extra: { message } }) Sentry.captureException(err, { extra: { message } })
console.log('Failed to send message:', JSON.stringify(message, null, 2)) console.log('Failed to send message:', JSON.stringify(message, null, 2))
if (err instanceof HTTPError) if (err instanceof HTTPError)
console.log('HTTPError', err.response.statusCode, err.response.body) console.log(
'HTTPError',
err.response.status,
await err.response.text()
)
} }
} }
} }
@ -253,7 +257,11 @@ const executeClientSideAction =
Sentry.captureException(err, { extra: { message } }) Sentry.captureException(err, { extra: { message } })
console.log('Failed to send message:', JSON.stringify(message, null, 2)) console.log('Failed to send message:', JSON.stringify(message, null, 2))
if (err instanceof HTTPError) if (err instanceof HTTPError)
console.log('HTTPError', err.response.statusCode, err.response.body) console.log(
'HTTPError',
err.response.status,
await err.response.text()
)
} }
} }
} }

View File

@ -3,7 +3,7 @@ import {
WhatsAppSendingMessage, WhatsAppSendingMessage,
} from '@typebot.io/schemas/features/whatsapp' } from '@typebot.io/schemas/features/whatsapp'
import { env } from '@typebot.io/env' import { env } from '@typebot.io/env'
import got from 'got' import ky from 'ky'
type Props = { type Props = {
to: string to: string
@ -16,7 +16,7 @@ export const sendWhatsAppMessage = async ({
message, message,
credentials, credentials,
}: Props) => }: Props) =>
got.post( ky.post(
`${env.WHATSAPP_CLOUD_API_URL}/v17.0/${credentials.phoneNumberId}/messages`, `${env.WHATSAPP_CLOUD_API_URL}/v17.0/${credentials.phoneNumberId}/messages`,
{ {
headers: { headers: {

View File

@ -1,5 +1,6 @@
import { createAction, option } from '@typebot.io/forge' import { createAction, option } from '@typebot.io/forge'
import { auth } from '../auth' import { auth } from '../auth'
import { Anthropic } from '@anthropic-ai/sdk'
import { AnthropicStream } from 'ai' import { AnthropicStream } from 'ai'
import { anthropicModels, defaultAnthropicOptions } from '../constants' import { anthropicModels, defaultAnthropicOptions } from '../constants'
import { parseChatMessages } from '../helpers/parseChatMessages' import { parseChatMessages } from '../helpers/parseChatMessages'
@ -103,8 +104,6 @@ export const createChatMessage = createAction({
responseMapping?.map((res) => res.variableId).filter(isDefined) ?? [], responseMapping?.map((res) => res.variableId).filter(isDefined) ?? [],
run: { run: {
server: async ({ credentials: { apiKey }, options, variables, logs }) => { server: async ({ credentials: { apiKey }, options, variables, logs }) => {
const { Anthropic } = await import('@anthropic-ai/sdk')
const client = new Anthropic({ const client = new Anthropic({
apiKey: apiKey, apiKey: apiKey,
}) })
@ -150,8 +149,6 @@ export const createChatMessage = createAction({
(res) => res.item === 'Message Content' || !res.item (res) => res.item === 'Message Content' || !res.item
)?.variableId, )?.variableId,
run: async ({ credentials: { apiKey }, options, variables }) => { run: async ({ credentials: { apiKey }, options, variables }) => {
const { Anthropic } = await import('@anthropic-ai/sdk')
const client = new Anthropic({ const client = new Anthropic({
apiKey: apiKey, apiKey: apiKey,
}) })

View File

@ -1,6 +1,6 @@
import { createAction, option } from '@typebot.io/forge' import { createAction, option } from '@typebot.io/forge'
import { isDefined, isEmpty } from '@typebot.io/lib' import { isDefined, isEmpty } from '@typebot.io/lib'
import { HTTPError, got } from 'got' import ky, { HTTPError } from 'ky'
import { apiBaseUrl } from '../constants' import { apiBaseUrl } from '../constants'
import { auth } from '../auth' import { auth } from '../auth'
import { ChatNodeResponse } from '../types' import { ChatNodeResponse } from '../types'
@ -40,7 +40,7 @@ export const sendMessage = createAction({
logs, logs,
}) => { }) => {
try { try {
const res: ChatNodeResponse = await got const res: ChatNodeResponse = await ky
.post(apiBaseUrl + botId, { .post(apiBaseUrl + botId, {
headers: { headers: {
Authorization: `Bearer ${apiKey}`, Authorization: `Bearer ${apiKey}`,
@ -66,7 +66,7 @@ export const sendMessage = createAction({
return logs.add({ return logs.add({
status: 'error', status: 'error',
description: error.message, description: error.message,
details: error.response.body, details: await error.response.text(),
}) })
console.error(error) console.error(error)
} }

View File

@ -11,6 +11,6 @@
"@typebot.io/tsconfig": "workspace:*", "@typebot.io/tsconfig": "workspace:*",
"@types/react": "18.2.15", "@types/react": "18.2.15",
"typescript": "5.3.2", "typescript": "5.3.2",
"got": "12.6.0" "ky": "1.2.3"
} }
} }

View File

@ -1,9 +1,9 @@
import { createAction, option } from '@typebot.io/forge' import { createAction, option } from '@typebot.io/forge'
import { isDefined, isEmpty, isNotEmpty } from '@typebot.io/lib' import { isDefined, isEmpty, isNotEmpty } from '@typebot.io/lib'
import { HTTPError, got } from 'got'
import { auth } from '../auth' import { auth } from '../auth'
import { defaultBaseUrl } from '../constants' import { defaultBaseUrl } from '../constants'
import { Chunk } from '../types' import { Chunk } from '../types'
import ky from 'ky'
export const createChatMessage = createAction({ export const createChatMessage = createAction({
auth, auth,
@ -44,13 +44,15 @@ export const createChatMessage = createAction({
logs, logs,
}) => { }) => {
try { try {
const stream = got.post( const response = await ky(
(apiEndpoint ?? defaultBaseUrl) + '/v1/chat-messages', (apiEndpoint ?? defaultBaseUrl) + '/v1/chat-messages',
{ {
method: 'POST',
headers: { headers: {
Authorization: `Bearer ${apiKey}`, Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
}, },
json: { body: JSON.stringify({
inputs: inputs:
inputs?.reduce((acc, { key, value }) => { inputs?.reduce((acc, { key, value }) => {
if (isEmpty(key) || isEmpty(value)) return acc if (isEmpty(key) || isEmpty(value)) return acc
@ -64,56 +66,70 @@ export const createChatMessage = createAction({
conversation_id, conversation_id,
user, user,
files: [], files: [],
}, }),
isStream: true,
} }
) )
const reader = response.body?.getReader()
if (!reader)
return logs.add({
status: 'error',
description: 'Failed to read response stream',
})
const { answer, conversationId, totalTokens } = await new Promise<{ const { answer, conversationId, totalTokens } = await new Promise<{
answer: string answer: string
conversationId: string | undefined conversationId: string | undefined
totalTokens: number | undefined totalTokens: number | undefined
}>((resolve, reject) => { }>(async (resolve, reject) => {
let jsonChunk = '' let jsonChunk = ''
let answer = '' let answer = ''
let conversationId: string | undefined let conversationId: string | undefined
let totalTokens: number | undefined let totalTokens: number | undefined
stream.on('data', (chunk) => { try {
const lines = chunk.toString().split('\n') as string[] while (true) {
lines const { value, done } = await reader.read()
.filter((line) => line.length > 0 && line !== '\n') if (done) {
.forEach((line) => { resolve({ answer, conversationId, totalTokens })
jsonChunk += line return
if (jsonChunk.startsWith('event: ')) { }
const chunk = new TextDecoder().decode(value)
const lines = chunk.toString().split('\n') as string[]
lines
.filter((line) => line.length > 0 && line !== '\n')
.forEach((line) => {
jsonChunk += line
if (jsonChunk.startsWith('event: ')) {
jsonChunk = ''
return
}
if (
!jsonChunk.startsWith('data: ') ||
!jsonChunk.endsWith('}')
)
return
const data = JSON.parse(jsonChunk.slice(6)) as Chunk
jsonChunk = '' jsonChunk = ''
return if (
} data.event === 'message' ||
if (!jsonChunk.startsWith('data: ') || !jsonChunk.endsWith('}')) data.event === 'agent_message'
return ) {
answer += data.answer
const data = JSON.parse(jsonChunk.slice(6)) as Chunk }
jsonChunk = '' if (data.event === 'message_end') {
if ( totalTokens = data.metadata.usage.total_tokens
data.event === 'message' || conversationId = data.conversation_id
data.event === 'agent_message' }
) { })
answer += data.answer }
} } catch (e) {
if (data.event === 'message_end') { reject(e)
totalTokens = data.metadata.usage.total_tokens }
conversationId = data.conversation_id
}
})
})
stream.on('end', () => {
resolve({ answer, conversationId, totalTokens })
})
stream.on('error', (error) => {
reject(error)
})
}) })
responseMapping?.forEach((mapping) => { responseMapping?.forEach((mapping) => {
@ -130,12 +146,10 @@ export const createChatMessage = createAction({
variables.set(mapping.variableId, totalTokens) variables.set(mapping.variableId, totalTokens)
}) })
} catch (error) { } catch (error) {
if (error instanceof HTTPError) logs.add({
return logs.add({ status: 'error',
status: 'error', description: 'Failed to create chat message',
description: error.message, })
details: error.response.body,
})
console.error(error) console.error(error)
} }
}, },

View File

@ -10,7 +10,7 @@
"@typebot.io/lib": "workspace:*", "@typebot.io/lib": "workspace:*",
"@typebot.io/tsconfig": "workspace:*", "@typebot.io/tsconfig": "workspace:*",
"@types/react": "18.2.15", "@types/react": "18.2.15",
"got": "12.6.0", "ky": "1.2.3",
"typescript": "5.3.2" "typescript": "5.3.2"
} }
} }

View File

@ -2,7 +2,7 @@ import { createAction, option } from '@typebot.io/forge'
import { auth } from '../auth' import { auth } from '../auth'
import { baseUrl } from '../constants' import { baseUrl } from '../constants'
import { ModelsResponse, VoicesResponse } from '../type' import { ModelsResponse, VoicesResponse } from '../type'
import got, { HTTPError } from 'got' import got, { HTTPError } from 'ky'
import { uploadFileToBucket } from '@typebot.io/lib/s3/uploadFileToBucket' import { uploadFileToBucket } from '@typebot.io/lib/s3/uploadFileToBucket'
import { createId } from '@typebot.io/lib/createId' import { createId } from '@typebot.io/lib/createId'
@ -93,10 +93,10 @@ export const convertTextToSpeech = createAction({
text: options.text, text: options.text,
}, },
}) })
.buffer() .arrayBuffer()
const url = await uploadFileToBucket({ const url = await uploadFileToBucket({
file: response, file: Buffer.from(response),
key: `tmp/elevenlabs/audio/${createId() + createId()}.mp3`, key: `tmp/elevenlabs/audio/${createId() + createId()}.mp3`,
mimeType: 'audio/mpeg', mimeType: 'audio/mpeg',
}) })
@ -107,7 +107,7 @@ export const convertTextToSpeech = createAction({
return logs.add({ return logs.add({
status: 'error', status: 'error',
description: err.message, description: err.message,
details: err.response.body, details: await err.response.text(),
}) })
} }
} }

View File

@ -13,7 +13,7 @@
"typescript": "5.3.2" "typescript": "5.3.2"
}, },
"dependencies": { "dependencies": {
"got": "12.6.0", "ky": "1.2.3",
"@typebot.io/lib": "workspace:*" "@typebot.io/lib": "workspace:*"
} }
} }

View File

@ -3,6 +3,8 @@ import { isDefined } from '@typebot.io/lib'
import { auth } from '../auth' import { auth } from '../auth'
import { parseMessages } from '../helpers/parseMessages' import { parseMessages } from '../helpers/parseMessages'
import { OpenAIStream } from 'ai' import { OpenAIStream } from 'ai'
// @ts-ignore
import MistralClient from '../helpers/client'
const nativeMessageContentSchema = { const nativeMessageContentSchema = {
content: option.string.layout({ content: option.string.layout({
@ -95,15 +97,17 @@ export const createChatCompletion = createAction({
id: 'fetchModels', id: 'fetchModels',
dependencies: [], dependencies: [],
fetch: async ({ credentials }) => { fetch: async ({ credentials }) => {
const MistralClient = (await import('@mistralai/mistralai')).default
const client = new MistralClient(credentials.apiKey) const client = new MistralClient(credentials.apiKey)
const listModelsResponse = await client.listModels() const listModelsResponse: any = await client.listModels()
return ( return (
listModelsResponse.data listModelsResponse.data
.sort((a, b) => b.created - a.created) .sort(
.map((model) => model.id) ?? [] (a: { created: number }, b: { created: number }) =>
b.created - a.created
)
.map((model: { id: any }) => model.id) ?? []
) )
}, },
}, },
@ -111,10 +115,9 @@ export const createChatCompletion = createAction({
run: { run: {
server: async ({ credentials: { apiKey }, options, variables, logs }) => { server: async ({ credentials: { apiKey }, options, variables, logs }) => {
if (!options.model) return logs.add('No model selected') if (!options.model) return logs.add('No model selected')
const MistralClient = (await import('@mistralai/mistralai')).default
const client = new MistralClient(apiKey) const client = new MistralClient(apiKey)
const response = await client.chat({ const response: any = await client.chat({
model: options.model, model: options.model,
messages: parseMessages({ options, variables }), messages: parseMessages({ options, variables }),
}) })
@ -132,15 +135,13 @@ export const createChatCompletion = createAction({
)?.variableId, )?.variableId,
run: async ({ credentials: { apiKey }, options, variables }) => { run: async ({ credentials: { apiKey }, options, variables }) => {
if (!options.model) return if (!options.model) return
const MistralClient = (await import('@mistralai/mistralai')).default
const client = new MistralClient(apiKey) const client = new MistralClient(apiKey)
const response = client.chatStream({ const response: any = client.chatStream({
model: options.model, model: options.model,
messages: parseMessages({ options, variables }), messages: parseMessages({ options, variables }),
}) })
// @ts-ignore https://github.com/vercel/ai/issues/936
return OpenAIStream(response) return OpenAIStream(response)
}, },
}, },

View File

@ -0,0 +1,341 @@
// Taken from https://github.com/mistralai/client-js/blob/main/src/client.js
// Lib seems not actively maintained, and we need this patch: https://github.com/mistralai/client-js/pull/42
let isNode = false
let fetch
const VERSION = '0.0.3'
const RETRY_STATUS_CODES = [429, 500, 502, 503, 504]
const ENDPOINT = 'https://api.mistral.ai'
/**
* Initialize fetch
* @return {Promise<void>}
*/
async function initializeFetch() {
if (typeof globalThis.fetch === 'undefined')
throw new Error('No fetch implementation found')
if (typeof window === 'undefined') {
isNode = true
}
fetch = globalThis.fetch
}
initializeFetch()
/**
* MistralAPIError
* @return {MistralAPIError}
* @extends {Error}
*/
class MistralAPIError extends Error {
/**
* A simple error class for Mistral API errors
* @param {*} message
*/
constructor(message) {
super(message)
this.name = 'MistralAPIError'
}
}
/**
* MistralClient
* @return {MistralClient}
*/
class MistralClient {
/**
* A simple and lightweight client for the Mistral API
* @param {*} apiKey can be set as an environment variable MISTRAL_API_KEY,
* or provided in this parameter
* @param {*} endpoint defaults to https://api.mistral.ai
* @param {*} maxRetries defaults to 5
* @param {*} timeout defaults to 120 seconds
*/
constructor(
apiKey = process.env.MISTRAL_API_KEY,
endpoint = ENDPOINT,
maxRetries = 5,
timeout = 120
) {
this.endpoint = endpoint
this.apiKey = apiKey
this.maxRetries = maxRetries
this.timeout = timeout
if (this.endpoint.indexOf('inference.azure.com')) {
this.modelDefault = 'mistral'
}
}
/**
*
* @param {*} method
* @param {*} path
* @param {*} request
* @return {Promise<*>}
*/
_request = async function (method, path, request) {
const url = `${this.endpoint}/${path}`
const options = {
method: method,
headers: {
'User-Agent': `mistral-client-js/${VERSION}`,
Accept: request?.stream ? 'text/event-stream' : 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${this.apiKey}`,
},
body: method !== 'get' ? JSON.stringify(request) : null,
timeout: this.timeout * 1000,
}
for (let attempts = 0; attempts < this.maxRetries; attempts++) {
try {
const response = await fetch(url, options)
if (response.ok) {
if (request?.stream) {
if (isNode) {
return response.body
} else {
const reader = response.body.getReader()
// Chrome does not support async iterators yet, so polyfill it
const asyncIterator = async function* () {
try {
while (true) {
// Read from the stream
const { done, value } = await reader.read()
// Exit if we're done
if (done) return
// Else yield the chunk
yield value
}
} finally {
reader.releaseLock()
}
}
return asyncIterator()
}
}
return await response.json()
} else if (RETRY_STATUS_CODES.includes(response.status)) {
console.debug(
`Retrying request on response status: ${response.status}`,
`Response: ${await response.text()}`,
`Attempt: ${attempts + 1}`
)
// eslint-disable-next-line max-len
await new Promise((resolve) =>
setTimeout(resolve, Math.pow(2, attempts + 1) * 500)
)
} else {
throw new MistralAPIError(
`HTTP error! status: ${response.status} ` +
`Response: \n${await response.text()}`
)
}
} catch (error) {
console.error(`Request failed: ${error.message}`)
if (error.name === 'MistralAPIError') {
throw error
}
if (attempts === this.maxRetries - 1) throw error
// eslint-disable-next-line max-len
await new Promise((resolve) =>
setTimeout(resolve, Math.pow(2, attempts + 1) * 500)
)
}
}
throw new Error('Max retries reached')
}
/**
* Creates a chat completion request
* @param {*} model
* @param {*} messages
* @param {*} tools
* @param {*} temperature
* @param {*} maxTokens
* @param {*} topP
* @param {*} randomSeed
* @param {*} stream
* @param {*} safeMode deprecated use safePrompt instead
* @param {*} safePrompt
* @param {*} toolChoice
* @param {*} responseFormat
* @return {Promise<Object>}
*/
_makeChatCompletionRequest = function (
model,
messages,
tools,
temperature,
maxTokens,
topP,
randomSeed,
stream,
safeMode,
safePrompt,
toolChoice,
responseFormat
) {
// if modelDefault and model are undefined, throw an error
if (!model && !this.modelDefault) {
throw new MistralAPIError('You must provide a model name')
}
return {
model: model ?? this.modelDefault,
messages: messages,
tools: tools ?? undefined,
temperature: temperature ?? undefined,
max_tokens: maxTokens ?? undefined,
top_p: topP ?? undefined,
random_seed: randomSeed ?? undefined,
stream: stream ?? undefined,
safe_prompt: (safeMode || safePrompt) ?? undefined,
tool_choice: toolChoice ?? undefined,
response_format: responseFormat ?? undefined,
}
}
/**
* Returns a list of the available models
* @return {Promise<Object>}
*/
listModels = async function () {
const response = await this._request('get', 'v1/models')
return response
}
/**
* A chat endpoint without streaming
* @param {*} model the name of the model to chat with, e.g. mistral-tiny
* @param {*} messages an array of messages to chat with, e.g.
* [{role: 'user', content: 'What is the best French cheese?'}]
* @param {*} tools a list of tools to use.
* @param {*} temperature the temperature to use for sampling, e.g. 0.5
* @param {*} maxTokens the maximum number of tokens to generate, e.g. 100
* @param {*} topP the cumulative probability of tokens to generate, e.g. 0.9
* @param {*} randomSeed the random seed to use for sampling, e.g. 42
* @param {*} safeMode deprecated use safePrompt instead
* @param {*} safePrompt whether to use safe mode, e.g. true
* @param {*} toolChoice the tool to use, e.g. 'auto'
* @param {*} responseFormat the format of the response, e.g. 'json_format'
* @return {Promise<Object>}
*/
chat = async function ({
model,
messages,
tools,
temperature,
maxTokens,
topP,
randomSeed,
safeMode,
safePrompt,
toolChoice,
responseFormat,
}) {
const request = this._makeChatCompletionRequest(
model,
messages,
tools,
temperature,
maxTokens,
topP,
randomSeed,
false,
safeMode,
safePrompt,
toolChoice,
responseFormat
)
const response = await this._request('post', 'v1/chat/completions', request)
return response
}
/**
* A chat endpoint that streams responses.
* @param {*} model the name of the model to chat with, e.g. mistral-tiny
* @param {*} messages an array of messages to chat with, e.g.
* [{role: 'user', content: 'What is the best French cheese?'}]
* @param {*} tools a list of tools to use.
* @param {*} temperature the temperature to use for sampling, e.g. 0.5
* @param {*} maxTokens the maximum number of tokens to generate, e.g. 100
* @param {*} topP the cumulative probability of tokens to generate, e.g. 0.9
* @param {*} randomSeed the random seed to use for sampling, e.g. 42
* @param {*} safeMode deprecated use safePrompt instead
* @param {*} safePrompt whether to use safe mode, e.g. true
* @param {*} toolChoice the tool to use, e.g. 'auto'
* @param {*} responseFormat the format of the response, e.g. 'json_format'
* @return {Promise<Object>}
*/
chatStream = async function* ({
model,
messages,
tools,
temperature,
maxTokens,
topP,
randomSeed,
safeMode,
safePrompt,
toolChoice,
responseFormat,
}) {
const request = this._makeChatCompletionRequest(
model,
messages,
tools,
temperature,
maxTokens,
topP,
randomSeed,
true,
safeMode,
safePrompt,
toolChoice,
responseFormat
)
const response = await this._request('post', 'v1/chat/completions', request)
let buffer = ''
const decoder = new TextDecoder()
for await (const chunk of response) {
buffer += decoder.decode(chunk, { stream: true })
let firstNewline
while ((firstNewline = buffer.indexOf('\n')) !== -1) {
const chunkLine = buffer.substring(0, firstNewline)
buffer = buffer.substring(firstNewline + 1)
if (chunkLine.startsWith('data:')) {
const json = chunkLine.substring(6).trim()
if (json !== '[DONE]') {
yield JSON.parse(json)
}
}
}
}
}
/**
* An embeddings endpoint that returns embeddings for a single,
* or batch of inputs
* @param {*} model The embedding model to use, e.g. mistral-embed
* @param {*} input The input to embed,
* e.g. ['What is the best French cheese?']
* @return {Promise<Object>}
*/
embeddings = async function ({ model, input }) {
const request = {
model: model,
input: input,
}
const response = await this._request('post', 'v1/embeddings', request)
return response
}
}
export default MistralClient

View File

@ -9,11 +9,11 @@
"@typebot.io/forge": "workspace:*", "@typebot.io/forge": "workspace:*",
"@typebot.io/lib": "workspace:*", "@typebot.io/lib": "workspace:*",
"@typebot.io/tsconfig": "workspace:*", "@typebot.io/tsconfig": "workspace:*",
"@types/node": "^20.12.4",
"@types/react": "18.2.15", "@types/react": "18.2.15",
"typescript": "5.3.2" "typescript": "5.3.2"
}, },
"dependencies": { "dependencies": {
"@mistralai/mistralai": "0.1.3",
"ai": "3.0.12" "ai": "3.0.12"
} }
} }

View File

@ -1,6 +1,6 @@
{ {
"extends": "@typebot.io/tsconfig/base.json", "extends": "@typebot.io/tsconfig/base.json",
"include": ["**/*.ts", "**/*.tsx"], "include": ["**/*.ts", "**/*.tsx", "helpers/client.ts"],
"exclude": ["node_modules"], "exclude": ["node_modules"],
"compilerOptions": { "compilerOptions": {
"lib": ["ESNext", "DOM"], "lib": ["ESNext", "DOM"],

View File

@ -6,7 +6,7 @@ import { getChatCompletionStreamVarId } from '@typebot.io/openai-block/shared/ge
import { runChatCompletion } from '@typebot.io/openai-block/shared/runChatCompletion' import { runChatCompletion } from '@typebot.io/openai-block/shared/runChatCompletion'
import { runChatCompletionStream } from '@typebot.io/openai-block/shared/runChatCompletionStream' import { runChatCompletionStream } from '@typebot.io/openai-block/shared/runChatCompletionStream'
import { defaultOpenRouterOptions } from '../constants' import { defaultOpenRouterOptions } from '../constants'
import { got } from 'got' import ky from 'ky'
import { ModelsResponse } from '../types' import { ModelsResponse } from '../types'
export const createChatCompletion = createAction({ export const createChatCompletion = createAction({
@ -42,7 +42,7 @@ export const createChatCompletion = createAction({
id: 'fetchModels', id: 'fetchModels',
dependencies: [], dependencies: [],
fetch: async () => { fetch: async () => {
const response = await got const response = await ky
.get(defaultOpenRouterOptions.baseUrl + '/models') .get(defaultOpenRouterOptions.baseUrl + '/models')
.json<ModelsResponse>() .json<ModelsResponse>()

View File

@ -12,6 +12,6 @@
"typescript": "5.3.2", "typescript": "5.3.2",
"@typebot.io/lib": "workspace:*", "@typebot.io/lib": "workspace:*",
"@typebot.io/openai-block": "workspace:*", "@typebot.io/openai-block": "workspace:*",
"got": "12.6.0" "ky": "1.2.3"
} }
} }

View File

@ -1,4 +1,5 @@
import { createAction, option } from '@typebot.io/forge' import { createAction, option } from '@typebot.io/forge'
import { toBuffer as generateQrCodeBuffer } from 'qrcode'
import { uploadFileToBucket } from '@typebot.io/lib/s3/uploadFileToBucket' import { uploadFileToBucket } from '@typebot.io/lib/s3/uploadFileToBucket'
import { createId } from '@typebot.io/lib/createId' import { createId } from '@typebot.io/lib/createId'
@ -28,8 +29,6 @@ export const generateQrCode = createAction({
'QR code image URL is not specified. Please select a variable to save the generated QR code image.' 'QR code image URL is not specified. Please select a variable to save the generated QR code image.'
) )
const generateQrCodeBuffer = (await import('qrcode')).toBuffer
const url = await uploadFileToBucket({ const url = await uploadFileToBucket({
file: await generateQrCodeBuffer(options.data), file: await generateQrCodeBuffer(options.data),
key: `tmp/qrcodes/${createId() + createId()}.png`, key: `tmp/qrcodes/${createId() + createId()}.png`,

View File

@ -1,7 +1,7 @@
import { createAction, option } from '@typebot.io/forge' import { createAction, option } from '@typebot.io/forge'
import { isDefined } from '@typebot.io/lib' import { isDefined } from '@typebot.io/lib'
import { ZemanticAiResponse } from '../types' import { ZemanticAiResponse } from '../types'
import { got } from 'got' import ky from 'ky'
import { apiBaseUrl } from '../constants' import { apiBaseUrl } from '../constants'
import { auth } from '../auth' import { auth } from '../auth'
import { baseOptions } from '../baseOptions' import { baseOptions } from '../baseOptions'
@ -63,7 +63,7 @@ export const searchDocuments = createAction({
}, },
variables, variables,
}) => { }) => {
const res: ZemanticAiResponse = await got const res = await ky
.post(apiBaseUrl, { .post(apiBaseUrl, {
headers: { headers: {
Authorization: `Bearer ${apiKey}`, Authorization: `Bearer ${apiKey}`,
@ -79,7 +79,7 @@ export const searchDocuments = createAction({
}, },
}, },
}) })
.json() .json<ZemanticAiResponse>()
responseMapping?.forEach((mapping) => { responseMapping?.forEach((mapping) => {
if (!mapping.variableId || !mapping.item) return if (!mapping.variableId || !mapping.item) return

View File

@ -8,6 +8,7 @@ export const auth = {
label: 'API key', label: 'API key',
isRequired: true, isRequired: true,
placeholder: 'ze...', placeholder: 'ze...',
inputType: 'password',
helperText: helperText:
'You can generate an API key [here](https://zemantic.ai/dashboard/settings).', 'You can generate an API key [here](https://zemantic.ai/dashboard/settings).',
isDebounceDisabled: true, isDebounceDisabled: true,

View File

@ -1,6 +1,6 @@
import { createBlock } from '@typebot.io/forge' import { createBlock } from '@typebot.io/forge'
import { ZemanticAiLogo } from './logo' import { ZemanticAiLogo } from './logo'
import { got } from 'got' import ky from 'ky'
import { searchDocuments } from './actions/searchDocuments' import { searchDocuments } from './actions/searchDocuments'
import { auth } from './auth' import { auth } from './auth'
import { baseOptions } from './baseOptions' import { baseOptions } from './baseOptions'
@ -19,7 +19,7 @@ export const zemanticAiBlock = createBlock({
fetch: async ({ credentials: { apiKey } }) => { fetch: async ({ credentials: { apiKey } }) => {
const url = 'https://api.zemantic.ai/v1/projects' const url = 'https://api.zemantic.ai/v1/projects'
const response = await got const response = await ky
.get(url, { .get(url, {
headers: { headers: {
Authorization: `Bearer ${apiKey}`, Authorization: `Bearer ${apiKey}`,

View File

@ -11,6 +11,6 @@
"@types/react": "18.2.15", "@types/react": "18.2.15",
"typescript": "5.3.2", "typescript": "5.3.2",
"@typebot.io/lib": "workspace:*", "@typebot.io/lib": "workspace:*",
"got": "12.6.0" "ky": "1.2.3"
} }
} }

View File

@ -38,7 +38,7 @@
"@udecode/plate-paragraph": "30.5.3", "@udecode/plate-paragraph": "30.5.3",
"escape-html": "1.0.3", "escape-html": "1.0.3",
"google-auth-library": "8.9.0", "google-auth-library": "8.9.0",
"got": "12.6.0", "ky": "1.2.3",
"minio": "7.1.3", "minio": "7.1.3",
"posthog-node": "3.1.1", "posthog-node": "3.1.1",
"remark-parse": "11.0.0", "remark-parse": "11.0.0",

View File

@ -1,8 +1,7 @@
import { PrismaClient } from '@typebot.io/prisma' import { PrismaClient } from '@typebot.io/prisma'
import { promptAndSetEnvironment } from './utils' import { promptAndSetEnvironment } from './utils'
import got, { HTTPError } from 'got' import ky, { HTTPError } from 'ky'
import { confirm, text, isCancel } from '@clack/prompts' import { confirm, text, isCancel } from '@clack/prompts'
import { writeFileSync } from 'fs'
const insertUsersInBrevoList = async () => { const insertUsersInBrevoList = async () => {
await promptAndSetEnvironment('production') await promptAndSetEnvironment('production')
@ -44,7 +43,7 @@ const insertUsersInBrevoList = async () => {
} }
try { try {
await got.post('https://api.brevo.com/v3/contacts/import', { await ky.post('https://api.brevo.com/v3/contacts/import', {
headers: { headers: {
'api-key': process.env.BREVO_API_KEY, 'api-key': process.env.BREVO_API_KEY,
}, },
@ -58,7 +57,7 @@ const insertUsersInBrevoList = async () => {
}) })
} catch (err) { } catch (err) {
if (err instanceof HTTPError) { if (err instanceof HTTPError) {
console.log(err.response.body) console.log(await err.response.text())
return return
} }
console.log(err) console.log(err)

View File

@ -42,7 +42,7 @@
"@types/papaparse": "5.3.7", "@types/papaparse": "5.3.7",
"@types/prompts": "2.4.4", "@types/prompts": "2.4.4",
"deep-object-diff": "1.1.9", "deep-object-diff": "1.1.9",
"got": "12.6.0", "ky": "1.2.3",
"prompts": "2.4.2", "prompts": "2.4.2",
"stripe": "12.13.0", "stripe": "12.13.0",
"tsx": "3.12.7", "tsx": "3.12.7",

View File

@ -8,7 +8,7 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@typebot.io/schemas": "workspace:*", "@typebot.io/schemas": "workspace:*",
"got": "12.6.0", "ky": "1.2.3",
"posthog-node": "3.1.1", "posthog-node": "3.1.1",
"@typebot.io/env": "workspace:*" "@typebot.io/env": "workspace:*"
}, },

View File

@ -1,7 +1,7 @@
import { env } from '@typebot.io/env' import { env } from '@typebot.io/env'
import { TelemetryEvent } from '@typebot.io/schemas/features/telemetry' import { TelemetryEvent } from '@typebot.io/schemas/features/telemetry'
import { PostHog } from 'posthog-node' import { PostHog } from 'posthog-node'
import got from 'got' import ky from 'ky'
export const trackEvents = async (events: TelemetryEvent[]) => { export const trackEvents = async (events: TelemetryEvent[]) => {
if (!env.NEXT_PUBLIC_POSTHOG_KEY) return if (!env.NEXT_PUBLIC_POSTHOG_KEY) return
@ -17,7 +17,7 @@ export const trackEvents = async (events: TelemetryEvent[]) => {
}) })
if (env.USER_CREATED_WEBHOOK_URL) { if (env.USER_CREATED_WEBHOOK_URL) {
try { try {
await got.post(env.USER_CREATED_WEBHOOK_URL, { await ky.post(env.USER_CREATED_WEBHOOK_URL, {
json: { json: {
email: event.data.email, email: event.data.email,
name: event.data.name ? event.data.name.split(' ')[0] : undefined, name: event.data.name ? event.data.name.split(' ')[0] : undefined,

186
pnpm-lock.yaml generated
View File

@ -191,15 +191,15 @@ importers:
google-spreadsheet: google-spreadsheet:
specifier: 4.1.1 specifier: 4.1.1
version: 4.1.1(google-auth-library@8.9.0) version: 4.1.1(google-auth-library@8.9.0)
got:
specifier: 12.6.0
version: 12.6.0
immer: immer:
specifier: 10.0.2 specifier: 10.0.2
version: 10.0.2 version: 10.0.2
jsonwebtoken: jsonwebtoken:
specifier: 9.0.1 specifier: 9.0.1
version: 9.0.1 version: 9.0.1
ky:
specifier: 1.2.3
version: 1.2.3
libphonenumber-js: libphonenumber-js:
specifier: 1.10.37 specifier: 1.10.37
version: 1.10.37 version: 1.10.37
@ -593,6 +593,9 @@ importers:
got: got:
specifier: 12.6.0 specifier: 12.6.0
version: 12.6.0 version: 12.6.0
ky:
specifier: 1.2.3
version: 1.2.3
next: next:
specifier: 14.1.0 specifier: 14.1.0
version: 14.1.0(@babel/core@7.22.9)(react-dom@18.2.0)(react@18.2.0) version: 14.1.0(@babel/core@7.22.9)(react-dom@18.2.0)(react@18.2.0)
@ -781,12 +784,9 @@ importers:
google-spreadsheet: google-spreadsheet:
specifier: 4.1.1 specifier: 4.1.1
version: 4.1.1(google-auth-library@8.9.0) version: 4.1.1(google-auth-library@8.9.0)
got:
specifier: 12.6.0
version: 12.6.0
ky: ky:
specifier: ^1.1.3 specifier: 1.2.3
version: 1.2.0 version: 1.2.3
libphonenumber-js: libphonenumber-js:
specifier: 1.10.37 specifier: 1.10.37
version: 1.10.37 version: 1.10.37
@ -812,6 +812,9 @@ importers:
'@typebot.io/forge-repository': '@typebot.io/forge-repository':
specifier: workspace:* specifier: workspace:*
version: link:../forge/repository version: link:../forge/repository
'@types/node':
specifier: ^20.12.3
version: 20.12.3
'@types/nodemailer': '@types/nodemailer':
specifier: 6.4.14 specifier: 6.4.14
version: 6.4.14 version: 6.4.14
@ -1334,9 +1337,9 @@ importers:
'@types/react': '@types/react':
specifier: 18.2.15 specifier: 18.2.15
version: 18.2.15 version: 18.2.15
got: ky:
specifier: 12.6.0 specifier: 1.2.3
version: 12.6.0 version: 1.2.3
typescript: typescript:
specifier: 5.3.2 specifier: 5.3.2
version: 5.3.2 version: 5.3.2
@ -1355,9 +1358,9 @@ importers:
'@types/react': '@types/react':
specifier: 18.2.15 specifier: 18.2.15
version: 18.2.15 version: 18.2.15
got: ky:
specifier: 12.6.0 specifier: 1.2.3
version: 12.6.0 version: 1.2.3
typescript: typescript:
specifier: 5.3.2 specifier: 5.3.2
version: 5.3.2 version: 5.3.2
@ -1367,9 +1370,9 @@ importers:
'@typebot.io/lib': '@typebot.io/lib':
specifier: workspace:* specifier: workspace:*
version: link:../../../lib version: link:../../../lib
got: ky:
specifier: 12.6.0 specifier: 1.2.3
version: 12.6.0 version: 1.2.3
devDependencies: devDependencies:
'@typebot.io/forge': '@typebot.io/forge':
specifier: workspace:* specifier: workspace:*
@ -1386,9 +1389,6 @@ importers:
packages/forge/blocks/mistral: packages/forge/blocks/mistral:
dependencies: dependencies:
'@mistralai/mistralai':
specifier: 0.1.3
version: 0.1.3
ai: ai:
specifier: 3.0.12 specifier: 3.0.12
version: 3.0.12(react@18.2.0)(solid-js@1.7.8)(svelte@4.2.12)(vue@3.4.21)(zod@3.22.4) version: 3.0.12(react@18.2.0)(solid-js@1.7.8)(svelte@4.2.12)(vue@3.4.21)(zod@3.22.4)
@ -1402,6 +1402,9 @@ importers:
'@typebot.io/tsconfig': '@typebot.io/tsconfig':
specifier: workspace:* specifier: workspace:*
version: link:../../../tsconfig version: link:../../../tsconfig
'@types/node':
specifier: ^20.12.4
version: 20.12.4
'@types/react': '@types/react':
specifier: 18.2.15 specifier: 18.2.15
version: 18.2.15 version: 18.2.15
@ -1426,9 +1429,9 @@ importers:
'@types/react': '@types/react':
specifier: 18.2.15 specifier: 18.2.15
version: 18.2.15 version: 18.2.15
got: ky:
specifier: 12.6.0 specifier: 1.2.3
version: 12.6.0 version: 1.2.3
typescript: typescript:
specifier: 5.3.2 specifier: 5.3.2
version: 5.3.2 version: 5.3.2
@ -1524,9 +1527,9 @@ importers:
'@types/react': '@types/react':
specifier: 18.2.15 specifier: 18.2.15
version: 18.2.15 version: 18.2.15
got: ky:
specifier: 12.6.0 specifier: 1.2.3
version: 12.6.0 version: 1.2.3
typescript: typescript:
specifier: 5.3.2 specifier: 5.3.2
version: 5.3.2 version: 5.3.2
@ -1645,9 +1648,9 @@ importers:
google-auth-library: google-auth-library:
specifier: 8.9.0 specifier: 8.9.0
version: 8.9.0 version: 8.9.0
got: ky:
specifier: 12.6.0 specifier: 1.2.3
version: 12.6.0 version: 1.2.3
minio: minio:
specifier: 7.1.3 specifier: 7.1.3
version: 7.1.3 version: 7.1.3
@ -1884,9 +1887,9 @@ importers:
deep-object-diff: deep-object-diff:
specifier: 1.1.9 specifier: 1.1.9
version: 1.1.9 version: 1.1.9
got: ky:
specifier: 12.6.0 specifier: 1.2.3
version: 12.6.0 version: 1.2.3
prompts: prompts:
specifier: 2.4.2 specifier: 2.4.2
version: 2.4.2 version: 2.4.2
@ -1911,9 +1914,9 @@ importers:
'@typebot.io/schemas': '@typebot.io/schemas':
specifier: workspace:* specifier: workspace:*
version: link:../schemas version: link:../schemas
got: ky:
specifier: 12.6.0 specifier: 1.2.3
version: 12.6.0 version: 1.2.3
posthog-node: posthog-node:
specifier: 3.1.1 specifier: 3.1.1
version: 3.1.1 version: 3.1.1
@ -6838,7 +6841,7 @@ packages:
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies: dependencies:
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.11.26 '@types/node': 20.12.3
chalk: 4.1.2 chalk: 4.1.2
jest-message-util: 29.7.0 jest-message-util: 29.7.0
jest-util: 29.7.0 jest-util: 29.7.0
@ -6859,14 +6862,14 @@ packages:
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/transform': 29.7.0 '@jest/transform': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.11.26 '@types/node': 20.12.3
ansi-escapes: 4.3.2 ansi-escapes: 4.3.2
chalk: 4.1.2 chalk: 4.1.2
ci-info: 3.9.0 ci-info: 3.9.0
exit: 0.1.2 exit: 0.1.2
graceful-fs: 4.2.11 graceful-fs: 4.2.11
jest-changed-files: 29.7.0 jest-changed-files: 29.7.0
jest-config: 29.7.0(@types/node@20.11.26) jest-config: 29.7.0(@types/node@20.12.3)
jest-haste-map: 29.7.0 jest-haste-map: 29.7.0
jest-message-util: 29.7.0 jest-message-util: 29.7.0
jest-regex-util: 29.6.3 jest-regex-util: 29.6.3
@ -6894,7 +6897,7 @@ packages:
dependencies: dependencies:
'@jest/fake-timers': 29.7.0 '@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.11.26 '@types/node': 20.12.3
jest-mock: 29.7.0 jest-mock: 29.7.0
dev: true dev: true
@ -6921,7 +6924,7 @@ packages:
dependencies: dependencies:
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@sinonjs/fake-timers': 10.3.0 '@sinonjs/fake-timers': 10.3.0
'@types/node': 20.11.26 '@types/node': 20.12.3
jest-message-util: 29.7.0 jest-message-util: 29.7.0
jest-mock: 29.7.0 jest-mock: 29.7.0
jest-util: 29.7.0 jest-util: 29.7.0
@ -6954,7 +6957,7 @@ packages:
'@jest/transform': 29.7.0 '@jest/transform': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@jridgewell/trace-mapping': 0.3.25 '@jridgewell/trace-mapping': 0.3.25
'@types/node': 20.11.26 '@types/node': 20.12.3
chalk: 4.1.2 chalk: 4.1.2
collect-v8-coverage: 1.0.2 collect-v8-coverage: 1.0.2
exit: 0.1.2 exit: 0.1.2
@ -7042,7 +7045,7 @@ packages:
'@jest/schemas': 29.6.3 '@jest/schemas': 29.6.3
'@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-lib-coverage': 2.0.6
'@types/istanbul-reports': 3.0.4 '@types/istanbul-reports': 3.0.4
'@types/node': 20.11.26 '@types/node': 20.12.3
'@types/yargs': 17.0.32 '@types/yargs': 17.0.32
chalk: 4.1.2 chalk: 4.1.2
dev: true dev: true
@ -7461,14 +7464,6 @@ packages:
zod-to-json-schema: 3.22.4(zod@3.22.4) zod-to-json-schema: 3.22.4(zod@3.22.4)
dev: true dev: true
/@mistralai/mistralai@0.1.3:
resolution: {integrity: sha512-WUHxC2xdeqX9PTXJEqdiNY54vT2ir72WSJrZTTBKRnkfhX6zIfCYA24faRlWjUB5WTpn+wfdGsTMl3ArijlXFA==}
dependencies:
node-fetch: 2.7.0
transitivePeerDependencies:
- encoding
dev: false
/@next/env@14.0.5-canary.46: /@next/env@14.0.5-canary.46:
resolution: {integrity: sha512-dvNzrArTfe3VY1VIscpb3E2e7SZ1qwFe82WGzpOVbxilT3JcsnVGYF/uq8Jj1qKWPI5C/aePNXwA97JRNAXpRQ==} resolution: {integrity: sha512-dvNzrArTfe3VY1VIscpb3E2e7SZ1qwFe82WGzpOVbxilT3JcsnVGYF/uq8Jj1qKWPI5C/aePNXwA97JRNAXpRQ==}
dev: false dev: false
@ -7827,7 +7822,7 @@ packages:
engines: {node: '>=16'} engines: {node: '>=16'}
hasBin: true hasBin: true
dependencies: dependencies:
'@types/node': 20.11.26 '@types/node': 20.12.3
playwright-core: 1.36.0 playwright-core: 1.36.0
optionalDependencies: optionalDependencies:
fsevents: 2.3.2 fsevents: 2.3.2
@ -9453,7 +9448,7 @@ packages:
resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
dependencies: dependencies:
'@types/connect': 3.4.38 '@types/connect': 3.4.38
'@types/node': 20.11.26 '@types/node': 20.12.3
dev: false dev: false
/@types/canvas-confetti@1.6.0: /@types/canvas-confetti@1.6.0:
@ -9463,13 +9458,13 @@ packages:
/@types/cli-progress@3.11.5: /@types/cli-progress@3.11.5:
resolution: {integrity: sha512-D4PbNRbviKyppS5ivBGyFO29POlySLmA2HyUFE4p5QGazAMM3CwkKWcvTl8gvElSuxRh6FPKL8XmidX873ou4g==} resolution: {integrity: sha512-D4PbNRbviKyppS5ivBGyFO29POlySLmA2HyUFE4p5QGazAMM3CwkKWcvTl8gvElSuxRh6FPKL8XmidX873ou4g==}
dependencies: dependencies:
'@types/node': 20.11.26 '@types/node': 20.12.3
dev: true dev: true
/@types/connect@3.4.38: /@types/connect@3.4.38:
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
dependencies: dependencies:
'@types/node': 20.11.26 '@types/node': 20.12.3
dev: false dev: false
/@types/content-type@1.1.8: /@types/content-type@1.1.8:
@ -9482,7 +9477,7 @@ packages:
/@types/cors@2.8.13: /@types/cors@2.8.13:
resolution: {integrity: sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==} resolution: {integrity: sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==}
dependencies: dependencies:
'@types/node': 20.11.26 '@types/node': 20.12.3
/@types/debug@4.1.12: /@types/debug@4.1.12:
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
@ -9528,7 +9523,7 @@ packages:
/@types/express-serve-static-core@4.17.43: /@types/express-serve-static-core@4.17.43:
resolution: {integrity: sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==} resolution: {integrity: sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==}
dependencies: dependencies:
'@types/node': 20.11.26 '@types/node': 20.12.3
'@types/qs': 6.9.7 '@types/qs': 6.9.7
'@types/range-parser': 1.2.7 '@types/range-parser': 1.2.7
'@types/send': 0.17.4 '@types/send': 0.17.4
@ -9546,7 +9541,7 @@ packages:
/@types/graceful-fs@4.1.9: /@types/graceful-fs@4.1.9:
resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==}
dependencies: dependencies:
'@types/node': 20.11.26 '@types/node': 20.12.3
dev: true dev: true
/@types/hast@2.3.10: /@types/hast@2.3.10:
@ -9602,7 +9597,7 @@ packages:
/@types/jsdom@20.0.1: /@types/jsdom@20.0.1:
resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==} resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==}
dependencies: dependencies:
'@types/node': 20.11.26 '@types/node': 20.12.3
'@types/tough-cookie': 4.0.5 '@types/tough-cookie': 4.0.5
parse5: 7.1.2 parse5: 7.1.2
dev: true dev: true
@ -9617,7 +9612,7 @@ packages:
/@types/jsonwebtoken@9.0.2: /@types/jsonwebtoken@9.0.2:
resolution: {integrity: sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==} resolution: {integrity: sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==}
dependencies: dependencies:
'@types/node': 20.11.26 '@types/node': 20.12.3
dev: true dev: true
/@types/katex@0.16.7: /@types/katex@0.16.7:
@ -9655,7 +9650,7 @@ packages:
/@types/micro@7.3.7: /@types/micro@7.3.7:
resolution: {integrity: sha512-MFsX7eCj0Tg3TtphOQvANNvNtFpya+s/rYOCdV6o+DFjOQPFi2EVRbBALjbbgZTXUaJP1Q281MJiJOD40d0UxQ==} resolution: {integrity: sha512-MFsX7eCj0Tg3TtphOQvANNvNtFpya+s/rYOCdV6o+DFjOQPFi2EVRbBALjbbgZTXUaJP1Q281MJiJOD40d0UxQ==}
dependencies: dependencies:
'@types/node': 20.11.26 '@types/node': 20.12.3
dev: true dev: true
/@types/mime@1.3.5: /@types/mime@1.3.5:
@ -9676,7 +9671,7 @@ packages:
/@types/node-fetch@2.6.11: /@types/node-fetch@2.6.11:
resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==}
dependencies: dependencies:
'@types/node': 20.11.26 '@types/node': 20.12.3
form-data: 4.0.0 form-data: 4.0.0
dev: false dev: false
@ -9687,6 +9682,18 @@ packages:
resolution: {integrity: sha512-YwOMmyhNnAWijOBQweOJnQPl068Oqd4K3OFbTc6AHJwzweUwwWG3GIFY74OKks2PJUDkQPeddOQES9mLn1CTEQ==} resolution: {integrity: sha512-YwOMmyhNnAWijOBQweOJnQPl068Oqd4K3OFbTc6AHJwzweUwwWG3GIFY74OKks2PJUDkQPeddOQES9mLn1CTEQ==}
dependencies: dependencies:
undici-types: 5.26.5 undici-types: 5.26.5
dev: true
/@types/node@20.12.3:
resolution: {integrity: sha512-sD+ia2ubTeWrOu+YMF+MTAB7E+O7qsMqAbMfW7DG3K1URwhZ5hN1pLlRVGbf4wDFzSfikL05M17EyorS86jShw==}
dependencies:
undici-types: 5.26.5
/@types/node@20.12.4:
resolution: {integrity: sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw==}
dependencies:
undici-types: 5.26.5
dev: true
/@types/node@20.4.2: /@types/node@20.4.2:
resolution: {integrity: sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==} resolution: {integrity: sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==}
@ -9698,13 +9705,13 @@ packages:
/@types/nodemailer@6.4.14: /@types/nodemailer@6.4.14:
resolution: {integrity: sha512-fUWthHO9k9DSdPCSPRqcu6TWhYyxTBg382vlNIttSe9M7XfsT06y0f24KHXtbnijPGGRIcVvdKHTNikOI6qiHA==} resolution: {integrity: sha512-fUWthHO9k9DSdPCSPRqcu6TWhYyxTBg382vlNIttSe9M7XfsT06y0f24KHXtbnijPGGRIcVvdKHTNikOI6qiHA==}
dependencies: dependencies:
'@types/node': 20.11.26 '@types/node': 20.12.3
dev: true dev: true
/@types/nodemailer@6.4.8: /@types/nodemailer@6.4.8:
resolution: {integrity: sha512-oVsJSCkqViCn8/pEu2hfjwVO+Gb3e+eTWjg3PcjeFKRItfKpKwHphQqbYmPQrlMk+op7pNNWPbsJIEthpFN/OQ==} resolution: {integrity: sha512-oVsJSCkqViCn8/pEu2hfjwVO+Gb3e+eTWjg3PcjeFKRItfKpKwHphQqbYmPQrlMk+op7pNNWPbsJIEthpFN/OQ==}
dependencies: dependencies:
'@types/node': 20.11.26 '@types/node': 20.12.3
dev: true dev: true
/@types/normalize-package-data@2.4.4: /@types/normalize-package-data@2.4.4:
@ -9717,7 +9724,7 @@ packages:
/@types/papaparse@5.3.7: /@types/papaparse@5.3.7:
resolution: {integrity: sha512-f2HKmlnPdCvS0WI33WtCs5GD7X1cxzzS/aduaxSu3I7TbhWlENjSPs6z5TaB9K0J+BH1jbmqTaM+ja5puis4wg==} resolution: {integrity: sha512-f2HKmlnPdCvS0WI33WtCs5GD7X1cxzzS/aduaxSu3I7TbhWlENjSPs6z5TaB9K0J+BH1jbmqTaM+ja5puis4wg==}
dependencies: dependencies:
'@types/node': 20.11.26 '@types/node': 20.12.3
dev: true dev: true
/@types/parse-json@4.0.2: /@types/parse-json@4.0.2:
@ -9735,7 +9742,7 @@ packages:
/@types/prompts@2.4.4: /@types/prompts@2.4.4:
resolution: {integrity: sha512-p5N9uoTH76lLvSAaYSZtBCdEXzpOOufsRjnhjVSrZGXikVGHX9+cc9ERtHRV4hvBKHyZb1bg4K+56Bd2TqUn4A==} resolution: {integrity: sha512-p5N9uoTH76lLvSAaYSZtBCdEXzpOOufsRjnhjVSrZGXikVGHX9+cc9ERtHRV4hvBKHyZb1bg4K+56Bd2TqUn4A==}
dependencies: dependencies:
'@types/node': 20.11.26 '@types/node': 20.12.3
kleur: 3.0.3 kleur: 3.0.3
dev: true dev: true
@ -9745,7 +9752,7 @@ packages:
/@types/qrcode@1.5.5: /@types/qrcode@1.5.5:
resolution: {integrity: sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==} resolution: {integrity: sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==}
dependencies: dependencies:
'@types/node': 20.11.26 '@types/node': 20.12.3
dev: true dev: true
/@types/qs@6.9.7: /@types/qs@6.9.7:
@ -9801,7 +9808,7 @@ packages:
resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==}
dependencies: dependencies:
'@types/mime': 1.3.5 '@types/mime': 1.3.5
'@types/node': 20.11.26 '@types/node': 20.12.3
dev: false dev: false
/@types/serve-static@1.15.5: /@types/serve-static@1.15.5:
@ -9809,7 +9816,7 @@ packages:
dependencies: dependencies:
'@types/http-errors': 2.0.4 '@types/http-errors': 2.0.4
'@types/mime': 3.0.4 '@types/mime': 3.0.4
'@types/node': 20.11.26 '@types/node': 20.12.3
dev: false dev: false
/@types/stack-utils@2.0.3: /@types/stack-utils@2.0.3:
@ -9841,7 +9848,7 @@ packages:
/@types/webpack@5.28.5(@swc/core@1.3.101)(esbuild@0.19.11): /@types/webpack@5.28.5(@swc/core@1.3.101)(esbuild@0.19.11):
resolution: {integrity: sha512-wR87cgvxj3p6D0Crt1r5avwqffqPXUkNlnQ1mjU93G7gCuFjufZR4I6j8cz5g1F1tTYpfOOFvly+cmIQwL9wvw==} resolution: {integrity: sha512-wR87cgvxj3p6D0Crt1r5avwqffqPXUkNlnQ1mjU93G7gCuFjufZR4I6j8cz5g1F1tTYpfOOFvly+cmIQwL9wvw==}
dependencies: dependencies:
'@types/node': 20.11.26 '@types/node': 20.12.3
tapable: 2.2.1 tapable: 2.2.1
webpack: 5.90.3(@swc/core@1.3.101)(esbuild@0.19.11) webpack: 5.90.3(@swc/core@1.3.101)(esbuild@0.19.11)
transitivePeerDependencies: transitivePeerDependencies:
@ -12457,7 +12464,7 @@ packages:
chalk: 4.1.2 chalk: 4.1.2
exit: 0.1.2 exit: 0.1.2
graceful-fs: 4.2.11 graceful-fs: 4.2.11
jest-config: 29.7.0(@types/node@20.11.26) jest-config: 29.7.0(@types/node@20.12.3)
jest-util: 29.7.0 jest-util: 29.7.0
prompts: 2.4.2 prompts: 2.4.2
transitivePeerDependencies: transitivePeerDependencies:
@ -13170,7 +13177,7 @@ packages:
dependencies: dependencies:
'@types/cookie': 0.4.1 '@types/cookie': 0.4.1
'@types/cors': 2.8.13 '@types/cors': 2.8.13
'@types/node': 20.11.26 '@types/node': 20.12.3
accepts: 1.3.8 accepts: 1.3.8
base64id: 2.0.0 base64id: 2.0.0
cookie: 0.4.2 cookie: 0.4.2
@ -15928,7 +15935,7 @@ packages:
'@jest/expect': 29.7.0 '@jest/expect': 29.7.0
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.11.26 '@types/node': 20.12.3
chalk: 4.1.2 chalk: 4.1.2
co: 4.6.0 co: 4.6.0
dedent: 1.5.1 dedent: 1.5.1
@ -15966,7 +15973,7 @@ packages:
create-jest: 29.7.0 create-jest: 29.7.0
exit: 0.1.2 exit: 0.1.2
import-local: 3.1.0 import-local: 3.1.0
jest-config: 29.7.0(@types/node@20.11.26) jest-config: 29.7.0(@types/node@20.12.3)
jest-util: 29.7.0 jest-util: 29.7.0
jest-validate: 29.7.0 jest-validate: 29.7.0
yargs: 17.7.2 yargs: 17.7.2
@ -15977,7 +15984,7 @@ packages:
- ts-node - ts-node
dev: true dev: true
/jest-config@29.7.0(@types/node@20.11.26): /jest-config@29.7.0(@types/node@20.12.3):
resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
peerDependencies: peerDependencies:
@ -15992,7 +15999,7 @@ packages:
'@babel/core': 7.22.9 '@babel/core': 7.22.9
'@jest/test-sequencer': 29.7.0 '@jest/test-sequencer': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.11.26 '@types/node': 20.12.3
babel-jest: 29.7.0(@babel/core@7.22.9) babel-jest: 29.7.0(@babel/core@7.22.9)
chalk: 4.1.2 chalk: 4.1.2
ci-info: 3.9.0 ci-info: 3.9.0
@ -16058,7 +16065,7 @@ packages:
'@jest/fake-timers': 29.7.0 '@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/jsdom': 20.0.1 '@types/jsdom': 20.0.1
'@types/node': 20.11.26 '@types/node': 20.12.3
jest-mock: 29.7.0 jest-mock: 29.7.0
jest-util: 29.7.0 jest-util: 29.7.0
jsdom: 20.0.3 jsdom: 20.0.3
@ -16075,7 +16082,7 @@ packages:
'@jest/environment': 29.7.0 '@jest/environment': 29.7.0
'@jest/fake-timers': 29.7.0 '@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.11.26 '@types/node': 20.12.3
jest-mock: 29.7.0 jest-mock: 29.7.0
jest-util: 29.7.0 jest-util: 29.7.0
dev: true dev: true
@ -16091,7 +16098,7 @@ packages:
dependencies: dependencies:
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/graceful-fs': 4.1.9 '@types/graceful-fs': 4.1.9
'@types/node': 20.11.26 '@types/node': 20.12.3
anymatch: 3.1.3 anymatch: 3.1.3
fb-watchman: 2.0.2 fb-watchman: 2.0.2
graceful-fs: 4.2.11 graceful-fs: 4.2.11
@ -16142,7 +16149,7 @@ packages:
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies: dependencies:
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.11.26 '@types/node': 20.12.3
jest-util: 29.7.0 jest-util: 29.7.0
dev: true dev: true
@ -16197,7 +16204,7 @@ packages:
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/transform': 29.7.0 '@jest/transform': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.11.26 '@types/node': 20.12.3
chalk: 4.1.2 chalk: 4.1.2
emittery: 0.13.1 emittery: 0.13.1
graceful-fs: 4.2.11 graceful-fs: 4.2.11
@ -16228,7 +16235,7 @@ packages:
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/transform': 29.7.0 '@jest/transform': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.11.26 '@types/node': 20.12.3
chalk: 4.1.2 chalk: 4.1.2
cjs-module-lexer: 1.2.3 cjs-module-lexer: 1.2.3
collect-v8-coverage: 1.0.2 collect-v8-coverage: 1.0.2
@ -16280,7 +16287,7 @@ packages:
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies: dependencies:
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.11.26 '@types/node': 20.12.3
chalk: 4.1.2 chalk: 4.1.2
ci-info: 3.9.0 ci-info: 3.9.0
graceful-fs: 4.2.11 graceful-fs: 4.2.11
@ -16305,7 +16312,7 @@ packages:
dependencies: dependencies:
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.11.26 '@types/node': 20.12.3
ansi-escapes: 4.3.2 ansi-escapes: 4.3.2
chalk: 4.1.2 chalk: 4.1.2
emittery: 0.13.1 emittery: 0.13.1
@ -16317,7 +16324,7 @@ packages:
resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
engines: {node: '>= 10.13.0'} engines: {node: '>= 10.13.0'}
dependencies: dependencies:
'@types/node': 20.11.26 '@types/node': 20.12.3
merge-stream: 2.0.0 merge-stream: 2.0.0
supports-color: 8.1.1 supports-color: 8.1.1
dev: false dev: false
@ -16326,7 +16333,7 @@ packages:
resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies: dependencies:
'@types/node': 20.11.26 '@types/node': 20.12.3
jest-util: 29.7.0 jest-util: 29.7.0
merge-stream: 2.0.0 merge-stream: 2.0.0
supports-color: 8.1.1 supports-color: 8.1.1
@ -16658,10 +16665,9 @@ packages:
engines: {node: '>=18'} engines: {node: '>=18'}
dev: false dev: false
/ky@1.2.0: /ky@1.2.3:
resolution: {integrity: sha512-dnPW+T78MuJ9tLAiF/apJV7bP7RRRCARXQxsCmsWiKLXqGtMBOgDVOFRYzCAfNe/OrRyFyor5ESgvvC+QWEqOA==} resolution: {integrity: sha512-2IM3VssHfG2zYz2FsHRUqIp8chhLc9uxDMcK2THxgFfv8pQhnMfN8L0ul+iW4RdBl5AglF8ooPIflRm3yNH0IA==}
engines: {node: '>=18'} engines: {node: '>=18'}
dev: false
/language-subtag-registry@0.3.22: /language-subtag-registry@0.3.22:
resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==} resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==}
@ -18540,7 +18546,7 @@ packages:
engines: {node: '>=14'} engines: {node: '>=14'}
dependencies: dependencies:
'@types/express': 4.17.21 '@types/express': 4.17.21
'@types/node': 20.11.26 '@types/node': 20.12.3
accepts: 1.3.8 accepts: 1.3.8
content-disposition: 0.5.4 content-disposition: 0.5.4
depd: 1.1.2 depd: 1.1.2
@ -21400,7 +21406,7 @@ packages:
resolution: {integrity: sha512-mn7CxL71FCRWkQp33jcJ7+xfRF7HGzPYZlq2c87U+6kxL1qd7f/N3S1g1E5uaSWe83V5v3jN/IiWqg9y8+kWRw==} resolution: {integrity: sha512-mn7CxL71FCRWkQp33jcJ7+xfRF7HGzPYZlq2c87U+6kxL1qd7f/N3S1g1E5uaSWe83V5v3jN/IiWqg9y8+kWRw==}
engines: {node: '>=12.*'} engines: {node: '>=12.*'}
dependencies: dependencies:
'@types/node': 20.11.26 '@types/node': 20.12.3
qs: 6.11.2 qs: 6.11.2
/strnum@1.0.5: /strnum@1.0.5: