Merge branch 'main' into feature/save-prompt-tab-context-menu

This commit is contained in:
Ricardo Silverio 2024-10-06 13:32:30 -03:00
commit 34b5b4769f
18 changed files with 101 additions and 57 deletions

View File

@ -69,6 +69,7 @@ if (!SERVER_RENDERED) {
'bru.getVar(key)',
'bru.setVar(key,value)',
'bru.deleteVar(key)',
'bru.deleteAllVars()',
'bru.setNextRequest(requestName)',
'req.disableParsingResponseJson()',
'bru.getRequestVar(key)',

View File

@ -34,7 +34,7 @@ const StyledWrapper = styled.div`
.key-button {
display: inline-block;
color: ${(props) => props.theme.colors.text.white};
color: ${(props) => props.theme.table.input.color};
border-radius: 4px;
padding: 1px 5px;
font-family: monospace;

View File

@ -185,7 +185,7 @@ const Sidebar = () => {
Star
</GitHubButton> */}
</div>
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.31.0</div>
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.32.1</div>
</div>
</div>
</div>

View File

@ -19,7 +19,7 @@ import {
runRequestEvent,
scriptEnvironmentUpdateEvent
} from 'providers/ReduxStore/slices/collections';
import { collectionAddEnvFileEvent, openCollectionEvent, hydrateCollectionsWithUiStateSnapshot } from 'providers/ReduxStore/slices/collections/actions';
import { collectionAddEnvFileEvent, openCollectionEvent, hydrateCollectionWithUiStateSnapshot } from 'providers/ReduxStore/slices/collections/actions';
import toast from 'react-hot-toast';
import { useDispatch } from 'react-redux';
import { isElectron } from 'utils/common/platform';
@ -150,7 +150,7 @@ const useIpcEvents = () => {
});
const removeSnapshotHydrationListener = ipcRenderer.on('main:hydrate-app-with-ui-state-snapshot', (val) => {
dispatch(hydrateCollectionsWithUiStateSnapshot(val));
dispatch(hydrateCollectionWithUiStateSnapshot(val));
})
return () => {

View File

@ -60,7 +60,7 @@ const trackStart = () => {
event: 'start',
properties: {
os: platformLib.os.family,
version: '1.31.0'
version: '1.32.1'
}
});
};

View File

@ -971,17 +971,19 @@ export const selectEnvironment = (environmentUid, collectionUid) => (dispatch, g
}
const collectionCopy = cloneDeep(collection);
if (environmentUid) {
const environment = findEnvironmentInCollection(collectionCopy, environmentUid);
if (environment) {
ipcRenderer.invoke('renderer:update-ui-state-snapshot', { type: 'COLLECTION_ENVIRONMENT', data: { collectionPath: collection?.pathname, environmentName: environment?.name } })
dispatch(_selectEnvironment({ environmentUid, collectionUid }));
resolve();
}
else {
return reject(new Error('Environment not found'));
}
}
const environmentName = environmentUid
? findEnvironmentInCollection(collectionCopy, environmentUid)?.name
: null;
if (environmentUid && !environmentName) {
return reject(new Error('Environment not found'));
}
ipcRenderer.invoke('renderer:update-ui-state-snapshot', { type: 'COLLECTION_ENVIRONMENT', data: { collectionPath: collection?.pathname, environmentName }});
dispatch(_selectEnvironment({ environmentUid, collectionUid }));
resolve();
});
};
@ -1146,7 +1148,7 @@ export const saveCollectionSecurityConfig = (collectionUid, securityConfig) => (
};
export const hydrateCollectionsWithUiStateSnapshot = (payload) => (dispatch, getState) => {
export const hydrateCollectionWithUiStateSnapshot = (payload) => (dispatch, getState) => {
const collectionSnapshotData = payload;
return new Promise((resolve, reject) => {
const state = getState();

View File

@ -74,17 +74,17 @@ const interpolateVars = (request, envVars = {}, runtimeVariables = {}, processEn
} else if (contentType === 'application/x-www-form-urlencoded') {
if (typeof request.data === 'object') {
try {
let parsed = JSON.stringify(request.data);
parsed = _interpolate(parsed);
request.data = JSON.parse(parsed);
forOwn(request?.data, (value, key) => {
request.data[key] = _interpolate(value);
});
} catch (err) {}
}
} else if (contentType === 'multipart/form-data') {
if (typeof request.data === 'object' && !(request?.data instanceof FormData)) {
try {
let parsed = JSON.stringify(request.data);
parsed = _interpolate(parsed);
request.data = JSON.parse(parsed);
forOwn(request?.data, (value, key) => {
request.data[key] = _interpolate(value);
});
} catch (err) {}
}
} else {

View File

@ -76,17 +76,17 @@ const prepareRequest = (request, collectionRoot) => {
const password = get(request, 'auth.wsse.password', '');
const ts = new Date().toISOString();
const nonce = crypto.randomBytes(16).toString('base64');
const nonce = crypto.randomBytes(16).toString('hex');
// Create the password digest using SHA-256
const hash = crypto.createHash('sha256');
// Create the password digest using SHA-1 as required for WSSE
const hash = crypto.createHash('sha1');
hash.update(nonce + ts + password);
const digest = hash.digest('base64');
const digest = Buffer.from(hash.digest('hex').toString('utf8')).toString('base64');
// Construct the WSSE header
axiosRequest.headers[
'X-WSSE'
] = `UsernameToken Username="${username}", PasswordDigest="${digest}", Created="${ts}", Nonce="${nonce}"`;
] = `UsernameToken Username="${username}", PasswordDigest="${digest}", Nonce="${nonce}", Created="${ts}"`;
}
}

View File

@ -1,5 +1,5 @@
{
"version": "v1.31.0",
"version": "v1.32.1",
"name": "bruno",
"description": "Opensource API Client for Exploring and Testing APIs",
"homepage": "https://www.usebruno.com",

View File

@ -25,11 +25,11 @@ const { moveRequestUid, deleteRequestUid } = require('../cache/requestUids');
const { deleteCookiesForDomain, getDomainsWithCookies } = require('../utils/cookies');
const EnvironmentSecretsStore = require('../store/env-secrets');
const CollectionSecurityStore = require('../store/collection-security');
const UiStateSnapshot = require('../store/ui-state-snapshot');
const UiStateSnapshotStore = require('../store/ui-state-snapshot');
const environmentSecretsStore = new EnvironmentSecretsStore();
const collectionSecurityStore = new CollectionSecurityStore();
const UiStateSnapshotStore = new UiStateSnapshot();
const uiStateSnapshotStore = new UiStateSnapshotStore();
const envHasSecrets = (environment = {}) => {
const secrets = _.filter(environment.variables, (v) => v.secret);
@ -700,7 +700,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
ipcMain.handle('renderer:update-ui-state-snapshot', (event, { type, data }) => {
try {
UiStateSnapshotStore.update({ type, data });
uiStateSnapshotStore.update({ type, data });
} catch (error) {
throw new Error(error.message);
}

View File

@ -78,17 +78,17 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
} else if (contentType === 'application/x-www-form-urlencoded') {
if (typeof request.data === 'object') {
try {
let parsed = JSON.stringify(request.data);
parsed = _interpolate(parsed);
request.data = JSON.parse(parsed);
forOwn(request?.data, (value, key) => {
request.data[key] = _interpolate(value);
});
} catch (err) {}
}
} else if (contentType === 'multipart/form-data') {
if (typeof request.data === 'object' && !(request.data instanceof FormData)) {
try {
let parsed = JSON.stringify(request.data);
parsed = _interpolate(parsed);
request.data = JSON.parse(parsed);
forOwn(request?.data, (value, key) => {
request.data[key] = _interpolate(value);
});
} catch (err) {}
}
} else {

View File

@ -224,17 +224,17 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
const password = get(request, 'auth.wsse.password', '');
const ts = new Date().toISOString();
const nonce = crypto.randomBytes(16).toString('base64');
const nonce = crypto.randomBytes(16).toString('hex');
// Create the password digest using SHA-256
const hash = crypto.createHash('sha256');
// Create the password digest using SHA-1 as required for WSSE
const hash = crypto.createHash('sha1');
hash.update(nonce + ts + password);
const digest = hash.digest('base64');
const digest = Buffer.from(hash.digest('hex').toString('utf8')).toString('base64');
// Construct the WSSE header
axiosRequest.headers[
'X-WSSE'
] = `UsernameToken Username="${username}", PasswordDigest="${digest}", Created="${ts}", Nonce="${nonce}"`;
] = `UsernameToken Username="${username}", PasswordDigest="${digest}", Nonce="${nonce}", Created="${ts}"`;
break;
case 'apikey':
const apiKeyAuth = get(collectionAuth, 'apikey');
@ -318,17 +318,17 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
const password = get(request, 'auth.wsse.password', '');
const ts = new Date().toISOString();
const nonce = crypto.randomBytes(16).toString('base64');
const nonce = crypto.randomBytes(16).toString('hex');
// Create the password digest using SHA-256
const hash = crypto.createHash('sha256');
// Create the password digest using SHA-1 as required for WSSE
const hash = crypto.createHash('sha1');
hash.update(nonce + ts + password);
const digest = hash.digest('base64');
const digest = Buffer.from(hash.digest('hex').toString('utf8')).toString('base64');
// Construct the WSSE header
axiosRequest.headers[
'X-WSSE'
] = `UsernameToken Username="${username}", PasswordDigest="${digest}", Created="${ts}", Nonce="${nonce}"`;
] = `UsernameToken Username="${username}", PasswordDigest="${digest}", Nonce="${nonce}", Created="${ts}"`;
break;
case 'apikey':
const apiKeyAuth = get(request, 'auth.apikey');

View File

@ -1,6 +1,6 @@
const Store = require('electron-store');
class UiStateSnapshot {
class UiStateSnapshotStore {
constructor() {
this.store = new Store({
name: 'ui-state-snapshot',
@ -57,4 +57,4 @@ class UiStateSnapshot {
}
}
module.exports = UiStateSnapshot;
module.exports = UiStateSnapshotStore;

View File

@ -97,6 +97,14 @@ class Bru {
delete this.runtimeVariables[key];
}
deleteAllVars() {
for (let key in this.runtimeVariables) {
if (this.runtimeVariables.hasOwnProperty(key)) {
delete this.runtimeVariables[key];
}
}
}
getCollectionVar(key) {
return this._interpolate(this.collectionVariables[key]);
}

View File

@ -50,7 +50,9 @@ class VarsRuntime {
_.each(enabledVars, (v) => {
try {
const value = evaluateJsExpressionBasedOnRuntime(v.value, context, this.runtime);
bru.setVar(v.name, value);
if (v.name) {
bru.setVar(v.name, value);
}
} catch (error) {
errors.set(v.name, error);
}

View File

@ -21,6 +21,12 @@ const addBruShimToContext = (vm, bru) => {
vm.setProp(bruObject, 'getProcessEnv', getProcessEnv);
getProcessEnv.dispose();
let hasEnvVar = vm.newFunction('hasEnvVar', function (key) {
return marshallToVm(bru.hasEnvVar(vm.dump(key)), vm);
});
vm.setProp(bruObject, 'hasEnvVar', hasEnvVar);
hasEnvVar.dispose();
let getEnvVar = vm.newFunction('getEnvVar', function (key) {
return marshallToVm(bru.getEnvVar(vm.dump(key)), vm);
});
@ -33,6 +39,12 @@ const addBruShimToContext = (vm, bru) => {
vm.setProp(bruObject, 'setEnvVar', setEnvVar);
setEnvVar.dispose();
let hasVar = vm.newFunction('hasVar', function (key) {
return marshallToVm(bru.hasVar(vm.dump(key)), vm);
});
vm.setProp(bruObject, 'hasVar', hasVar);
hasVar.dispose();
let getVar = vm.newFunction('getVar', function (key) {
return marshallToVm(bru.getVar(vm.dump(key)), vm);
});
@ -45,6 +57,18 @@ const addBruShimToContext = (vm, bru) => {
vm.setProp(bruObject, 'setVar', setVar);
setVar.dispose();
let deleteVar = vm.newFunction('deleteVar', function (key) {
bru.deleteVar(vm.dump(key));
});
vm.setProp(bruObject, 'deleteVar', deleteVar);
deleteVar.dispose();
let deleteAllVars = vm.newFunction('deleteAllVars', function () {
bru.deleteAllVars();
});
vm.setProp(bruObject, 'deleteAllVars', deleteAllVars);
deleteAllVars.dispose();
let setNextRequest = vm.newFunction('setNextRequest', function (nextRequest) {
bru.setNextRequest(vm.dump(nextRequest));
});

View File

@ -12,12 +12,15 @@ post {
body:form-urlencoded {
form-data-key: {{form-data-key}}
}
script:pre-request {
bru.setVar('form-data-key', 'form-data-value');
form-data-stringified-object: {{form-data-stringified-object}}
}
assert {
res.body: eq form-data-key=form-data-value
res.body: eq form-data-key=form-data-value&form-data-stringified-object=%7B%22foo%22%3A123%7D
}
script:pre-request {
let obj = JSON.stringify({foo:123});
bru.setVar('form-data-key', 'form-data-value');
bru.setVar('form-data-stringified-object', obj);
}

View File

@ -11,14 +11,18 @@ post {
}
body:multipart-form {
foo: {{form-data-key}}
form-data-key: {{form-data-key}}
form-data-stringified-object: {{form-data-stringified-object}}
file: @file(bruno.png)
}
assert {
res.body: contains form-data-value
res.body: contains {"foo":123}
}
script:pre-request {
let obj = JSON.stringify({foo:123});
bru.setVar('form-data-key', 'form-data-value');
bru.setVar('form-data-stringified-object', obj);
}