@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.
@@ -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
  }