2
0
Files
bot/packages/bot-engine/src/services/logic.ts

188 lines
5.6 KiB
TypeScript
Raw Normal View History

2022-03-09 15:12:00 +01:00
import { LinkedTypebot } from 'contexts/TypebotContext'
import { Log } from 'db'
import {
LogicStep,
LogicStepType,
LogicalOperator,
ConditionStep,
Variable,
ComparisonOperators,
2022-01-20 07:21:08 +01:00
SetVariableStep,
RedirectStep,
Comparison,
2022-03-07 17:39:57 +01:00
CodeStep,
2022-03-09 15:12:00 +01:00
TypebotLinkStep,
PublicTypebot,
Typebot,
Edge,
} from 'models'
2022-03-09 15:12:00 +01:00
import { byId, isDefined, isNotDefined, sendRequest } from 'utils'
2022-01-20 07:21:08 +01:00
import { sanitizeUrl } from './utils'
import { evaluateExpression, parseVariables } from './variable'
2022-01-20 07:21:08 +01:00
type EdgeId = string
2022-03-09 15:12:00 +01:00
type LogicContext = {
isPreview: boolean
apiHost: string
typebot: PublicTypebot
linkedTypebots: LinkedTypebot[]
updateVariableValue: (variableId: string, value: string) => void
injectLinkedTypebot: (typebot: Typebot | PublicTypebot) => LinkedTypebot
onNewLog: (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => void
createEdge: (edge: Edge) => void
}
export const executeLogic = async (
step: LogicStep,
2022-03-09 15:12:00 +01:00
context: LogicContext
): Promise<{
nextEdgeId?: EdgeId
linkedTypebot?: PublicTypebot | LinkedTypebot
}> => {
switch (step.type) {
2022-01-20 07:21:08 +01:00
case LogicStepType.SET_VARIABLE:
2022-03-09 15:12:00 +01:00
return { nextEdgeId: executeSetVariable(step, context) }
2022-01-20 07:21:08 +01:00
case LogicStepType.CONDITION:
2022-03-09 15:12:00 +01:00
return { nextEdgeId: executeCondition(step, context) }
2022-01-20 07:21:08 +01:00
case LogicStepType.REDIRECT:
2022-03-09 15:12:00 +01:00
return { nextEdgeId: executeRedirect(step, context) }
2022-03-07 17:39:57 +01:00
case LogicStepType.CODE:
return { nextEdgeId: executeCode(step, context) }
2022-03-09 15:12:00 +01:00
case LogicStepType.TYPEBOT_LINK:
return await executeTypebotLink(step, context)
}
}
2022-01-20 07:21:08 +01:00
const executeSetVariable = (
step: SetVariableStep,
2022-03-09 15:12:00 +01:00
{ typebot: { variables }, updateVariableValue }: LogicContext
2022-01-20 07:21:08 +01:00
): EdgeId | undefined => {
if (!step.options?.variableId || !step.options.expressionToEvaluate)
return step.outgoingEdgeId
2022-01-20 07:21:08 +01:00
const expression = step.options.expressionToEvaluate
const evaluatedExpression = evaluateExpression(
parseVariables(variables)(expression)
)
2022-01-20 07:21:08 +01:00
updateVariableValue(step.options.variableId, evaluatedExpression)
return step.outgoingEdgeId
2022-01-20 07:21:08 +01:00
}
const executeCondition = (
step: ConditionStep,
2022-03-09 15:12:00 +01:00
{ typebot: { variables } }: LogicContext
2022-01-20 07:21:08 +01:00
): EdgeId | undefined => {
const { content } = step.items[0]
2022-01-20 07:21:08 +01:00
const isConditionPassed =
content.logicalOperator === LogicalOperator.AND
? content.comparisons.every(executeComparison(variables))
: content.comparisons.some(executeComparison(variables))
return isConditionPassed ? step.items[0].outgoingEdgeId : step.outgoingEdgeId
2022-01-20 07:21:08 +01:00
}
const executeComparison =
(variables: Variable[]) => (comparison: Comparison) => {
if (!comparison?.variableId) return false
const inputValue =
variables.find((v) => v.id === comparison.variableId)?.value ?? ''
const value = parseVariables(variables)(comparison.value)
if (isNotDefined(value)) return false
switch (comparison.comparisonOperator) {
case ComparisonOperators.CONTAINS: {
return inputValue.toString().includes(value.toString())
}
case ComparisonOperators.EQUAL: {
return inputValue.toString() === value.toString()
}
case ComparisonOperators.NOT_EQUAL: {
return inputValue.toString() !== value.toString()
}
case ComparisonOperators.GREATER: {
return parseFloat(inputValue) >= parseFloat(value)
}
case ComparisonOperators.LESS: {
return parseFloat(inputValue) <= parseFloat(value)
}
case ComparisonOperators.IS_SET: {
return isDefined(inputValue) && inputValue.length > 0
}
}
}
2022-01-20 07:21:08 +01:00
const executeRedirect = (
step: RedirectStep,
2022-03-09 15:12:00 +01:00
{ typebot: { variables } }: LogicContext
2022-01-20 07:21:08 +01:00
): EdgeId | undefined => {
if (!step.options?.url) return step.outgoingEdgeId
2022-01-20 07:21:08 +01:00
window.open(
sanitizeUrl(parseVariables(variables)(step.options?.url)),
2022-01-20 07:21:08 +01:00
step.options.isNewTab ? '_blank' : '_self'
)
return step.outgoingEdgeId
2022-01-20 07:21:08 +01:00
}
2022-03-07 17:39:57 +01:00
const executeCode = (
step: CodeStep,
{ typebot: { variables } }: LogicContext
) => {
2022-03-07 17:39:57 +01:00
if (!step.options.content) return
Function(parseVariables(variables)(step.options.content))()
2022-03-07 17:39:57 +01:00
return step.outgoingEdgeId
}
2022-03-09 15:12:00 +01:00
const executeTypebotLink = async (
step: TypebotLinkStep,
context: LogicContext
): Promise<{
nextEdgeId?: EdgeId
linkedTypebot?: PublicTypebot | LinkedTypebot
}> => {
const { typebot, linkedTypebots, onNewLog, createEdge } = context
const linkedTypebot =
[typebot, ...linkedTypebots].find(byId(step.options.typebotId)) ??
(await fetchAndInjectTypebot(step, context))
if (!linkedTypebot) {
onNewLog({
status: 'error',
description: 'Failed to link typebot',
details: '',
})
return { nextEdgeId: step.outgoingEdgeId }
}
const nextBlockId =
step.options.blockId ??
linkedTypebot.blocks.find((b) => b.steps.some((s) => s.type === 'start'))
?.id
if (!nextBlockId) return { nextEdgeId: step.outgoingEdgeId }
const newEdge: Edge = {
id: (Math.random() * 1000).toString(),
from: { stepId: '', blockId: '' },
to: {
blockId: nextBlockId,
},
}
createEdge(newEdge)
return {
nextEdgeId: newEdge.id,
linkedTypebot: {
...linkedTypebot,
edges: [...linkedTypebot.edges, newEdge],
},
}
}
const fetchAndInjectTypebot = async (
step: TypebotLinkStep,
{ apiHost, injectLinkedTypebot, isPreview }: LogicContext
): Promise<LinkedTypebot | undefined> => {
const { data, error } = isPreview
? await sendRequest<{ typebot: Typebot }>(
`/api/typebots/${step.options.typebotId}`
)
: await sendRequest<{ typebot: PublicTypebot }>(
`${apiHost}/api/publicTypebots/${step.options.typebotId}`
)
if (!data || error) return
return injectLinkedTypebot(data.typebot)
}