2022-03-09 15:12:00 +01:00
|
|
|
import { LinkedTypebot } from 'contexts/TypebotContext'
|
|
|
|
import { Log } from 'db'
|
2022-01-18 18:25:18 +01:00
|
|
|
import {
|
|
|
|
LogicStep,
|
|
|
|
LogicStepType,
|
|
|
|
LogicalOperator,
|
|
|
|
ConditionStep,
|
|
|
|
Variable,
|
|
|
|
ComparisonOperators,
|
2022-01-20 07:21:08 +01:00
|
|
|
SetVariableStep,
|
|
|
|
RedirectStep,
|
2022-02-04 19:00:08 +01:00
|
|
|
Comparison,
|
2022-03-07 17:39:57 +01:00
|
|
|
CodeStep,
|
2022-03-09 15:12:00 +01:00
|
|
|
TypebotLinkStep,
|
|
|
|
PublicTypebot,
|
|
|
|
Typebot,
|
|
|
|
Edge,
|
2022-03-28 17:07:47 +02:00
|
|
|
VariableWithValue,
|
2022-01-18 18:25:18 +01:00
|
|
|
} 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'
|
2022-03-02 18:58:10 +01:00
|
|
|
import { evaluateExpression, parseVariables } from './variable'
|
2022-01-18 18:25:18 +01:00
|
|
|
|
2022-01-20 07:21:08 +01:00
|
|
|
type EdgeId = string
|
2022-02-04 19:00:08 +01:00
|
|
|
|
2022-03-09 15:12:00 +01:00
|
|
|
type LogicContext = {
|
|
|
|
isPreview: boolean
|
|
|
|
apiHost: string
|
|
|
|
typebot: PublicTypebot
|
|
|
|
linkedTypebots: LinkedTypebot[]
|
2022-03-31 10:33:35 +02:00
|
|
|
setCurrentTypebotId: (id: string) => void
|
2022-03-09 15:12:00 +01:00
|
|
|
updateVariableValue: (variableId: string, value: string) => void
|
2022-03-28 17:07:47 +02:00
|
|
|
updateVariables: (variables: VariableWithValue[]) => void
|
2022-03-09 15:12:00 +01:00
|
|
|
injectLinkedTypebot: (typebot: Typebot | PublicTypebot) => LinkedTypebot
|
|
|
|
onNewLog: (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => void
|
|
|
|
createEdge: (edge: Edge) => void
|
|
|
|
}
|
|
|
|
|
|
|
|
export const executeLogic = async (
|
2022-01-18 18:25:18 +01:00
|
|
|
step: LogicStep,
|
2022-03-09 15:12:00 +01:00
|
|
|
context: LogicContext
|
|
|
|
): Promise<{
|
|
|
|
nextEdgeId?: EdgeId
|
|
|
|
linkedTypebot?: PublicTypebot | LinkedTypebot
|
|
|
|
}> => {
|
2022-01-18 18:25:18 +01:00
|
|
|
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:
|
2022-03-25 16:25:37 +01:00
|
|
|
return { nextEdgeId: await executeCode(step, context) }
|
2022-03-09 15:12:00 +01:00
|
|
|
case LogicStepType.TYPEBOT_LINK:
|
|
|
|
return await executeTypebotLink(step, context)
|
2022-01-18 18:25:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-20 07:21:08 +01:00
|
|
|
const executeSetVariable = (
|
|
|
|
step: SetVariableStep,
|
2022-03-28 17:07:47 +02:00
|
|
|
{ typebot: { variables }, updateVariableValue, updateVariables }: LogicContext
|
2022-01-20 07:21:08 +01:00
|
|
|
): EdgeId | undefined => {
|
|
|
|
if (!step.options?.variableId || !step.options.expressionToEvaluate)
|
2022-02-04 19:00:08 +01:00
|
|
|
return step.outgoingEdgeId
|
2022-01-20 07:21:08 +01:00
|
|
|
const expression = step.options.expressionToEvaluate
|
2022-03-02 18:58:10 +01:00
|
|
|
const evaluatedExpression = evaluateExpression(
|
|
|
|
parseVariables(variables)(expression)
|
|
|
|
)
|
2022-03-28 17:07:47 +02:00
|
|
|
const existingVariable = variables.find(byId(step.options.variableId))
|
|
|
|
if (!existingVariable) return step.outgoingEdgeId
|
|
|
|
updateVariableValue(existingVariable.id, evaluatedExpression)
|
|
|
|
updateVariables([{ ...existingVariable, value: evaluatedExpression }])
|
2022-02-04 19:00:08 +01:00
|
|
|
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 => {
|
2022-02-04 19:00:08 +01:00
|
|
|
const { content } = step.items[0]
|
2022-01-20 07:21:08 +01:00
|
|
|
const isConditionPassed =
|
2022-02-04 19:00:08 +01:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2022-01-18 18:25:18 +01:00
|
|
|
const executeComparison =
|
2022-02-04 19:00:08 +01:00
|
|
|
(variables: Variable[]) => (comparison: Comparison) => {
|
2022-01-18 18:25:18 +01:00
|
|
|
if (!comparison?.variableId) return false
|
2022-02-04 19:00:08 +01:00
|
|
|
const inputValue =
|
|
|
|
variables.find((v) => v.id === comparison.variableId)?.value ?? ''
|
2022-02-20 11:21:04 +01:00
|
|
|
const value = parseVariables(variables)(comparison.value)
|
2022-02-02 08:05:02 +01:00
|
|
|
if (isNotDefined(value)) return false
|
2022-01-18 18:25:18 +01:00
|
|
|
switch (comparison.comparisonOperator) {
|
|
|
|
case ComparisonOperators.CONTAINS: {
|
2022-02-23 18:50:41 +01:00
|
|
|
return inputValue.toString().includes(value.toString())
|
2022-01-18 18:25:18 +01:00
|
|
|
}
|
|
|
|
case ComparisonOperators.EQUAL: {
|
2022-02-23 18:50:41 +01:00
|
|
|
return inputValue.toString() === value.toString()
|
2022-01-18 18:25:18 +01:00
|
|
|
}
|
|
|
|
case ComparisonOperators.NOT_EQUAL: {
|
2022-02-23 18:50:41 +01:00
|
|
|
return inputValue.toString() !== value.toString()
|
2022-01-18 18:25:18 +01:00
|
|
|
}
|
|
|
|
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 => {
|
2022-02-04 19:00:08 +01:00
|
|
|
if (!step.options?.url) return step.outgoingEdgeId
|
2022-01-20 07:21:08 +01:00
|
|
|
window.open(
|
2022-02-07 18:06:37 +01:00
|
|
|
sanitizeUrl(parseVariables(variables)(step.options?.url)),
|
2022-01-20 07:21:08 +01:00
|
|
|
step.options.isNewTab ? '_blank' : '_self'
|
|
|
|
)
|
2022-02-04 19:00:08 +01:00
|
|
|
return step.outgoingEdgeId
|
2022-01-20 07:21:08 +01:00
|
|
|
}
|
2022-03-07 17:39:57 +01:00
|
|
|
|
2022-03-25 16:25:37 +01:00
|
|
|
const executeCode = async (
|
2022-03-15 18:53:59 +01:00
|
|
|
step: CodeStep,
|
|
|
|
{ typebot: { variables } }: LogicContext
|
|
|
|
) => {
|
2022-03-07 17:39:57 +01:00
|
|
|
if (!step.options.content) return
|
2022-03-25 16:25:37 +01:00
|
|
|
await 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
|
|
|
|
}> => {
|
2022-03-31 10:33:35 +02:00
|
|
|
const { typebot, linkedTypebots, onNewLog, createEdge, setCurrentTypebotId } =
|
|
|
|
context
|
2022-03-09 15:12:00 +01:00
|
|
|
const linkedTypebot =
|
2022-03-25 14:59:40 +01:00
|
|
|
step.options.typebotId === 'current'
|
|
|
|
? typebot
|
|
|
|
: [typebot, ...linkedTypebots].find(byId(step.options.typebotId)) ??
|
|
|
|
(await fetchAndInjectTypebot(step, context))
|
2022-03-09 15:12:00 +01:00
|
|
|
if (!linkedTypebot) {
|
|
|
|
onNewLog({
|
|
|
|
status: 'error',
|
|
|
|
description: 'Failed to link typebot',
|
|
|
|
details: '',
|
|
|
|
})
|
|
|
|
return { nextEdgeId: step.outgoingEdgeId }
|
|
|
|
}
|
2022-03-31 10:33:35 +02:00
|
|
|
setCurrentTypebotId(linkedTypebot.id)
|
2022-03-09 15:12:00 +01:00
|
|
|
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)
|
|
|
|
}
|