2
0

(webhook) Add custom timeout option

Closes #1128
This commit is contained in:
Baptiste Arnaud
2024-01-04 08:08:00 +01:00
parent d247e02cad
commit 34917b00ef
9 changed files with 241 additions and 15 deletions

View File

@ -32,9 +32,12 @@ import { VariableForTestInputs } from './VariableForTestInputs'
import { SwitchWithRelatedSettings } from '@/components/SwitchWithRelatedSettings'
import {
HttpMethod,
defaultTimeout,
defaultWebhookAttributes,
defaultWebhookBlockOptions,
maxTimeout,
} from '@typebot.io/schemas/features/blocks/integrations/webhook/constants'
import { NumberInput } from '@/components/inputs'
type Props = {
blockId: string
@ -81,6 +84,9 @@ export const WebhookAdvancedConfigForm = ({
const updateIsCustomBody = (isCustomBody: boolean) =>
onOptionsChange({ ...options, isCustomBody })
const updateTimeout = (timeout: number | undefined) =>
onOptionsChange({ ...options, timeout })
const executeTestRequest = async () => {
if (!typebot) return
setIsTestResponseLoading(true)
@ -196,6 +202,22 @@ export const WebhookAdvancedConfigForm = ({
)}
</AccordionPanel>
</AccordionItem>
<AccordionItem>
<AccordionButton justifyContent="space-between">
Advanced parameters
<AccordionIcon />
</AccordionButton>
<AccordionPanel pt="4">
<NumberInput
label="Timeout (s)"
defaultValue={options?.timeout ?? defaultTimeout}
min={1}
max={maxTimeout}
onValueChange={updateTimeout}
withVariableButton={false}
/>
</AccordionPanel>
</AccordionItem>
<AccordionItem>
<AccordionButton justifyContent="space-between">
Variable values for test

View File

@ -0,0 +1,9 @@
import { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
await new Promise((resolve) => setTimeout(resolve, 11000))
res.status(200).json({ name: 'John Doe' })
}

View File

@ -108,6 +108,10 @@ Possibilities are endless when it comes to API calls, you can litteraly call any
Feel free to ask the [community](https://typebot.io/discord) for help if you struggle setting up a Webhook block.
## Timeout
By default, the Webhook block will wait 10 seconds for the 3rd party service to respond. If it doesn't respond in time, the block will fail. You can customize this timeout value in the "Advanced params" section of your Webhook block settings.
## Troubleshooting
The Webhook block request fail or didn't seem to trigger? Make sure to check the [logs](/results/overview#logs). If you still can't figure out what went wrong, shoot me a message using the chat button directly in the tool 👍

View File

@ -1411,6 +1411,11 @@
"required": [
"id"
]
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
},
@ -1863,6 +1868,11 @@
"required": [
"id"
]
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
},
@ -2077,6 +2087,11 @@
"required": [
"id"
]
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
},
@ -2226,6 +2241,11 @@
"required": [
"id"
]
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
},
@ -7595,6 +7615,11 @@
"required": [
"id"
]
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
},
@ -8047,6 +8072,11 @@
"required": [
"id"
]
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
},
@ -8261,6 +8291,11 @@
"required": [
"id"
]
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
},
@ -8410,6 +8445,11 @@
"required": [
"id"
]
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
},
@ -12096,6 +12136,11 @@
"required": [
"id"
]
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
},
@ -12548,6 +12593,11 @@
"required": [
"id"
]
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
},
@ -12762,6 +12812,11 @@
"required": [
"id"
]
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
},
@ -12911,6 +12966,11 @@
"required": [
"id"
]
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
},
@ -25701,6 +25761,11 @@
"type": "string"
}
}
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
}
@ -26144,6 +26209,11 @@
"type": "string"
}
}
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
}
@ -26349,6 +26419,11 @@
"type": "string"
}
}
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
}
@ -26489,6 +26564,11 @@
"type": "string"
}
}
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
}
@ -28977,6 +29057,11 @@
"required": [
"id"
]
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
},
@ -29429,6 +29514,11 @@
"required": [
"id"
]
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
},
@ -29643,6 +29733,11 @@
"required": [
"id"
]
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
},
@ -29792,6 +29887,11 @@
"required": [
"id"
]
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
},
@ -31766,6 +31866,11 @@
"required": [
"id"
]
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
},
@ -32218,6 +32323,11 @@
"required": [
"id"
]
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
},
@ -32432,6 +32542,11 @@
"required": [
"id"
]
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
},
@ -32581,6 +32696,11 @@
"required": [
"id"
]
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
},

View File

@ -5529,6 +5529,11 @@
"required": [
"id"
]
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
},
@ -5981,6 +5986,11 @@
"required": [
"id"
]
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
},
@ -6195,6 +6205,11 @@
"required": [
"id"
]
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
},
@ -6344,6 +6359,11 @@
"required": [
"id"
]
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
},
@ -9211,6 +9231,11 @@
"type": "string"
}
}
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
}
@ -9654,6 +9679,11 @@
"type": "string"
}
}
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
}
@ -9859,6 +9889,11 @@
"type": "string"
}
}
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
}
@ -9999,6 +10034,11 @@
"type": "string"
}
}
},
"timeout": {
"type": "number",
"minimum": 1,
"maximum": 120
}
}
}

