2
0
Files
bot/packages/variables/executeFunction.ts
Baptiste Arnaud 8d66b52a39 🔒 Use isolated-vm
2024-05-22 12:12:02 +02:00

108 lines
2.9 KiB
TypeScript

import { parseVariables } from './parseVariables'
import { extractVariablesFromText } from './extractVariablesFromText'
import { parseGuessedValueType } from './parseGuessedValueType'
import { isDefined } from '@typebot.io/lib'
import { safeStringify } from '@typebot.io/lib/safeStringify'
import { Variable } from './types'
import ivm from 'isolated-vm'
const defaultTimeout = 10 * 1000
type Props = {
variables: Variable[]
body: string
args?: Record<string, unknown>
}
export const executeFunction = async ({
variables,
body,
args: initialArgs,
}: Props) => {
const parsedBody = parseVariables(variables, {
fieldToParse: 'id',
})(body)
const args = (
extractVariablesFromText(variables)(body).map((variable) => ({
id: variable.id,
value: parseGuessedValueType(variable.value),
})) as { id: string; value: unknown }[]
).concat(
initialArgs
? Object.entries(initialArgs).map(([id, value]) => ({ id, value }))
: []
)
let updatedVariables: Record<string, any> = {}
const setVariable = (key: string, value: any) => {
updatedVariables[key] = value
}
const isolate = new ivm.Isolate()
const context = isolate.createContextSync()
const jail = context.global
jail.setSync('global', jail.derefInto())
context.evalClosure(
'globalThis.setVariable = (...args) => $0.apply(undefined, args, { arguments: { copy: true }, promise: true, result: { copy: true, promise: true } })',
[new ivm.Reference(setVariable)]
)
context.evalClosure(
'globalThis.fetch = (...args) => $0.apply(undefined, args, { arguments: { copy: true }, promise: true, result: { copy: true, promise: true } })',
[
new ivm.Reference(async (...args: any[]) => {
// @ts-ignore
const response = await fetch(...args)
return response.text()
}),
]
)
args.forEach(({ id, value }) => {
jail.setSync(id, value)
})
const run = (code: string) =>
context.evalClosure(
`return (async function() {
const AsyncFunction = async function () {}.constructor;
return new AsyncFunction($0)();
}())`,
[code],
{ result: { copy: true, promise: true }, timeout: defaultTimeout }
)
try {
const output = await run(parsedBody)
console.log('Output', output)
return {
output: safeStringify(output) ?? '',
newVariables: Object.entries(updatedVariables)
.map(([name, value]) => {
const existingVariable = variables.find((v) => v.name === name)
if (!existingVariable) return
return {
id: existingVariable.id,
name: existingVariable.name,
value,
}
})
.filter(isDefined),
}
} catch (e) {
console.log('Error while executing script')
console.error(e)
const error =
typeof e === 'string'
? e
: e instanceof Error
? e.message
: JSON.stringify(e)
return {
error,
output: error,
}
}
}