@usebruno/js 0.37.0 → 0.39.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usebruno/js",
3
- "version": "0.37.0",
3
+ "version": "0.39.0",
4
4
  "license": "MIT",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -16,8 +16,7 @@
16
16
  "prepack": "npm run test"
17
17
  },
18
18
  "dependencies": {
19
- "@usebruno/common": "0.11.0",
20
- "@usebruno/crypto-js": "^3.1.9",
19
+ "@usebruno/common": "0.12.1",
21
20
  "@usebruno/query": "0.1.0",
22
21
  "ajv": "^8.12.0",
23
22
  "ajv-formats": "^2.1.1",
@@ -27,7 +26,7 @@
27
26
  "chai": "^4.3.7",
28
27
  "chai-string": "^1.5.0",
29
28
  "cheerio": "^1.0.0",
30
- "crypto-js": "^4.1.1",
29
+ "crypto-js": "^4.2.0",
31
30
  "json-query": "^2.2.2",
32
31
  "lodash": "^4.17.21",
33
32
  "moment": "^2.29.4",
@@ -46,4 +45,4 @@
46
45
  "rollup": "3.29.5",
47
46
  "rollup-plugin-terser": "^7.0.2"
48
47
  }
49
- }
48
+ }
package/src/index.js CHANGED
@@ -2,10 +2,12 @@ const ScriptRuntime = require('./runtime/script-runtime');
2
2
  const TestRuntime = require('./runtime/test-runtime');
3
3
  const VarsRuntime = require('./runtime/vars-runtime');
4
4
  const AssertRuntime = require('./runtime/assert-runtime');
5
+ const { runScriptInNodeVm } = require('./sandbox/node-vm');
5
6
 
6
7
  module.exports = {
7
8
  ScriptRuntime,
8
9
  TestRuntime,
9
10
  VarsRuntime,
10
- AssertRuntime
11
+ AssertRuntime,
12
+ runScriptInNodeVm
11
13
  };
@@ -14,6 +14,7 @@ const BrunoRequest = require('../bruno-request');
14
14
  const BrunoResponse = require('../bruno-response');
15
15
  const { cleanJson } = require('../utils');
16
16
  const { createBruTestResultMethods } = require('../utils/results');
17
+ const { runScriptInNodeVm } = require('../sandbox/node-vm');
17
18
 
18
19
  // Inbuilt Library Support
19
20
  const ajv = require('ajv');
@@ -119,6 +120,28 @@ class ScriptRuntime {
119
120
  context.bru.runRequest = runRequestByItemPathname;
120
121
  }
121
122
 
