parent
d6b8fc6f10
commit
3ea02d3cc1
@ -0,0 +1,7 @@ |
||||
--- |
||||
'@rocket.chat/meteor': major |
||||
'@rocket.chat/core-typings': patch |
||||
'@rocket.chat/rest-typings': patch |
||||
--- |
||||
|
||||
Removed the deprecated "Compatible Sandbox" option from integration scripts and the dependencies that this sandbox mode relied on. |
||||
@ -1,88 +0,0 @@ |
||||
import * as Models from '@rocket.chat/models'; |
||||
import moment from 'moment'; |
||||
import _ from 'underscore'; |
||||
|
||||
import * as s from '../../../../../lib/utils/stringUtils'; |
||||
import { deasyncPromise } from '../../../../../server/deasync/deasync'; |
||||
import { httpCall } from '../../../../../server/lib/http/call'; |
||||
|
||||
const forbiddenModelMethods: readonly (keyof typeof Models)[] = ['registerModel', 'getCollectionName']; |
||||
|
||||
type ModelName = Exclude<keyof typeof Models, (typeof forbiddenModelMethods)[number]>; |
||||
|
||||
export type Vm2Sandbox<IsIncoming extends boolean> = { |
||||
scriptTimeout: (reject: (reason?: any) => void) => ReturnType<typeof setTimeout>; |
||||
_: typeof _; |
||||
s: typeof s; |
||||
console: typeof console; |
||||
moment: typeof moment; |
||||
Promise: typeof Promise; |
||||
Store: { |
||||
set: IsIncoming extends true ? (key: string, value: any) => any : (key: string, value: any) => void; |
||||
get: (key: string) => any; |
||||
}; |
||||
HTTP: (method: string, url: string, options: Record<string, any>) => unknown; |
||||
} & (IsIncoming extends true ? { Livechat: undefined } : never) & |
||||
Record<ModelName, (typeof Models)[ModelName]>; |
||||
|
||||
export const buildSandbox = <IsIncoming extends boolean>( |
||||
store: Record<string, any>, |
||||
isIncoming?: IsIncoming, |
||||
): { |
||||
store: Record<string, any>; |
||||
sandbox: Vm2Sandbox<IsIncoming>; |
||||
} => { |
||||
const httpAsync = async (method: string, url: string, options: Record<string, any>) => { |
||||
try { |
||||
return { |
||||
result: await httpCall(method, url, options), |
||||
}; |
||||
} catch (error) { |
||||
return { error }; |
||||
} |
||||
}; |
||||
|
||||
const sandbox = { |
||||
scriptTimeout(reject: (reason?: any) => void) { |
||||
return setTimeout(() => reject('timed out'), 3000); |
||||
}, |
||||
_, |
||||
s, |
||||
console, |
||||
moment, |
||||
Promise, |
||||
// There's a small difference between the sandbox that is sent to incoming and to outgoing scripts
|
||||
// Technically we could unify this but since we're deprecating vm2 anyway I'm keeping this old behavior here until the feature is removed completely
|
||||
...(isIncoming |
||||
? { |
||||
Livechat: undefined, |
||||
Store: { |
||||
set: (key: string, val: any): any => { |
||||
store[key] = val; |
||||
return val; |
||||
}, |
||||
get: (key: string) => store[key], |
||||
}, |
||||
} |
||||
: { |
||||
Store: { |
||||
set: (key: string, val: any): void => { |
||||
store[key] = val; |
||||
}, |
||||
get: (key: string) => store[key], |
||||
}, |
||||
}), |
||||
HTTP: (method: string, url: string, options: Record<string, any>) => { |
||||
// TODO: deprecate, track and alert
|
||||
return deasyncPromise(httpAsync(method, url, options)); |
||||
}, |
||||
} as Vm2Sandbox<IsIncoming>; |
||||
|
||||
(Object.keys(Models) as ModelName[]) |
||||
.filter((k) => !forbiddenModelMethods.includes(k)) |
||||
.forEach((k) => { |
||||
sandbox[k] = Models[k]; |
||||
}); |
||||
|
||||
return { store, sandbox }; |
||||
}; |
||||
@ -1,111 +0,0 @@ |
||||
import type { IIntegration } from '@rocket.chat/core-typings'; |
||||
import { VM, VMScript } from 'vm2'; |
||||
|
||||
import { IntegrationScriptEngine } from '../ScriptEngine'; |
||||
import type { IScriptClass } from '../definition'; |
||||
import { buildSandbox, type Vm2Sandbox } from './buildSandbox'; |
||||
|
||||
const DISABLE_INTEGRATION_SCRIPTS = ['yes', 'true', 'vm2'].includes(String(process.env.DISABLE_INTEGRATION_SCRIPTS).toLowerCase()); |
||||
|
||||
export class VM2ScriptEngine<IsIncoming extends boolean> extends IntegrationScriptEngine<IsIncoming> { |
||||
protected isDisabled(): boolean { |
||||
return DISABLE_INTEGRATION_SCRIPTS; |
||||
} |
||||
|
||||
protected buildSandbox(store: Record<string, any> = {}): { store: Record<string, any>; sandbox: Vm2Sandbox<IsIncoming> } { |
||||
return buildSandbox<IsIncoming>(store, this.incoming); |
||||
} |
||||
|
||||
protected async runScriptMethod({ |
||||
integrationId, |
||||
script, |
||||
method, |
||||
params, |
||||
}: { |
||||
integrationId: IIntegration['_id']; |
||||
script: IScriptClass; |
||||
method: keyof IScriptClass; |
||||
params: Record<string, any>; |
||||
}): Promise<any> { |
||||
const { sandbox } = this.buildSandbox(this.compiledScripts[integrationId].store); |
||||
|
||||
const vm = new VM({ |
||||
timeout: 3000, |
||||
sandbox: { |
||||
...sandbox, |
||||
script, |
||||
method, |
||||
params, |
||||
...(this.incoming && 'request' in params ? { request: params.request } : {}), |
||||
}, |
||||
}); |
||||
|
||||
return new Promise((resolve, reject) => { |
||||
process.nextTick(async () => { |
||||
try { |
||||
const scriptResult = await vm.run(` |
||||
new Promise((resolve, reject) => { |
||||
scriptTimeout(reject); |
||||
try { |
||||
resolve(script[method](params)) |
||||
} catch(e) { |
||||
reject(e); |
||||
} |
||||
}).catch((error) => { throw new Error(error); }); |
||||
`);
|
||||
|
||||
resolve(scriptResult); |
||||
} catch (e) { |
||||
reject(e); |
||||
} |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
protected async getIntegrationScript(integration: IIntegration): Promise<Partial<IScriptClass>> { |
||||
if (this.disabled) { |
||||
throw new Error('integration-scripts-disabled'); |
||||
} |
||||
|
||||
const compiledScript = this.compiledScripts[integration._id]; |
||||
if (compiledScript && +compiledScript._updatedAt === +integration._updatedAt) { |
||||
return compiledScript.script; |
||||
} |
||||
|
||||
const script = integration.scriptCompiled; |
||||
const { store, sandbox } = this.buildSandbox(); |
||||
|
||||
try { |
||||
this.logger.info({ msg: 'Will evaluate script of Trigger', integration: integration.name }); |
||||
this.logger.debug(script); |
||||
|
||||
const vmScript = new VMScript(`${script}; Script;`, 'script.js'); |
||||
const vm = new VM({ |
||||
sandbox, |
||||
}); |
||||
|
||||
const ScriptClass = vm.run(vmScript); |
||||
|
||||
if (ScriptClass) { |
||||
this.compiledScripts[integration._id] = { |
||||
script: new ScriptClass(), |
||||
store, |
||||
_updatedAt: integration._updatedAt, |
||||
}; |
||||
|
||||
return this.compiledScripts[integration._id].script; |
||||
} |
||||
} catch (err) { |
||||
this.logger.error({ |
||||
msg: 'Error evaluating Script in Trigger', |
||||
integration: integration.name, |
||||
script, |
||||
err, |
||||
}); |
||||
throw new Error('error-evaluating-script'); |
||||
} |
||||
|
||||
this.logger.error({ msg: 'Class "Script" not in Trigger', integration: integration.name }); |
||||
throw new Error('class-script-not-found'); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue