aimyapi 1.0.9 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,28 +1,29 @@
1
1
  import "quickjs-emscripten";
2
- import { QuickJSDeferredPromise } from "quickjs-emscripten";
2
+ import { isSuccess, QuickJSContext, QuickJSDeferredPromise } from "quickjs-emscripten";
3
+ import { z, ZodError, ZodObject } from "zod";
4
+ import { Logger } from "./logger";
3
5
 
6
+ type QuickJSPromiseWithId = QuickJSDeferredPromise & { _promiseId: number };
4
7
 
5
8
  let promiseId = 0;
6
- let openPromises = {};
9
+ let openPromises: Record<number, QuickJSPromiseWithId | null> = {};
7
10
 
8
- export function wrapPromise(vm, promise: Promise<any>, beginAsyncProcess: () => void, endAsyncProcess: () => void): any {
11
+ export function wrapPromise(vm: QuickJSContext, promise: Promise<any>, beginAsyncProcess: () => void, endAsyncProcess: () => void, logger: Logger): any {
9
12
  promiseId += 1;
10
13
 
11
- const vmPromise = vm.newPromise()
14
+ const vmPromise = vm.newPromise() as QuickJSPromiseWithId
12
15
  vmPromise._promiseId = promiseId;
13
16
  beginAsyncProcess();
14
17
  promise.then((...args) => {
15
18
  //console.log("Promised resolved", args);
16
19
  endAsyncProcess();
17
- const wrappedArgs = args.map((arg) => wrap(vm, arg, undefined, beginAsyncProcess, endAsyncProcess));
20
+ const wrappedArgs = args.map((arg) => wrap(vm, arg, undefined, beginAsyncProcess, endAsyncProcess, logger));
18
21
  openPromises[vmPromise._promiseId] = null;
19
22
  vmPromise.resolve(...wrappedArgs);
20
23
  wrappedArgs.forEach((arg) => arg.dispose());
21
- }).catch((...args) => {
22
- //console.log("Promised rejected", args);
23
- const wrappedArgs = args.map((arg) => wrap(vm, arg, undefined, beginAsyncProcess, endAsyncProcess));
24
- vmPromise.reject(...wrappedArgs);
25
- wrappedArgs.forEach((arg) => arg.dispose());
24
+ }).catch(error => {
25
+ vmPromise.reject(vm.newError(error?.message || "An error occurred"));
26
+
26
27
  openPromises[vmPromise._promiseId] = null;
27
28
  });
28
29
  vmPromise.settled.then( () => {
@@ -35,37 +36,42 @@ export function wrapPromise(vm, promise: Promise<any>, beginAsyncProcess: () =>
35
36
  return vmPromise.handle
36
37
  }
37
38
 
38
- export function rejectOpenPromises(vm) {
39
+ export function rejectOpenPromises(vm: QuickJSContext) {
39
40
  for (const [id, promise ] of Object.entries(openPromises)) {
40
41
  if (promise !== null) {
41
42
  (promise as QuickJSDeferredPromise).reject(vm.newError("Shutting down"));
42
- openPromises[id] = null;
43
+ openPromises[id as unknown as number] = null;
43
44
  }
44
45
  }
45
46
 
46
47
  vm.runtime.executePendingJobs();
47
48
  }
48
49
 
49
- export function wrap(vm, value: any, context: object, beginAsyncProcess: () => void, endAsyncProcess: () => void): any {
50
+ const isPromise = (value: any) => { return (typeof value === 'object' && typeof value.then === 'function') };
51
+
52
+ // Value - member variable to wrap
53
+ // context - owning object
54
+ export function wrap(vm: QuickJSContext, value: any, context: object | undefined, beginAsyncProcess: () => void, endAsyncProcess: () => void, logger: Logger): any {
50
55
  if (value === null) {
56
+
51
57
  return null;
52
- } else if (typeof value === 'object' && typeof value.then === 'function') {
53
- //console.log("Wrapped promise");
54
- return wrapPromise(vm, value, beginAsyncProcess, endAsyncProcess);
58
+ } else if (isPromise(value)) {
59
+ return wrapPromise(vm, value, beginAsyncProcess, endAsyncProcess, logger);
55
60
  } else if (typeof value === "function") {
56
61
  //console.log("Wrapped function")
57
- return wrapGenericFunction(vm, value, context, beginAsyncProcess, endAsyncProcess);
62
+ return wrapGenericFunction(vm, value, context, beginAsyncProcess, endAsyncProcess, logger);
58
63
  } else if (Array.isArray(value)) {
59
64
  //console.log("Wrapped array")
60
- return wrapArray(vm, value, beginAsyncProcess, endAsyncProcess);
65
+ return wrapArray(vm, value, beginAsyncProcess, endAsyncProcess, logger);
61
66
  } else if (value === null) {
62
67
  //console.log("Wrapped null")
63
68
  return vm.null;
64
69
  } else if (value instanceof Date) {
65
- return vm.evalCode(`new Date(${value.getTime()})`).value;
70
+ const res = vm.evalCode(`new Date(${value.getTime()})`);
71
+ return isSuccess(res) ? res.value : null;
66
72
  } else if (typeof value === "object") {
67
73
  //console.log("Wrapped object")
68
- return wrapObject(vm, value, beginAsyncProcess, endAsyncProcess);
74
+ return wrapObject(vm, value, beginAsyncProcess, endAsyncProcess, logger);
69
75
  } else if (typeof value === "string") {
70
76
  //console.log("Wrapped string")
71
77
  return vm.newString(value)
@@ -77,20 +83,21 @@ export function wrap(vm, value: any, context: object, beginAsyncProcess: () =>
77
83
  return vm.newBigInt(value);
78
84
  } else if (typeof value === "boolean") {
79
85
  // since there is no newBoolean, we have to do this
80
- return vm.evalCode(value ? "true" : "false").value;
86
+ const res = vm.evalCode(value ? "true" : "false")
87
+ return isSuccess(res) ? res.value : null;
81
88
  } else if (typeof value === "undefined") {
82
89
  return vm.undefined
83
90
  } else {
91
+ console.error("Unknown type", value)
84
92
  return null
85
93
  }
86
94
  }
87
95
 
88
- export function wrapObject(vm, obj: any, beginAsyncProcess: () => void, endAsyncProcess: () => void): any {
96
+ export function wrapObject(vm: QuickJSContext, obj: any, beginAsyncProcess: () => void, endAsyncProcess: () => void, logger: Logger): any {
89
97
  const vmObject = vm.newObject();
90
98
 
91
99
  for (const [key, value] of Object.entries(obj)) {
92
- //console.log("wrapping", key, value)
93
- let wrappedValue = wrap(vm, value, obj, beginAsyncProcess, endAsyncProcess);
100
+ let wrappedValue = wrap(vm, value, obj, beginAsyncProcess, endAsyncProcess, logger);
94
101
 
95
102
  if (wrappedValue !== null) {
96
103
  vm.setProp(vmObject, key, wrappedValue);
@@ -103,30 +110,30 @@ export function wrapObject(vm, obj: any, beginAsyncProcess: () => void, endAsync
103
110
  return vmObject;
104
111
  }
105
112
 
106
- export function wrapObjectWhitelist(vm, obj: any, whitelist: string[], beginAsyncProcess: () => void, endAsyncProcess: () => void): any {
113
+ export function wrapObjectWhitelist(vm: QuickJSContext, obj: any, whitelist: string[], beginAsyncProcess: () => void, endAsyncProcess: () => void, logger: Logger): any {
107
114
  const vmObject = vm.newObject();
108
115
 
109
116
  whitelist.forEach((key) => {
110
- const value = obj[key];
111
- //console.log("wrapping", key, value)
112
- const wrappedValue = wrap(vm, value, obj, beginAsyncProcess, endAsyncProcess);
113
-
114
- if (wrappedValue !== null) {
115
- vm.setProp(vmObject, key, wrappedValue);
116
- if (wrappedValue != vm.undefined && wrappedValue != vm.null && wrappedValue.dispose) {
117
- wrappedValue.dispose();
117
+ // console.log("wrapping", key, value)
118
+ const value = obj[key];
119
+ const wrappedValue = wrap(vm, value, obj, beginAsyncProcess, endAsyncProcess, logger);
120
+
121
+ if (wrappedValue !== null) {
122
+ vm.setProp(vmObject, key, wrappedValue);
123
+ if (wrappedValue != vm.undefined && wrappedValue != vm.null && wrappedValue.dispose) {
124
+ wrappedValue.dispose();
125
+ }
118
126
  }
119
- }
120
127
  });
121
128
 
122
129
  return vmObject;
123
130
  }
124
131
 
125
- export function wrapArray(vm, arr: any[], beginAsyncProcess: () => void, endAsyncProcess: () => void): any {
132
+ export function wrapArray(vm: QuickJSContext, arr: any[], beginAsyncProcess: () => void, endAsyncProcess: () => void, logger: Logger): any {
126
133
  const vmArray = vm.newArray();
127
134
 
128
135
  for (let i = 0; i < arr.length; i++) {
129
- let wrappedValue = wrap(vm, arr[i], arr, beginAsyncProcess, endAsyncProcess);
136
+ let wrappedValue = wrap(vm, arr[i], arr, beginAsyncProcess, endAsyncProcess, logger);
130
137
  vm.setProp(vmArray, i, wrappedValue);
131
138
  if (wrappedValue != vm.undefined && wrappedValue != vm.null) {
132
139
  wrappedValue.dispose();
@@ -136,26 +143,64 @@ export function wrapArray(vm, arr: any[], beginAsyncProcess: () => void, endAsyn
136
143
  return vmArray;
137
144
  }
138
145
 
139
- export function wrapGenericFunction(vm, fn: Function, context: object = undefined, beginAsyncProcess: () => void, endAsyncProcess: () => void): any {
146
+ function validationDecorator(schema: ZodObject) {
147
+ return function(...args: unknown[]) {
148
+ let argumentObject: Record<string, unknown> = {};
149
+ const params = Object.keys(schema.def.shape);
150
+ args.forEach((value, index) => {
151
+ if(index < params.length) {
152
+ argumentObject[params[index]] = value;
153
+ }
154
+ });
155
+
156
+ schema.parse(argumentObject);
157
+ }
158
+ }
159
+
160
+ export function wrapGenericFunction(vm: QuickJSContext, fn: Function, context: object | undefined = undefined, beginAsyncProcess: () => void, endAsyncProcess: () => void, logger: Logger): any {
140
161
  const vmFn = vm.newFunction(fn.name, (...args) => {
141
162
  const unwrappedArgs: any[] = args.map(vm.dump)
142
163
  //console.log("Wrapped function called", fn.name, unwrappedArgs )
143
164
  try {
165
+ const validatorName = `_params_${fn.name}`;
166
+ const hasValidator = context != undefined && validatorName in context && "parse" in (context as Record<string, any>)[validatorName];
167
+
168
+ if(hasValidator) {
169
+ const schema = (context as Record<string, ZodObject>)[validatorName];
170
+ try {
171
+ const validator = validationDecorator(schema);
172
+ validator(...unwrappedArgs); // will throw if validation fails
173
+ } catch(e) {
174
+ // Throwing (not vm.throw) is how a host function raises an exception in the VM;
175
+ // newFunction catches it and turns it into a sandbox exception.
176
+ if(e instanceof ZodError) {
177
+ throw new Error(`Parameter validation failed for function "${fn.name}". Expected Parameters: [${Object.keys(schema.def.shape).join(",")}]. Errors:\n${z.prettifyError(e)}`);
178
+ }
179
+ throw new Error(`Parameter validation error calling ${fn.name}`);
180
+ }
181
+ }
182
+
144
183
  const result = fn.call(context, ...unwrappedArgs);
145
- //console.log("result", result);
146
- const wrappedResult = wrap(vm, result, undefined, beginAsyncProcess, endAsyncProcess);
184
+
185
+ logger.trace(fn.name, unwrappedArgs, result);
186
+
187
+ const wrappedResult = wrap(vm, result, undefined, beginAsyncProcess, endAsyncProcess, logger);
147
188
  return wrappedResult;
148
- } catch (e) {
149
- console.log("Error", e);
189
+ } catch (e: any) {
190
+ logger.error([e], "sandbox");
191
+ if(e instanceof Error) {
192
+ throw e;
193
+ }
194
+ throw new Error("An exception occurred calling " + fn.name);
150
195
  }
151
196
 
152
197
  });
153
198
  return vmFn;
154
199
  }
155
200
 
156
- export function injectTimingFunctions(vm, cbAddedTimer, cbRemovedTimer, maxTimeout = 600000): void {
201
+ export function injectTimingFunctions(vm: QuickJSContext, cbAddedTimer: () => void, cbRemovedTimer: () => void, maxTimeout = 600000): void {
157
202
  // Should we impose a limit on how long things can run for?
158
- const timeoutFunctionHandles = {};
203
+ const timeoutFunctionHandles: any = {};
159
204
  const _setTimeout = vm.newFunction("setTimeout", (vmFnHandle, timeoutHandle) => {
160
205
  // Make a copy because otherwise vmFnHandle does not live long enough to call after the timeout
161
206
  const vmFnHandleCopy = vmFnHandle.dup()
@@ -181,12 +226,12 @@ export function injectTimingFunctions(vm, cbAddedTimer, cbRemovedTimer, maxTimeo
181
226
  }, timeout)
182
227
  timeoutFunctionHandles[timeoutID.toString()] = vmFnHandleCopy;
183
228
 
184
- return vm.newNumber(timeoutID);
229
+ return vm.newNumber(timeoutID as unknown as number);
185
230
  });
186
231
 
187
232
  vm.setProp(vm.global, "setTimeout", _setTimeout);
188
233
  _setTimeout.dispose();
189
- const intervalFunctionHandles = {};
234
+ const intervalFunctionHandles: any = {};
190
235
  const _setInterval = vm.newFunction("setInterval", (vmFnHandle, timeoutHandle) => {
191
236
  // Make a copy because otherwise vmFnHandle does not live long enough to call after the timeout
192
237
  const vmFnHandleCopy = vmFnHandle.dup()
@@ -221,7 +266,7 @@ export function injectTimingFunctions(vm, cbAddedTimer, cbRemovedTimer, maxTimeo
221
266
  }, timeout);
222
267
  intervalFunctionHandles[intervalId.toString()] = vmFnHandleCopy;
223
268
 
224
- return vm.newNumber(intervalId);
269
+ return vm.newNumber(intervalId as unknown as number);
225
270
  });
226
271
  vm.setProp(vm.global, "setInterval", _setInterval);
227
272
  _setInterval.dispose();