123
+ if (this.runtime === 'nodevm') {
124
+ await runScriptInNodeVm({
125
+ script,
126
+ context,
127
+ collectionPath,
128
+ scriptingConfig
129
+ });
130
+
131
+ return {
132
+ request,
133
+ envVariables: cleanJson(envVariables),
134
+ runtimeVariables: cleanJson(runtimeVariables),
135
+ visualizations,
136
+ persistentEnvVariables: bru.persistentEnvVariables,
137
+ globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
138
+ results: cleanJson(__brunoTestResults.getResults()),
139
+ nextRequestName: bru.nextRequest,
140
+ skipRequest: bru.skipRequest,
141
+ stopExecution: bru.stopExecution
142
+ };
143
+ }
144
+
122
145
  if (this.runtime === 'quickjs') {
123
146
  await executeQuickJsVmAsync({
124
147
  script: script,
@@ -277,6 +300,28 @@ class ScriptRuntime {
277
300
  context.bru.runRequest = runRequestByItemPathname;
278
301
  }
279
302
 
303
+ if (this.runtime === 'nodevm') {
304
+ await runScriptInNodeVm({
305
+ script,
306
+ context,
307
+ collectionPath,
308
+ scriptingConfig
309
+ });
310
+
311
+ return {
312
+ response,
313
+ envVariables: cleanJson(envVariables),
314
+ persistentEnvVariables: cleanJson(bru.persistentEnvVariables),
315
+ runtimeVariables: cleanJson(runtimeVariables),
316
+ visualizations,
317
+ globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
318
+ results: cleanJson(__brunoTestResults.getResults()),
319
+ nextRequestName: bru.nextRequest,
320
+ skipRequest: bru.skipRequest,
321
+ stopExecution: bru.stopExecution
322
+ };
323
+ }
324
+
280
325
  if (this.runtime === 'quickjs') {
281
326
  await executeQuickJsVmAsync({
282
327
  script: script,
@@ -1,4 +1,5 @@
1
1
  const { NodeVM } = require('@usebruno/vm2');
2
+ const { runScriptInNodeVm } = require('../sandbox/node-vm');
2
3
  const chai = require('chai');
3
4
  const path = require('path');
4
5
  const http = require('http');
@@ -134,7 +135,15 @@ class TestRuntime {
134
135
  if (this.runtime === 'quickjs') {
135
136
  await executeQuickJsVmAsync({
136
137
  script: testsFile,
137
- context: context
138
+ context: context,
139
+ collectionPath
140
+ });
141
+ } else if (this.runtime === 'nodevm') {
142
+ await runScriptInNodeVm({
143
+ script: testsFile,
144
+ context,
145
+ collectionPath,
146
+ scriptingConfig
138
147
  });
139
148
  } else {
140
149
  // default runtime is vm2
@@ -143,6 +152,7 @@ class TestRuntime {
143
152
  require: {
144
153
  context: 'sandbox',
145
154
  external: true,
155
+ builtin: ['*'],
146
156
  root: [collectionPath, ...additionalContextRootsAbsolute],
147
157
  mock: {
148
158
  // node libs
@@ -11,7 +11,7 @@ const bundleLibraries = async () => {
11
11
  import moment from "moment";
12
12
  import btoa from "btoa";
13
13
  import atob from "atob";
14
- import * as CryptoJS from "@usebruno/crypto-js";
14
+ import * as cryptoJs from 'crypto-js';
15
15
  import tv4 from "tv4";
16
16
  globalThis.expect = expect;
17
17
  globalThis.assert = assert;
@@ -19,7 +19,6 @@ const bundleLibraries = async () => {
19
19
  globalThis.btoa = btoa;
20
20
  globalThis.atob = atob;
21
21
  globalThis.Buffer = Buffer;
22
- globalThis.CryptoJS = CryptoJS;
23
22
  globalThis.tv4 = tv4;
24
23
  globalThis.requireObject = {
25
24
  ...(globalThis.requireObject || {}),
@@ -28,7 +27,7 @@ const bundleLibraries = async () => {
28
27
  'buffer': { Buffer },
29
28
  'btoa': btoa,
30
29
  'atob': atob,
31
- 'crypto-js': CryptoJS,
30
+ 'crypto-js': cryptoJs,
32
31
  'tv4': tv4
33
32
  };
34
33
  `;
@@ -0,0 +1,223 @@
1
+ const vm = require('node:vm');
2
+ const fs = require('node:fs');
3
+ const path = require('node:path');
4
+ const { get } = require('lodash');
5
+ const lodash = require('lodash');
6
+ const { cleanJson } = require('../../utils');
7
+
8
+ class ScriptError extends Error {
9
+ constructor(error, script) {
10
+ super(error.message);
11
+ this.name = 'ScriptError';
12
+ this.originalError = error;
13
+ this.script = script;
14
+ this.stack = error.stack;
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Executes a script in a Node.js VM context with enhanced security and module loading
20
+ * @param {Object} options - Configuration options
21
+ * @param {string} options.script - The script code to execute
22
+ * @param {Object} options.context - The execution context with Bruno objects
23
+ * @param {string} options.collectionPath - Path to the collection directory
24
+ * @param {Object} options.scriptingConfig - Scripting configuration options
25
+ * @returns {Promise<Object>} Execution results including variables and test results
26
+ * @throws {ScriptError} When script execution fails
27
+ */
28
+ async function runScriptInNodeVm({
29
+ script,
30
+ context,
31
+ collectionPath,
32
+ scriptingConfig
33
+ }) {
34
+ if (script.trim().length === 0) {
35
+ return;
36
+ }
37
+
38
+ try {
39
+ // Create script context with all necessary variables
40
+ const scriptContext = {
41
+ // Bruno context
42
+ console: context.console,
43
+ req: context.req,
44
+ res: context.res,
45
+ bru: context.bru,
46
+ expect: context.expect,
47
+ assert: context.assert,
48
+ __brunoTestResults: context.__brunoTestResults,
49
+ test: context.test,
50
+ // Configuration for nested module loading
51
+ scriptingConfig: scriptingConfig,
52
+ // Global objects
53
+ Buffer: global.Buffer,
54
+ process: global.process,
55
+ setTimeout: global.setTimeout,
56
+ setInterval: global.setInterval,
57
+ clearTimeout: global.clearTimeout,
58
+ clearInterval: global.clearInterval,
59
+ setImmediate: global.setImmediate,
60
+ clearImmediate: global.clearImmediate
61
+ };
62
+
63
+ // Create shared cache for local modules
64
+ const localModuleCache = new Map();
65
+
66
+ // Create a custom require function and add it to the context
67
+ scriptContext.require = createCustomRequire({
68
+ scriptingConfig,
69
+ collectionPath,
70
+ scriptContext,
71
+ currentModuleDir: collectionPath,
72
+ localModuleCache
73
+ });
74
+
75
+ // Execute the script in an isolated VM context
76
+ await vm.runInNewContext(`
77
+ (async function(){
78
+ ${script}
79
+ })();
80
+ `, scriptContext, {
81
+ filename: path.join(collectionPath, 'script.js'),
82
+ displayErrors: true
83
+ });
84
+ } catch (error) {
85
+ throw new ScriptError(error, script);
86
+ }
87
+
88
+ return;
89
+ }
90
+
91
+
92
+ /**
93
+ * Creates a custom require function with enhanced security and local module support
94
+ * @param {Object} options - Configuration options
95
+ * @param {Object} options.scriptingConfig - Scripting configuration with additional context roots
96
+ * @param {string} options.collectionPath - Base collection path for security checks
97
+ * @param {Object} options.scriptContext - Script execution context
98
+ * @param {string} options.currentModuleDir - Current module directory for relative imports
99
+ * @param {Map} options.localModuleCache - Cache for loaded local modules
100
+ * @returns {Function} Custom require function
101
+ */
102
+ function createCustomRequire({
103
+ scriptingConfig,
104
+ collectionPath,
105
+ scriptContext,
106
+ currentModuleDir = collectionPath,
107
+ localModuleCache = new Map()
108
+ }) {
109
+ const additionalContextRoots = get(scriptingConfig, 'additionalContextRoots', []);
110
+ const additionalContextRootsAbsolute = lodash
111
+ .chain(additionalContextRoots)
112
+ .map((acr) => (acr.startsWith('/') ? acr : path.join(collectionPath, acr)))
113
+ .value();
114
+ additionalContextRootsAbsolute.push(collectionPath);
115
+
116
+ return (moduleName) => {
117
+ // Check if it's a local module (starts with ./ or ../)
118
+ if (moduleName.startsWith('./') || moduleName.startsWith('../')) {
119
+ return loadLocalModule({ moduleName, collectionPath, scriptContext, localModuleCache, currentModuleDir });
120
+ }
121
+
122
+ // First try to require as a native/npm module
123
+ try {
124
+ return require(moduleName);
125
+ } catch {
126
+ // If that fails, try to resolve from additionalContextRoots
127
+ try {
128
+ const modulePath = require.resolve(moduleName, { paths: additionalContextRootsAbsolute });
129
+ return require(modulePath);
130
+ } catch (error) {
131
+ throw new Error(`Could not resolve module "${moduleName}": ${error.message}\n\nThis most likely means you did not install the module under "additionalContextRoots" using a package manager like npm.\n\nThese are your current "additionalContextRoots":\n${additionalContextRootsAbsolute.map(root => ` - ${root}`).join('\n') || ' - No "additionalContextRoots" defined'}`);
132
+ }
133
+ }
134
+ };
135
+ }
136
+
137
+ /**
138
+ * Loads a local module from the filesystem with security checks and caching
139
+ * @param {Object} options - Configuration options
140
+ * @param {string} options.moduleName - Name/path of the module to load
141
+ * @param {string} options.collectionPath - Base collection path for security validation
142
+ * @param {Object} options.scriptContext - Script execution context to inherit
143
+ * @param {Map} options.localModuleCache - Cache for loaded modules
144
+ * @param {string} options.currentModuleDir - Directory of the current module for relative resolution
145
+ * @returns {*} The exported content of the loaded module
146
+ * @throws {Error} When module is outside collection path or cannot be loaded
147
+ */
148
+ function loadLocalModule({
149
+ moduleName,
150
+ collectionPath,
151
+ scriptContext,
152
+ localModuleCache,
153
+ currentModuleDir
154
+ }) {
155
+ // Check if the filename has an extension
156
+ const hasExtension = path.extname(moduleName) !== '';
157
+ const resolvedFilename = hasExtension ? moduleName : `${moduleName}.js`;
158
+
159
+ // Resolve the file path relative to the current module's directory
160
+ const filePath = path.resolve(currentModuleDir, resolvedFilename);
161
+ const normalizedFilePath = path.normalize(filePath);
162
+ const normalizedCollectionPath = path.normalize(collectionPath);
163
+
164
+ // Cross-platform security check: ensure the resolved file is within collectionPath
165
+ const relativePath = path.relative(normalizedCollectionPath, normalizedFilePath);
166
+ if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
167
+ throw new Error(`Access to files outside of the collectionPath is not allowed: ${moduleName}`);
168
+ }
169
+
170
+ // Check cache first (use normalized path as key)
171
+ if (localModuleCache.has(normalizedFilePath)) {
172
+ return localModuleCache.get(normalizedFilePath);
173
+ }
174
+
175
+ if (!fs.existsSync(normalizedFilePath)) {
176
+ throw new Error(`Cannot find module ${moduleName}`);
177
+ }
178
+
179
+ // Read and execute the local module
180
+ const moduleCode = fs.readFileSync(normalizedFilePath, 'utf8');
181
+
182
+ // Create module object
183
+ const moduleObj = { exports: {} };
184
+
185
+ // Get the directory of this module for nested imports
186
+ const moduleDir = path.dirname(normalizedFilePath);
187
+
188
+ // Create a new context that inherits from the script context
189
+ const moduleContext = {
190
+ ...scriptContext,
191
+ module: moduleObj,
192
+ exports: moduleObj.exports,
193
+ __filename: normalizedFilePath,
194
+ __dirname: moduleDir,
195
+ // Create a custom require function for this module that resolves relative to its directory
196
+ require: createCustomRequire({
197
+ scriptingConfig: scriptContext.scriptingConfig || {},
198
+ collectionPath,
199
+ scriptContext,
200
+ currentModuleDir: moduleDir,
201
+ localModuleCache
202
+ })
203
+ };
204
+
205
+ try {
206
+ // Execute the module code in the shared context
207
+ vm.runInNewContext(moduleCode, moduleContext, {
208
+ filename: normalizedFilePath,
209
+ displayErrors: true
210
+ });
211
+
212
+ // Cache the result using normalized path
213
+ localModuleCache.set(normalizedFilePath, moduleObj.exports);
214
+
215
+ return moduleObj.exports;
216
+ } catch (error) {
217
+ throw new Error(`Error loading local module ${moduleName}: ${error.message}`);
218
+ }
219
+ }
220
+
221
+ module.exports = {
222
+ runScriptInNodeVm
223
+ };
@@ -11,6 +11,7 @@ const { newQuickJSWASMModule, memoizePromiseFactory } = require('quickjs-emscrip
11
11
  const getBundledCode = require('../bundle-browser-rollup');
12
12
  const addPathShimToContext = require('./shims/lib/path');
13
13
  const { marshallToVm } = require('./utils');
14
+ const addCryptoUtilsShimToContext = require('./shims/lib/crypto-utils');
14
15
 
15
16
  let QuickJSSyncContext;
16
17
  const loader = memoizePromiseFactory(() => newQuickJSWASMModule());
@@ -98,6 +99,9 @@ const executeQuickJsVmAsync = async ({ script: externalScript, context: external
98
99
  const module = await newQuickJSWASMModule();
99
100
  const vm = module.newContext();
100
101
 
102
+ // add crypto utilities required by the crypto-js library in bundledCode
103
+ await addCryptoUtilsShimToContext(vm);
104
+
101
105
  const bundledCode = getBundledCode?.toString() || '';
102
106
  const moduleLoaderCode = function () {
103
107
  return `
@@ -0,0 +1,104 @@
1
+ const crypto = require('node:crypto');
2
+ const { marshallToVm } = require('../../utils');
3
+ const { serializeTypedArray, deserializeTypedArray } = require('./utils');
4
+
5
+ /**
6
+ * Node.js crypto module shim for QuickJS sandbox
7
+ * Implements crypto.randomBytes and crypto.getRandomValues functions
8
+ */
9
+ const addCryptoUtilsShimToContext = async (vm) => {
10
+ let randomBytesHandle = vm.newFunction('randomBytes', function (sizeHandle) {
11
+ try {
12
+ let size = vm.dump(sizeHandle);
13
+
14
+ if (typeof size !== 'number') {
15
+ throw new TypeError('The "size" argument must be of type number');
16
+ }
17
+
18
+ size = Math.trunc(size);
19
+
20
+ if (size < 0) {
21
+ throw new RangeError('The "size" argument must be >= 0');
22
+ }
23
+
24
+ if (size > 65536) { // 2^31 - 1 (max safe integer for practical use)
25
+ throw new RangeError('The "size" argument is too large');
26
+ }
27
+
28
+ if (size === 0) {
29
+ return marshallToVm([], vm);
30
+ }
31
+
32
+ const buffer = crypto.randomBytes(size);
33
+
34
+ const byteArray = Array.from(buffer);
35
+
36
+ return marshallToVm(byteArray, vm);
37
+
38
+ } catch (error) {
39
+ const vmError = vm.newError(error.message);
40
+ vm.setProp(vmError, 'name', vm.newString(error.name));
41
+
42
+ throw vmError;
43
+ }
44
+ });
45
+
46
+ let getRandomValuesHandle = vm.newFunction('getRandomValues', function (arrayHandle) {
47
+ try {
48
+ // Receive the serialized array data directly
49
+ const serializedArray = vm.dump(arrayHandle);
50
+ const typedArray = deserializeTypedArray(serializedArray);
51
+
52
+ if (typedArray.length === 0) {
53
+ return marshallToVm([], vm);
54
+ }
55
+
56
+ if (typedArray.length > 65536) {
57
+ throw new Error('getRandomValues: ArrayBufferView byte length exceeds 65536');
58
+ }
59
+
60
+ crypto.getRandomValues(typedArray);
61
+
62
+ const byteArray = Array.from(typedArray);
63
+
64
+ return marshallToVm(byteArray, vm);
65
+
66
+ } catch (error) {
67
+ const vmError = vm.newError(error.message);
68
+ vm.setProp(vmError, 'name', vm.newString(error.name));
69
+
70
+ throw vmError;
71
+ }
72
+ });
73
+
74
+ // Set the functions in global context
75
+ vm.setProp(vm.global, '__bruno__crypto__randomBytes', randomBytesHandle);
76
+ vm.setProp(vm.global, '__bruno__crypto__getRandomValues', getRandomValuesHandle);
77
+ randomBytesHandle.dispose();
78
+ getRandomValuesHandle.dispose();
79
+
80
+ vm.evalCode(`
81
+ // Helper function for typed array serialization
82
+ ${serializeTypedArray.toString()}
83
+
84
+ // Create crypto module object following Node.js specifications
85
+ const cryptoModule = {
86
+ // node.js crypto.randomBytes API
87
+ randomBytes: function(size) {
88
+ const byteArray = globalThis.__bruno__crypto__randomBytes(size);
89
+ return Buffer.from(Array.from(byteArray));
90
+ },
91
+ // node.js crypto.getRandomValues API
92
+ getRandomValues: function(typedArray) {
93
+ const serializedTypedArray = serializeTypedArray(typedArray);
94
+ typedArray.set(globalThis.__bruno__crypto__getRandomValues(serializedTypedArray));
95
+ return typedArray;
96
+ },
97
+ };
98
+
99
+ // Make crypto available globally
100
+ globalThis.crypto = cryptoModule;
101
+ `);
102
+ };
103
+
104
+ module.exports = addCryptoUtilsShimToContext;
@@ -0,0 +1,73 @@
1
+ const { describe, it, expect } = require('@jest/globals');
2
+ const { newQuickJSWASMModule } = require('quickjs-emscripten');
3
+ const addCryptoUtilsShimToContext = require('./crypto-utils');
4
+ const getBundledCode = require('../../../bundle-browser-rollup');
5
+
6
+ describe('crypto-utils shims tests', () => {
7
+ let vm, module;
8
+
9
+ beforeAll(async () => {
10
+ module = await newQuickJSWASMModule();
11
+ });
12
+
13
+ beforeEach(async () => {
14
+ vm = module.newContext();
15
+ await addCryptoUtilsShimToContext(vm);
16
+ // required for `Buffer` library usage
17
+ const bundledCode = getBundledCode?.toString() || '';
18
+ vm.evalCode(
19
+ `
20
+ (${bundledCode})()
21
+ `
22
+ );
23
+ });
24
+
25
+ it('should provide crypto.randomBytes function', async () => {
26
+ const result = vm.evalCode('typeof crypto.randomBytes');
27
+ const handle = vm.unwrapResult(result);
28
+ const type = vm.dump(handle);
29
+ handle.dispose();
30
+
31
+ expect(type).toBe('function');
32
+ });
33
+
34
+ it('should provide crypto.getRandomValues function', async () => {
35
+ const result = vm.evalCode('typeof crypto.getRandomValues');
36
+ const handle = vm.unwrapResult(result);
37
+ const type = vm.dump(handle);
38
+ handle.dispose();
39
+
40
+ expect(type).toBe('function');
41
+ });
42
+
43
+ it('should generate random bytes with correct length', async () => {
44
+ const result = vm.evalCode('crypto.randomBytes(8).length');
45
+ const handle = vm.unwrapResult(result);
46
+ const length = vm.dump(handle);
47
+ handle.dispose();
48
+
49
+ expect(length).toBe(8);
50
+ });
51
+
52
+ it('should convert random bytes to hex string', async () => {
53
+ const result = vm.evalCode('crypto.randomBytes(4).toString("hex").length');
54
+ const handle = vm.unwrapResult(result);
55
+ const hexLength = vm.dump(handle);
56
+ handle.dispose();
57
+
58
+ expect(hexLength).toBe(8); // 4 bytes = 8 hex chars
59
+ });
60
+
61
+ it('should fill Uint8Array with getRandomValues', async () => {
62
+ const result = vm.evalCode(`
63
+ const arr = new Uint8Array(5);
64
+ crypto.getRandomValues(arr);
65
+ arr.length;
66
+ `);
67
+ const handle = vm.unwrapResult(result);
68
+ const length = vm.dump(handle);
69
+ handle.dispose();
70
+
71
+ expect(length).toBe(5);
72
+ });
73
+ });
@@ -0,0 +1,48 @@
1
+ function serializeTypedArray(ta) {
2
+ return {
3
+ type: ta.constructor.name,
4
+ array: Array.from(ta),
5
+ length: ta.length
6
+ };
7
+ }
8
+
9
+ function deserializeTypedArray(obj) {
10
+ // Allowed typed array constructors for crypto operations
11
+ const allowedConstructors = new Set([
12
+ 'Int8Array',
13
+ 'Uint8Array',
14
+ 'Uint8ClampedArray',
15
+ 'Int16Array',
16
+ 'Uint16Array',
17
+ 'Int32Array',
18
+ 'Uint32Array',
19
+ 'Float32Array',
20
+ 'Float64Array',
21
+ 'BigInt64Array',
22
+ 'BigUint64Array'
23
+ ]);
24
+
25
+ if (!obj || typeof obj !== 'object') {
26
+ throw new TypeError('getRandomValues: Invalid typed array object');
27
+ }
28
+
29
+ if (typeof obj.type !== 'string' || !allowedConstructors.has(obj.type)) {
30
+ throw new TypeError(`getRandomValues: Invalid or unsupported typed array type: ${obj.type}`);
31
+ }
32
+
33
+ if (!obj.array || typeof obj.length !== 'number') {
34
+ throw new TypeError('getRandomValues: Invalid typed array properties');
35
+ }
36
+
37
+ const ctor = globalThis[obj.type];
38
+ if (typeof ctor !== 'function') {
39
+ throw new TypeError(`getRandomValues: Constructor ${obj.type} is not available`);
40
+ }
41
+
42
+ return new ctor(obj.array, 0, obj.length);
43
+ }
44
+
45
+ module.exports = {
46
+ serializeTypedArray,
47
+ deserializeTypedArray
48
+ }