View File

@ -23,14 +23,14 @@ 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/lib/getBlockById'
import {
convertKeyValueTableToObject,
longReqTimeoutWhitelist,
longRequestTimeout,
responseDefaultTimeout,
} from '@typebot.io/bot-engine/blocks/integrations/webhook/executeWebhookBlock'
const cors = initMiddleware(Cors())
@ -184,7 +184,7 @@ export const executeWebhook =
: undefined,
body: body && !isJson ? body : undefined,
timeout: {
response: isLongRequest ? longRequestTimeout : responseDefaultTimeout,
response: isLongRequest ? maxTimeout : defaultTimeout,
},
}
try {

View File

@ -22,7 +22,9 @@ import { parseVariables } from '@typebot.io/variables/parseVariables'
import prisma from '@typebot.io/lib/prisma'
import {
HttpMethod,
defaultTimeout,
defaultWebhookAttributes,
maxTimeout,
} from '@typebot.io/schemas/features/blocks/integrations/webhook/constants'
type ParsedWebhook = ExecutableWebhook & {
@ -30,9 +32,6 @@ type ParsedWebhook = ExecutableWebhook & {
isJson: boolean
}
export const responseDefaultTimeout = 10000
export const longRequestTimeout = 120000
export const longReqTimeoutWhitelist = [
'https://api.openai.com',
'https://retune.so',
@ -44,7 +43,7 @@ export const longReqTimeoutWhitelist = [
export const webhookSuccessDescription = `Webhook successfuly executed.`
export const webhookErrorDescription = `Webhook returned an error.`
type Params = { disableRequestTimeout?: boolean }
type Params = { disableRequestTimeout?: boolean; timeout?: number }
export const executeWebhookBlock = async (
state: SessionState,
@ -86,7 +85,10 @@ export const executeWebhookBlock = async (
response: webhookResponse,
logs: executeWebhookLogs,
startTimeShouldBeUpdated,
} = await executeWebhook(parsedWebhook, params)
} = await executeWebhook(parsedWebhook, {
...params,
timeout: block.options?.timeout,
})
return {
...resumeWebhookExecution({
@ -196,7 +198,12 @@ export const executeWebhook = async (
contentType?.includes('x-www-form-urlencoded') && body ? body : undefined,
body: body && !isJson ? (body as string) : undefined,
timeout: {
response: isLongRequest ? longRequestTimeout : responseDefaultTimeout,
response:
params.timeout && params.timeout !== defaultTimeout
? Math.min(params.timeout, maxTimeout) * 1000
: isLongRequest
? maxTimeout * 1000
: defaultTimeout * 1000,
},
} satisfies OptionsInit
@ -207,8 +214,8 @@ export const executeWebhook = async (
description: webhookSuccessDescription,
details: {
statusCode: response.statusCode,
request,
response: safeJsonParse(response.body).data,
request,
},
})
return {
@ -217,7 +224,7 @@ export const executeWebhook = async (
data: safeJsonParse(response.body).data,
},
logs,
startTimeShouldBeUpdated: isLongRequest,
startTimeShouldBeUpdated: true,
}
} catch (error) {
if (error instanceof HTTPError) {
@ -234,7 +241,27 @@ export const executeWebhook = async (
response,
},
})
return { response, logs, startTimeShouldBeUpdated: isLongRequest }
return { response, logs, startTimeShouldBeUpdated: true }
}
if (
typeof error === 'object' &&
error &&
'code' in error &&
error.code === 'ETIMEDOUT'
) {
const response = {
statusCode: 408,
data: { message: `Request timed out.` },
}
logs.push({
status: 'error',
description: `Webhook request timed out. (${request.timeout.response}ms)`,
details: {
response,
request,
},
})
return { response, logs, startTimeShouldBeUpdated: true }
}
const response = {
statusCode: 500,
@ -245,11 +272,11 @@ export const executeWebhook = async (
status: 'error',
description: `Webhook failed to execute.`,
details: {
request,
response,
request,
},
})
return { response, logs, startTimeShouldBeUpdated: isLongRequest }
return { response, logs, startTimeShouldBeUpdated: true }
}
}

View File

@ -21,3 +21,6 @@ export const defaultWebhookBlockOptions = {
isCustomBody: false,
isExecutedOnClient: false,
} as const satisfies WebhookBlockV6['options']
export const defaultTimeout = 10
export const maxTimeout = 120

View File

@ -1,7 +1,7 @@
import { z } from '../../../../zod'
import { blockBaseSchema } from '../../shared'
import { IntegrationBlockType } from '../constants'
import { HttpMethod } from './constants'
import { HttpMethod, maxTimeout } from './constants'
const variableForTestSchema = z.object({
id: z.string(),
@ -46,6 +46,7 @@ export const webhookOptionsV5Schema = z.object({
isCustomBody: z.boolean().optional(),
isExecutedOnClient: z.boolean().optional(),
webhook: webhookSchemas.v5.optional(),
timeout: z.number().min(1).max(maxTimeout).optional(),
})
const webhookOptionsSchemas = {