@usebruno/js 0.45.1 → 0.46.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 -3
- package/src/bru.js +110 -18
- package/src/bruno-request.js +21 -19
- package/src/index.js +4 -1
- package/src/runtime/script-runtime.js +107 -62
- package/src/runtime/test-runtime.js +8 -3
- package/src/sandbox/node-vm/console.js +102 -0
- package/src/sandbox/node-vm/index.js +68 -8
- package/src/sandbox/node-vm/utils.js +15 -0
- package/src/sandbox/quickjs/index.js +6 -21
- package/src/sandbox/quickjs/shims/bru.js +108 -3
- package/src/sandbox/quickjs/shims/bruno-request.js +12 -0
- package/src/sandbox/quickjs/shims/console.js +97 -5
- package/src/sandbox/quickjs/shims/lib/axios.js +26 -16
- package/src/sandbox/quickjs/shims/lib/axios.spec.js +495 -0
- package/src/test.js +6 -4
- package/src/utils/error-formatter.js +426 -0
- package/src/utils/error-formatter.spec.js +388 -0
- package/src/utils/sandbox.js +64 -0
- package/src/utils.js +27 -6
|
@@ -2,10 +2,12 @@ const vm = require('node:vm');
|
|
|
2
2
|
const path = require('node:path');
|
|
3
3
|
const { get } = require('lodash');
|
|
4
4
|
const lodash = require('lodash');
|
|
5
|
-
const {
|
|
5
|
+
const { wrapConsoleWithSerializers } = require('./console');
|
|
6
|
+
const { ScriptError, resolveVmFilename } = require('./utils');
|
|
6
7
|
const { createCustomRequire } = require('./cjs-loader');
|
|
7
8
|
const { safeGlobals } = require('./constants');
|
|
8
9
|
const { mixinTypedArrays } = require('../mixins/typed-arrays');
|
|
10
|
+
const { wrapScriptInClosure, SANDBOX } = require('../../utils/sandbox');
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Executes a script in a Node.js VM context with enhanced security and module loading
|
|
@@ -15,10 +17,17 @@ const { mixinTypedArrays } = require('../mixins/typed-arrays');
|
|
|
15
17
|
* @param {Object} options.context - The execution context with Bruno objects
|
|
16
18
|
* @param {string} options.collectionPath - Path to the collection directory
|
|
17
19
|
* @param {Object} options.scriptingConfig - Scripting configuration options
|
|
18
|
-
* @
|
|
20
|
+
* @param {string} [options.scriptPath] - Path to the source file for accurate stack traces
|
|
21
|
+
* @returns {Promise<Object>} Execution results including variables and test results
|
|
19
22
|
* @throws {ScriptError} When script execution fails
|
|
20
23
|
*/
|
|
21
|
-
async function runScriptInNodeVm({
|
|
24
|
+
async function runScriptInNodeVm({
|
|
25
|
+
script,
|
|
26
|
+
context,
|
|
27
|
+
collectionPath,
|
|
28
|
+
scriptingConfig,
|
|
29
|
+
scriptPath
|
|
30
|
+
}) {
|
|
22
31
|
if (script.trim().length === 0) {
|
|
23
32
|
return;
|
|
24
33
|
}
|
|
@@ -57,13 +66,61 @@ async function runScriptInNodeVm({ script, context, collectionPath, scriptingCon
|
|
|
57
66
|
additionalContextRootsAbsolute
|
|
58
67
|
});
|
|
59
68
|
|
|
69
|
+
const vmFilename = resolveVmFilename(scriptPath, collectionPath);
|
|
70
|
+
|
|
60
71
|
// Execute the script in the isolated context
|
|
61
|
-
const wrappedScript =
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
72
|
+
const wrappedScript = wrapScriptInClosure(script, SANDBOX.NODEVM);
|
|
73
|
+
let compiledScript;
|
|
74
|
+
try {
|
|
75
|
+
compiledScript = new vm.Script(wrappedScript, {
|
|
76
|
+
filename: vmFilename
|
|
77
|
+
});
|
|
78
|
+
} catch (error) {
|
|
79
|
+
// V8 puts "filename:line" as the first line of syntax error stacks.
|
|
80
|
+
// Parse it so the error formatter can map to the correct source location.
|
|
81
|
+
const firstLine = error.stack?.split('\n')[0];
|
|
82
|
+
const match = firstLine?.match(/^(.+):(\d+)$/);
|
|
83
|
+
if (match && match[1] === vmFilename) {
|
|
84
|
+
error.__callSites = [{
|
|
85
|
+
filePath: vmFilename,
|
|
86
|
+
line: parseInt(match[2], 10),
|
|
87
|
+
column: null,
|
|
88
|
+
functionName: null
|
|
89
|
+
}];
|
|
90
|
+
}
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Capture structured call sites for error-formatter line mapping
|
|
95
|
+
const originalPrepareStackTrace = Error.prepareStackTrace;
|
|
96
|
+
Error.prepareStackTrace = (error, callSites) => {
|
|
97
|
+
error.__callSites = callSites
|
|
98
|
+
.filter((site) => site.getFileName() === vmFilename)
|
|
99
|
+
.map((site) => ({
|
|
100
|
+
filePath: site.getFileName(),
|
|
101
|
+
line: site.getLineNumber(),
|
|
102
|
+
column: site.getColumnNumber(),
|
|
103
|
+
functionName: site.getFunctionName() || null
|
|
104
|
+
}));
|
|
65
105
|
|
|
66
|
-
|
|
106
|
+
return error.toString() + '\n' + callSites
|
|
107
|
+
.map((site) => ` at ${site}`)
|
|
108
|
+
.join('\n');
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
await compiledScript.runInContext(isolatedContext, {
|
|
113
|
+
displayErrors: true
|
|
114
|
+
});
|
|
115
|
+
} catch (error) {
|
|
116
|
+
// V8 invokes prepareStackTrace lazily on first .stack access.
|
|
117
|
+
// Reading .stack here so custom handler runs and populates error.__callSites
|
|
118
|
+
// (used later by the error formatter to map stack frames to the .bru/.yml script)
|
|
119
|
+
void error.stack;
|
|
120
|
+
throw error;
|
|
121
|
+
} finally {
|
|
122
|
+
Error.prepareStackTrace = originalPrepareStackTrace;
|
|
123
|
+
}
|
|
67
124
|
} catch (error) {
|
|
68
125
|
throw new ScriptError(error, script);
|
|
69
126
|
}
|
|
@@ -79,6 +136,9 @@ function buildScriptContext(context, scriptingConfig) {
|
|
|
79
136
|
const scriptContext = {
|
|
80
137
|
...context,
|
|
81
138
|
|
|
139
|
+
// Bruno context (wrap console with Set/Map support)
|
|
140
|
+
console: wrapConsoleWithSerializers(context.console),
|
|
141
|
+
|
|
82
142
|
// Configuration for nested module loading
|
|
83
143
|
scriptingConfig: scriptingConfig,
|
|
84
144
|
|
|
@@ -25,6 +25,19 @@ function isPathWithinAllowedRoots(normalizedPath, additionalContextRootsAbsolute
|
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the VM filename for the script
|
|
30
|
+
* @param {string|null} scriptPath - Path to the source file
|
|
31
|
+
* @param {string} collectionPath - Path to the collection directory
|
|
32
|
+
* @returns {string} Absolute path to use as the VM filename
|
|
33
|
+
*/
|
|
34
|
+
function resolveVmFilename(scriptPath, collectionPath) {
|
|
35
|
+
if (scriptPath) {
|
|
36
|
+
return path.isAbsolute(scriptPath) ? scriptPath : path.join(collectionPath, scriptPath);
|
|
37
|
+
}
|
|
38
|
+
return path.join(collectionPath, 'script.js');
|
|
39
|
+
}
|
|
40
|
+
|
|
28
41
|
class ScriptError extends Error {
|
|
29
42
|
constructor(error, script) {
|
|
30
43
|
super(error.message);
|
|
@@ -32,11 +45,13 @@ class ScriptError extends Error {
|
|
|
32
45
|
this.originalError = error;
|
|
33
46
|
this.script = script;
|
|
34
47
|
this.stack = error.stack;
|
|
48
|
+
this.__callSites = error.__callSites || null;
|
|
35
49
|
}
|
|
36
50
|
}
|
|
37
51
|
|
|
38
52
|
module.exports = {
|
|
39
53
|
isBuiltinModule,
|
|
40
54
|
isPathWithinAllowedRoots,
|
|
55
|
+
resolveVmFilename,
|
|
41
56
|
ScriptError
|
|
42
57
|
};
|
|
@@ -12,6 +12,7 @@ const getBundledCode = require('../bundle-browser-rollup');
|
|
|
12
12
|
const addPathShimToContext = require('./shims/lib/path');
|
|
13
13
|
const { marshallToVm } = require('./utils');
|
|
14
14
|
const addCryptoUtilsShimToContext = require('./shims/lib/crypto-utils');
|
|
15
|
+
const { wrapScriptInClosure, SANDBOX } = require('../../utils/sandbox');
|
|
15
16
|
|
|
16
17
|
let QuickJSSyncContext;
|
|
17
18
|
const loader = memoizePromiseFactory(() => newQuickJSWASMModule());
|
|
@@ -89,7 +90,7 @@ const executeQuickJsVm = ({ script: externalScript, context: externalContext, sc
|
|
|
89
90
|
}
|
|
90
91
|
};
|
|
91
92
|
|
|
92
|
-
const executeQuickJsVmAsync = async ({ script: externalScript, context: externalContext, collectionPath }) => {
|
|
93
|
+
const executeQuickJsVmAsync = async ({ script: externalScript, context: externalContext, collectionPath, scriptPath }) => {
|
|
93
94
|
if (!externalScript?.length || typeof externalScript !== 'string') {
|
|
94
95
|
return externalScript;
|
|
95
96
|
}
|
|
@@ -157,25 +158,9 @@ const executeQuickJsVmAsync = async ({ script: externalScript, context: external
|
|
|
157
158
|
|
|
158
159
|
test && __brunoTestResults && addTestShimToContext(vm, __brunoTestResults);
|
|
159
160
|
|
|
160
|
-
const script =
|
|
161
|
-
(async () => {
|
|
162
|
-
const setTimeout = async(fn, timer) => {
|
|
163
|
-
v = await bru.sleep(timer);
|
|
164
|
-
fn.apply();
|
|
165
|
-
}
|
|
166
|
-
await bru.sleep(0);
|
|
167
|
-
try {
|
|
168
|
-
${externalScript}
|
|
169
|
-
}
|
|
170
|
-
catch(error) {
|
|
171
|
-
console?.debug?.('quick-js:execution-end:with-error', error?.message);
|
|
172
|
-
throw new Error(error?.message);
|
|
173
|
-
}
|
|
174
|
-
return 'done';
|
|
175
|
-
})()
|
|
176
|
-
`;
|
|
161
|
+
const script = wrapScriptInClosure(externalScript, SANDBOX.QUICKJS);
|
|
177
162
|
|
|
178
|
-
const result = vm.evalCode(script);
|
|
163
|
+
const result = vm.evalCode(script, scriptPath);
|
|
179
164
|
const promiseHandle = vm.unwrapResult(result);
|
|
180
165
|
const resolvedResult = await vm.resolvePromise(promiseHandle);
|
|
181
166
|
promiseHandle.dispose();
|
|
@@ -184,8 +169,8 @@ const executeQuickJsVmAsync = async ({ script: externalScript, context: external
|
|
|
184
169
|
// vm.dispose();
|
|
185
170
|
return;
|
|
186
171
|
} catch (error) {
|
|
187
|
-
|
|
188
|
-
throw
|
|
172
|
+
error.__isQuickJS = true;
|
|
173
|
+
throw error;
|
|
189
174
|
}
|
|
190
175
|
};
|
|
191
176
|
|
|
@@ -61,11 +61,23 @@ const addBruShimToContext = (vm, bru) => {
|
|
|
61
61
|
setEnvVar.dispose();
|
|
62
62
|
|
|
63
63
|
let deleteEnvVar = vm.newFunction('deleteEnvVar', function (key) {
|
|
64
|
-
|
|
64
|
+
bru.deleteEnvVar(vm.dump(key));
|
|
65
65
|
});
|
|
66
66
|
vm.setProp(bruObject, 'deleteEnvVar', deleteEnvVar);
|
|
67
67
|
deleteEnvVar.dispose();
|
|
68
68
|
|
|
69
|
+
let getAllEnvVars = vm.newFunction('getAllEnvVars', function () {
|
|
70
|
+
return marshallToVm(bru.getAllEnvVars(), vm);
|
|
71
|
+
});
|
|
72
|
+
vm.setProp(bruObject, 'getAllEnvVars', getAllEnvVars);
|
|
73
|
+
getAllEnvVars.dispose();
|
|
74
|
+
|
|
75
|
+
let deleteAllEnvVars = vm.newFunction('deleteAllEnvVars', function () {
|
|
76
|
+
bru.deleteAllEnvVars();
|
|
77
|
+
});
|
|
78
|
+
vm.setProp(bruObject, 'deleteAllEnvVars', deleteAllEnvVars);
|
|
79
|
+
deleteAllEnvVars.dispose();
|
|
80
|
+
|
|
69
81
|
let getGlobalEnvVar = vm.newFunction('getGlobalEnvVar', function (key) {
|
|
70
82
|
return marshallToVm(bru.getGlobalEnvVar(vm.dump(key)), vm);
|
|
71
83
|
});
|
|
@@ -78,12 +90,40 @@ const addBruShimToContext = (vm, bru) => {
|
|
|
78
90
|
vm.setProp(bruObject, 'getOauth2CredentialVar', getOauth2CredentialVar);
|
|
79
91
|
getOauth2CredentialVar.dispose();
|
|
80
92
|
|
|
93
|
+
let resetOauth2Credential = vm.newFunction('resetOauth2Credential', function (credentialId) {
|
|
94
|
+
bru.resetOauth2Credential(vm.dump(credentialId));
|
|
95
|
+
});
|
|
96
|
+
vm.setProp(bruObject, 'resetOauth2Credential', resetOauth2Credential);
|
|
97
|
+
resetOauth2Credential.dispose();
|
|
98
|
+
|
|
81
99
|
let setGlobalEnvVar = vm.newFunction('setGlobalEnvVar', function (key, value) {
|
|
82
100
|
bru.setGlobalEnvVar(vm.dump(key), vm.dump(value));
|
|
83
101
|
});
|
|
84
102
|
vm.setProp(bruObject, 'setGlobalEnvVar', setGlobalEnvVar);
|
|
85
103
|
setGlobalEnvVar.dispose();
|
|
86
104
|
|
|
105
|
+
// TODO: deleteGlobalEnvVar works in the request lifecycle but does not update the UI.
|
|
106
|
+
// Re-enable once the UI sync issue is resolved.
|
|
107
|
+
// let deleteGlobalEnvVar = vm.newFunction('deleteGlobalEnvVar', function (key) {
|
|
108
|
+
// bru.deleteGlobalEnvVar(vm.dump(key));
|
|
109
|
+
// });
|
|
110
|
+
// vm.setProp(bruObject, 'deleteGlobalEnvVar', deleteGlobalEnvVar);
|
|
111
|
+
// deleteGlobalEnvVar.dispose();
|
|
112
|
+
|
|
113
|
+
let getAllGlobalEnvVars = vm.newFunction('getAllGlobalEnvVars', function () {
|
|
114
|
+
return marshallToVm(bru.getAllGlobalEnvVars(), vm);
|
|
115
|
+
});
|
|
116
|
+
vm.setProp(bruObject, 'getAllGlobalEnvVars', getAllGlobalEnvVars);
|
|
117
|
+
getAllGlobalEnvVars.dispose();
|
|
118
|
+
|
|
119
|
+
// TODO: deleteAllGlobalEnvVars works in the request lifecycle but does not update the UI.
|
|
120
|
+
// Re-enable once the UI sync issue is resolved.
|
|
121
|
+
// let deleteAllGlobalEnvVars = vm.newFunction('deleteAllGlobalEnvVars', function () {
|
|
122
|
+
// bru.deleteAllGlobalEnvVars();
|
|
123
|
+
// });
|
|
124
|
+
// vm.setProp(bruObject, 'deleteAllGlobalEnvVars', deleteAllGlobalEnvVars);
|
|
125
|
+
// deleteAllGlobalEnvVars.dispose();
|
|
126
|
+
|
|
87
127
|
let hasVar = vm.newFunction('hasVar', function (key) {
|
|
88
128
|
return marshallToVm(bru.hasVar(vm.dump(key)), vm);
|
|
89
129
|
});
|
|
@@ -114,6 +154,12 @@ const addBruShimToContext = (vm, bru) => {
|
|
|
114
154
|
vm.setProp(bruObject, 'deleteAllVars', deleteAllVars);
|
|
115
155
|
deleteAllVars.dispose();
|
|
116
156
|
|
|
157
|
+
let getAllVars = vm.newFunction('getAllVars', function () {
|
|
158
|
+
return marshallToVm(bru.getAllVars(), vm);
|
|
159
|
+
});
|
|
160
|
+
vm.setProp(bruObject, 'getAllVars', getAllVars);
|
|
161
|
+
getAllVars.dispose();
|
|
162
|
+
|
|
117
163
|
let setNextRequest = vm.newFunction('setNextRequest', function (nextRequest) {
|
|
118
164
|
bru.setNextRequest(vm.dump(nextRequest));
|
|
119
165
|
});
|
|
@@ -212,6 +258,44 @@ const addBruShimToContext = (vm, bru) => {
|
|
|
212
258
|
vm.setProp(bruObject, 'getCollectionVar', getCollectionVar);
|
|
213
259
|
getCollectionVar.dispose();
|
|
214
260
|
|
|
261
|
+
// TODO: setCollectionVar works in the request lifecycle but does not update the UI.
|
|
262
|
+
// Re-enable once the UI sync issue is resolved.
|
|
263
|
+
// let setCollectionVar = vm.newFunction('setCollectionVar', function (key, value) {
|
|
264
|
+
// bru.setCollectionVar(vm.dump(key), vm.dump(value));
|
|
265
|
+
// });
|
|
266
|
+
// vm.setProp(bruObject, 'setCollectionVar', setCollectionVar);
|
|
267
|
+
// setCollectionVar.dispose();
|
|
268
|
+
|
|
269
|
+
let hasCollectionVar = vm.newFunction('hasCollectionVar', function (key) {
|
|
270
|
+
return marshallToVm(bru.hasCollectionVar(vm.dump(key)), vm);
|
|
271
|
+
});
|
|
272
|
+
vm.setProp(bruObject, 'hasCollectionVar', hasCollectionVar);
|
|
273
|
+
hasCollectionVar.dispose();
|
|
274
|
+
|
|
275
|
+
// TODO: deleteCollectionVar works in the request lifecycle but does not update the UI.
|
|
276
|
+
// Re-enable once the UI sync issue is resolved.
|
|
277
|
+
// let deleteCollectionVar = vm.newFunction('deleteCollectionVar', function (key) {
|
|
278
|
+
// bru.deleteCollectionVar(vm.dump(key));
|
|
279
|
+
// });
|
|
280
|
+
// vm.setProp(bruObject, 'deleteCollectionVar', deleteCollectionVar);
|
|
281
|
+
// deleteCollectionVar.dispose();
|
|
282
|
+
|
|
283
|
+
// TODO: deleteAllCollectionVars works in the request lifecycle but does not update the UI.
|
|
284
|
+
// Re-enable once the UI sync issue is resolved.
|
|
285
|
+
// let deleteAllCollectionVars = vm.newFunction('deleteAllCollectionVars', function () {
|
|
286
|
+
// bru.deleteAllCollectionVars();
|
|
287
|
+
// });
|
|
288
|
+
// vm.setProp(bruObject, 'deleteAllCollectionVars', deleteAllCollectionVars);
|
|
289
|
+
// deleteAllCollectionVars.dispose();
|
|
290
|
+
|
|
291
|
+
// TODO: getAllCollectionVars works in the request lifecycle but does not update the UI.
|
|
292
|
+
// Re-enable once the UI sync issue is resolved.
|
|
293
|
+
// let getAllCollectionVars = vm.newFunction('getAllCollectionVars', function () {
|
|
294
|
+
// return marshallToVm(bru.getAllCollectionVars(), vm);
|
|
295
|
+
// });
|
|
296
|
+
// vm.setProp(bruObject, 'getAllCollectionVars', getAllCollectionVars);
|
|
297
|
+
// getAllCollectionVars.dispose();
|
|
298
|
+
|
|
215
299
|
let getTestResults = vm.newFunction('getTestResults', () => {
|
|
216
300
|
const promise = vm.newPromise();
|
|
217
301
|
bru
|
|
@@ -432,6 +516,20 @@ const addBruShimToContext = (vm, bru) => {
|
|
|
432
516
|
});
|
|
433
517
|
_deleteCookieFn.consume((handle) => vm.setProp(jarObj, '_deleteCookie', handle));
|
|
434
518
|
|
|
519
|
+
const _hasCookieFn = vm.newFunction('_hasCookie', (url, cookieName) => {
|
|
520
|
+
const promise = vm.newPromise();
|
|
521
|
+
nativeJar.hasCookie(vm.dump(url), vm.dump(cookieName), (err, exists) => {
|
|
522
|
+
if (err) {
|
|
523
|
+
promise.reject(marshallToVm(cleanJson(err), vm));
|
|
524
|
+
} else {
|
|
525
|
+
promise.resolve(marshallToVm(exists, vm));
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
promise.settled.then(vm.runtime.executePendingJobs);
|
|
529
|
+
return promise.handle;
|
|
530
|
+
});
|
|
531
|
+
_hasCookieFn.consume((handle) => vm.setProp(jarObj, '_hasCookie', handle));
|
|
532
|
+
|
|
435
533
|
return jarObj;
|
|
436
534
|
});
|
|
437
535
|
_jarFn.consume((handle) => vm.setProp(bruCookiesObject, '_jar', handle));
|
|
@@ -446,20 +544,26 @@ const addBruShimToContext = (vm, bru) => {
|
|
|
446
544
|
bruObject.dispose();
|
|
447
545
|
|
|
448
546
|
vm.evalCode(`
|
|
547
|
+
// sendRequest with callback: normalize error.status (axios uses error.response.status) so
|
|
548
|
+
// tests like expect(error.status).to.eql(404) pass in safe sandbox; return response after
|
|
549
|
+
// success callback for consistent promise resolution.
|
|
449
550
|
globalThis.bru.sendRequest = async (requestConfig, callback) => {
|
|
450
551
|
if (!callback) return await globalThis.bru._sendRequest(requestConfig);
|
|
451
552
|
try {
|
|
452
553
|
const response = await globalThis.bru._sendRequest(requestConfig);
|
|
453
554
|
try {
|
|
454
555
|
await callback(null, response);
|
|
556
|
+
return response;
|
|
455
557
|
}
|
|
456
558
|
catch(error) {
|
|
457
559
|
return Promise.reject(error);
|
|
458
560
|
}
|
|
459
561
|
}
|
|
460
562
|
catch(error) {
|
|
563
|
+
const errObj = JSON.parse(JSON.stringify(error));
|
|
564
|
+
if (errObj && errObj.response && typeof errObj.response.status === 'number') errObj.status = errObj.response.status;
|
|
461
565
|
try {
|
|
462
|
-
await callback(
|
|
566
|
+
await callback(errObj, null);
|
|
463
567
|
}
|
|
464
568
|
catch(err) {
|
|
465
569
|
return Promise.reject(err);
|
|
@@ -497,7 +601,8 @@ const addBruShimToContext = (vm, bru) => {
|
|
|
497
601
|
setCookies: (url, cookiesArray, cb) => callWithCallback(() => _jar._setCookies(url, cookiesArray), cb),
|
|
498
602
|
clear: (cb) => callWithCallback(() => _jar._clear(), cb),
|
|
499
603
|
deleteCookies: (url, cb) => callWithCallback(() => _jar._deleteCookies(url), cb),
|
|
500
|
-
deleteCookie: (url, name, cb) => callWithCallback(() => _jar._deleteCookie(url, name), cb)
|
|
604
|
+
deleteCookie: (url, name, cb) => callWithCallback(() => _jar._deleteCookie(url, name), cb),
|
|
605
|
+
hasCookie: (url, name, cb) => callWithCallback(() => _jar._hasCookie(url, name), cb)
|
|
501
606
|
};
|
|
502
607
|
};
|
|
503
608
|
`);
|
|
@@ -102,6 +102,12 @@ const addBrunoRequestShimToContext = (vm, req) => {
|
|
|
102
102
|
vm.setProp(reqObject, 'setHeaders', setHeaders);
|
|
103
103
|
setHeaders.dispose();
|
|
104
104
|
|
|
105
|
+
let deleteHeaders = vm.newFunction('deleteHeaders', function (headers) {
|
|
106
|
+
req.deleteHeaders(vm.dump(headers));
|
|
107
|
+
});
|
|
108
|
+
vm.setProp(reqObject, 'deleteHeaders', deleteHeaders);
|
|
109
|
+
deleteHeaders.dispose();
|
|
110
|
+
|
|
105
111
|
let getHeader = vm.newFunction('getHeader', function (name) {
|
|
106
112
|
return marshallToVm(req.getHeader(vm.dump(name)), vm);
|
|
107
113
|
});
|
|
@@ -114,6 +120,12 @@ const addBrunoRequestShimToContext = (vm, req) => {
|
|
|
114
120
|
vm.setProp(reqObject, 'setHeader', setHeader);
|
|
115
121
|
setHeader.dispose();
|
|
116
122
|
|
|
123
|
+
let deleteHeader = vm.newFunction('deleteHeader', function (header) {
|
|
124
|
+
req.deleteHeader(vm.dump(header));
|
|
125
|
+
});
|
|
126
|
+
vm.setProp(reqObject, 'deleteHeader', deleteHeader);
|
|
127
|
+
deleteHeader.dispose();
|
|
128
|
+
|
|
117
129
|
let getBody = vm.newFunction('getBody', function (options = {}) {
|
|
118
130
|
return marshallToVm(req.getBody(vm.dump(options)), vm);
|
|
119
131
|
});
|
|
@@ -1,30 +1,122 @@
|
|
|
1
1
|
const addConsoleShimToContext = (vm, console) => {
|
|
2
2
|
if (!console) return;
|
|
3
3
|
|
|
4
|
+
// Helper function to convert QuickJS values to native values with Set/Map support
|
|
5
|
+
const dumpWithSerializers = (arg) => {
|
|
6
|
+
// Track all handles for centralized disposal
|
|
7
|
+
let nameProp, constructorProp, constructorNameProp, toStringFn, toStringResult;
|
|
8
|
+
let arrayFn, fromFn, arrayResult;
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const argType = vm.typeof(arg);
|
|
12
|
+
|
|
13
|
+
// Early return for primitives (string, number, boolean, undefined, null)
|
|
14
|
+
if (arg == null || arg === vm.null || arg === vm.undefined) {
|
|
15
|
+
return vm.dump(arg);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (argType !== 'object' && argType !== 'function') {
|
|
19
|
+
return vm.dump(arg);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Handle functions - show clean wrapper
|
|
23
|
+
if (argType === 'function') {
|
|
24
|
+
nameProp = vm.getProp(arg, 'name');
|
|
25
|
+
const name = nameProp ? vm.dump(nameProp) || 'anonymous' : 'anonymous';
|
|
26
|
+
return `function ${name}() {\n [native code]\n}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Try to get the constructor name to detect Set/Map
|
|
30
|
+
constructorProp = vm.getProp(arg, 'constructor');
|
|
31
|
+
if (!constructorProp) {
|
|
32
|
+
return vm.dump(arg);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let constructorName = null;
|
|
36
|
+
constructorNameProp = vm.getProp(constructorProp, 'name');
|
|
37
|
+
if (constructorNameProp) {
|
|
38
|
+
constructorName = vm.dump(constructorNameProp);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Handle Date, RegExp, Error - call toString()
|
|
42
|
+
if (constructorName === 'Date' || constructorName === 'RegExp' || constructorName?.endsWith?.('Error')) {
|
|
43
|
+
toStringFn = vm.getProp(arg, 'toString');
|
|
44
|
+
if (toStringFn) {
|
|
45
|
+
toStringResult = vm.callFunction(toStringFn, arg);
|
|
46
|
+
if (toStringResult.error) {
|
|
47
|
+
return vm.dump(arg);
|
|
48
|
+
}
|
|
49
|
+
return vm.dump(toStringResult.value);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// If not a Set or Map, use standard dump
|
|
54
|
+
if (constructorName !== 'Set' && constructorName !== 'Map') {
|
|
55
|
+
return vm.dump(arg);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Convert Set or Map to array via Array.from
|
|
59
|
+
arrayFn = vm.getProp(vm.global, 'Array');
|
|
60
|
+
if (!arrayFn) {
|
|
61
|
+
return vm.dump(arg);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fromFn = vm.getProp(arrayFn, 'from');
|
|
65
|
+
if (!fromFn) {
|
|
66
|
+
return vm.dump(arg);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
arrayResult = vm.callFunction(fromFn, arrayFn, arg);
|
|
70
|
+
if (arrayResult.error) {
|
|
71
|
+
return vm.dump(arg);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
__brunoType: constructorName,
|
|
76
|
+
__brunoValue: vm.dump(arrayResult.value)
|
|
77
|
+
};
|
|
78
|
+
} catch (e) {
|
|
79
|
+
// Fallback to normal dump
|
|
80
|
+
return vm.dump(arg);
|
|
81
|
+
} finally {
|
|
82
|
+
// Centralized handle disposal - dispose all handles regardless of success or error
|
|
83
|
+
nameProp?.dispose();
|
|
84
|
+
constructorProp?.dispose();
|
|
85
|
+
constructorNameProp?.dispose();
|
|
86
|
+
toStringFn?.dispose();
|
|
87
|
+
toStringResult?.value?.dispose();
|
|
88
|
+
toStringResult?.error?.dispose();
|
|
89
|
+
arrayFn?.dispose();
|
|
90
|
+
fromFn?.dispose();
|
|
91
|
+
arrayResult?.value?.dispose();
|
|
92
|
+
arrayResult?.error?.dispose();
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
4
96
|
const consoleHandle = vm.newObject();
|
|
5
97
|
|
|
6
98
|
const logHandle = vm.newFunction('log', (...args) => {
|
|
7
|
-
const nativeArgs = args.map(
|
|
99
|
+
const nativeArgs = args.map(dumpWithSerializers);
|
|
8
100
|
console?.log?.(...nativeArgs);
|
|
9
101
|
});
|
|
10
102
|
|
|
11
103
|
const debugHandle = vm.newFunction('debug', (...args) => {
|
|
12
|
-
const nativeArgs = args.map(
|
|
104
|
+
const nativeArgs = args.map(dumpWithSerializers);
|
|
13
105
|
console?.debug?.(...nativeArgs);
|
|
14
106
|
});
|
|
15
107
|
|
|
16
108
|
const infoHandle = vm.newFunction('info', (...args) => {
|
|
17
|
-
const nativeArgs = args.map(
|
|
109
|
+
const nativeArgs = args.map(dumpWithSerializers);
|
|
18
110
|
console?.info?.(...nativeArgs);
|
|
19
111
|
});
|
|
20
112
|
|
|
21
113
|
const warnHandle = vm.newFunction('warn', (...args) => {
|
|
22
|
-
const nativeArgs = args.map(
|
|
114
|
+
const nativeArgs = args.map(dumpWithSerializers);
|
|
23
115
|
console?.warn?.(...nativeArgs);
|
|
24
116
|
});
|
|
25
117
|
|
|
26
118
|
const errorHandle = vm.newFunction('error', (...args) => {
|
|
27
|
-
const nativeArgs = args.map(
|
|
119
|
+
const nativeArgs = args.map(dumpWithSerializers);
|
|
28
120
|
console?.error?.(...nativeArgs);
|
|
29
121
|
});
|
|
30
122
|
|
|
@@ -4,6 +4,30 @@ const { marshallToVm } = require('../../utils');
|
|
|
4
4
|
|
|
5
5
|
const methods = ['get', 'post', 'put', 'patch', 'delete'];
|
|
6
6
|
|
|
7
|
+
const buildAxiosErrorData = (err) => {
|
|
8
|
+
return {
|
|
9
|
+
message: err.message,
|
|
10
|
+
code: err.code,
|
|
11
|
+
isAxiosError: err.isAxiosError,
|
|
12
|
+
...(err.response && {
|
|
13
|
+
response: {
|
|
14
|
+
status: err.response.status,
|
|
15
|
+
statusText: err.response.statusText,
|
|
16
|
+
headers: err.response.headers,
|
|
17
|
+
data: err.response.data
|
|
18
|
+
}
|
|
19
|
+
}),
|
|
20
|
+
...(err.config && {
|
|
21
|
+
config: {
|
|
22
|
+
url: err.config.url,
|
|
23
|
+
method: err.config.method,
|
|
24
|
+
headers: err.config.headers,
|
|
25
|
+
data: err.config.data
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
|
|
7
31
|
const addAxiosShimToContext = async (vm) => {
|
|
8
32
|
methods?.forEach((method) => {
|
|
9
33
|
const axiosHandle = vm.newFunction(method, (...args) => {
|
|
@@ -15,14 +39,7 @@ const addAxiosShimToContext = async (vm) => {
|
|
|
15
39
|
promise.resolve(marshallToVm(cleanJson({ status, headers, data }), vm));
|
|
16
40
|
})
|
|
17
41
|
.catch((err) => {
|
|
18
|
-
promise.
|
|
19
|
-
marshallToVm(
|
|
20
|
-
cleanJson({
|
|
21
|
-
message: err.message
|
|
22
|
-
}),
|
|
23
|
-
vm
|
|
24
|
-
)
|
|
25
|
-
);
|
|
42
|
+
promise.reject(marshallToVm(cleanJson(buildAxiosErrorData(err)), vm));
|
|
26
43
|
});
|
|
27
44
|
promise.settled.then(vm.runtime.executePendingJobs);
|
|
28
45
|
return promise.handle;
|
|
@@ -39,14 +56,7 @@ const addAxiosShimToContext = async (vm) => {
|
|
|
39
56
|
promise.resolve(marshallToVm(cleanJson({ status, headers, data }), vm));
|
|
40
57
|
})
|
|
41
58
|
.catch((err) => {
|
|
42
|
-
promise.
|
|
43
|
-
marshallToVm(
|
|
44
|
-
cleanJson({
|
|
45
|
-
message: err.message
|
|
46
|
-
}),
|
|
47
|
-
vm
|
|
48
|
-
)
|
|
49
|
-
);
|
|
59
|
+
promise.reject(marshallToVm(cleanJson(buildAxiosErrorData(err)), vm));
|
|
50
60
|
});
|
|
51
61
|
promise.settled.then(vm.runtime.executePendingJobs);
|
|
52
62
|
return promise.handle;
|