163 lines
5.8 KiB
TypeScript
163 lines
5.8 KiB
TypeScript
import { SessionState, SetVariableBlock, Variable } from '@typebot.io/schemas'
|
|
import { byId, isEmpty } from '@typebot.io/lib'
|
|
import { ExecuteLogicResponse } from '../../../types'
|
|
import { parseScriptToExecuteClientSideAction } from '../script/executeScript'
|
|
import { parseGuessedValueType } from '@typebot.io/variables/parseGuessedValueType'
|
|
import { parseVariables } from '@typebot.io/variables/parseVariables'
|
|
import { updateVariablesInSession } from '@typebot.io/variables/updateVariablesInSession'
|
|
import { createId } from '@paralleldrive/cuid2'
|
|
import { utcToZonedTime, format as tzFormat } from 'date-fns-tz'
|
|
import vm from 'vm'
|
|
|
|
export const executeSetVariable = (
|
|
state: SessionState,
|
|
block: SetVariableBlock
|
|
): ExecuteLogicResponse => {
|
|
const { variables } = state.typebotsQueue[0].typebot
|
|
if (!block.options?.variableId)
|
|
return {
|
|
outgoingEdgeId: block.outgoingEdgeId,
|
|
}
|
|
const expressionToEvaluate = getExpressionToEvaluate(state)(block.options)
|
|
const isCustomValue = !block.options.type || block.options.type === 'Custom'
|
|
if (
|
|
expressionToEvaluate &&
|
|
!state.whatsApp &&
|
|
((isCustomValue && block.options.isExecutedOnClient) ||
|
|
block.options.type === 'Moment of the day')
|
|
) {
|
|
const scriptToExecute = parseScriptToExecuteClientSideAction(
|
|
variables,
|
|
expressionToEvaluate
|
|
)
|
|
return {
|
|
outgoingEdgeId: block.outgoingEdgeId,
|
|
clientSideActions: [
|
|
{
|
|
type: 'setVariable',
|
|
setVariable: {
|
|
scriptToExecute,
|
|
},
|
|
expectsDedicatedReply: true,
|
|
},
|
|
],
|
|
}
|
|
}
|
|
const evaluatedExpression = expressionToEvaluate
|
|
? evaluateSetVariableExpression(variables)(expressionToEvaluate)
|
|
: undefined
|
|
const existingVariable = variables.find(byId(block.options.variableId))
|
|
if (!existingVariable) return { outgoingEdgeId: block.outgoingEdgeId }
|
|
const newVariable = {
|
|
...existingVariable,
|
|
value: evaluatedExpression,
|
|
}
|
|
const newSessionState = updateVariablesInSession(state)([newVariable])
|
|
return {
|
|
outgoingEdgeId: block.outgoingEdgeId,
|
|
newSessionState,
|
|
}
|
|
}
|
|
|
|
const evaluateSetVariableExpression =
|
|
(variables: Variable[]) =>
|
|
(str: string): unknown => {
|
|
const isSingleVariable =
|
|
str.startsWith('{{') && str.endsWith('}}') && str.split('{{').length === 2
|
|
if (isSingleVariable) return parseVariables(variables)(str)
|
|
// To avoid octal number evaluation
|
|
if (!isNaN(str as unknown as number) && /0[^.].+/.test(str)) return str
|
|
const evaluating = parseVariables(variables, { fieldToParse: 'id' })(
|
|
`(function() {${str.includes('return ') ? str : 'return ' + str}})()`
|
|
)
|
|
try {
|
|
const sandbox = vm.createContext({
|
|
...Object.fromEntries(
|
|
variables.map((v) => [v.id, parseGuessedValueType(v.value)])
|
|
),
|
|
fetch,
|
|
})
|
|
return vm.runInContext(evaluating, sandbox)
|
|
} catch (err) {
|
|
return parseVariables(variables)(str)
|
|
}
|
|
}
|
|
|
|
const getExpressionToEvaluate =
|
|
(state: SessionState) =>
|
|
(options: SetVariableBlock['options']): string | null => {
|
|
switch (options?.type) {
|
|
case 'Contact name':
|
|
return state.whatsApp?.contact.name ?? null
|
|
case 'Phone number': {
|
|
const phoneNumber = state.whatsApp?.contact.phoneNumber
|
|
return phoneNumber ? `"${state.whatsApp?.contact.phoneNumber}"` : null
|
|
}
|
|
case 'Now': {
|
|
const timeZone = parseVariables(
|
|
state.typebotsQueue[0].typebot.variables
|
|
)(options.timeZone)
|
|
if (isEmpty(timeZone)) return 'new Date().toISOString()'
|
|
return toISOWithTz(new Date(), timeZone)
|
|
}
|
|
|
|
case 'Today':
|
|
return 'new Date().toISOString()'
|
|
case 'Tomorrow': {
|
|
const timeZone = parseVariables(
|
|
state.typebotsQueue[0].typebot.variables
|
|
)(options.timeZone)
|
|
if (isEmpty(timeZone))
|
|
return 'new Date(Date.now() + 86400000).toISOString()'
|
|
return toISOWithTz(new Date(Date.now() + 86400000), timeZone)
|
|
}
|
|
case 'Yesterday': {
|
|
const timeZone = parseVariables(
|
|
state.typebotsQueue[0].typebot.variables
|
|
)(options.timeZone)
|
|
if (isEmpty(timeZone))
|
|
return 'new Date(Date.now() - 86400000).toISOString()'
|
|
return toISOWithTz(new Date(Date.now() - 86400000), timeZone)
|
|
}
|
|
case 'Random ID': {
|
|
return `"${createId()}"`
|
|
}
|
|
case 'Result ID':
|
|
case 'User ID': {
|
|
return state.typebotsQueue[0].resultId ?? `"${createId()}"`
|
|
}
|
|
case 'Map item with same index': {
|
|
return `const itemIndex = ${options.mapListItemParams?.baseListVariableId}.indexOf(${options.mapListItemParams?.baseItemVariableId})
|
|
return ${options.mapListItemParams?.targetListVariableId}.at(itemIndex)`
|
|
}
|
|
case 'Append value(s)': {
|
|
return `if(!${options.item}) return ${options.variableId};
|
|
if(!${options.variableId}) return [${options.item}];
|
|
if(!Array.isArray(${options.variableId})) return [${options.variableId}, ${options.item}];
|
|
return (${options.variableId}).concat(${options.item});`
|
|
}
|
|
case 'Empty': {
|
|
return null
|
|
}
|
|
case 'Moment of the day': {
|
|
return `const now = new Date()
|
|
if(now.getHours() < 12) return 'morning'
|
|
if(now.getHours() >= 12 && now.getHours() < 18) return 'afternoon'
|
|
if(now.getHours() >= 18) return 'evening'
|
|
if(now.getHours() >= 22 || now.getHours() < 6) return 'night'`
|
|
}
|
|
case 'Environment name': {
|
|
return state.whatsApp ? 'whatsapp' : 'web'
|
|
}
|
|
case 'Custom':
|
|
case undefined: {
|
|
return options?.expressionToEvaluate ?? null
|
|
}
|
|
}
|
|
}
|
|
|
|
const toISOWithTz = (date: Date, timeZone: string) => {
|
|
const zonedDate = utcToZonedTime(date, timeZone)
|
|
return tzFormat(zonedDate, "yyyy-MM-dd'T'HH:mm:ssXXX", { timeZone })
|
|
}
|