diff options
author | Tyler Schicke <tyler_schicke@brown.edu> | 2019-04-11 17:16:46 -0400 |
---|---|---|
committer | Tyler Schicke <tyler_schicke@brown.edu> | 2019-04-11 17:16:46 -0400 |
commit | 6dd21d8ca48beef6d5054f60d04bb4412b6b33b5 (patch) | |
tree | 7e70d5766096558834923ce5e34810f12a89a895 /src | |
parent | 6e90a5a7f4d4ed3e92d2ff8af6c1400768c95cd0 (diff) |
Fixed another subtle server related bug
Added optional capturing to scripts
Diffstat (limited to 'src')
-rw-r--r-- | src/client/SocketStub.ts | 7 | ||||
-rw-r--r-- | src/client/util/Scripting.ts | 33 | ||||
-rw-r--r-- | src/client/views/collections/CollectionBaseView.tsx | 9 | ||||
-rw-r--r-- | src/fields/ScriptField.ts | 63 |
4 files changed, 85 insertions, 27 deletions
diff --git a/src/client/SocketStub.ts b/src/client/SocketStub.ts index 257973e3d..5e2ca6a98 100644 --- a/src/client/SocketStub.ts +++ b/src/client/SocketStub.ts @@ -62,10 +62,13 @@ export class SocketStub { public static SEND_FIELDS_REQUEST(fieldIds: FieldId[], callback: (fields: FieldMap) => any) { Utils.EmitCallback(Server.Socket, MessageStore.GetFields, fieldIds, (fields: any[]) => { let fieldMap: any = {}; + let proms: Promise<any>[] = []; for (let field of fields) { - fieldMap[field._id] = ServerUtils.FromJson(field); + let f = ServerUtils.FromJson(field); + fieldMap[field._id] = f; + proms.push(new Promise(res => f.init(res))); } - callback(fieldMap); + Promise.all(proms).then(() => callback(fieldMap)); }); } diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index 9015f21cf..c67cc067a 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -54,15 +54,20 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an let paramNames = ["KeyStore", "Documents", ...fieldTypes.map(fn => fn.name)]; let params: any[] = [KeyStore, Documents, ...fieldTypes]; let compiledFunction = new Function(...paramNames, `return ${script}`); + let { capturedVariables = {} } = options; let run = (args: { [name: string]: any } = {}): ScriptResult => { let argsArray: any[] = []; for (let name of customParams) { if (name === "this") { continue; } - argsArray.push(args[name]); + if (name in args) { + argsArray.push(args[name]); + } else { + argsArray.push(capturedVariables[name]); + } } - let thisParam = args.this; + let thisParam = args.this || capturedVariables.this; try { const result = compiledFunction.apply(thisParam, params).apply(thisParam, argsArray); return { success: true, result }; @@ -130,22 +135,30 @@ export interface ScriptOptions { requiredType?: string; addReturn?: boolean; params?: { [name: string]: string }; + capturedVariables?: { [name: string]: Field }; } -export function CompileScript(script: string, { requiredType = "", addReturn = false, params = {} }: ScriptOptions = {}): CompileResult { +export function CompileScript(script: string, options: ScriptOptions = {}): CompileResult { + const { requiredType = "", addReturn = false, params = {}, capturedVariables = {} } = options; let host = new ScriptingCompilerHost; - let paramArray: string[] = []; - if ("this" in params) { - paramArray.push("this"); + let paramNames: string[] = []; + if ("this" in params || "this" in capturedVariables) { + paramNames.push("this"); } for (const key in params) { if (key === "this") continue; - paramArray.push(key); + paramNames.push(key); } - let paramString = paramArray.map(key => { + let paramList = paramNames.map(key => { const val = params[key]; return `${key}: ${val}`; - }).join(", "); + }); + for (const key in capturedVariables) { + if (key === "this") continue; + paramNames.push(key); + paramList.push(`${key}: ${capturedVariables[key].constructor.name}`); + } + let paramString = paramList.join(", "); let funcScript = `(function(${paramString})${requiredType ? `: ${requiredType}` : ''} { ${addReturn ? `return ${script};` : script} })`; @@ -157,7 +170,7 @@ export function CompileScript(script: string, { requiredType = "", addReturn = f let diagnostics = ts.getPreEmitDiagnostics(program).concat(testResult.diagnostics); - return Run(outputText, paramArray, diagnostics, script, { requiredType, addReturn, params }); + return Run(outputText, paramNames, diagnostics, script, options); } export function OrLiteralType(returnType: string): string { diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index 4380c8194..ac320eda3 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -8,6 +8,8 @@ import { ListField } from '../../../fields/ListField'; import { NumberField } from '../../../fields/NumberField'; import { ContextMenu } from '../ContextMenu'; import { FieldViewProps } from '../nodes/FieldView'; +import { CompileScript } from '../../util/Scripting'; +import { ScriptField } from '../../../fields/ScriptField'; export enum CollectionViewType { Invalid, @@ -107,15 +109,18 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { const field = new ListField([doc]); // const script = CompileScript(` // if(added) { - // console.log("added " + field.Title); + // console.log("added " + field.Title + " " + doc.Title); // } else { - // console.log("removed " + field.Title); + // console.log("removed " + field.Title + " " + doc.Title); // } // `, { // addReturn: false, // params: { // field: Document.name, // added: "boolean" + // }, + // capturedVariables: { + // doc: this.props.Document // } // }); // if (script.compiled) { diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index 24c1d9b3a..34c8a7544 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -2,33 +2,34 @@ import { Field, FieldId } from "./Field"; import { Types } from "../server/Message"; import { CompileScript, ScriptOptions, CompiledScript } from "../client/util/Scripting"; import { Server } from "../client/Server"; +import { Without } from "../Utils"; + +export interface SerializableOptions extends Without<ScriptOptions, "capturedVariables"> { + capturedIds: { [id: string]: string }; +} export interface ScriptData { script: string; - options: ScriptOptions; + options: SerializableOptions; } export class ScriptField extends Field { - readonly script: CompiledScript; + private _script?: CompiledScript; + get script(): CompiledScript { + return this._script!; + } + private options?: ScriptData; - constructor(script: CompiledScript, id?: FieldId, save: boolean = true) { + constructor(script?: CompiledScript, id?: FieldId, save: boolean = true) { super(id); - this.script = script; + this._script = script; if (save) { Server.UpdateField(this); } } - static FromJson(id: string, data: ScriptData): ScriptField { - const script = CompileScript(data.script, data.options); - if (!script.compiled) { - throw new Error("Can't compile script"); - } - return new ScriptField(script, id, false); - } - ToScriptString() { return "new ScriptField(...)"; } @@ -45,14 +46,50 @@ export class ScriptField extends Field { throw new Error("Script fields currently can't be updated"); } + static FromJson(id: string, data: ScriptData): ScriptField { + let field = new ScriptField(undefined, id, false); + field.options = data; + return field; + } + + init(callback: (res: Field) => any) { + const options = this.options!; + const keys = Object.keys(options.options.capturedIds); + Server.GetFields(keys).then(fields => { + let captured: { [name: string]: Field } = {}; + keys.forEach(key => captured[options.options.capturedIds[key]] = fields[key]); + const opts: ScriptOptions = { + addReturn: options.options.addReturn, + params: options.options.params, + requiredType: options.options.requiredType, + capturedVariables: captured + }; + const script = CompileScript(options.script, opts); + if (!script.compiled) { + throw new Error("Can't compile script"); + } + this._script = script; + callback(this); + }); + } + ToJson(): { _id: string, type: Types, data: ScriptData } { const { options, originalScript } = this.script; + let capturedIds: { [id: string]: string } = {}; + for (const capt in options.capturedVariables) { + capturedIds[options.capturedVariables[capt].Id] = capt; + } + const opts: SerializableOptions = { + ...options, + capturedIds + }; + delete (opts as any).capturedVariables; return { _id: this.Id, type: Types.Script, data: { script: originalScript, - options + options: opts, }, }; } |