@usebruno/js 0.39.0 → 0.41.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/package.json +4 -2
- package/src/bru.js +46 -2
- package/src/bruno-request.js +9 -0
- package/src/runtime/script-runtime.js +12 -0
- package/src/runtime/test-runtime.js +9 -1
- package/src/sandbox/bundle-browser-rollup.js +11 -11
- package/src/sandbox/mixins/typed-arrays.js +15 -0
- package/src/sandbox/node-vm/index.js +3 -0
- package/src/sandbox/quickjs/shims/bruno-request.js +14 -4
- package/src/sandbox/quickjs/shims/lib/index.js +2 -0
- package/src/sandbox/quickjs/shims/lib/jwt.js +181 -0
- package/src/sandbox/quickjs/utils/index.js +49 -1
- package/src/utils.js +49 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
exports.mixinTypedArrays = (obj) => {
|
|
2
|
+
Object.assign(obj, {
|
|
3
|
+
Int8Array: Int8Array,
|
|
4
|
+
Uint8Array: Uint8Array,
|
|
5
|
+
Uint8ClampedArray: Uint8ClampedArray,
|
|
6
|
+
Int16Array: Int16Array,
|
|
7
|
+
Uint16Array: Uint16Array,
|
|
8
|
+
Int32Array: Int32Array,
|
|
9
|
+
Uint32Array: Uint32Array,
|
|
10
|
+
Float32Array: Float32Array,
|
|
11
|
+
Float64Array: Float64Array,
|
|
12
|
+
BigInt64Array: BigInt64Array,
|
|
13
|
+
BigUint64Array: BigUint64Array
|
|
14
|
+
});
|
|
15
|
+
};
|
|
@@ -4,6 +4,7 @@ const path = require('node:path');
|
|
|
4
4
|
const { get } = require('lodash');
|
|
5
5
|
const lodash = require('lodash');
|
|
6
6
|
const { cleanJson } = require('../../utils');
|
|
7
|
+
const { mixinTypedArrays } = require('../mixins/typed-arrays');
|
|
7
8
|
|
|
8
9
|
class ScriptError extends Error {
|
|
9
10
|
constructor(error, script) {
|
|
@@ -60,6 +61,8 @@ async function runScriptInNodeVm({
|
|
|
60
61
|
clearImmediate: global.clearImmediate
|
|
61
62
|
};
|
|
62
63
|
|
|
64
|
+
mixinTypedArrays(scriptContext);
|
|
65
|
+
|
|
63
66
|
// Create shared cache for local modules
|
|
64
67
|
const localModuleCache = new Map();
|
|
65
68
|
|
|
@@ -9,6 +9,7 @@ const addBrunoRequestShimToContext = (vm, req) => {
|
|
|
9
9
|
const body = marshallToVm(req.getBody(), vm);
|
|
10
10
|
const timeout = marshallToVm(req.getTimeout(), vm);
|
|
11
11
|
const name = marshallToVm(req.getName(), vm);
|
|
12
|
+
const tags = marshallToVm(req.getTags(), vm);
|
|
12
13
|
|
|
13
14
|
vm.setProp(reqObject, 'url', url);
|
|
14
15
|
vm.setProp(reqObject, 'method', method);
|
|
@@ -16,6 +17,7 @@ const addBrunoRequestShimToContext = (vm, req) => {
|
|
|
16
17
|
vm.setProp(reqObject, 'body', body);
|
|
17
18
|
vm.setProp(reqObject, 'timeout', timeout);
|
|
18
19
|
vm.setProp(reqObject, 'name', name);
|
|
20
|
+
vm.setProp(reqObject, 'tags', tags);
|
|
19
21
|
|
|
20
22
|
url.dispose();
|
|
21
23
|
method.dispose();
|
|
@@ -23,6 +25,7 @@ const addBrunoRequestShimToContext = (vm, req) => {
|
|
|
23
25
|
body.dispose();
|
|
24
26
|
timeout.dispose();
|
|
25
27
|
name.dispose();
|
|
28
|
+
tags.dispose();
|
|
26
29
|
|
|
27
30
|
let getUrl = vm.newFunction('getUrl', function () {
|
|
28
31
|
return marshallToVm(req.getUrl(), vm);
|
|
@@ -84,14 +87,15 @@ const addBrunoRequestShimToContext = (vm, req) => {
|
|
|
84
87
|
vm.setProp(reqObject, 'setHeader', setHeader);
|
|
85
88
|
setHeader.dispose();
|
|
86
89
|
|
|
87
|
-
let getBody = vm.newFunction('getBody', function () {
|
|
88
|
-
return marshallToVm(req.getBody(), vm);
|
|
90
|
+
let getBody = vm.newFunction('getBody', function (options = {}) {
|
|
91
|
+
return marshallToVm(req.getBody(vm.dump(options)), vm);
|
|
89
92
|
});
|
|
93
|
+
|
|
90
94
|
vm.setProp(reqObject, 'getBody', getBody);
|
|
91
95
|
getBody.dispose();
|
|
92
96
|
|
|
93
|
-
let setBody = vm.newFunction('setBody', function (data) {
|
|
94
|
-
req.setBody(vm.dump(data));
|
|
97
|
+
let setBody = vm.newFunction('setBody', function (data, options = {}) {
|
|
98
|
+
req.setBody(vm.dump(data), vm.dump(options));
|
|
95
99
|
});
|
|
96
100
|
vm.setProp(reqObject, 'setBody', setBody);
|
|
97
101
|
setBody.dispose();
|
|
@@ -126,6 +130,12 @@ const addBrunoRequestShimToContext = (vm, req) => {
|
|
|
126
130
|
vm.setProp(reqObject, 'getExecutionMode', getExecutionMode);
|
|
127
131
|
getExecutionMode.dispose();
|
|
128
132
|
|
|
133
|
+
let getTags = vm.newFunction('getTags', function () {
|
|
134
|
+
return marshallToVm(req.getTags(), vm);
|
|
135
|
+
});
|
|
136
|
+
vm.setProp(reqObject, 'getTags', getTags);
|
|
137
|
+
getTags.dispose();
|
|
138
|
+
|
|
129
139
|
vm.setProp(vm.global, 'req', reqObject);
|
|
130
140
|
reqObject.dispose();
|
|
131
141
|
};
|
|
@@ -2,12 +2,14 @@ const addAxiosShimToContext = require('./axios');
|
|
|
2
2
|
const addNanoidShimToContext = require('./nanoid');
|
|
3
3
|
const addPathShimToContext = require('./path');
|
|
4
4
|
const addUuidShimToContext = require('./uuid');
|
|
5
|
+
const addJwtShimToContext = require('./jwt');
|
|
5
6
|
|
|
6
7
|
const addLibraryShimsToContext = async (vm) => {
|
|
7
8
|
await addNanoidShimToContext(vm);
|
|
8
9
|
await addAxiosShimToContext(vm);
|
|
9
10
|
await addUuidShimToContext(vm);
|
|
10
11
|
await addPathShimToContext(vm);
|
|
12
|
+
await addJwtShimToContext(vm);
|
|
11
13
|
};
|
|
12
14
|
|
|
13
15
|
module.exports = addLibraryShimsToContext;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
const jwt = require('jsonwebtoken');
|
|
2
|
+
const { marshallToVm, invokeFunction } = require('../../utils');
|
|
3
|
+
|
|
4
|
+
const addJwtShimToContext = async (vm) => {
|
|
5
|
+
// --- sign ---
|
|
6
|
+
const _jwtSign = vm.newFunction('sign', function (payload, secret, options, callback) {
|
|
7
|
+
const nativePayload = vm.dump(payload);
|
|
8
|
+
const nativeSecret = vm.dump(secret);
|
|
9
|
+
|
|
10
|
+
let nativeOptions;
|
|
11
|
+
let callbackHandle = callback;
|
|
12
|
+
const optionsType = options === undefined ? 'undefined' : vm.typeof(options);
|
|
13
|
+
if (optionsType === 'function') {
|
|
14
|
+
callbackHandle = options;
|
|
15
|
+
nativeOptions = undefined;
|
|
16
|
+
} else if (optionsType === 'object' && options !== null) {
|
|
17
|
+
nativeOptions = vm.dump(options);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// If a callback is provided
|
|
21
|
+
if (callbackHandle && vm.typeof(callbackHandle) === 'function') {
|
|
22
|
+
let tokenResult;
|
|
23
|
+
let hostError;
|
|
24
|
+
try {
|
|
25
|
+
tokenResult = nativeOptions
|
|
26
|
+
? jwt.sign(nativePayload, nativeSecret, nativeOptions)
|
|
27
|
+
: jwt.sign(nativePayload, nativeSecret);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
hostError = err;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
if (hostError) {
|
|
34
|
+
const errVm = vm.newError(hostError.message || String(hostError));
|
|
35
|
+
invokeFunction(vm, callbackHandle, [errVm, vm.undefined])
|
|
36
|
+
.catch((e) => {
|
|
37
|
+
console.warn('[JWT SHIM][sign.cb] callback invocation error:', e);
|
|
38
|
+
})
|
|
39
|
+
.finally(() => {
|
|
40
|
+
errVm.dispose();
|
|
41
|
+
callbackHandle.dispose();
|
|
42
|
+
});
|
|
43
|
+
} else {
|
|
44
|
+
const tokenVm = marshallToVm(String(tokenResult), vm);
|
|
45
|
+
invokeFunction(vm, callbackHandle, [vm.null, tokenVm])
|
|
46
|
+
.catch((e) => {
|
|
47
|
+
console.warn('[JWT SHIM][sign.cb] callback invocation error:', e);
|
|
48
|
+
})
|
|
49
|
+
.finally(() => {
|
|
50
|
+
tokenVm.dispose();
|
|
51
|
+
callbackHandle.dispose();
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
} catch (e) {
|
|
55
|
+
console.warn('[JWT SHIM][sign.cb] unexpected error:', e);
|
|
56
|
+
callbackHandle.dispose();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return vm.undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const token = nativeOptions
|
|
64
|
+
? jwt.sign(nativePayload, nativeSecret, nativeOptions)
|
|
65
|
+
: jwt.sign(nativePayload, nativeSecret);
|
|
66
|
+
return marshallToVm(token, vm);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
throw vm.newError(err.message || String(err));
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
vm.setProp(vm.global, '__bruno__jwt__sign', _jwtSign);
|
|
73
|
+
_jwtSign.dispose();
|
|
74
|
+
|
|
75
|
+
// --- verify ---
|
|
76
|
+
const _jwtVerify = vm.newFunction('verify', function (token, secret, options, callback) {
|
|
77
|
+
const nativeToken = vm.dump(token);
|
|
78
|
+
const nativeSecret = vm.dump(secret);
|
|
79
|
+
|
|
80
|
+
let nativeOptions;
|
|
81
|
+
let actualCallback = callback;
|
|
82
|
+
|
|
83
|
+
const optionsType = options === undefined ? 'undefined' : vm.typeof(options);
|
|
84
|
+
if (optionsType === 'function') {
|
|
85
|
+
actualCallback = options;
|
|
86
|
+
nativeOptions = undefined;
|
|
87
|
+
} else if (optionsType === 'object' && options !== null) {
|
|
88
|
+
nativeOptions = vm.dump(options);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (actualCallback && vm.typeof(actualCallback) === 'function') {
|
|
92
|
+
let decodedResult;
|
|
93
|
+
let hostError;
|
|
94
|
+
try {
|
|
95
|
+
decodedResult = nativeOptions
|
|
96
|
+
? jwt.verify(nativeToken, nativeSecret, nativeOptions)
|
|
97
|
+
: jwt.verify(nativeToken, nativeSecret);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
hostError = err;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
if (hostError) {
|
|
104
|
+
const vmErr = vm.newError(hostError.message || String(hostError));
|
|
105
|
+
invokeFunction(vm, actualCallback, [vmErr, vm.undefined])
|
|
106
|
+
.catch((e) => {
|
|
107
|
+
console.warn('[JWT SHIM][verify.cb] callback invocation error:', e);
|
|
108
|
+
})
|
|
109
|
+
.finally(() => {
|
|
110
|
+
vmErr.dispose();
|
|
111
|
+
actualCallback.dispose();
|
|
112
|
+
});
|
|
113
|
+
} else {
|
|
114
|
+
const vmNull = vm.null;
|
|
115
|
+
const vmDecoded = marshallToVm(decodedResult, vm);
|
|
116
|
+
invokeFunction(vm, actualCallback, [vmNull, vmDecoded])
|
|
117
|
+
.catch((e) => {
|
|
118
|
+
console.warn('[JWT SHIM][verify.cb] callback invocation error:', e);
|
|
119
|
+
})
|
|
120
|
+
.finally(() => {
|
|
121
|
+
vmDecoded.dispose();
|
|
122
|
+
actualCallback.dispose();
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
} catch (e) {
|
|
126
|
+
console.warn('[JWT SHIM][verify.cb] unexpected error:', e);
|
|
127
|
+
actualCallback.dispose();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return vm.undefined;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const decoded = nativeOptions
|
|
135
|
+
? jwt.verify(nativeToken, nativeSecret, nativeOptions)
|
|
136
|
+
: jwt.verify(nativeToken, nativeSecret);
|
|
137
|
+
return marshallToVm(decoded, vm);
|
|
138
|
+
} catch (err) {
|
|
139
|
+
throw vm.newError(err.message || String(err));
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
vm.setProp(vm.global, '__bruno__jwt__verify', _jwtVerify);
|
|
144
|
+
_jwtVerify.dispose();
|
|
145
|
+
|
|
146
|
+
// --- decode ---
|
|
147
|
+
const _jwtDecode = vm.newFunction('decode', function (token, options) {
|
|
148
|
+
const nativeToken = vm.dump(token);
|
|
149
|
+
|
|
150
|
+
let nativeOptions;
|
|
151
|
+
const optionsType = options === undefined ? 'undefined' : vm.typeof(options);
|
|
152
|
+
if (optionsType === 'object' && options !== null) {
|
|
153
|
+
nativeOptions = vm.dump(options);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const decoded = nativeOptions
|
|
158
|
+
? jwt.decode(nativeToken, nativeOptions)
|
|
159
|
+
: jwt.decode(nativeToken);
|
|
160
|
+
return marshallToVm(decoded, vm);
|
|
161
|
+
} catch (err) {
|
|
162
|
+
throw vm.newError(err.message || String(err));
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
vm.setProp(vm.global, '__bruno__jwt__decode', _jwtDecode);
|
|
167
|
+
_jwtDecode.dispose();
|
|
168
|
+
|
|
169
|
+
vm.evalCode(`
|
|
170
|
+
globalThis.jwt = {};
|
|
171
|
+
globalThis.jwt.sign = globalThis.__bruno__jwt__sign;
|
|
172
|
+
globalThis.jwt.verify = globalThis.__bruno__jwt__verify;
|
|
173
|
+
globalThis.jwt.decode = globalThis.__bruno__jwt__decode;
|
|
174
|
+
globalThis.requireObject = {
|
|
175
|
+
...globalThis.requireObject,
|
|
176
|
+
'jsonwebtoken': globalThis.jwt,
|
|
177
|
+
};
|
|
178
|
+
`);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
module.exports = addJwtShimToContext;
|
|
@@ -30,6 +30,54 @@ const marshallToVm = (value, vm) => {
|
|
|
30
30
|
}
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Invokes a QuickJS function handle.
|
|
35
|
+
* - Returns a Promise
|
|
36
|
+
*
|
|
37
|
+
* @param {Object} vm - QuickJS VM instance
|
|
38
|
+
* @param {QuickJSHandle} quickFn - A QuickJS function handle
|
|
39
|
+
* @param {Array} args - Arguments to pass to the function
|
|
40
|
+
* @returns {Promise<any>} - The result as a Promise
|
|
41
|
+
*/
|
|
42
|
+
async function invokeFunction(vm, quickFn, args = []) {
|
|
43
|
+
if (vm.typeof(quickFn) !== 'function') {
|
|
44
|
+
throw new TypeError('Target is not a QuickJS function');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const result = vm.callFunction(quickFn, vm.global, ...args);
|
|
48
|
+
|
|
49
|
+
if (result.error) {
|
|
50
|
+
const error = vm.dump(result.error);
|
|
51
|
+
result.error.dispose();
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Check if the result is a QuickJS Promise handle (async functions)
|
|
56
|
+
if (vm.typeof(result.value) === 'object' && result.value.constructor && vm.typeof(result.value.constructor) === 'function') {
|
|
57
|
+
try {
|
|
58
|
+
const promiseHandle = vm.unwrapResult(result);
|
|
59
|
+
const resolvedResult = await vm.resolvePromise(promiseHandle);
|
|
60
|
+
promiseHandle.dispose();
|
|
61
|
+
const resolvedHandle = vm.unwrapResult(resolvedResult);
|
|
62
|
+
const value = vm.dump(resolvedHandle);
|
|
63
|
+
resolvedHandle.dispose();
|
|
64
|
+
return Promise.resolve(value);
|
|
65
|
+
} catch (promiseError) {
|
|
66
|
+
// If it's not a valid Promise, throw an error
|
|
67
|
+
result.value.dispose();
|
|
68
|
+
throw new Error(`Invalid Promise handle: ${promiseError.message}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const value = vm.dump(result.value);
|
|
73
|
+
result.value.dispose();
|
|
74
|
+
|
|
75
|
+
return (value && typeof value.then === 'function')
|
|
76
|
+
? value
|
|
77
|
+
: Promise.resolve(value);
|
|
78
|
+
}
|
|
79
|
+
|
|
33
80
|
module.exports = {
|
|
34
|
-
marshallToVm
|
|
81
|
+
marshallToVm,
|
|
82
|
+
invokeFunction
|
|
35
83
|
};
|
package/src/utils.js
CHANGED
|
@@ -137,10 +137,58 @@ const createResponseParser = (response = {}) => {
|
|
|
137
137
|
* Remove the cleanJson fix and execute the below post response script
|
|
138
138
|
* bru.setVar("a", {b:3});
|
|
139
139
|
* Todo: Find a better fix
|
|
140
|
+
*
|
|
141
|
+
* serializes typedArrays by using Buffer to handle most binary cases
|
|
142
|
+
* // TODO: reaper, replace with `devalue` after evaluating all cases, current setup is
|
|
143
|
+
* more of a hotfix
|
|
140
144
|
*/
|
|
141
145
|
const cleanJson = (data) => {
|
|
146
|
+
const typedArrays = [
|
|
147
|
+
// Baseline typed arrays
|
|
148
|
+
Int8Array,
|
|
149
|
+
Uint8Array,
|
|
150
|
+
Uint8ClampedArray,
|
|
151
|
+
Int16Array,
|
|
152
|
+
Uint16Array,
|
|
153
|
+
Int32Array,
|
|
154
|
+
Uint32Array,
|
|
155
|
+
Float32Array,
|
|
156
|
+
Float64Array,
|
|
157
|
+
BigInt64Array,
|
|
158
|
+
BigUint64Array,
|
|
159
|
+
|
|
160
|
+
// Baseline 2025 Newly available
|
|
161
|
+
'Float16Array' in globalThis ? globalThis['Float16Array'] : null
|
|
162
|
+
].filter(Boolean);
|
|
163
|
+
const binaryNames = typedArrays.map((d) => d.name);
|
|
164
|
+
|
|
165
|
+
const replacer = (key, value) => {
|
|
166
|
+
const isBinary = typedArrays.find((d) => value instanceof d);
|
|
167
|
+
if (isBinary) {
|
|
168
|
+
return {
|
|
169
|
+
__cleanJSONType: isBinary.name,
|
|
170
|
+
__cleanJSONValue: Buffer.from(value.buffer).toJSON()
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return value;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const reviver = (key, value) => {
|
|
177
|
+
if (typeof value !== 'object' || value === null) {
|
|
178
|
+
return value;
|
|
179
|
+
}
|
|
180
|
+
if ('__cleanJSONType' in value && '__cleanJSONValue' in value) {
|
|
181
|
+
const matchedName = binaryNames.find((d) => value.__cleanJSONType === d);
|
|
182
|
+
if (!matchedName) return value;
|
|
183
|
+
const binConstructor = typedArrays.find((d) => d.name === matchedName);
|
|
184
|
+
|
|
185
|
+
return binConstructor.from(Buffer.from(value.__cleanJSONValue));
|
|
186
|
+
}
|
|
187
|
+
return value;
|
|
188
|
+
};
|
|
189
|
+
|
|
142
190
|
try {
|
|
143
|
-
return JSON.parse(JSON.stringify(data));
|
|
191
|
+
return JSON.parse(JSON.stringify(data, replacer), reviver);
|
|
144
192
|
} catch (e) {
|
|
145
193
|
return data;
|
|
146
194
|
}
|