⚡ (webhook) Add client execution option
This commit is contained in:
@ -30,6 +30,7 @@ import { getDeepKeys } from '../helpers/getDeepKeys'
|
|||||||
import { QueryParamsInputs, HeadersInputs } from './KeyValueInputs'
|
import { QueryParamsInputs, HeadersInputs } from './KeyValueInputs'
|
||||||
import { DataVariableInputs } from './ResponseMappingInputs'
|
import { DataVariableInputs } from './ResponseMappingInputs'
|
||||||
import { VariableForTestInputs } from './VariableForTestInputs'
|
import { VariableForTestInputs } from './VariableForTestInputs'
|
||||||
|
import { SwitchWithRelatedSettings } from '@/components/SwitchWithRelatedSettings'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
blockId: string
|
blockId: string
|
||||||
@ -52,32 +53,31 @@ export const WebhookAdvancedConfigForm = ({
|
|||||||
const [responseKeys, setResponseKeys] = useState<string[]>([])
|
const [responseKeys, setResponseKeys] = useState<string[]>([])
|
||||||
const { showToast } = useToast()
|
const { showToast } = useToast()
|
||||||
|
|
||||||
const handleMethodChange = (method: HttpMethod) =>
|
const updateMethod = (method: HttpMethod) =>
|
||||||
onWebhookChange({ ...webhook, method })
|
onWebhookChange({ ...webhook, method })
|
||||||
|
|
||||||
const handleQueryParamsChange = (queryParams: KeyValue[]) =>
|
const updateQueryParams = (queryParams: KeyValue[]) =>
|
||||||
onWebhookChange({ ...webhook, queryParams })
|
onWebhookChange({ ...webhook, queryParams })
|
||||||
|
|
||||||
const handleHeadersChange = (headers: KeyValue[]) =>
|
const updateHeaders = (headers: KeyValue[]) =>
|
||||||
onWebhookChange({ ...webhook, headers })
|
onWebhookChange({ ...webhook, headers })
|
||||||
|
|
||||||
const handleBodyChange = (body: string) =>
|
const updateBody = (body: string) => onWebhookChange({ ...webhook, body })
|
||||||
onWebhookChange({ ...webhook, body })
|
|
||||||
|
|
||||||
const handleVariablesChange = (variablesForTest: VariableForTest[]) =>
|
const updateVariablesForTest = (variablesForTest: VariableForTest[]) =>
|
||||||
onOptionsChange({ ...options, variablesForTest })
|
onOptionsChange({ ...options, variablesForTest })
|
||||||
|
|
||||||
const handleResponseMappingChange = (
|
const updateResponseVariableMapping = (
|
||||||
responseVariableMapping: ResponseVariableMapping[]
|
responseVariableMapping: ResponseVariableMapping[]
|
||||||
) => onOptionsChange({ ...options, responseVariableMapping })
|
) => onOptionsChange({ ...options, responseVariableMapping })
|
||||||
|
|
||||||
const handleAdvancedConfigChange = (isAdvancedConfig: boolean) =>
|
const updateAdvancedConfig = (isAdvancedConfig: boolean) =>
|
||||||
onOptionsChange({ ...options, isAdvancedConfig })
|
onOptionsChange({ ...options, isAdvancedConfig })
|
||||||
|
|
||||||
const handleBodyFormStateChange = (isCustomBody: boolean) =>
|
const updateIsCustomBody = (isCustomBody: boolean) =>
|
||||||
onOptionsChange({ ...options, isCustomBody })
|
onOptionsChange({ ...options, isCustomBody })
|
||||||
|
|
||||||
const handleTestRequestClick = async () => {
|
const executeTestRequest = async () => {
|
||||||
if (!typebot || !webhook) return
|
if (!typebot || !webhook) return
|
||||||
setIsTestResponseLoading(true)
|
setIsTestResponseLoading(true)
|
||||||
await Promise.all([updateWebhook(webhook.id, webhook), save()])
|
await Promise.all([updateWebhook(webhook.id, webhook), save()])
|
||||||
@ -96,6 +96,9 @@ export const WebhookAdvancedConfigForm = ({
|
|||||||
setIsTestResponseLoading(false)
|
setIsTestResponseLoading(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateIsExecutedOnClient = (isExecutedOnClient: boolean) =>
|
||||||
|
onOptionsChange({ ...options, isExecutedOnClient })
|
||||||
|
|
||||||
const ResponseMappingInputs = useMemo(
|
const ResponseMappingInputs = useMemo(
|
||||||
() =>
|
() =>
|
||||||
function Component(props: TableListItemProps<ResponseVariableMapping>) {
|
function Component(props: TableListItemProps<ResponseVariableMapping>) {
|
||||||
@ -106,93 +109,96 @@ export const WebhookAdvancedConfigForm = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SwitchWithLabel
|
<SwitchWithRelatedSettings
|
||||||
label="Advanced configuration"
|
label="Advanced configuration"
|
||||||
initialValue={options.isAdvancedConfig ?? true}
|
initialValue={options.isAdvancedConfig ?? true}
|
||||||
onCheckChange={handleAdvancedConfigChange}
|
onCheckChange={updateAdvancedConfig}
|
||||||
/>
|
>
|
||||||
{(options.isAdvancedConfig ?? true) && (
|
<SwitchWithLabel
|
||||||
<>
|
label="Execute on client"
|
||||||
<HStack justify="space-between">
|
moreInfoContent="If enabled, the webhook will be executed on the client. It means it will be executed in the browser of your visitor. Make sure to enable CORS and do not expose sensitive data."
|
||||||
<Text>Method:</Text>
|
initialValue={options.isExecutedOnClient ?? false}
|
||||||
<DropdownList
|
onCheckChange={updateIsExecutedOnClient}
|
||||||
currentItem={webhook.method as HttpMethod}
|
/>
|
||||||
onItemSelect={handleMethodChange}
|
<HStack justify="space-between">
|
||||||
items={Object.values(HttpMethod)}
|
<Text>Method:</Text>
|
||||||
/>
|
<DropdownList
|
||||||
</HStack>
|
currentItem={webhook.method as HttpMethod}
|
||||||
<Accordion allowMultiple>
|
onItemSelect={updateMethod}
|
||||||
<AccordionItem>
|
items={Object.values(HttpMethod)}
|
||||||
<AccordionButton justifyContent="space-between">
|
/>
|
||||||
Query params
|
</HStack>
|
||||||
<AccordionIcon />
|
<Accordion allowMultiple>
|
||||||
</AccordionButton>
|
<AccordionItem>
|
||||||
<AccordionPanel pt="4">
|
<AccordionButton justifyContent="space-between">
|
||||||
<TableList<KeyValue>
|
Query params
|
||||||
initialItems={webhook.queryParams}
|
<AccordionIcon />
|
||||||
onItemsChange={handleQueryParamsChange}
|
</AccordionButton>
|
||||||
Item={QueryParamsInputs}
|
<AccordionPanel pt="4">
|
||||||
addLabel="Add a param"
|
<TableList<KeyValue>
|
||||||
|
initialItems={webhook.queryParams}
|
||||||
|
onItemsChange={updateQueryParams}
|
||||||
|
Item={QueryParamsInputs}
|
||||||
|
addLabel="Add a param"
|
||||||
|
/>
|
||||||
|
</AccordionPanel>
|
||||||
|
</AccordionItem>
|
||||||
|
<AccordionItem>
|
||||||
|
<AccordionButton justifyContent="space-between">
|
||||||
|
Headers
|
||||||
|
<AccordionIcon />
|
||||||
|
</AccordionButton>
|
||||||
|
<AccordionPanel pt="4">
|
||||||
|
<TableList<KeyValue>
|
||||||
|
initialItems={webhook.headers}
|
||||||
|
onItemsChange={updateHeaders}
|
||||||
|
Item={HeadersInputs}
|
||||||
|
addLabel="Add a value"
|
||||||
|
/>
|
||||||
|
</AccordionPanel>
|
||||||
|
</AccordionItem>
|
||||||
|
<AccordionItem>
|
||||||
|
<AccordionButton justifyContent="space-between">
|
||||||
|
Body
|
||||||
|
<AccordionIcon />
|
||||||
|
</AccordionButton>
|
||||||
|
<AccordionPanel py={4} as={Stack} spacing="6">
|
||||||
|
<SwitchWithLabel
|
||||||
|
label="Custom body"
|
||||||
|
initialValue={options.isCustomBody ?? true}
|
||||||
|
onCheckChange={updateIsCustomBody}
|
||||||
|
/>
|
||||||
|
{(options.isCustomBody ?? true) && (
|
||||||
|
<CodeEditor
|
||||||
|
defaultValue={webhook.body ?? ''}
|
||||||
|
lang="json"
|
||||||
|
onChange={updateBody}
|
||||||
|
debounceTimeout={0}
|
||||||
/>
|
/>
|
||||||
</AccordionPanel>
|
)}
|
||||||
</AccordionItem>
|
</AccordionPanel>
|
||||||
<AccordionItem>
|
</AccordionItem>
|
||||||
<AccordionButton justifyContent="space-between">
|
<AccordionItem>
|
||||||
Headers
|
<AccordionButton justifyContent="space-between">
|
||||||
<AccordionIcon />
|
Variable values for test
|
||||||
</AccordionButton>
|
<AccordionIcon />
|
||||||
<AccordionPanel pt="4">
|
</AccordionButton>
|
||||||
<TableList<KeyValue>
|
<AccordionPanel pt="4">
|
||||||
initialItems={webhook.headers}
|
<TableList<VariableForTest>
|
||||||
onItemsChange={handleHeadersChange}
|
initialItems={
|
||||||
Item={HeadersInputs}
|
options?.variablesForTest ?? { byId: {}, allIds: [] }
|
||||||
addLabel="Add a value"
|
}
|
||||||
/>
|
onItemsChange={updateVariablesForTest}
|
||||||
</AccordionPanel>
|
Item={VariableForTestInputs}
|
||||||
</AccordionItem>
|
addLabel="Add an entry"
|
||||||
<AccordionItem>
|
/>
|
||||||
<AccordionButton justifyContent="space-between">
|
</AccordionPanel>
|
||||||
Body
|
</AccordionItem>
|
||||||
<AccordionIcon />
|
</Accordion>
|
||||||
</AccordionButton>
|
</SwitchWithRelatedSettings>
|
||||||
<AccordionPanel py={4} as={Stack} spacing="6">
|
|
||||||
<SwitchWithLabel
|
|
||||||
label="Custom body"
|
|
||||||
initialValue={options.isCustomBody ?? true}
|
|
||||||
onCheckChange={handleBodyFormStateChange}
|
|
||||||
/>
|
|
||||||
{(options.isCustomBody ?? true) && (
|
|
||||||
<CodeEditor
|
|
||||||
defaultValue={webhook.body ?? ''}
|
|
||||||
lang="json"
|
|
||||||
onChange={handleBodyChange}
|
|
||||||
debounceTimeout={0}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</AccordionPanel>
|
|
||||||
</AccordionItem>
|
|
||||||
<AccordionItem>
|
|
||||||
<AccordionButton justifyContent="space-between">
|
|
||||||
Variable values for test
|
|
||||||
<AccordionIcon />
|
|
||||||
</AccordionButton>
|
|
||||||
<AccordionPanel pt="4">
|
|
||||||
<TableList<VariableForTest>
|
|
||||||
initialItems={
|
|
||||||
options?.variablesForTest ?? { byId: {}, allIds: [] }
|
|
||||||
}
|
|
||||||
onItemsChange={handleVariablesChange}
|
|
||||||
Item={VariableForTestInputs}
|
|
||||||
addLabel="Add an entry"
|
|
||||||
/>
|
|
||||||
</AccordionPanel>
|
|
||||||
</AccordionItem>
|
|
||||||
</Accordion>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{webhook.url && (
|
{webhook.url && (
|
||||||
<Button
|
<Button
|
||||||
onClick={handleTestRequestClick}
|
onClick={executeTestRequest}
|
||||||
colorScheme="blue"
|
colorScheme="blue"
|
||||||
isLoading={isTestResponseLoading}
|
isLoading={isTestResponseLoading}
|
||||||
>
|
>
|
||||||
@ -211,7 +217,7 @@ export const WebhookAdvancedConfigForm = ({
|
|||||||
<AccordionPanel pt="4">
|
<AccordionPanel pt="4">
|
||||||
<TableList<ResponseVariableMapping>
|
<TableList<ResponseVariableMapping>
|
||||||
initialItems={options.responseVariableMapping}
|
initialItems={options.responseVariableMapping}
|
||||||
onItemsChange={handleResponseMappingChange}
|
onItemsChange={updateResponseVariableMapping}
|
||||||
Item={ResponseMappingInputs}
|
Item={ResponseMappingInputs}
|
||||||
addLabel="Add an entry"
|
addLabel="Add an entry"
|
||||||
/>
|
/>
|
||||||
|
@ -71,8 +71,7 @@ if (window.$chatwoot) {
|
|||||||
|
|
||||||
export const executeChatwootBlock = (
|
export const executeChatwootBlock = (
|
||||||
{ typebot, result }: SessionState,
|
{ typebot, result }: SessionState,
|
||||||
block: ChatwootBlock,
|
block: ChatwootBlock
|
||||||
lastBubbleBlockId?: string
|
|
||||||
): ExecuteIntegrationResponse => {
|
): ExecuteIntegrationResponse => {
|
||||||
const chatwootCode =
|
const chatwootCode =
|
||||||
block.options.task === 'Close widget'
|
block.options.task === 'Close widget'
|
||||||
@ -88,7 +87,6 @@ export const executeChatwootBlock = (
|
|||||||
outgoingEdgeId: block.outgoingEdgeId,
|
outgoingEdgeId: block.outgoingEdgeId,
|
||||||
clientSideActions: [
|
clientSideActions: [
|
||||||
{
|
{
|
||||||
lastBubbleBlockId,
|
|
||||||
chatwoot: {
|
chatwoot: {
|
||||||
scriptToExecute: {
|
scriptToExecute: {
|
||||||
content: parseVariables(typebot.variables, { fieldToParse: 'id' })(
|
content: parseVariables(typebot.variables, { fieldToParse: 'id' })(
|
||||||
|
@ -5,8 +5,7 @@ import { GoogleAnalyticsBlock, SessionState } from '@typebot.io/schemas'
|
|||||||
|
|
||||||
export const executeGoogleAnalyticsBlock = (
|
export const executeGoogleAnalyticsBlock = (
|
||||||
{ typebot: { variables } }: SessionState,
|
{ typebot: { variables } }: SessionState,
|
||||||
block: GoogleAnalyticsBlock,
|
block: GoogleAnalyticsBlock
|
||||||
lastBubbleBlockId?: string
|
|
||||||
): ExecuteIntegrationResponse => {
|
): ExecuteIntegrationResponse => {
|
||||||
const googleAnalytics = deepParseVariables(variables)(block.options)
|
const googleAnalytics = deepParseVariables(variables)(block.options)
|
||||||
return {
|
return {
|
||||||
@ -19,7 +18,6 @@ export const executeGoogleAnalyticsBlock = (
|
|||||||
? Number(googleAnalytics.value)
|
? Number(googleAnalytics.value)
|
||||||
: undefined,
|
: undefined,
|
||||||
},
|
},
|
||||||
lastBubbleBlockId,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import {
|
|||||||
ZapierBlock,
|
ZapierBlock,
|
||||||
MakeComBlock,
|
MakeComBlock,
|
||||||
PabblyConnectBlock,
|
PabblyConnectBlock,
|
||||||
VariableWithUnknowValue,
|
|
||||||
SessionState,
|
SessionState,
|
||||||
Webhook,
|
Webhook,
|
||||||
Typebot,
|
Typebot,
|
||||||
@ -17,17 +16,23 @@ import {
|
|||||||
KeyValue,
|
KeyValue,
|
||||||
ReplyLog,
|
ReplyLog,
|
||||||
ResultInSession,
|
ResultInSession,
|
||||||
|
ExecutableWebhook,
|
||||||
} from '@typebot.io/schemas'
|
} from '@typebot.io/schemas'
|
||||||
import { stringify } from 'qs'
|
import { stringify } from 'qs'
|
||||||
import { byId, omit } from '@typebot.io/lib'
|
import { omit } from '@typebot.io/lib'
|
||||||
import { parseAnswers } from '@typebot.io/lib/results'
|
import { parseAnswers } from '@typebot.io/lib/results'
|
||||||
import got, { Method, Headers, HTTPError } from 'got'
|
import got, { Method, HTTPError, OptionsInit } from 'got'
|
||||||
import { parseSampleResult } from './parseSampleResult'
|
import { parseSampleResult } from './parseSampleResult'
|
||||||
import { ExecuteIntegrationResponse } from '@/features/chat/types'
|
import { ExecuteIntegrationResponse } from '@/features/chat/types'
|
||||||
import { saveErrorLog } from '@/features/logs/saveErrorLog'
|
import { saveErrorLog } from '@/features/logs/saveErrorLog'
|
||||||
import { saveSuccessLog } from '@/features/logs/saveSuccessLog'
|
import { saveSuccessLog } from '@/features/logs/saveSuccessLog'
|
||||||
import { updateVariables } from '@/features/variables/updateVariables'
|
|
||||||
import { parseVariables } from '@/features/variables/parseVariables'
|
import { parseVariables } from '@/features/variables/parseVariables'
|
||||||
|
import { resumeWebhookExecution } from './resumeWebhookExecution'
|
||||||
|
|
||||||
|
type ParsedWebhook = ExecutableWebhook & {
|
||||||
|
basicAuth: { username?: string; password?: string }
|
||||||
|
isJson: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export const executeWebhookBlock = async (
|
export const executeWebhookBlock = async (
|
||||||
state: SessionState,
|
state: SessionState,
|
||||||
@ -51,70 +56,34 @@ export const executeWebhookBlock = async (
|
|||||||
return { outgoingEdgeId: block.outgoingEdgeId, logs: [log] }
|
return { outgoingEdgeId: block.outgoingEdgeId, logs: [log] }
|
||||||
}
|
}
|
||||||
const preparedWebhook = prepareWebhookAttributes(webhook, block.options)
|
const preparedWebhook = prepareWebhookAttributes(webhook, block.options)
|
||||||
const webhookResponse = await executeWebhook({ typebot })(
|
const parsedWebhook = await parseWebhookAttributes(
|
||||||
preparedWebhook,
|
typebot,
|
||||||
typebot.variables,
|
|
||||||
block.groupId,
|
block.groupId,
|
||||||
result
|
result
|
||||||
)
|
)(preparedWebhook)
|
||||||
const status = webhookResponse.statusCode.toString()
|
if (!parsedWebhook) {
|
||||||
const isError = status.startsWith('4') || status.startsWith('5')
|
|
||||||
|
|
||||||
if (isError) {
|
|
||||||
log = {
|
log = {
|
||||||
status: 'error',
|
status: 'error',
|
||||||
description: `Webhook returned error: ${webhookResponse.data}`,
|
description: `Couldn't parse webhook attributes`,
|
||||||
details: JSON.stringify(webhookResponse.data, null, 2).substring(0, 1000),
|
|
||||||
}
|
}
|
||||||
result &&
|
result &&
|
||||||
(await saveErrorLog({
|
(await saveErrorLog({
|
||||||
resultId: result.id,
|
resultId: result.id,
|
||||||
message: log.description,
|
message: log.description,
|
||||||
details: log.details,
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
log = {
|
|
||||||
status: 'success',
|
|
||||||
description: `Webhook executed successfully!`,
|
|
||||||
details: JSON.stringify(webhookResponse.data, null, 2).substring(0, 1000),
|
|
||||||
}
|
|
||||||
result &&
|
|
||||||
(await saveSuccessLog({
|
|
||||||
resultId: result.id,
|
|
||||||
message: log.description,
|
|
||||||
details: JSON.stringify(webhookResponse.data, null, 2).substring(
|
|
||||||
0,
|
|
||||||
1000
|
|
||||||
),
|
|
||||||
}))
|
}))
|
||||||
|
return { outgoingEdgeId: block.outgoingEdgeId, logs: [log] }
|
||||||
}
|
}
|
||||||
|
if (block.options.isExecutedOnClient)
|
||||||
const newVariables = block.options.responseVariableMapping.reduce<
|
|
||||||
VariableWithUnknowValue[]
|
|
||||||
>((newVariables, varMapping) => {
|
|
||||||
if (!varMapping?.bodyPath || !varMapping.variableId) return newVariables
|
|
||||||
const existingVariable = typebot.variables.find(byId(varMapping.variableId))
|
|
||||||
if (!existingVariable) return newVariables
|
|
||||||
const func = Function(
|
|
||||||
'data',
|
|
||||||
`return data.${parseVariables(typebot.variables)(varMapping?.bodyPath)}`
|
|
||||||
)
|
|
||||||
try {
|
|
||||||
const value: unknown = func(webhookResponse)
|
|
||||||
return [...newVariables, { ...existingVariable, value }]
|
|
||||||
} catch (err) {
|
|
||||||
return newVariables
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
if (newVariables.length > 0) {
|
|
||||||
const newSessionState = await updateVariables(state)(newVariables)
|
|
||||||
return {
|
return {
|
||||||
outgoingEdgeId: block.outgoingEdgeId,
|
outgoingEdgeId: block.outgoingEdgeId,
|
||||||
newSessionState,
|
clientSideActions: [
|
||||||
|
{
|
||||||
|
webhookToExecute: parsedWebhook,
|
||||||
|
},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}
|
const webhookResponse = await executeWebhook(parsedWebhook, result)
|
||||||
|
return resumeWebhookExecution(state, block)(webhookResponse)
|
||||||
return { outgoingEdgeId: block.outgoingEdgeId, logs: log ? [log] : undefined }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const prepareWebhookAttributes = (
|
const prepareWebhookAttributes = (
|
||||||
@ -131,19 +100,15 @@ const prepareWebhookAttributes = (
|
|||||||
|
|
||||||
const checkIfBodyIsAVariable = (body: string) => /^{{.+}}$/.test(body)
|
const checkIfBodyIsAVariable = (body: string) => /^{{.+}}$/.test(body)
|
||||||
|
|
||||||
export const executeWebhook =
|
const parseWebhookAttributes =
|
||||||
({ typebot }: Pick<SessionState, 'typebot'>) =>
|
(
|
||||||
async (
|
typebot: SessionState['typebot'],
|
||||||
webhook: Webhook,
|
|
||||||
variables: Variable[],
|
|
||||||
groupId: string,
|
groupId: string,
|
||||||
result: ResultInSession
|
result: ResultInSession
|
||||||
): Promise<WebhookResponse> => {
|
) =>
|
||||||
if (!webhook.url || !webhook.method)
|
async (webhook: Webhook): Promise<ParsedWebhook | undefined> => {
|
||||||
return {
|
if (!webhook.url || !webhook.method) return
|
||||||
statusCode: 400,
|
const { variables } = typebot
|
||||||
data: { message: `Webhook doesn't have url or method` },
|
|
||||||
}
|
|
||||||
const basicAuth: { username?: string; password?: string } = {}
|
const basicAuth: { username?: string; password?: string } = {}
|
||||||
const basicAuthHeaderIdx = webhook.headers.findIndex(
|
const basicAuthHeaderIdx = webhook.headers.findIndex(
|
||||||
(h) =>
|
(h) =>
|
||||||
@ -161,13 +126,11 @@ export const executeWebhook =
|
|||||||
webhook.headers.splice(basicAuthHeaderIdx, 1)
|
webhook.headers.splice(basicAuthHeaderIdx, 1)
|
||||||
}
|
}
|
||||||
const headers = convertKeyValueTableToObject(webhook.headers, variables) as
|
const headers = convertKeyValueTableToObject(webhook.headers, variables) as
|
||||||
| Headers
|
| ExecutableWebhook['headers']
|
||||||
| undefined
|
| undefined
|
||||||
const queryParams = stringify(
|
const queryParams = stringify(
|
||||||
convertKeyValueTableToObject(webhook.queryParams, variables)
|
convertKeyValueTableToObject(webhook.queryParams, variables)
|
||||||
)
|
)
|
||||||
const contentType = headers ? headers['Content-Type'] : undefined
|
|
||||||
|
|
||||||
const bodyContent = await getBodyContent(
|
const bodyContent = await getBodyContent(
|
||||||
typebot,
|
typebot,
|
||||||
[]
|
[]
|
||||||
@ -186,62 +149,62 @@ export const executeWebhook =
|
|||||||
)
|
)
|
||||||
: { data: undefined, isJson: false }
|
: { data: undefined, isJson: false }
|
||||||
|
|
||||||
const request = {
|
return {
|
||||||
url: parseVariables(variables)(
|
url: parseVariables(variables)(
|
||||||
webhook.url + (queryParams !== '' ? `?${queryParams}` : '')
|
webhook.url + (queryParams !== '' ? `?${queryParams}` : '')
|
||||||
),
|
),
|
||||||
method: webhook.method as Method,
|
basicAuth,
|
||||||
|
method: webhook.method,
|
||||||
headers,
|
headers,
|
||||||
...basicAuth,
|
body,
|
||||||
json:
|
isJson,
|
||||||
!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,
|
|
||||||
}
|
}
|
||||||
try {
|
}
|
||||||
const response = await got(request.url, omit(request, 'url'))
|
|
||||||
await saveSuccessLog({
|
export const executeWebhook = async (
|
||||||
resultId: result.id,
|
webhook: ParsedWebhook,
|
||||||
message: 'Webhook successfuly executed.',
|
result: ResultInSession
|
||||||
details: {
|
): Promise<WebhookResponse> => {
|
||||||
statusCode: response.statusCode,
|
const { headers, url, method, basicAuth, body, isJson } = webhook
|
||||||
request,
|
const contentType = headers ? headers['Content-Type'] : undefined
|
||||||
response: safeJsonParse(response.body).data,
|
|
||||||
},
|
const request = {
|
||||||
})
|
url,
|
||||||
return {
|
method: method as Method,
|
||||||
|
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 as string) : undefined,
|
||||||
|
} satisfies OptionsInit
|
||||||
|
try {
|
||||||
|
const response = await got(request.url, omit(request, 'url'))
|
||||||
|
await saveSuccessLog({
|
||||||
|
resultId: result.id,
|
||||||
|
message: 'Webhook successfuly executed.',
|
||||||
|
details: {
|
||||||
statusCode: response.statusCode,
|
statusCode: response.statusCode,
|
||||||
data: safeJsonParse(response.body).data,
|
request,
|
||||||
}
|
response: safeJsonParse(response.body).data,
|
||||||
} catch (error) {
|
},
|
||||||
if (error instanceof HTTPError) {
|
})
|
||||||
const response = {
|
return {
|
||||||
statusCode: error.response.statusCode,
|
statusCode: response.statusCode,
|
||||||
data: safeJsonParse(error.response.body as string).data,
|
data: safeJsonParse(response.body).data,
|
||||||
}
|
}
|
||||||
await saveErrorLog({
|
} catch (error) {
|
||||||
resultId: result.id,
|
if (error instanceof HTTPError) {
|
||||||
message: 'Webhook returned an error',
|
|
||||||
details: {
|
|
||||||
request,
|
|
||||||
response,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
const response = {
|
const response = {
|
||||||
statusCode: 500,
|
statusCode: error.response.statusCode,
|
||||||
data: { message: `Error from Typebot server: ${error}` },
|
data: safeJsonParse(error.response.body as string).data,
|
||||||
}
|
}
|
||||||
console.error(error)
|
|
||||||
await saveErrorLog({
|
await saveErrorLog({
|
||||||
resultId: result.id,
|
resultId: result.id,
|
||||||
message: 'Webhook failed to execute',
|
message: 'Webhook returned an error',
|
||||||
details: {
|
details: {
|
||||||
request,
|
request,
|
||||||
response,
|
response,
|
||||||
@ -249,7 +212,22 @@ export const executeWebhook =
|
|||||||
})
|
})
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
const response = {
|
||||||
|
statusCode: 500,
|
||||||
|
data: { message: `Error from Typebot server: ${error}` },
|
||||||
|
}
|
||||||
|
console.error(error)
|
||||||
|
await saveErrorLog({
|
||||||
|
resultId: result.id,
|
||||||
|
message: 'Webhook failed to execute',
|
||||||
|
details: {
|
||||||
|
request,
|
||||||
|
response,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return response
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const getBodyContent =
|
const getBodyContent =
|
||||||
(
|
(
|
||||||
|
@ -0,0 +1,87 @@
|
|||||||
|
import { ExecuteIntegrationResponse } from '@/features/chat/types'
|
||||||
|
import { saveErrorLog } from '@/features/logs/saveErrorLog'
|
||||||
|
import { saveSuccessLog } from '@/features/logs/saveSuccessLog'
|
||||||
|
import { parseVariables } from '@/features/variables/parseVariables'
|
||||||
|
import { updateVariables } from '@/features/variables/updateVariables'
|
||||||
|
import { byId } from '@typebot.io/lib'
|
||||||
|
import {
|
||||||
|
MakeComBlock,
|
||||||
|
PabblyConnectBlock,
|
||||||
|
VariableWithUnknowValue,
|
||||||
|
WebhookBlock,
|
||||||
|
ZapierBlock,
|
||||||
|
} from '@typebot.io/schemas'
|
||||||
|
import { ReplyLog, SessionState } from '@typebot.io/schemas/features/chat'
|
||||||
|
|
||||||
|
export const resumeWebhookExecution =
|
||||||
|
(
|
||||||
|
state: SessionState,
|
||||||
|
block: WebhookBlock | ZapierBlock | MakeComBlock | PabblyConnectBlock
|
||||||
|
) =>
|
||||||
|
async (response: {
|
||||||
|
statusCode: number
|
||||||
|
data?: unknown
|
||||||
|
}): Promise<ExecuteIntegrationResponse> => {
|
||||||
|
const { typebot, result } = state
|
||||||
|
let log: ReplyLog | undefined
|
||||||
|
const status = response.statusCode.toString()
|
||||||
|
const isError = status.startsWith('4') || status.startsWith('5')
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
log = {
|
||||||
|
status: 'error',
|
||||||
|
description: `Webhook returned error: ${response.data}`,
|
||||||
|
details: JSON.stringify(response.data, null, 2).substring(0, 1000),
|
||||||
|
}
|
||||||
|
result &&
|
||||||
|
(await saveErrorLog({
|
||||||
|
resultId: result.id,
|
||||||
|
message: log.description,
|
||||||
|
details: log.details,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
log = {
|
||||||
|
status: 'success',
|
||||||
|
description: `Webhook executed successfully!`,
|
||||||
|
details: JSON.stringify(response.data, null, 2).substring(0, 1000),
|
||||||
|
}
|
||||||
|
result &&
|
||||||
|
(await saveSuccessLog({
|
||||||
|
resultId: result.id,
|
||||||
|
message: log.description,
|
||||||
|
details: JSON.stringify(response.data, null, 2).substring(0, 1000),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const newVariables = block.options.responseVariableMapping.reduce<
|
||||||
|
VariableWithUnknowValue[]
|
||||||
|
>((newVariables, varMapping) => {
|
||||||
|
if (!varMapping?.bodyPath || !varMapping.variableId) return newVariables
|
||||||
|
const existingVariable = typebot.variables.find(
|
||||||
|
byId(varMapping.variableId)
|
||||||
|
)
|
||||||
|
if (!existingVariable) return newVariables
|
||||||
|
const func = Function(
|
||||||
|
'data',
|
||||||
|
`return data.${parseVariables(typebot.variables)(varMapping?.bodyPath)}`
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
const value: unknown = func(response)
|
||||||
|
return [...newVariables, { ...existingVariable, value }]
|
||||||
|
} catch (err) {
|
||||||
|
return newVariables
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
if (newVariables.length > 0) {
|
||||||
|
const newSessionState = await updateVariables(state)(newVariables)
|
||||||
|
return {
|
||||||
|
outgoingEdgeId: block.outgoingEdgeId,
|
||||||
|
newSessionState,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
outgoingEdgeId: block.outgoingEdgeId,
|
||||||
|
logs: log ? [log] : undefined,
|
||||||
|
}
|
||||||
|
}
|
@ -5,15 +5,13 @@ import { ExecuteLogicResponse } from '@/features/chat/types'
|
|||||||
|
|
||||||
export const executeRedirect = (
|
export const executeRedirect = (
|
||||||
{ typebot: { variables } }: SessionState,
|
{ typebot: { variables } }: SessionState,
|
||||||
block: RedirectBlock,
|
block: RedirectBlock
|
||||||
lastBubbleBlockId?: string
|
|
||||||
): ExecuteLogicResponse => {
|
): ExecuteLogicResponse => {
|
||||||
if (!block.options?.url) return { outgoingEdgeId: block.outgoingEdgeId }
|
if (!block.options?.url) return { outgoingEdgeId: block.outgoingEdgeId }
|
||||||
const formattedUrl = sanitizeUrl(parseVariables(variables)(block.options.url))
|
const formattedUrl = sanitizeUrl(parseVariables(variables)(block.options.url))
|
||||||
return {
|
return {
|
||||||
clientSideActions: [
|
clientSideActions: [
|
||||||
{
|
{
|
||||||
lastBubbleBlockId,
|
|
||||||
redirect: { url: formattedUrl, isNewTab: block.options.isNewTab },
|
redirect: { url: formattedUrl, isNewTab: block.options.isNewTab },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -6,8 +6,7 @@ import { ScriptBlock, SessionState, Variable } from '@typebot.io/schemas'
|
|||||||
|
|
||||||
export const executeScript = (
|
export const executeScript = (
|
||||||
{ typebot: { variables } }: SessionState,
|
{ typebot: { variables } }: SessionState,
|
||||||
block: ScriptBlock,
|
block: ScriptBlock
|
||||||
lastBubbleBlockId?: string
|
|
||||||
): ExecuteLogicResponse => {
|
): ExecuteLogicResponse => {
|
||||||
if (!block.options.content) return { outgoingEdgeId: block.outgoingEdgeId }
|
if (!block.options.content) return { outgoingEdgeId: block.outgoingEdgeId }
|
||||||
|
|
||||||
@ -21,7 +20,6 @@ export const executeScript = (
|
|||||||
clientSideActions: [
|
clientSideActions: [
|
||||||
{
|
{
|
||||||
scriptToExecute: scriptToExecute,
|
scriptToExecute: scriptToExecute,
|
||||||
lastBubbleBlockId,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,7 @@ import { parseScriptToExecuteClientSideAction } from '../script/executeScript'
|
|||||||
|
|
||||||
export const executeSetVariable = async (
|
export const executeSetVariable = async (
|
||||||
state: SessionState,
|
state: SessionState,
|
||||||
block: SetVariableBlock,
|
block: SetVariableBlock
|
||||||
lastBubbleBlockId?: string
|
|
||||||
): Promise<ExecuteLogicResponse> => {
|
): Promise<ExecuteLogicResponse> => {
|
||||||
const { variables } = state.typebot
|
const { variables } = state.typebot
|
||||||
if (!block.options?.variableId)
|
if (!block.options?.variableId)
|
||||||
@ -28,7 +27,6 @@ export const executeSetVariable = async (
|
|||||||
setVariable: {
|
setVariable: {
|
||||||
scriptToExecute,
|
scriptToExecute,
|
||||||
},
|
},
|
||||||
lastBubbleBlockId,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,7 @@ import { SessionState, WaitBlock } from '@typebot.io/schemas'
|
|||||||
|
|
||||||
export const executeWait = async (
|
export const executeWait = async (
|
||||||
{ typebot: { variables } }: SessionState,
|
{ typebot: { variables } }: SessionState,
|
||||||
block: WaitBlock,
|
block: WaitBlock
|
||||||
lastBubbleBlockId?: string
|
|
||||||
): Promise<ExecuteLogicResponse> => {
|
): Promise<ExecuteLogicResponse> => {
|
||||||
if (!block.options.secondsToWaitFor)
|
if (!block.options.secondsToWaitFor)
|
||||||
return { outgoingEdgeId: block.outgoingEdgeId }
|
return { outgoingEdgeId: block.outgoingEdgeId }
|
||||||
@ -19,7 +18,6 @@ export const executeWait = async (
|
|||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
wait: { secondsToWaitFor: parsedSecondsToWaitFor },
|
wait: { secondsToWaitFor: parsedSecondsToWaitFor },
|
||||||
lastBubbleBlockId,
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: undefined,
|
: undefined,
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
ResultInSession,
|
ResultInSession,
|
||||||
SessionState,
|
SessionState,
|
||||||
SetVariableBlock,
|
SetVariableBlock,
|
||||||
|
WebhookBlock,
|
||||||
} from '@typebot.io/schemas'
|
} from '@typebot.io/schemas'
|
||||||
import { isInputBlock, isNotDefined, byId, isDefined } from '@typebot.io/lib'
|
import { isInputBlock, isNotDefined, byId, isDefined } from '@typebot.io/lib'
|
||||||
import { executeGroup } from './executeGroup'
|
import { executeGroup } from './executeGroup'
|
||||||
@ -26,6 +27,7 @@ import { updateVariables } from '@/features/variables/updateVariables'
|
|||||||
import { parseVariables } from '@/features/variables/parseVariables'
|
import { parseVariables } from '@/features/variables/parseVariables'
|
||||||
import { OpenAIBlock } from '@typebot.io/schemas/features/blocks/integrations/openai'
|
import { OpenAIBlock } from '@typebot.io/schemas/features/blocks/integrations/openai'
|
||||||
import { resumeChatCompletion } from '@/features/blocks/integrations/openai/resumeChatCompletion'
|
import { resumeChatCompletion } from '@/features/blocks/integrations/openai/resumeChatCompletion'
|
||||||
|
import { resumeWebhookExecution } from '@/features/blocks/integrations/webhook/resumeWebhookExecution'
|
||||||
|
|
||||||
export const continueBotFlow =
|
export const continueBotFlow =
|
||||||
(state: SessionState) =>
|
(state: SessionState) =>
|
||||||
@ -60,6 +62,12 @@ export const continueBotFlow =
|
|||||||
}
|
}
|
||||||
newSessionState = await updateVariables(state)([newVariable])
|
newSessionState = await updateVariables(state)([newVariable])
|
||||||
}
|
}
|
||||||
|
} else if (reply && block.type === IntegrationBlockType.WEBHOOK) {
|
||||||
|
const result = await resumeWebhookExecution(
|
||||||
|
state,
|
||||||
|
block
|
||||||
|
)(JSON.parse(reply))
|
||||||
|
if (result.newSessionState) newSessionState = result.newSessionState
|
||||||
} else if (
|
} else if (
|
||||||
isDefined(reply) &&
|
isDefined(reply) &&
|
||||||
block.type === IntegrationBlockType.OPEN_AI &&
|
block.type === IntegrationBlockType.OPEN_AI &&
|
||||||
@ -250,7 +258,7 @@ const computeStorageUsed = async (reply: string) => {
|
|||||||
const getOutgoingEdgeId =
|
const getOutgoingEdgeId =
|
||||||
({ typebot: { variables } }: Pick<SessionState, 'typebot'>) =>
|
({ typebot: { variables } }: Pick<SessionState, 'typebot'>) =>
|
||||||
(
|
(
|
||||||
block: InputBlock | SetVariableBlock | OpenAIBlock,
|
block: InputBlock | SetVariableBlock | OpenAIBlock | WebhookBlock,
|
||||||
reply: string | null
|
reply: string | null
|
||||||
) => {
|
) => {
|
||||||
if (
|
if (
|
||||||
|
@ -67,9 +67,9 @@ export const executeGroup =
|
|||||||
logs,
|
logs,
|
||||||
}
|
}
|
||||||
const executionResponse = isLogicBlock(block)
|
const executionResponse = isLogicBlock(block)
|
||||||
? await executeLogic(newSessionState, lastBubbleBlockId)(block)
|
? await executeLogic(newSessionState)(block)
|
||||||
: isIntegrationBlock(block)
|
: isIntegrationBlock(block)
|
||||||
? await executeIntegration(newSessionState, lastBubbleBlockId)(block)
|
? await executeIntegration(newSessionState)(block)
|
||||||
: null
|
: null
|
||||||
|
|
||||||
if (!executionResponse) continue
|
if (!executionResponse) continue
|
||||||
@ -83,12 +83,17 @@ export const executeGroup =
|
|||||||
) {
|
) {
|
||||||
clientSideActions = [
|
clientSideActions = [
|
||||||
...(clientSideActions ?? []),
|
...(clientSideActions ?? []),
|
||||||
...executionResponse.clientSideActions,
|
...executionResponse.clientSideActions.map((action) => ({
|
||||||
|
...action,
|
||||||
|
lastBubbleBlockId,
|
||||||
|
})),
|
||||||
]
|
]
|
||||||
if (
|
if (
|
||||||
executionResponse.clientSideActions?.find(
|
executionResponse.clientSideActions?.find(
|
||||||
(action) =>
|
(action) =>
|
||||||
'setVariable' in action || 'streamOpenAiChatCompletion' in action
|
'setVariable' in action ||
|
||||||
|
'streamOpenAiChatCompletion' in action ||
|
||||||
|
'webhookToExecute' in action
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
|
@ -12,15 +12,15 @@ import {
|
|||||||
import { ExecuteIntegrationResponse } from '../types'
|
import { ExecuteIntegrationResponse } from '../types'
|
||||||
|
|
||||||
export const executeIntegration =
|
export const executeIntegration =
|
||||||
(state: SessionState, lastBubbleBlockId?: string) =>
|
(state: SessionState) =>
|
||||||
async (block: IntegrationBlock): Promise<ExecuteIntegrationResponse> => {
|
async (block: IntegrationBlock): Promise<ExecuteIntegrationResponse> => {
|
||||||
switch (block.type) {
|
switch (block.type) {
|
||||||
case IntegrationBlockType.GOOGLE_SHEETS:
|
case IntegrationBlockType.GOOGLE_SHEETS:
|
||||||
return executeGoogleSheetBlock(state, block)
|
return executeGoogleSheetBlock(state, block)
|
||||||
case IntegrationBlockType.CHATWOOT:
|
case IntegrationBlockType.CHATWOOT:
|
||||||
return executeChatwootBlock(state, block, lastBubbleBlockId)
|
return executeChatwootBlock(state, block)
|
||||||
case IntegrationBlockType.GOOGLE_ANALYTICS:
|
case IntegrationBlockType.GOOGLE_ANALYTICS:
|
||||||
return executeGoogleAnalyticsBlock(state, block, lastBubbleBlockId)
|
return executeGoogleAnalyticsBlock(state, block)
|
||||||
case IntegrationBlockType.EMAIL:
|
case IntegrationBlockType.EMAIL:
|
||||||
return executeSendEmailBlock(state, block)
|
return executeSendEmailBlock(state, block)
|
||||||
case IntegrationBlockType.WEBHOOK:
|
case IntegrationBlockType.WEBHOOK:
|
||||||
|
@ -10,21 +10,21 @@ import { executeTypebotLink } from '@/features/blocks/logic/typebotLink/executeT
|
|||||||
import { executeAbTest } from '@/features/blocks/logic/abTest/executeAbTest'
|
import { executeAbTest } from '@/features/blocks/logic/abTest/executeAbTest'
|
||||||
|
|
||||||
export const executeLogic =
|
export const executeLogic =
|
||||||
(state: SessionState, lastBubbleBlockId?: string) =>
|
(state: SessionState) =>
|
||||||
async (block: LogicBlock): Promise<ExecuteLogicResponse> => {
|
async (block: LogicBlock): Promise<ExecuteLogicResponse> => {
|
||||||
switch (block.type) {
|
switch (block.type) {
|
||||||
case LogicBlockType.SET_VARIABLE:
|
case LogicBlockType.SET_VARIABLE:
|
||||||
return executeSetVariable(state, block, lastBubbleBlockId)
|
return executeSetVariable(state, block)
|
||||||
case LogicBlockType.CONDITION:
|
case LogicBlockType.CONDITION:
|
||||||
return executeCondition(state, block)
|
return executeCondition(state, block)
|
||||||
case LogicBlockType.REDIRECT:
|
case LogicBlockType.REDIRECT:
|
||||||
return executeRedirect(state, block, lastBubbleBlockId)
|
return executeRedirect(state, block)
|
||||||
case LogicBlockType.SCRIPT:
|
case LogicBlockType.SCRIPT:
|
||||||
return executeScript(state, block, lastBubbleBlockId)
|
return executeScript(state, block)
|
||||||
case LogicBlockType.TYPEBOT_LINK:
|
case LogicBlockType.TYPEBOT_LINK:
|
||||||
return executeTypebotLink(state, block)
|
return executeTypebotLink(state, block)
|
||||||
case LogicBlockType.WAIT:
|
case LogicBlockType.WAIT:
|
||||||
return executeWait(state, block, lastBubbleBlockId)
|
return executeWait(state, block)
|
||||||
case LogicBlockType.JUMP:
|
case LogicBlockType.JUMP:
|
||||||
return executeJumpBlock(state, block.options)
|
return executeJumpBlock(state, block.options)
|
||||||
case LogicBlockType.AB_TEST:
|
case LogicBlockType.AB_TEST:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@typebot.io/js",
|
"name": "@typebot.io/js",
|
||||||
"version": "0.0.55",
|
"version": "0.0.56",
|
||||||
"description": "Javascript library to display typebots on your website",
|
"description": "Javascript library to display typebots on your website",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
@ -33,7 +33,7 @@
|
|||||||
"eslint-plugin-solid": "0.12.1",
|
"eslint-plugin-solid": "0.12.1",
|
||||||
"postcss": "8.4.23",
|
"postcss": "8.4.23",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"rollup": "3.23.0",
|
"rollup": "3.20.2",
|
||||||
"rollup-plugin-postcss": "4.0.2",
|
"rollup-plugin-postcss": "4.0.2",
|
||||||
"rollup-plugin-typescript-paths": "1.4.0",
|
"rollup-plugin-typescript-paths": "1.4.0",
|
||||||
"tailwindcss": "3.3.2",
|
"tailwindcss": "3.3.2",
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
import { getOpenAiStreamerQuery } from '@/queries/getOpenAiStreamerQuery'
|
||||||
|
import { ClientSideActionContext } from '@/types'
|
||||||
|
|
||||||
|
export const streamChat =
|
||||||
|
(context: ClientSideActionContext) =>
|
||||||
|
async (
|
||||||
|
messages: {
|
||||||
|
content?: string | undefined
|
||||||
|
role?: 'system' | 'user' | 'assistant' | undefined
|
||||||
|
}[],
|
||||||
|
{ onStreamedMessage }: { onStreamedMessage?: (message: string) => void }
|
||||||
|
) => {
|
||||||
|
const data = await getOpenAiStreamerQuery(context)(messages)
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = data.getReader()
|
||||||
|
const decoder = new TextDecoder()
|
||||||
|
let done = false
|
||||||
|
|
||||||
|
let message = ''
|
||||||
|
while (!done) {
|
||||||
|
const { value, done: doneReading } = await reader.read()
|
||||||
|
done = doneReading
|
||||||
|
const chunkValue = decoder.decode(value)
|
||||||
|
message += chunkValue
|
||||||
|
onStreamedMessage?.(message)
|
||||||
|
}
|
||||||
|
return message
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ExecutableWebhook } from '@typebot.io/schemas'
|
||||||
|
|
||||||
|
export const executeWebhook = async (
|
||||||
|
webhookToExecute: ExecutableWebhook
|
||||||
|
): Promise<string> => {
|
||||||
|
const { url, method, body, headers } = webhookToExecute
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method,
|
||||||
|
body: method !== 'GET' && body ? JSON.stringify(body) : undefined,
|
||||||
|
headers,
|
||||||
|
})
|
||||||
|
const statusCode = response.status
|
||||||
|
const data = await response.json()
|
||||||
|
return JSON.stringify({ statusCode, data })
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
return JSON.stringify({
|
||||||
|
statusCode: 500,
|
||||||
|
data: 'An error occured while executing the webhook on the client',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -22,3 +22,8 @@ export type OutgoingLog = {
|
|||||||
description: string
|
description: string
|
||||||
details?: unknown
|
details?: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ClientSideActionContext = {
|
||||||
|
apiHost?: string
|
||||||
|
sessionId: string
|
||||||
|
}
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
import { executeChatwoot } from '@/features/blocks/integrations/chatwoot'
|
import { executeChatwoot } from '@/features/blocks/integrations/chatwoot'
|
||||||
import { executeGoogleAnalyticsBlock } from '@/features/blocks/integrations/googleAnalytics/utils/executeGoogleAnalytics'
|
import { executeGoogleAnalyticsBlock } from '@/features/blocks/integrations/googleAnalytics/utils/executeGoogleAnalytics'
|
||||||
|
import { streamChat } from '@/features/blocks/integrations/openai/streamChat'
|
||||||
import { executeRedirect } from '@/features/blocks/logic/redirect'
|
import { executeRedirect } from '@/features/blocks/logic/redirect'
|
||||||
import { executeScript } from '@/features/blocks/logic/script/executeScript'
|
import { executeScript } from '@/features/blocks/logic/script/executeScript'
|
||||||
import { executeSetVariable } from '@/features/blocks/logic/setVariable/executeSetVariable'
|
import { executeSetVariable } from '@/features/blocks/logic/setVariable/executeSetVariable'
|
||||||
import { executeWait } from '@/features/blocks/logic/wait/utils/executeWait'
|
import { executeWait } from '@/features/blocks/logic/wait/utils/executeWait'
|
||||||
import { getOpenAiStreamerQuery } from '@/queries/getOpenAiStreamerQuery'
|
import { executeWebhook } from '@/features/blocks/integrations/webhook/executeWebhook'
|
||||||
|
import { ClientSideActionContext } from '@/types'
|
||||||
import type { ChatReply } from '@typebot.io/schemas'
|
import type { ChatReply } from '@typebot.io/schemas'
|
||||||
|
|
||||||
type ClientSideActionContext = {
|
|
||||||
apiHost?: string
|
|
||||||
sessionId: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const executeClientSideAction = async (
|
export const executeClientSideAction = async (
|
||||||
clientSideAction: NonNullable<ChatReply['clientSideActions']>[0],
|
clientSideAction: NonNullable<ChatReply['clientSideActions']>[0],
|
||||||
context: ClientSideActionContext,
|
context: ClientSideActionContext,
|
||||||
@ -44,34 +41,8 @@ export const executeClientSideAction = async (
|
|||||||
)
|
)
|
||||||
return { replyToSend: text }
|
return { replyToSend: text }
|
||||||
}
|
}
|
||||||
}
|
if ('webhookToExecute' in clientSideAction) {
|
||||||
|
const response = await executeWebhook(clientSideAction.webhookToExecute)
|
||||||
const streamChat =
|
return { replyToSend: response }
|
||||||
(context: ClientSideActionContext) =>
|
|
||||||
async (
|
|
||||||
messages: {
|
|
||||||
content?: string | undefined
|
|
||||||
role?: 'system' | 'user' | 'assistant' | undefined
|
|
||||||
}[],
|
|
||||||
{ onStreamedMessage }: { onStreamedMessage?: (message: string) => void }
|
|
||||||
) => {
|
|
||||||
const data = await getOpenAiStreamerQuery(context)(messages)
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const reader = data.getReader()
|
|
||||||
const decoder = new TextDecoder()
|
|
||||||
let done = false
|
|
||||||
|
|
||||||
let message = ''
|
|
||||||
while (!done) {
|
|
||||||
const { value, done: doneReading } = await reader.read()
|
|
||||||
done = doneReading
|
|
||||||
const chunkValue = decoder.decode(value)
|
|
||||||
message += chunkValue
|
|
||||||
onStreamedMessage?.(message)
|
|
||||||
}
|
|
||||||
return message
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@typebot.io/react",
|
"name": "@typebot.io/react",
|
||||||
"version": "0.0.55",
|
"version": "0.0.56",
|
||||||
"description": "React library to display typebots on your website",
|
"description": "React library to display typebots on your website",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
@ -33,7 +33,7 @@
|
|||||||
"eslint-config-custom": "workspace:*",
|
"eslint-config-custom": "workspace:*",
|
||||||
"@typebot.io/schemas": "workspace:*",
|
"@typebot.io/schemas": "workspace:*",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"rollup": "3.23.0",
|
"rollup": "3.20.2",
|
||||||
"rollup-plugin-typescript-paths": "1.4.0",
|
"rollup-plugin-typescript-paths": "1.4.0",
|
||||||
"@typebot.io/tsconfig": "workspace:*",
|
"@typebot.io/tsconfig": "workspace:*",
|
||||||
"tslib": "2.5.2",
|
"tslib": "2.5.2",
|
||||||
|
@ -19,6 +19,7 @@ export const webhookOptionsSchema = z.object({
|
|||||||
responseVariableMapping: z.array(responseVariableMappingSchema),
|
responseVariableMapping: z.array(responseVariableMappingSchema),
|
||||||
isAdvancedConfig: z.boolean().optional(),
|
isAdvancedConfig: z.boolean().optional(),
|
||||||
isCustomBody: z.boolean().optional(),
|
isCustomBody: z.boolean().optional(),
|
||||||
|
isExecutedOnClient: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const webhookBlockSchema = blockBaseSchema.merge(
|
export const webhookBlockSchema = blockBaseSchema.merge(
|
||||||
|
@ -18,6 +18,7 @@ import { answerSchema } from './answer'
|
|||||||
import { BubbleBlockType } from './blocks/bubbles/enums'
|
import { BubbleBlockType } from './blocks/bubbles/enums'
|
||||||
import { inputBlockSchemas } from './blocks/schemas'
|
import { inputBlockSchemas } from './blocks/schemas'
|
||||||
import { chatCompletionMessageSchema } from './blocks/integrations/openai'
|
import { chatCompletionMessageSchema } from './blocks/integrations/openai'
|
||||||
|
import { executableWebhookSchema } from './webhooks'
|
||||||
|
|
||||||
const typebotInSessionStateSchema = publicTypebotSchema.pick({
|
const typebotInSessionStateSchema = publicTypebotSchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
@ -237,6 +238,11 @@ const clientSideActionSchema = z
|
|||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
.or(
|
||||||
|
z.object({
|
||||||
|
webhookToExecute: executableWebhookSchema,
|
||||||
|
})
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
export const chatReplySchema = z.object({
|
export const chatReplySchema = z.object({
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Webhook as WebhookFromPrisma } from '@typebot.io/prisma'
|
import { Webhook as WebhookFromPrisma } from '@typebot.io/prisma'
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
export enum HttpMethod {
|
export enum HttpMethod {
|
||||||
POST = 'POST',
|
POST = 'POST',
|
||||||
@ -36,3 +37,12 @@ export const defaultWebhookAttributes: Omit<
|
|||||||
headers: [],
|
headers: [],
|
||||||
queryParams: [],
|
queryParams: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const executableWebhookSchema = z.object({
|
||||||
|
url: z.string(),
|
||||||
|
headers: z.record(z.string()).optional(),
|
||||||
|
body: z.unknown().optional(),
|
||||||
|
method: z.nativeEnum(HttpMethod).optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type ExecutableWebhook = z.infer<typeof executableWebhookSchema>
|
||||||
|
56
pnpm-lock.yaml
generated
56
pnpm-lock.yaml
generated
@ -825,16 +825,16 @@ importers:
|
|||||||
version: 7.21.5(@babel/core@7.21.8)
|
version: 7.21.5(@babel/core@7.21.8)
|
||||||
'@rollup/plugin-babel':
|
'@rollup/plugin-babel':
|
||||||
specifier: 6.0.3
|
specifier: 6.0.3
|
||||||
version: 6.0.3(@babel/core@7.21.8)(rollup@3.23.0)
|
version: 6.0.3(@babel/core@7.21.8)(rollup@3.20.2)
|
||||||
'@rollup/plugin-node-resolve':
|
'@rollup/plugin-node-resolve':
|
||||||
specifier: 15.0.2
|
specifier: 15.0.2
|
||||||
version: 15.0.2(rollup@3.23.0)
|
version: 15.0.2(rollup@3.20.2)
|
||||||
'@rollup/plugin-terser':
|
'@rollup/plugin-terser':
|
||||||
specifier: 0.4.3
|
specifier: 0.4.3
|
||||||
version: 0.4.3(rollup@3.23.0)
|
version: 0.4.3(rollup@3.20.2)
|
||||||
'@rollup/plugin-typescript':
|
'@rollup/plugin-typescript':
|
||||||
specifier: 11.1.1
|
specifier: 11.1.1
|
||||||
version: 11.1.1(rollup@3.23.0)(tslib@2.5.2)(typescript@5.0.4)
|
version: 11.1.1(rollup@3.20.2)(tslib@2.5.2)(typescript@5.0.4)
|
||||||
'@typebot.io/lib':
|
'@typebot.io/lib':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../lib
|
version: link:../../lib
|
||||||
@ -866,8 +866,8 @@ importers:
|
|||||||
specifier: 18.2.0
|
specifier: 18.2.0
|
||||||
version: 18.2.0
|
version: 18.2.0
|
||||||
rollup:
|
rollup:
|
||||||
specifier: 3.23.0
|
specifier: 3.20.2
|
||||||
version: 3.23.0
|
version: 3.20.2
|
||||||
rollup-plugin-postcss:
|
rollup-plugin-postcss:
|
||||||
specifier: 4.0.2
|
specifier: 4.0.2
|
||||||
version: 4.0.2(postcss@8.4.23)
|
version: 4.0.2(postcss@8.4.23)
|
||||||
@ -895,16 +895,16 @@ importers:
|
|||||||
version: 7.21.5(@babel/core@7.21.8)
|
version: 7.21.5(@babel/core@7.21.8)
|
||||||
'@rollup/plugin-babel':
|
'@rollup/plugin-babel':
|
||||||
specifier: 6.0.3
|
specifier: 6.0.3
|
||||||
version: 6.0.3(@babel/core@7.21.8)(rollup@3.23.0)
|
version: 6.0.3(@babel/core@7.21.8)(rollup@3.20.2)
|
||||||
'@rollup/plugin-node-resolve':
|
'@rollup/plugin-node-resolve':
|
||||||
specifier: 15.0.2
|
specifier: 15.0.2
|
||||||
version: 15.0.2(rollup@3.23.0)
|
version: 15.0.2(rollup@3.20.2)
|
||||||
'@rollup/plugin-terser':
|
'@rollup/plugin-terser':
|
||||||
specifier: 0.4.3
|
specifier: 0.4.3
|
||||||
version: 0.4.3(rollup@3.23.0)
|
version: 0.4.3(rollup@3.20.2)
|
||||||
'@rollup/plugin-typescript':
|
'@rollup/plugin-typescript':
|
||||||
specifier: 11.1.1
|
specifier: 11.1.1
|
||||||
version: 11.1.1(rollup@3.23.0)(tslib@2.5.2)(typescript@5.0.4)
|
version: 11.1.1(rollup@3.20.2)(tslib@2.5.2)(typescript@5.0.4)
|
||||||
'@typebot.io/js':
|
'@typebot.io/js':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../js
|
version: link:../js
|
||||||
@ -936,8 +936,8 @@ importers:
|
|||||||
specifier: 18.2.0
|
specifier: 18.2.0
|
||||||
version: 18.2.0
|
version: 18.2.0
|
||||||
rollup:
|
rollup:
|
||||||
specifier: 3.23.0
|
specifier: 3.20.2
|
||||||
version: 3.23.0
|
version: 3.20.2
|
||||||
rollup-plugin-typescript-paths:
|
rollup-plugin-typescript-paths:
|
||||||
specifier: 1.4.0
|
specifier: 1.4.0
|
||||||
version: 1.4.0(typescript@5.0.4)
|
version: 1.4.0(typescript@5.0.4)
|
||||||
@ -7675,7 +7675,7 @@ packages:
|
|||||||
reselect: 4.1.7
|
reselect: 4.1.7
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@rollup/plugin-babel@6.0.3(@babel/core@7.21.8)(rollup@3.23.0):
|
/@rollup/plugin-babel@6.0.3(@babel/core@7.21.8)(rollup@3.20.2):
|
||||||
resolution: {integrity: sha512-fKImZKppa1A/gX73eg4JGo+8kQr/q1HBQaCGKECZ0v4YBBv3lFqi14+7xyApECzvkLTHCifx+7ntcrvtBIRcpg==}
|
resolution: {integrity: sha512-fKImZKppa1A/gX73eg4JGo+8kQr/q1HBQaCGKECZ0v4YBBv3lFqi14+7xyApECzvkLTHCifx+7ntcrvtBIRcpg==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -7690,8 +7690,8 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.21.8
|
'@babel/core': 7.21.8
|
||||||
'@babel/helper-module-imports': 7.18.6
|
'@babel/helper-module-imports': 7.18.6
|
||||||
'@rollup/pluginutils': 5.0.2(rollup@3.23.0)
|
'@rollup/pluginutils': 5.0.2(rollup@3.20.2)
|
||||||
rollup: 3.23.0
|
rollup: 3.20.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@rollup/plugin-commonjs@24.0.0(rollup@2.78.0):
|
/@rollup/plugin-commonjs@24.0.0(rollup@2.78.0):
|
||||||
@ -7712,7 +7712,7 @@ packages:
|
|||||||
rollup: 2.78.0
|
rollup: 2.78.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@rollup/plugin-node-resolve@15.0.2(rollup@3.23.0):
|
/@rollup/plugin-node-resolve@15.0.2(rollup@3.20.2):
|
||||||
resolution: {integrity: sha512-Y35fRGUjC3FaurG722uhUuG8YHOJRJQbI6/CkbRkdPotSpDj9NtIN85z1zrcyDcCQIW4qp5mgG72U+gJ0TAFEg==}
|
resolution: {integrity: sha512-Y35fRGUjC3FaurG722uhUuG8YHOJRJQbI6/CkbRkdPotSpDj9NtIN85z1zrcyDcCQIW4qp5mgG72U+gJ0TAFEg==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -7721,16 +7721,16 @@ packages:
|
|||||||
rollup:
|
rollup:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rollup/pluginutils': 5.0.2(rollup@3.23.0)
|
'@rollup/pluginutils': 5.0.2(rollup@3.20.2)
|
||||||
'@types/resolve': 1.20.2
|
'@types/resolve': 1.20.2
|
||||||
deepmerge: 4.3.1
|
deepmerge: 4.3.1
|
||||||
is-builtin-module: 3.2.1
|
is-builtin-module: 3.2.1
|
||||||
is-module: 1.0.0
|
is-module: 1.0.0
|
||||||
resolve: 1.22.1
|
resolve: 1.22.1
|
||||||
rollup: 3.23.0
|
rollup: 3.20.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@rollup/plugin-terser@0.4.3(rollup@3.23.0):
|
/@rollup/plugin-terser@0.4.3(rollup@3.20.2):
|
||||||
resolution: {integrity: sha512-EF0oejTMtkyhrkwCdg0HJ0IpkcaVg1MMSf2olHb2Jp+1mnLM04OhjpJWGma4HobiDTF0WCyViWuvadyE9ch2XA==}
|
resolution: {integrity: sha512-EF0oejTMtkyhrkwCdg0HJ0IpkcaVg1MMSf2olHb2Jp+1mnLM04OhjpJWGma4HobiDTF0WCyViWuvadyE9ch2XA==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -7739,13 +7739,13 @@ packages:
|
|||||||
rollup:
|
rollup:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
rollup: 3.23.0
|
rollup: 3.20.2
|
||||||
serialize-javascript: 6.0.1
|
serialize-javascript: 6.0.1
|
||||||
smob: 1.1.1
|
smob: 1.1.1
|
||||||
terser: 5.17.6
|
terser: 5.17.6
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@rollup/plugin-typescript@11.1.1(rollup@3.23.0)(tslib@2.5.2)(typescript@5.0.4):
|
/@rollup/plugin-typescript@11.1.1(rollup@3.20.2)(tslib@2.5.2)(typescript@5.0.4):
|
||||||
resolution: {integrity: sha512-Ioir+x5Bejv72Lx2Zbz3/qGg7tvGbxQZALCLoJaGrkNXak/19+vKgKYJYM3i/fJxvsb23I9FuFQ8CUBEfsmBRg==}
|
resolution: {integrity: sha512-Ioir+x5Bejv72Lx2Zbz3/qGg7tvGbxQZALCLoJaGrkNXak/19+vKgKYJYM3i/fJxvsb23I9FuFQ8CUBEfsmBRg==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -7758,9 +7758,9 @@ packages:
|
|||||||
tslib:
|
tslib:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rollup/pluginutils': 5.0.2(rollup@3.23.0)
|
'@rollup/pluginutils': 5.0.2(rollup@3.20.2)
|
||||||
resolve: 1.22.2
|
resolve: 1.22.2
|
||||||
rollup: 3.23.0
|
rollup: 3.20.2
|
||||||
tslib: 2.5.2
|
tslib: 2.5.2
|
||||||
typescript: 5.0.4
|
typescript: 5.0.4
|
||||||
dev: true
|
dev: true
|
||||||
@ -7780,7 +7780,7 @@ packages:
|
|||||||
rollup: 2.78.0
|
rollup: 2.78.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@rollup/pluginutils@5.0.2(rollup@3.23.0):
|
/@rollup/pluginutils@5.0.2(rollup@3.20.2):
|
||||||
resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==}
|
resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -7792,7 +7792,7 @@ packages:
|
|||||||
'@types/estree': 1.0.0
|
'@types/estree': 1.0.0
|
||||||
estree-walker: 2.0.2
|
estree-walker: 2.0.2
|
||||||
picomatch: 2.3.1
|
picomatch: 2.3.1
|
||||||
rollup: 3.23.0
|
rollup: 3.20.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@rushstack/eslint-patch@1.2.0:
|
/@rushstack/eslint-patch@1.2.0:
|
||||||
@ -19451,8 +19451,8 @@ packages:
|
|||||||
fsevents: 2.3.2
|
fsevents: 2.3.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/rollup@3.23.0:
|
/rollup@3.20.2:
|
||||||
resolution: {integrity: sha512-h31UlwEi7FHihLe1zbk+3Q7z1k/84rb9BSwmBSr/XjOCEaBJ2YyedQDuM0t/kfOS0IxM+vk1/zI9XxYj9V+NJQ==}
|
resolution: {integrity: sha512-3zwkBQl7Ai7MFYQE0y1MeQ15+9jsi7XxfrqwTb/9EK8D9C9+//EBR4M+CuA1KODRaNbFez/lWxA5vhEGZp4MUg==}
|
||||||
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
|
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
@ -21642,7 +21642,7 @@ packages:
|
|||||||
esbuild: 0.17.12
|
esbuild: 0.17.12
|
||||||
postcss: 8.4.23
|
postcss: 8.4.23
|
||||||
resolve: 1.22.2
|
resolve: 1.22.2
|
||||||
rollup: 3.23.0
|
rollup: 3.20.2
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.2
|
fsevents: 2.3.2
|
||||||
dev: false
|
dev: false
|
||||||
|
Reference in New Issue
Block a user