mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
Fix: safe serialise TypedArrays to avoid loosing constructor information (#5941)
* fix: enhance cleanJson to support serialization of typed arrays * fix: correctness of inference based checks * fix: remove duplicate Uint8Array reference * fix: correct export syntax for mixinTypedArrays Updated the export statement to use 'exports' instead of 'export' for proper module export functionality. * chore: code cleanup * test: add basics tests for cleanJson
This commit is contained in:
committed by
GitHub
parent
08c182a875
commit
e47d1ed353
@@ -35,6 +35,7 @@ const cheerio = require('cheerio');
|
||||
const tv4 = require('tv4');
|
||||
const jsonwebtoken = require('jsonwebtoken');
|
||||
const { executeQuickJsVmAsync } = require('../sandbox/quickjs');
|
||||
const { mixinTypedArrays } = require('../sandbox/mixins/typed-arrays');
|
||||
|
||||
class ScriptRuntime {
|
||||
constructor(props) {
|
||||
@@ -94,6 +95,10 @@ class ScriptRuntime {
|
||||
__brunoTestResults: __brunoTestResults
|
||||
};
|
||||
|
||||
if (this.runtime === 'vm2') {
|
||||
mixinTypedArrays(context);
|
||||
}
|
||||
|
||||
if (onConsoleLog && typeof onConsoleLog === 'function') {
|
||||
const customLogger = (type) => {
|
||||
return (...args) => {
|
||||
@@ -265,6 +270,10 @@ class ScriptRuntime {
|
||||
__brunoTestResults: __brunoTestResults
|
||||
};
|
||||
|
||||
if (this.runtime === 'vm2') {
|
||||
mixinTypedArrays(context);
|
||||
}
|
||||
|
||||
if (onConsoleLog && typeof onConsoleLog === 'function') {
|
||||
const customLogger = (type) => {
|
||||
return (...args) => {
|
||||
|
||||
@@ -37,6 +37,7 @@ const cheerio = require('cheerio');
|
||||
const tv4 = require('tv4');
|
||||
const jsonwebtoken = require('jsonwebtoken');
|
||||
const { executeQuickJsVmAsync } = require('../sandbox/quickjs');
|
||||
const { mixinTypedArrays } = require('../sandbox/mixins/typed-arrays');
|
||||
|
||||
class TestRuntime {
|
||||
constructor(props) {
|
||||
@@ -108,6 +109,10 @@ class TestRuntime {
|
||||
jwt: jsonwebtoken
|
||||
};
|
||||
|
||||
if (this.runtime === 'vm2') {
|
||||
mixinTypedArrays(context);
|
||||
}
|
||||
|
||||
if (onConsoleLog && typeof onConsoleLog === 'function') {
|
||||
const customLogger = (type) => {
|
||||
return (...args) => {
|
||||
|
||||
15
packages/bruno-js/src/sandbox/mixins/typed-arrays.js
Normal file
15
packages/bruno-js/src/sandbox/mixins/typed-arrays.js
Normal file
@@ -0,0 +1,15 @@
|
||||
exports.mixinTypedArrays = (obj) => {
|
||||
Object.assign(obj, {
|
||||
Int8Array: Int8Array,
|
||||
Uint8Array: Uint8Array,
|
||||
Uint8ClampedArray: Uint8ClampedArray,
|
||||
Int16Array: Int16Array,
|
||||
Uint16Array: Uint16Array,
|
||||
Int32Array: Int32Array,
|
||||
Uint32Array: Uint32Array,
|
||||
Float32Array: Float32Array,
|
||||
Float64Array: Float64Array,
|
||||
BigInt64Array: BigInt64Array,
|
||||
BigUint64Array: BigUint64Array
|
||||
});
|
||||
};
|
||||
@@ -4,6 +4,7 @@ const path = require('node:path');
|
||||
const { get } = require('lodash');
|
||||
const lodash = require('lodash');
|
||||
const { cleanJson } = require('../../utils');
|
||||
const { mixinTypedArrays } = require('../mixins/typed-arrays');
|
||||
|
||||
class ScriptError extends Error {
|
||||
constructor(error, script) {
|
||||
@@ -60,6 +61,8 @@ async function runScriptInNodeVm({
|
||||
clearImmediate: global.clearImmediate
|
||||
};
|
||||
|
||||
mixinTypedArrays(scriptContext);
|
||||
|
||||
// Create shared cache for local modules
|
||||
const localModuleCache = new Map();
|
||||
|
||||
|
||||
@@ -136,10 +136,58 @@ const createResponseParser = (response = {}) => {
|
||||
* Remove the cleanJson fix and execute the below post response script
|
||||
* bru.setVar("a", {b:3});
|
||||
* Todo: Find a better fix
|
||||
*
|
||||
* serializes typedArrays by using Buffer to handle most binary cases
|
||||
* // TODO: reaper, replace with `devalue` after evaluating all cases, current setup is
|
||||
* more of a hotfix
|
||||
*/
|
||||
const cleanJson = (data) => {
|
||||
const typedArrays = [
|
||||
// Baseline typed arrays
|
||||
Int8Array,
|
||||
Uint8Array,
|
||||
Uint8ClampedArray,
|
||||
Int16Array,
|
||||
Uint16Array,
|
||||
Int32Array,
|
||||
Uint32Array,
|
||||
Float32Array,
|
||||
Float64Array,
|
||||
BigInt64Array,
|
||||
BigUint64Array,
|
||||
|
||||
// Baseline 2025 Newly available
|
||||
'Float16Array' in globalThis ? Float16Array : null
|
||||
].filter(Boolean);
|
||||
const binaryNames = typedArrays.map((d) => d.name);
|
||||
|
||||
const replacer = (key, value) => {
|
||||
const isBinary = typedArrays.find((d) => value instanceof d);
|
||||
if (isBinary) {
|
||||
return {
|
||||
__cleanJSONType: isBinary.name,
|
||||
__cleanJSONValue: Buffer.from(value.buffer).toJSON()
|
||||
};
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const reviver = (key, value) => {
|
||||
if (typeof value !== 'object' || value === null) {
|
||||
return value;
|
||||
}
|
||||
if ('__cleanJSONType' in value && '__cleanJSONValue' in value) {
|
||||
const matchedName = binaryNames.find((d) => value.__cleanJSONType === d);
|
||||
if (!matchedName) return value;
|
||||
const binConstructor = typedArrays.find((d) => d.name === matchedName);
|
||||
|
||||
return binConstructor.from(Buffer.from(value.__cleanJSONValue));
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
try {
|
||||
return JSON.parse(JSON.stringify(data));
|
||||
return JSON.parse(JSON.stringify(data, replacer), reviver);
|
||||
} catch (e) {
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const { describe, it, expect } = require('@jest/globals');
|
||||
const { evaluateJsExpression, internalExpressionCache: cache, createResponseParser } = require('../src/utils');
|
||||
const { evaluateJsExpression, internalExpressionCache: cache, createResponseParser, cleanJson } = require('../src/utils');
|
||||
|
||||
describe('utils', () => {
|
||||
describe('expression evaluation', () => {
|
||||
@@ -153,4 +153,64 @@ describe('utils', () => {
|
||||
expect(value).toBe(20);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cleanJson', () => {
|
||||
it('primitives should be kept as is', () => {
|
||||
const input = {
|
||||
number: 1,
|
||||
string: 'hello world',
|
||||
booleanFalse: false,
|
||||
booleanTrue: true,
|
||||
float: 2.1,
|
||||
floatDeep: 2.2222222
|
||||
};
|
||||
expect(cleanJson(input)).toEqual(input);
|
||||
});
|
||||
|
||||
it('functions are lost', () => {
|
||||
const func = function (x, y) {
|
||||
return x + y;
|
||||
};
|
||||
|
||||
const input = {
|
||||
func,
|
||||
number: 1
|
||||
};
|
||||
|
||||
expect(cleanJson(input)).toEqual({
|
||||
number: 1
|
||||
});
|
||||
});
|
||||
|
||||
it('dates are serialized', () => {
|
||||
const date = new Date();
|
||||
const str = date.toISOString();
|
||||
|
||||
const input = {
|
||||
date
|
||||
};
|
||||
|
||||
expect(cleanJson(input)).toEqual({
|
||||
date: str
|
||||
});
|
||||
});
|
||||
|
||||
it('typed arrays should be kept as is', () => {
|
||||
const input = {
|
||||
Int8Array: Int8Array.from(Buffer.from('hello world').toString()),
|
||||
Uint8Array: Uint8Array.from(Buffer.from('hello world').toString()),
|
||||
Uint8ClampedArray: Uint8ClampedArray.from(Buffer.from('hello world').toString()),
|
||||
Int16Array: Int16Array.from(Buffer.from('hello world').toString()),
|
||||
Uint16Array: Uint16Array.from(Buffer.from('hello world').toString()),
|
||||
Int32Array: Int32Array.from(Buffer.from('hello world').toString()),
|
||||
Uint32Array: Uint32Array.from(Buffer.from('hello world').toString()),
|
||||
Float32Array: Float32Array.from(Buffer.from('hello world').toString()),
|
||||
Float64Array: Float64Array.from(Buffer.from('hello world').toString()),
|
||||
BigInt64Array: BigInt64Array.from(Buffer.from('123').toString()),
|
||||
BigUint64Array: BigUint64Array.from(Buffer.from('234').toString())
|
||||
};
|
||||
|
||||
expect(cleanJson(input)).toEqual(input);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user