quickjs-emscripten-sync 1.3.0 → 1.4.0
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.
- package/README.md +36 -34
- package/dist/index.d.ts +17 -15
- package/dist/quickjs-emscripten-sync.es.js +146 -135
- package/dist/quickjs-emscripten-sync.umd.js +6 -6
- package/package.json +3 -3
- package/src/index.test.ts +146 -60
- package/src/index.ts +40 -26
- package/src/marshal/function.test.ts +61 -53
- package/src/marshal/function.ts +7 -6
- package/src/marshal/index.test.ts +73 -65
- package/src/marshal/index.ts +12 -11
- package/src/marshal/json.test.ts +30 -30
- package/src/marshal/json.ts +4 -3
- package/src/marshal/object.test.ts +55 -50
- package/src/marshal/object.ts +6 -5
- package/src/marshal/primitive.test.ts +21 -17
- package/src/marshal/primitive.ts +10 -9
- package/src/marshal/promise.test.ts +14 -14
- package/src/marshal/promise.ts +3 -3
- package/src/marshal/properties.test.ts +17 -15
- package/src/marshal/properties.ts +10 -9
- package/src/marshal/symbol.test.ts +6 -6
- package/src/marshal/symbol.ts +5 -4
- package/src/unmarshal/function.test.ts +45 -43
- package/src/unmarshal/function.ts +9 -8
- package/src/unmarshal/index.test.ts +41 -40
- package/src/unmarshal/index.ts +9 -8
- package/src/unmarshal/object.test.ts +33 -31
- package/src/unmarshal/object.ts +12 -11
- package/src/unmarshal/primitive.test.ts +18 -15
- package/src/unmarshal/primitive.ts +9 -9
- package/src/unmarshal/promise.test.ts +11 -11
- package/src/unmarshal/promise.ts +9 -11
- package/src/unmarshal/properties.test.ts +6 -6
- package/src/unmarshal/properties.ts +47 -44
- package/src/unmarshal/symbol.test.ts +6 -6
- package/src/unmarshal/symbol.ts +4 -4
- package/src/util.test.ts +1 -0
- package/src/vmmap.test.ts +72 -88
- package/src/vmmap.ts +16 -16
- package/src/vmutil.test.ts +71 -73
- package/src/vmutil.ts +22 -18
- package/src/wrapper.test.ts +111 -88
- package/src/wrapper.ts +31 -27
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@ This library wraps [quickjs-emscripten](https://github.com/justjake/quickjs-emsc
|
|
|
12
12
|
- Functions
|
|
13
13
|
- Classes and instances
|
|
14
14
|
- Objects with prototypes and any property descriptors
|
|
15
|
+
- Promises
|
|
15
16
|
- Expose objects as a global object in QuickJS
|
|
16
17
|
- Marshaling limitation for specific objects
|
|
17
18
|
- Register a pair of objects that will be considered the same between the browser and QuickJS
|
|
@@ -32,10 +33,10 @@ class Cls {
|
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
const
|
|
36
|
-
const arena = new Arena(
|
|
36
|
+
const ctx = (await getQuickJS()).newContext();
|
|
37
|
+
const arena = new Arena(ctx, { isMarshalable: true });
|
|
37
38
|
|
|
38
|
-
// We can pass objects to the
|
|
39
|
+
// We can pass objects to the context and run code safely
|
|
39
40
|
const exposed = {
|
|
40
41
|
Cls,
|
|
41
42
|
cls: new Cls(),
|
|
@@ -48,12 +49,12 @@ arena.evalCode(`cls.field`); // returns 0
|
|
|
48
49
|
arena.evalCode(`cls.method()`); // returns 1
|
|
49
50
|
arena.evalCode(`cls.field`); // returns 1
|
|
50
51
|
|
|
51
|
-
arena.evalCode(`syncedCls.field`);
|
|
52
|
-
exposed.syncedCls.method();
|
|
53
|
-
arena.evalCode(`syncedCls.field`);
|
|
52
|
+
arena.evalCode(`syncedCls.field`); // returns 0
|
|
53
|
+
exposed.syncedCls.method(); // returns 1
|
|
54
|
+
arena.evalCode(`syncedCls.field`); // returns 1
|
|
54
55
|
|
|
55
56
|
arena.dispose();
|
|
56
|
-
|
|
57
|
+
ctx.dispose();
|
|
57
58
|
```
|
|
58
59
|
|
|
59
60
|
[Example code](src/index.test.ts) is available as the unit test code.
|
|
@@ -72,15 +73,14 @@ import { getQuickJS } from "quickjs-emscripten";
|
|
|
72
73
|
import { Arena } from "quickjs-emscripten-sync";
|
|
73
74
|
|
|
74
75
|
(async function() {
|
|
75
|
-
const
|
|
76
|
-
const vm = quickjs.createVm();
|
|
76
|
+
const ctx = (await getQuickJS()).newContext();
|
|
77
77
|
|
|
78
78
|
// init Arena
|
|
79
79
|
// ⚠️ Marshaling is opt-in for security reasons.
|
|
80
80
|
// ⚠️ Be careful when activating marshalling.
|
|
81
|
-
const arena = new Arena(
|
|
81
|
+
const arena = new Arena(ctx, { isMarshalable: true });
|
|
82
82
|
|
|
83
|
-
// expose objects as global objects in QuickJS
|
|
83
|
+
// expose objects as global objects in QuickJS context
|
|
84
84
|
arena.expose({
|
|
85
85
|
console: {
|
|
86
86
|
log: console.log
|
|
@@ -99,15 +99,15 @@ import { Arena } from "quickjs-emscripten-sync";
|
|
|
99
99
|
data.hoge = "changed!";
|
|
100
100
|
console.log(arena.evalCode(`data.hoge`)); // "changed!"
|
|
101
101
|
|
|
102
|
-
// Don't forget calling arena.dispose() before disposing QuickJS
|
|
102
|
+
// Don't forget calling arena.dispose() before disposing QuickJS context!
|
|
103
103
|
arena.dispose();
|
|
104
|
-
|
|
104
|
+
ctx.dispose();
|
|
105
105
|
})();
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
## Marshaling Limitations
|
|
109
109
|
|
|
110
|
-
Objects are automatically converted when they cross between the host and the QuickJS
|
|
110
|
+
Objects are automatically converted when they cross between the host and the QuickJS context. The conversion of a host's object to a handle is called marshaling, and the conversion of a handle to a host's object is called unmarshaling.
|
|
111
111
|
|
|
112
112
|
And for marshalling, it is possible to control whether the conversion is performed or not.
|
|
113
113
|
|
|
@@ -116,7 +116,7 @@ For example, exposing the host's global object to QuickJS is very heavy and dang
|
|
|
116
116
|
```js
|
|
117
117
|
import { Arena, complexity } from "quickjs-emscripten-sync";
|
|
118
118
|
|
|
119
|
-
const arena = new Arena(
|
|
119
|
+
const arena = new Arena(ctx, {
|
|
120
120
|
isMarshalable: (target: any) => {
|
|
121
121
|
// prevent passing globalThis to QuickJS
|
|
122
122
|
if (target === window) return false;
|
|
@@ -183,11 +183,11 @@ By default, quickjs-emscripten-sync doesn't prevent any marshaling, even in such
|
|
|
183
183
|
|
|
184
184
|
### `Arena`
|
|
185
185
|
|
|
186
|
-
The Arena class manages all generated handles at once by quickjs-emscripten and automatically converts objects between the host and the QuickJS
|
|
186
|
+
The Arena class manages all generated handles at once by quickjs-emscripten and automatically converts objects between the host and the QuickJS context.
|
|
187
187
|
|
|
188
|
-
#### `new Arena(
|
|
188
|
+
#### `new Arena(ctx: QuickJSContext, options?: Options)`
|
|
189
189
|
|
|
190
|
-
Constructs a new Arena instance. It requires a quickjs-emscripten
|
|
190
|
+
Constructs a new Arena instance. It requires a quickjs-emscripten context initialized with `quickjs.newContext()`.
|
|
191
191
|
|
|
192
192
|
Options accepted:
|
|
193
193
|
|
|
@@ -195,20 +195,22 @@ Options accepted:
|
|
|
195
195
|
type Options = {
|
|
196
196
|
isMarshalable?: boolean | "json" | ((target: any) => boolean | "json");
|
|
197
197
|
registeredObjects?: Iterable<[any, QuickJSHandle | string]>;
|
|
198
|
+
compat?: boolean;
|
|
198
199
|
}
|
|
199
200
|
```
|
|
200
201
|
|
|
201
|
-
- **`isMarshalable`**: Determines how marshalling will be done when sending objects from the host to the
|
|
202
|
-
- `"json"` (**default**, safety): Target object will be serialized as JSON in host and then parsed in
|
|
202
|
+
- **`isMarshalable`**: Determines how marshalling will be done when sending objects from the host to the context. **Make sure to set the marshalling to be the minimum necessary as it may reduce the security of your application.** [Please read the section on security above.](#security-warning)
|
|
203
|
+
- `"json"` (**default**, safety): Target object will be serialized as JSON in host and then parsed in context. Functions and classes will be lost in the process.
|
|
203
204
|
- `false` (safety): Target object will not be always marshalled as `undefined`.
|
|
204
205
|
- `(target: any) => boolean | "json"` (recoomended): You can control marshalling mode for each objects. If you want to do marshalling, usually use this method. Allow partial marshalling by returning `true` only for some objects.
|
|
205
206
|
- `true` (**risky and not recommended**): Target object will be always marshaled. This setting may reduce security.
|
|
206
|
-
- **`registeredObjects`**: You can pre-register a pair of objects that will be considered the same between the host and the QuickJS
|
|
207
|
+
- **`registeredObjects`**: You can pre-register a pair of objects that will be considered the same between the host and the QuickJS context. This will be used automatically during the conversion. By default, it will be registered automatically with [`defaultRegisteredObjects`](src/default.ts). If you want to add a new pair to this, please do the following:
|
|
208
|
+
- **`compat`**: If you want to use quickjs-emscripten v0.15 or older, enable this option to automatically inject code to the VM for compatibility.
|
|
207
209
|
|
|
208
210
|
```js
|
|
209
211
|
import { defaultRegisteredObjects } from "quickjs-emscripten-sync";
|
|
210
212
|
|
|
211
|
-
const arena = new Arena(
|
|
213
|
+
const arena = new Arena(ctx, {
|
|
212
214
|
registeredObjects: [
|
|
213
215
|
...defaultRegisteredObjects,
|
|
214
216
|
[Math, "Math"]
|
|
@@ -216,36 +218,36 @@ const arena = new Arena(vm, {
|
|
|
216
218
|
});
|
|
217
219
|
```
|
|
218
220
|
|
|
219
|
-
Instead of a string, you can also pass a QuickJSHandle directly. In that case, however, you have to dispose of them manually when destroying the
|
|
221
|
+
Instead of a string, you can also pass a QuickJSHandle directly. In that case, however, you have to dispose of them manually when destroying the context.
|
|
220
222
|
|
|
221
223
|
#### `dispose()`
|
|
222
224
|
|
|
223
|
-
Dispose of the arena and managed handles. This method won't dispose the
|
|
225
|
+
Dispose of the arena and managed handles. This method won't dispose the context itself, so the context has to be disposed of manually.
|
|
224
226
|
|
|
225
227
|
#### `evalCode<T = any>(code: string): T | undefined`
|
|
226
228
|
|
|
227
|
-
Evaluate JS code in the
|
|
229
|
+
Evaluate JS code in the context and get the result as an object on the host side. It also converts and re-throws error objects when an error is thrown during evaluation.
|
|
228
230
|
|
|
229
231
|
#### `executePendingJobs(): number`
|
|
230
232
|
|
|
231
|
-
Almost same as `
|
|
233
|
+
Almost same as `ctx.runtime.executePendingJobs()`, but it converts and re-throws error objects when an error is thrown during evaluation.
|
|
232
234
|
|
|
233
235
|
#### `expose(obj: { [k: string]: any })`
|
|
234
236
|
|
|
235
|
-
Expose objects as global objects in the
|
|
237
|
+
Expose objects as global objects in the context.
|
|
236
238
|
|
|
237
|
-
By default, exposed objects are not synchronized between the host and the
|
|
239
|
+
By default, exposed objects are not synchronized between the host and the context.
|
|
238
240
|
If you want to sync an objects, first wrap the object with `sync` method, and then expose the wrapped object.
|
|
239
241
|
|
|
240
242
|
#### `sync<T>(target: T): T`
|
|
241
243
|
|
|
242
|
-
Enables sync for the object between the host and the
|
|
244
|
+
Enables sync for the object between the host and the context and returns objects wrapped with proxies.
|
|
243
245
|
|
|
244
|
-
The return value is necessary in order to reflect changes to the object from the host to the
|
|
246
|
+
The return value is necessary in order to reflect changes to the object from the host to the context. Please note that setting a value in the field or deleting a field in the original object will not synchronize it.
|
|
245
247
|
|
|
246
248
|
#### `register(target: any, code: string | QuickJSHandle)`
|
|
247
249
|
|
|
248
|
-
Register a pair of objects that will be considered the same between the host and the QuickJS
|
|
250
|
+
Register a pair of objects that will be considered the same between the host and the QuickJS context.
|
|
249
251
|
|
|
250
252
|
#### `unregisterAll(targets: Iterable<[any, string | QuickJSHandle]>)`
|
|
251
253
|
|
|
@@ -271,9 +273,9 @@ Measure the complexity of an object as you traverse the field and prototype chai
|
|
|
271
273
|
|
|
272
274
|
### How to work
|
|
273
275
|
|
|
274
|
-
quickjs-emscripten can execute JS code safely, but it requires to deal with a lot of handles and lifetimes. Also, when destroying the
|
|
276
|
+
quickjs-emscripten can execute JS code safely, but it requires to deal with a lot of handles and lifetimes. Also, when destroying the context, any un-destroyed handle will result in an error.
|
|
275
277
|
|
|
276
|
-
quickjs-emscripten-sync will automatically manage all handles once generated by QuickJS
|
|
278
|
+
quickjs-emscripten-sync will automatically manage all handles once generated by QuickJS context in an Arena class.
|
|
277
279
|
And it will automatically "marshal" objects as handles and "unmarshal" handles as objects to enable seamless data exchange between the browser and QuickJS. It recursively traverses the object properties and prototype chain to transform objects. A function is called after its arguments and this arg are automatically converted for the environment in which the function is defined. The return value will be automatically converted to match the environment of the caller.
|
|
278
280
|
Most objects are wrapped by proxies during conversion, allowing "set" and "delete" operations on objects to be synchronized between the browser and QuickJS.
|
|
279
281
|
|
|
@@ -281,7 +283,7 @@ Most objects are wrapped by proxies during conversion, allowing "set" and "delet
|
|
|
281
283
|
|
|
282
284
|
#### Class constructor
|
|
283
285
|
|
|
284
|
-
When initializing a new instance, it is not possible to fully proxy this arg (a.k.a. `new.target`) inside the class constructor. Therefore, after the constructor call, the fields set for this are re-set to this on the
|
|
286
|
+
When initializing a new instance, it is not possible to fully proxy this arg (a.k.a. `new.target`) inside the class constructor. Therefore, after the constructor call, the fields set for this are re-set to this on the context side. Therefore, there may be some edge cases where the constructor may not work properly.
|
|
285
287
|
|
|
286
288
|
```js
|
|
287
289
|
class Cls {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
+
import type { QuickJSContext } from 'quickjs-emscripten';
|
|
1
2
|
import type { QuickJSDeferredPromise } from 'quickjs-emscripten';
|
|
2
|
-
import { QuickJSHandle } from 'quickjs-emscripten';
|
|
3
|
-
import {
|
|
4
|
-
import type {
|
|
5
|
-
import type { VmCallResult } from 'quickjs-emscripten/dist/vm-interface';
|
|
3
|
+
import type { QuickJSHandle } from 'quickjs-emscripten';
|
|
4
|
+
import type { SuccessOrFail } from 'quickjs-emscripten';
|
|
5
|
+
import type { VmCallResult } from 'quickjs-emscripten';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* The Arena class manages all generated handles at once by quickjs-emscripten and automatically converts objects between the host and the QuickJS VM.
|
|
9
9
|
*/
|
|
10
10
|
export declare class Arena {
|
|
11
|
-
|
|
11
|
+
context: QuickJSContext;
|
|
12
12
|
_map: VMMap;
|
|
13
13
|
_registeredMap: VMMap;
|
|
14
14
|
_registeredMapDispose: Set<any>;
|
|
@@ -17,8 +17,8 @@ export declare class Arena {
|
|
|
17
17
|
_symbol: symbol;
|
|
18
18
|
_symbolHandle: QuickJSHandle;
|
|
19
19
|
_options?: Options;
|
|
20
|
-
/** Constructs a new Arena instance. It requires a quickjs-emscripten
|
|
21
|
-
constructor(
|
|
20
|
+
/** Constructs a new Arena instance. It requires a quickjs-emscripten context initialized with `quickjs.newContext()`. */
|
|
21
|
+
constructor(ctx: QuickJSContext, options?: Options);
|
|
22
22
|
/**
|
|
23
23
|
* Dispose of the arena and managed handles. This method won't dispose the VM itself, so the VM has to be disposed of manually.
|
|
24
24
|
*/
|
|
@@ -85,7 +85,7 @@ export declare class Arena {
|
|
|
85
85
|
_unwrapHandle(target: QuickJSHandle): [QuickJSHandle, boolean];
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
export declare function call(
|
|
88
|
+
export declare function call(ctx: QuickJSContext, code: string, thisArg?: QuickJSHandle, ...args: QuickJSHandle[]): QuickJSHandle;
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
91
|
* Measure the complexity of an object as you traverse the field and prototype chain. If max is specified, when the complexity reaches max, the traversal is terminated and it returns the max. In this function, one object and function are counted as a complexity of 1, and primitives are not counted as a complexity.
|
|
@@ -99,15 +99,15 @@ export declare function consumeAll<T extends QuickJSHandle[], K>(handles: T, cb:
|
|
|
99
99
|
*/
|
|
100
100
|
export declare const defaultRegisteredObjects: [any, string][];
|
|
101
101
|
|
|
102
|
-
export declare function eq(
|
|
102
|
+
export declare function eq(ctx: QuickJSContext, a: QuickJSHandle, b: QuickJSHandle): boolean;
|
|
103
103
|
|
|
104
104
|
export declare function isES2015Class(cls: any): cls is new (...args: any[]) => any;
|
|
105
105
|
|
|
106
|
-
export declare function isHandleObject(
|
|
106
|
+
export declare function isHandleObject(ctx: QuickJSContext, h: QuickJSHandle): boolean;
|
|
107
107
|
|
|
108
108
|
export declare function isObject(value: any): value is object | Function;
|
|
109
109
|
|
|
110
|
-
export declare function json(
|
|
110
|
+
export declare function json(ctx: QuickJSContext, target: any): QuickJSHandle;
|
|
111
111
|
|
|
112
112
|
export declare function marshal(target: unknown, options: Options_2): QuickJSHandle;
|
|
113
113
|
|
|
@@ -119,10 +119,12 @@ export declare type Options = {
|
|
|
119
119
|
* Instead of a string, you can also pass a QuickJSHandle directly. In that case, however, you have to dispose of them manually when destroying the VM.
|
|
120
120
|
*/
|
|
121
121
|
registeredObjects?: Iterable<[any, QuickJSHandle | string]>;
|
|
122
|
+
/** Compatibility with quickjs-emscripten prior to v0.15. Inject code for compatibility into context at Arena class initialization time. */
|
|
123
|
+
compat?: boolean;
|
|
122
124
|
};
|
|
123
125
|
|
|
124
126
|
declare type Options_2 = {
|
|
125
|
-
|
|
127
|
+
ctx: QuickJSContext;
|
|
126
128
|
unmarshal: (handle: QuickJSHandle) => unknown;
|
|
127
129
|
isMarshalable?: (target: unknown) => boolean | "json";
|
|
128
130
|
find: (target: unknown) => QuickJSHandle | undefined;
|
|
@@ -131,7 +133,7 @@ declare type Options_2 = {
|
|
|
131
133
|
};
|
|
132
134
|
|
|
133
135
|
declare type Options_3 = {
|
|
134
|
-
|
|
136
|
+
ctx: QuickJSContext;
|
|
135
137
|
/** marshal returns handle and boolean indicates that the handle should be disposed after use */
|
|
136
138
|
marshal: (target: unknown) => [QuickJSHandle, boolean];
|
|
137
139
|
find: (handle: QuickJSHandle) => unknown | undefined;
|
|
@@ -141,7 +143,7 @@ declare type Options_3 = {
|
|
|
141
143
|
export declare function unmarshal(handle: QuickJSHandle, options: Options_3): any;
|
|
142
144
|
|
|
143
145
|
export declare class VMMap {
|
|
144
|
-
|
|
146
|
+
ctx: QuickJSContext;
|
|
145
147
|
_map1: Map<any, number>;
|
|
146
148
|
_map2: Map<any, number>;
|
|
147
149
|
_map3: Map<number, QuickJSHandle>;
|
|
@@ -153,7 +155,7 @@ export declare class VMMap {
|
|
|
153
155
|
_mapDelete: QuickJSHandle;
|
|
154
156
|
_mapClear: QuickJSHandle;
|
|
155
157
|
_counter: number;
|
|
156
|
-
constructor(
|
|
158
|
+
constructor(ctx: QuickJSContext);
|
|
157
159
|
set(key: any, handle: QuickJSHandle, key2?: any, handle2?: QuickJSHandle): boolean;
|
|
158
160
|
merge(iteratable: Iterable<[any, QuickJSHandle | undefined] | [any, QuickJSHandle | undefined, any, QuickJSHandle | undefined]> | undefined): void;
|
|
159
161
|
get(key: any): QuickJSHandle | undefined;
|