mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-26 22:25:40 +00:00
* feat(cookies): add direct cookie access methods and update translations
- Introduced new methods for direct cookie access: `bru.cookies.get`, `bru.cookies.has`, and `bru.cookies.toObject`.
- Updated translation mappings in `bruno-to-postman-translator` and `postman-to-bruno-translator` to support these new methods.
- Enhanced tests to verify correct translation between `bru` and `pm` cookie methods, including mixed usage scenarios.
- Updated `Bru` class to handle cookie access based on the current request URL.
* feat(cookies): enhance cookie management with new methods and refactor
- Added new cookie methods: `toString`, `clear`, `delete`, `one`, `all`, `idx`, `count`, `indexOf`, `find`, `filter`, `each`, `map`, and `reduce` to `bru.cookies`.
- Refactored `Bru` class to utilize a new `CookieList` for cookie management, improving structure and readability.
- Updated translation mappings in `bruno-to-postman-translator` and `postman-to-bruno-translator` to include new cookie methods.
- Introduced `PropertyList` and `ReadOnlyPropertyList` classes for better data structure management.
- Enhanced tests for comprehensive coverage of new cookie functionalities and their interactions.
* docs(readonly-property-list): clarify array usage in constructor comments
* feat(cookies): add direct cookie manipulation tests and methods
* feat(cookies): add hasCookie method for checking cookie existence
* fix
* refactor(cookies): simplify cookie method translations
* feat(cookies): expand cookie API with new methods and tests
- Added new cookie methods: `get`, `has`, `toString`, `clear`, `upsert`, `remove`, `idx`, and `indexOf` to enhance cookie management.
- Updated translation mappings for `bru.cookies` to include new methods in `bruno-to-postman-translator` and `postman-to-bruno-translator`.
- Introduced tests for new methods and their interactions, ensuring comprehensive coverage of cookie functionalities.
- Enhanced existing tests to validate correct behavior of cookie methods across different scenarios.
* refactor(cookies): update CookieList to extend PropertyList and improve error handling
* test(cookies): add regression tests for jar and direct cookie patterns
- Introduced regression tests to ensure that jar patterns are correctly prioritized over direct cookie access patterns in translations.
- Updated `CookieList` to extend `ReadOnlyPropertyList` instead of `PropertyList`, clarifying its functionality.
- Refactored cookie method handling in the `bru` shim to utilize a new asynchronous bridge for improved error handling and consistency.
* refactor(cookies): update translations and remove PropertyList
- Enhanced comments in `postman-translations.js` to clarify the order of cookie jar translations.
- Updated `cookie-list.js` comments to better describe the factory function for the cookie jar.
- Removed the `PropertyList` class and its associated tests, streamlining the codebase and focusing on `ReadOnlyPropertyList` and `CookieList` for cookie management.
* fix(cookies): normalize tough-cookie objects and improve remove method comments
- Updated `CookieList` to normalize tough-cookie instances to plain objects, preventing circular references and exposing internal structures.
- Enhanced comments in the `remove` method to clarify behavior when removing non-existent or empty-named cookies.
* test(cookies): update tests to use async/await for consistency
* test(cookies): use async/await in cookie tests for consistency
* refactor(readonly-property-list): update get and reduce methods for improved behavior
* fix(cookies): update cookie method signature in autocomplete hints and enhance translation comments
- Modified the autocomplete hint for `bru.cookies.has` to include the new signature with an optional value parameter.
- Improved comments in `postman-translations.js` to clarify the order of cookie jar translation patterns for better understanding.
* refactor(cookies): introduce PropertyList for enhanced cookie management
* refactor(property-list): simplify repopulate method and enhance item handling logic
* feat(cookies): implement PropertyList bridge for enhanced cookie management
- Introduced a new `createPropertyListBridge` utility to streamline the integration of cookie methods into the QuickJS VM.
- Replaced the previous async cookie bridge with a more flexible approach, allowing for both synchronous and asynchronous cookie operations.
- Added comprehensive tests to validate the functionality of the new cookie methods in both developer and safe modes.
- Updated existing cookie tests to ensure compatibility with the new PropertyList structure.
* fix(tests): correct expected passed requests in cookie tests
- Updated the expected number of passed requests in the cookie tests from 34 to 6 to reflect the correct validation results.
- Ensured consistency in test assertions across multiple test cases for the PropertyList API.
* fix(cookies): update cookie URLs to use localhost for testing
- Changed all cookie-related test scripts to use `{{localhost}}` instead of `{{host}}` for the ping URL, ensuring consistency in local testing environments.
- Updated the cookie test suite to reflect the new URL structure, enhancing the reliability of the tests.
- Removed outdated cookie test files to streamline the test suite.
* refactor(cookies): standardize cookie handling with localhost variable
- Updated cookie test scripts to utilize the `{{localhost}}` variable for setting and retrieving cookies, ensuring consistency across tests.
- Enhanced clarity in comments regarding cookie behavior for different domains.
- Improved test assertions to validate cookie management functionality more effectively.
* refactor(property-list, readonly-property-list): update methods to use private class fields
* feat(cookies): enhance CookieList API with detailed documentation and method improvements
- Updated the `CookieList` class to provide comprehensive documentation on cookie management methods, including `add`, `upsert`, `remove`, and `delete`.
- Improved method signatures to support both callback and Promise-based usage for asynchronous operations.
- Added detailed descriptions for read and write methods, including examples and expected behavior.
- Enhanced the integration of the `CookieList` with the QuickJS VM by updating the property list bridge to include `toJSON` in sync read object methods.
* feat(cookies): add detailed examples and improve async bridge documentation
- Enhanced the `createPropertyListBridge` function documentation with comprehensive examples for setting up cookie methods in QuickJS.
- Clarified the two-phase setup process for async write methods, detailing the registration of bridge functions and the generation of JavaScript code for method wrappers.
- Added a new test case for the `toJSON()` method to ensure it returns a cloned array of all cookies, validating the expected structure and properties.
* fix(assert-runtime): correct syntax error in response parser assignment
- Added a semicolon at the end of the response parser assignment to ensure proper syntax in the AssertRuntime class.
398 lines
12 KiB
JavaScript
398 lines
12 KiB
JavaScript
const { cloneDeep } = require('lodash');
|
|
const xmlFormat = require('xml-formatter');
|
|
const { interpolate: _interpolate } = require('@usebruno/common');
|
|
const { sendRequest, createSendRequest } = require('@usebruno/requests').scripting;
|
|
const { jar: createCookieJar, getCookiesForUrl } = require('@usebruno/requests').cookies;
|
|
const CookieList = require('./cookie-list');
|
|
|
|
const variableNameRegex = /^[\w-.]*$/;
|
|
|
|
class Bru {
|
|
/**
|
|
* @param {object} options - Single options object (destructured)
|
|
* @property {string} options.runtime - The runtime environment ('quickjs' or 'nodevm')
|
|
* @property {object} [options.envVariables={}] - Environment variables
|
|
* @property {object} [options.runtimeVariables={}] - Runtime variables
|
|
* @property {object} [options.processEnvVars={}] - Process environment variables (deep cloned)
|
|
* @property {string} [options.collectionPath] - Path to the collection
|
|
* @property {object} [options.collectionVariables={}] - Collection-level variables
|
|
* @property {object} [options.folderVariables={}] - Folder-level variables
|
|
* @property {object} [options.requestVariables={}] - Request-level variables
|
|
* @property {object} [options.globalEnvironmentVariables={}] - Global environment variables
|
|
* @property {object} [options.oauth2CredentialVariables={}] - OAuth2 credential variables
|
|
* @property {string} [options.collectionName] - Name of the collection
|
|
* @property {object} [options.promptVariables={}] - Prompt variables
|
|
* @property {object} [options.certsAndProxyConfig] - Configuration for bru.sendRequest (proxy, certs, TLS)
|
|
* @property {string} [options.certsAndProxyConfig.collectionPath] - Path to the collection
|
|
* @property {object} [options.certsAndProxyConfig.options] - TLS and proxy options
|
|
* @property {object} [options.certsAndProxyConfig.clientCertificates] - Client certificate configuration
|
|
* @property {object} [options.certsAndProxyConfig.collectionLevelProxy] - Collection-level proxy settings
|
|
* @property {object} [options.certsAndProxyConfig.systemProxyConfig] - System proxy configuration
|
|
* @property {string} [options.requestUrl] - The URL of the current request (used for cookie access)
|
|
*/
|
|
constructor({
|
|
runtime,
|
|
envVariables,
|
|
runtimeVariables,
|
|
processEnvVars,
|
|
collectionPath,
|
|
collectionVariables,
|
|
folderVariables,
|
|
requestVariables,
|
|
globalEnvironmentVariables,
|
|
oauth2CredentialVariables,
|
|
collectionName,
|
|
promptVariables,
|
|
certsAndProxyConfig,
|
|
requestUrl
|
|
}) {
|
|
this.envVariables = envVariables || {};
|
|
this.runtimeVariables = runtimeVariables || {};
|
|
this.promptVariables = promptVariables || {};
|
|
this.processEnvVars = cloneDeep(processEnvVars || {});
|
|
this.collectionVariables = collectionVariables || {};
|
|
this.folderVariables = folderVariables || {};
|
|
this.requestVariables = requestVariables || {};
|
|
this.globalEnvironmentVariables = globalEnvironmentVariables || {};
|
|
this.oauth2CredentialVariables = oauth2CredentialVariables || {};
|
|
this.collectionPath = collectionPath;
|
|
this.collectionName = collectionName;
|
|
// Use createSendRequest with config if provided, otherwise use default sendRequest
|
|
this.sendRequest = certsAndProxyConfig ? createSendRequest(certsAndProxyConfig) : sendRequest;
|
|
this.runtime = runtime;
|
|
this.requestUrl = requestUrl;
|
|
this.cookies = new CookieList({
|
|
getUrl: () => this.interpolate(this.requestUrl),
|
|
interpolate: (str) => this.interpolate(str),
|
|
createCookieJar,
|
|
getCookiesForUrl
|
|
});
|
|
// Holds variables that are marked as persistent by scripts
|
|
this.persistentEnvVariables = {};
|
|
// Holds credential IDs to be reset after script execution
|
|
this.oauth2CredentialsToReset = [];
|
|
this.runner = {
|
|
skipRequest: () => {
|
|
this.skipRequest = true;
|
|
},
|
|
stopExecution: () => {
|
|
this.stopExecution = true;
|
|
},
|
|
setNextRequest: (nextRequest) => {
|
|
this.nextRequest = nextRequest;
|
|
}
|
|
};
|
|
|
|
this.utils = {
|
|
minifyJson: (json) => {
|
|
if (json === null || json === undefined) {
|
|
throw new Error('Failed to minify');
|
|
}
|
|
|
|
if (typeof json === 'object') {
|
|
try {
|
|
return JSON.stringify(json);
|
|
} catch (err) {
|
|
throw new Error(`Failed to minify: ${err?.message || err}`);
|
|
}
|
|
}
|
|
|
|
if (typeof json === 'string') {
|
|
const trimmed = json.trim();
|
|
if (trimmed === '') return trimmed;
|
|
try {
|
|
return JSON.stringify(JSON.parse(trimmed));
|
|
} catch (err) {
|
|
throw new Error(`Failed to minify: ${err?.message || err}`);
|
|
}
|
|
}
|
|
|
|
throw new TypeError('minifyJson expects a string or object');
|
|
},
|
|
|
|
minifyXml: (xml) => {
|
|
if (xml === null || xml === undefined) {
|
|
throw new Error('Failed to minify');
|
|
}
|
|
|
|
if (typeof xml === 'string') {
|
|
try {
|
|
return xmlFormat(xml, { collapseContent: false, indentation: '', lineSeparator: '' });
|
|
} catch (err) {
|
|
throw new Error(`Failed to minify: ${err?.message || err}`);
|
|
}
|
|
}
|
|
|
|
throw new TypeError('minifyXml expects a string');
|
|
}
|
|
};
|
|
}
|
|
|
|
interpolate = (strOrObj) => {
|
|
if (!strOrObj) return strOrObj;
|
|
const isObj = typeof strOrObj === 'object';
|
|
const strToInterpolate = isObj ? JSON.stringify(strOrObj) : strOrObj;
|
|
|
|
const combinedVars = {
|
|
...this.globalEnvironmentVariables,
|
|
...this.collectionVariables,
|
|
...this.envVariables,
|
|
...this.folderVariables,
|
|
...this.requestVariables,
|
|
...this.oauth2CredentialVariables,
|
|
...this.runtimeVariables,
|
|
...this.promptVariables,
|
|
process: {
|
|
env: {
|
|
...this.processEnvVars
|
|
}
|
|
}
|
|
};
|
|
|
|
const interpolatedStr = _interpolate(strToInterpolate, combinedVars);
|
|
return isObj ? JSON.parse(interpolatedStr) : interpolatedStr;
|
|
};
|
|
|
|
cwd() {
|
|
return this.collectionPath;
|
|
}
|
|
|
|
getEnvName() {
|
|
return this.envVariables.__name__;
|
|
}
|
|
|
|
getProcessEnv(key) {
|
|
return this.processEnvVars[key];
|
|
}
|
|
|
|
hasEnvVar(key) {
|
|
return Object.hasOwn(this.envVariables, key);
|
|
}
|
|
|
|
getEnvVar(key) {
|
|
return this.interpolate(this.envVariables[key]);
|
|
}
|
|
|
|
setEnvVar(key, value, options = {}) {
|
|
if (!key) {
|
|
throw new Error('Creating a env variable without specifying a name is not allowed.');
|
|
}
|
|
|
|
if (variableNameRegex.test(key) === false) {
|
|
throw new Error(
|
|
`Variable name: "${key}" contains invalid characters! Names must only contain alpha-numeric characters, "-", "_", "."`
|
|
);
|
|
}
|
|
|
|
// When persist is true, only string values are allowed
|
|
if (options?.persist && typeof value !== 'string') {
|
|
throw new Error(`Persistent environment variables must be strings. Received ${typeof value} for key "${key}".`);
|
|
}
|
|
|
|
this.envVariables[key] = value;
|
|
|
|
if (options?.persist) {
|
|
this.persistentEnvVariables[key] = value;
|
|
} else {
|
|
if (this.persistentEnvVariables[key]) {
|
|
delete this.persistentEnvVariables[key];
|
|
}
|
|
}
|
|
}
|
|
|
|
deleteEnvVar(key) {
|
|
delete this.envVariables[key];
|
|
}
|
|
|
|
getAllEnvVars() {
|
|
const vars = Object.assign({}, this.envVariables);
|
|
delete vars.__name__;
|
|
return vars;
|
|
}
|
|
|
|
deleteAllEnvVars() {
|
|
const envName = this.envVariables.__name__;
|
|
for (let key in this.envVariables) {
|
|
if (this.envVariables.hasOwnProperty(key)) {
|
|
delete this.envVariables[key];
|
|
}
|
|
}
|
|
if (envName !== undefined) {
|
|
this.envVariables.__name__ = envName;
|
|
}
|
|
}
|
|
|
|
getGlobalEnvVar(key) {
|
|
return this.interpolate(this.globalEnvironmentVariables[key]);
|
|
}
|
|
|
|
setGlobalEnvVar(key, value) {
|
|
if (!key) {
|
|
throw new Error('Creating a env variable without specifying a name is not allowed.');
|
|
}
|
|
|
|
this.globalEnvironmentVariables[key] = value;
|
|
}
|
|
|
|
// TODO: deleteGlobalEnvVar works in the request lifecycle but does not update the UI.
|
|
// Re-enable once the UI sync issue is resolved.
|
|
// deleteGlobalEnvVar(key) {
|
|
// delete this.globalEnvironmentVariables[key];
|
|
// }
|
|
|
|
getAllGlobalEnvVars() {
|
|
return Object.assign({}, this.globalEnvironmentVariables);
|
|
}
|
|
|
|
// TODO: deleteAllGlobalEnvVars works in the request lifecycle but does not update the UI.
|
|
// Re-enable once the UI sync issue is resolved.
|
|
// deleteAllGlobalEnvVars() {
|
|
// for (let key in this.globalEnvironmentVariables) {
|
|
// if (this.globalEnvironmentVariables.hasOwnProperty(key)) {
|
|
// delete this.globalEnvironmentVariables[key];
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
getOauth2CredentialVar(key) {
|
|
return this.interpolate(this.oauth2CredentialVariables[key]);
|
|
}
|
|
|
|
resetOauth2Credential(credentialId) {
|
|
if (!credentialId || typeof credentialId !== 'string') {
|
|
throw new Error('credentialId must be a non-empty string');
|
|
}
|
|
|
|
if (!this.oauth2CredentialsToReset.includes(credentialId)) {
|
|
this.oauth2CredentialsToReset.push(credentialId);
|
|
}
|
|
|
|
// Remove matching credential variables so subsequent getOauth2CredentialVar() calls return undefined
|
|
const prefix = `$oauth2.${credentialId}.`;
|
|
for (const key of Object.keys(this.oauth2CredentialVariables)) {
|
|
if (key.startsWith(prefix)) {
|
|
delete this.oauth2CredentialVariables[key];
|
|
}
|
|
}
|
|
}
|
|
|
|
hasVar(key) {
|
|
return Object.hasOwn(this.runtimeVariables, key);
|
|
}
|
|
|
|
setVar(key, value) {
|
|
if (!key) {
|
|
throw new Error('Creating a variable without specifying a name is not allowed.');
|
|
}
|
|
|
|
if (variableNameRegex.test(key) === false) {
|
|
throw new Error(
|
|
`Variable name: "${key}" contains invalid characters!`
|
|
+ ' Names must only contain alpha-numeric characters, "-", "_", "."'
|
|
);
|
|
}
|
|
|
|
this.runtimeVariables[key] = value;
|
|
}
|
|
|
|
getVar(key) {
|
|
if (variableNameRegex.test(key) === false) {
|
|
throw new Error(
|
|
`Variable name: "${key}" contains invalid characters!`
|
|
+ ' Names must only contain alpha-numeric characters, "-", "_", "."'
|
|
);
|
|
}
|
|
|
|
return this.interpolate(this.runtimeVariables[key]);
|
|
}
|
|
|
|
deleteVar(key) {
|
|
delete this.runtimeVariables[key];
|
|
}
|
|
|
|
deleteAllVars() {
|
|
for (let key in this.runtimeVariables) {
|
|
if (this.runtimeVariables.hasOwnProperty(key)) {
|
|
delete this.runtimeVariables[key];
|
|
}
|
|
}
|
|
}
|
|
|
|
getAllVars() {
|
|
return Object.assign({}, this.runtimeVariables);
|
|
}
|
|
|
|
getCollectionVar(key) {
|
|
return this.interpolate(this.collectionVariables[key]);
|
|
}
|
|
|
|
// TODO: setCollectionVar works in the request lifecycle but does not update the UI.
|
|
// Re-enable once the UI sync issue is resolved.
|
|
// setCollectionVar(key, value) {
|
|
// if (!key) {
|
|
// throw new Error('Creating a variable without specifying a name is not allowed.');
|
|
// }
|
|
//
|
|
// if (variableNameRegex.test(key) === false) {
|
|
// throw new Error(
|
|
// `Variable name: "${key}" contains invalid characters!`
|
|
// + ' Names must only contain alpha-numeric characters, "-", "_", "."'
|
|
// );
|
|
// }
|
|
//
|
|
// this.collectionVariables[key] = value;
|
|
// }
|
|
|
|
hasCollectionVar(key) {
|
|
return Object.hasOwn(this.collectionVariables, key);
|
|
}
|
|
|
|
// TODO: deleteCollectionVar works in the request lifecycle but does not update the UI.
|
|
// Re-enable once the UI sync issue is resolved.
|
|
// deleteCollectionVar(key) {
|
|
// delete this.collectionVariables[key];
|
|
// }
|
|
|
|
// TODO: deleteAllCollectionVars works in the request lifecycle but does not update the UI.
|
|
// Re-enable once the UI sync issue is resolved.
|
|
// deleteAllCollectionVars() {
|
|
// for (let key in this.collectionVariables) {
|
|
// if (this.collectionVariables.hasOwnProperty(key)) {
|
|
// delete this.collectionVariables[key];
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// TODO: getAllCollectionVars works in the request lifecycle but does not update the UI.
|
|
// Re-enable once the UI sync issue is resolved.
|
|
// getAllCollectionVars() {
|
|
// return Object.assign({}, this.collectionVariables);
|
|
// }
|
|
|
|
getFolderVar(key) {
|
|
return this.interpolate(this.folderVariables[key]);
|
|
}
|
|
|
|
getRequestVar(key) {
|
|
return this.interpolate(this.requestVariables[key]);
|
|
}
|
|
|
|
setNextRequest(nextRequest) {
|
|
this.nextRequest = nextRequest;
|
|
}
|
|
|
|
sleep(ms) {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
}
|
|
|
|
getCollectionName() {
|
|
return this.collectionName;
|
|
}
|
|
|
|
isSafeMode() {
|
|
return this.runtime === 'quickjs';
|
|
}
|
|
}
|
|
|
|
module.exports = Bru;
|