From 51ee37cf969991449aa47fe4ad2255be19a76505 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Fri, 29 Sep 2023 01:35:22 +0530 Subject: [PATCH 01/55] feat(#119): bru lang support for basic and bearer auth --- packages/bruno-lang/v2/src/bruToJson.js | 43 ++++++++++++++++++- packages/bruno-lang/v2/src/jsonToBru.js | 19 +++++++- .../bruno-lang/v2/tests/fixtures/request.bru | 9 ++++ .../bruno-lang/v2/tests/fixtures/request.json | 9 ++++ 4 files changed, 77 insertions(+), 3 deletions(-) diff --git a/packages/bruno-lang/v2/src/bruToJson.js b/packages/bruno-lang/v2/src/bruToJson.js index 2241ca554..576c58c24 100644 --- a/packages/bruno-lang/v2/src/bruToJson.js +++ b/packages/bruno-lang/v2/src/bruToJson.js @@ -22,7 +22,8 @@ const { outdentString } = require('../../v1/src/utils'); * */ const grammar = ohm.grammar(`Bru { - BruFile = (meta | http | query | headers | bodies | varsandassert | script | tests | docs)* + BruFile = (meta | http | query | headers | auths | bodies | varsandassert | script | tests | docs)* + auths = authbasic | authbearer bodies = bodyjson | bodytext | bodyxml | bodygraphql | bodygraphqlvars | bodyforms | body bodyforms = bodyformurlencoded | bodymultipart @@ -75,6 +76,9 @@ const grammar = ohm.grammar(`Bru { varsres = "vars:post-response" dictionary assert = "assert" assertdictionary + authbasic = "auth:basic" dictionary + authbearer = "auth:bearer" dictionary + body = "body" st* "{" nl* textblock tagend bodyjson = "body:json" st* "{" nl* textblock tagend bodytext = "body:text" st* "{" nl* textblock tagend @@ -92,13 +96,21 @@ const grammar = ohm.grammar(`Bru { docs = "docs" st* "{" nl* textblock tagend }`); -const mapPairListToKeyValPairs = (pairList = []) => { +const mapPairListToKeyValPairs = (pairList = [], parseEnabled = true) => { if (!pairList.length) { return []; } return _.map(pairList[0], (pair) => { let name = _.keys(pair)[0]; let value = pair[name]; + + if (!parseEnabled) { + return { + name, + value + }; + } + let enabled = true; if (name && name.length && name.charAt(0) === '~') { name = name.slice(1); @@ -282,6 +294,33 @@ const sem = grammar.createSemantics().addAttribute('ast', { headers: mapPairListToKeyValPairs(dictionary.ast) }; }, + authbasic(_1, dictionary) { + const auth = mapPairListToKeyValPairs(dictionary.ast, false); + const usernameKey = _.find(auth, { name: 'username' }); + const passwordKey = _.find(auth, { name: 'password' }); + const username = usernameKey ? usernameKey.value : ''; + const password = passwordKey ? passwordKey.value : ''; + return { + auth: { + basic: { + username, + password + } + } + }; + }, + authbearer(_1, dictionary) { + const auth = mapPairListToKeyValPairs(dictionary.ast, false); + const tokenKey = _.find(auth, { name: 'token' }); + const token = tokenKey ? tokenKey.value : ''; + return { + auth: { + bearer: { + token + } + } + }; + }, bodyformurlencoded(_1, dictionary) { return { body: { diff --git a/packages/bruno-lang/v2/src/jsonToBru.js b/packages/bruno-lang/v2/src/jsonToBru.js index 818d7c9cb..b9a5779fa 100644 --- a/packages/bruno-lang/v2/src/jsonToBru.js +++ b/packages/bruno-lang/v2/src/jsonToBru.js @@ -13,7 +13,7 @@ const stripLastLine = (text) => { }; const jsonToBru = (json) => { - const { meta, http, query, headers, body, script, tests, vars, assertions, docs } = json; + const { meta, http, query, headers, auth, body, script, tests, vars, assertions, docs } = json; let bru = ''; @@ -82,6 +82,23 @@ const jsonToBru = (json) => { bru += '\n}\n\n'; } + if (auth && auth.basic) { + bru += `auth:basic { +${indentString(`username: ${auth.basic.username}`)} +${indentString(`password: ${auth.basic.password}`)} +} + +`; + } + + if (auth && auth.bearer) { + bru += `auth:bearer { +${indentString(`token: ${auth.bearer.token}`)} +} + +`; + } + if (body && body.json && body.json.length) { bru += `body:json { ${indentString(body.json)} diff --git a/packages/bruno-lang/v2/tests/fixtures/request.bru b/packages/bruno-lang/v2/tests/fixtures/request.bru index ae7318da5..f340bb9a4 100644 --- a/packages/bruno-lang/v2/tests/fixtures/request.bru +++ b/packages/bruno-lang/v2/tests/fixtures/request.bru @@ -21,6 +21,15 @@ headers { ~transaction-id: {{transactionId}} } +auth:basic { + username: john + password: secret +} + +auth:bearer { + token: 123 +} + body:json { { "hello": "world" diff --git a/packages/bruno-lang/v2/tests/fixtures/request.json b/packages/bruno-lang/v2/tests/fixtures/request.json index 867229de8..47995832d 100644 --- a/packages/bruno-lang/v2/tests/fixtures/request.json +++ b/packages/bruno-lang/v2/tests/fixtures/request.json @@ -43,6 +43,15 @@ "enabled": false } ], + "auth": { + "basic": { + "username": "john", + "password": "secret" + }, + "bearer": { + "token": "123" + } + }, "body": { "json": "{\n \"hello\": \"world\"\n}", "text": "This is a text body", From 0f1e330dc5be469947dac27c0178f4588f0b893a Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Fri, 29 Sep 2023 02:11:04 +0530 Subject: [PATCH 02/55] feat(#119): ui placeholders for basic and bearer auths --- .../Auth/AuthMode/StyledWrapper.js | 25 +++++++ .../RequestPane/Auth/AuthMode/index.js | 70 +++++++++++++++++++ .../RequestPane/Auth/StyledWrapper.js | 5 ++ .../src/components/RequestPane/Auth/index.js | 32 +++++++++ .../RequestPane/HttpRequestPane/index.js | 15 +++- .../ReduxStore/slices/collections/index.js | 15 ++++ .../bruno-app/src/utils/collections/index.js | 16 +++++ packages/bruno-electron/src/bru/index.js | 4 ++ .../bruno-schema/src/collections/index.js | 22 ++++++ 9 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 packages/bruno-app/src/components/RequestPane/Auth/AuthMode/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js create mode 100644 packages/bruno-app/src/components/RequestPane/Auth/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/RequestPane/Auth/index.js diff --git a/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/StyledWrapper.js new file mode 100644 index 000000000..6613dd1f3 --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/StyledWrapper.js @@ -0,0 +1,25 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + font-size: 0.8125rem; + + .auth-mode-selector { + background: ${(props) => props.theme.requestTabPanel.bodyModeSelect.color}; + border-radius: 3px; + + .dropdown-item { + padding: 0.2rem 0.6rem !important; + } + + .label-item { + padding: 0.2rem 0.6rem !important; + } + } + + .caret { + color: rgb(140, 140, 140); + fill: rgb(140 140 140); + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js b/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js new file mode 100644 index 000000000..3ad80ce13 --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js @@ -0,0 +1,70 @@ +import React, { useRef, forwardRef } from 'react'; +import get from 'lodash/get'; +import { IconCaretDown } from '@tabler/icons'; +import Dropdown from 'components/Dropdown'; +import { useDispatch } from 'react-redux'; +import { updateRequestAuthMode } from 'providers/ReduxStore/slices/collections'; +import { humanizeRequestAuthMode } from 'utils/collections'; +import StyledWrapper from './StyledWrapper'; + +const AuthMode = ({ item, collection }) => { + const dispatch = useDispatch(); + const dropdownTippyRef = useRef(); + const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); + const authMode = item.draft ? get(item, 'draft.request.auth.mode') : get(item, 'request.auth.mode'); + + const Icon = forwardRef((props, ref) => { + return ( +
+ {humanizeRequestAuthMode(authMode)} +
+ ); + }); + + const onModeChange = (value) => { + dispatch( + updateRequestAuthMode({ + itemUid: item.uid, + collectionUid: collection.uid, + mode: value + }) + ); + }; + + return ( + +
+ } placement="bottom-end"> +
{ + dropdownTippyRef.current.hide(); + onModeChange('basic'); + }} + > + Basic Auth +
+
{ + dropdownTippyRef.current.hide(); + onModeChange('bearer'); + }} + > + Bearer Token +
+
{ + dropdownTippyRef.current.hide(); + onModeChange('none'); + }} + > + No Auth +
+
+
+
+ ); +}; +export default AuthMode; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Auth/StyledWrapper.js new file mode 100644 index 000000000..e49220854 --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/StyledWrapper.js @@ -0,0 +1,5 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div``; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/index.js new file mode 100644 index 000000000..e2f345d23 --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/index.js @@ -0,0 +1,32 @@ +import React from 'react'; +import get from 'lodash/get'; +import { useDispatch } from 'react-redux'; +import { updateRequestBody } from 'providers/ReduxStore/slices/collections'; +import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; +import StyledWrapper from './StyledWrapper'; + +const RequestBody = ({ item, collection }) => { + const dispatch = useDispatch(); + const authMode = item.draft ? get(item, 'draft.request.auth.mode') : get(item, 'request.auth.mode'); + + const onEdit = (value) => { + // dispatch( + // updateRequestBody({ + // content: value, + // itemUid: item.uid, + // collectionUid: collection.uid + // }) + // ); + }; + + if (authMode === 'basic') { + return
Basic Auth
; + } + + if (authMode === 'bearer') { + return
Bearer Token
; + } + + return No Auth; +}; +export default RequestBody; diff --git a/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js b/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js index caace776f..652414a62 100644 --- a/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js +++ b/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js @@ -7,6 +7,8 @@ import QueryParams from 'components/RequestPane/QueryParams'; import RequestHeaders from 'components/RequestPane/RequestHeaders'; import RequestBody from 'components/RequestPane/RequestBody'; import RequestBodyMode from 'components/RequestPane/RequestBody/RequestBodyMode'; +import Auth from 'components/RequestPane/Auth'; +import AuthMode from 'components/RequestPane/Auth/AuthMode'; import Vars from 'components/RequestPane/Vars'; import Assertions from 'components/RequestPane/Assertions'; import Script from 'components/RequestPane/Script'; @@ -38,6 +40,9 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => { case 'headers': { return ; } + case 'auth': { + return ; + } case 'vars': { return ; } @@ -83,6 +88,9 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
selectTab('headers')}> Headers
+
selectTab('auth')}> + Auth +
selectTab('vars')}> Vars
@@ -95,13 +103,16 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
selectTab('tests')}> Tests
- {/* Moved to post mvp */} - {/*
selectTab('auth')}>Auth
*/} {focusedTab.requestPaneTab === 'body' ? (
) : null} + {focusedTab.requestPaneTab === 'auth' ? ( +
+ +
+ ) : null}
{getTabPanel(focusedTab.requestPaneTab)} diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js index 495989eb1..aca21025a 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -563,6 +563,20 @@ export const collectionsSlice = createSlice({ } } }, + updateRequestAuthMode: (state, action) => { + const collection = findCollectionByUid(state.collections, action.payload.collectionUid); + + if (collection && collection.items && collection.items.length) { + const item = findItemInCollection(collection, action.payload.itemUid); + + if (item && isItemARequest(item)) { + if (!item.draft) { + item.draft = cloneDeep(item); + } + item.draft.request.auth.mode = action.payload.mode; + } + } + }, updateRequestBodyMode: (state, action) => { const collection = findCollectionByUid(state.collections, action.payload.collectionUid); @@ -1170,6 +1184,7 @@ export const { addMultipartFormParam, updateMultipartFormParam, deleteMultipartFormParam, + updateRequestAuthMode, updateRequestBodyMode, updateRequestBody, updateRequestGraphqlQuery, diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js index 80fe41dd3..c982f4147 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -445,6 +445,22 @@ export const humanizeRequestBodyMode = (mode) => { return label; }; +export const humanizeRequestAuthMode = (mode) => { + let label = 'No Auth'; + switch (mode) { + case 'basic': { + label = 'Basic Auth'; + break; + } + case 'bearer': { + label = 'Bearer Token'; + break; + } + } + + return label; +}; + export const refreshUidsInItem = (item) => { item.uid = uuid(); diff --git a/packages/bruno-electron/src/bru/index.js b/packages/bruno-electron/src/bru/index.js index 45b10004f..a28c04a7b 100644 --- a/packages/bruno-electron/src/bru/index.js +++ b/packages/bruno-electron/src/bru/index.js @@ -61,6 +61,7 @@ const bruToJson = (bru) => { url: _.get(json, 'http.url'), params: _.get(json, 'query', []), headers: _.get(json, 'headers', []), + auth: _.get(json, 'auth', {}), body: _.get(json, 'body', {}), script: _.get(json, 'script', {}), vars: _.get(json, 'vars', {}), @@ -69,6 +70,7 @@ const bruToJson = (bru) => { } }; + transformedJson.request.auth.mode = _.get(json, 'http.auth', 'none'); transformedJson.request.body.mode = _.get(json, 'http.body', 'none'); return transformedJson; @@ -104,10 +106,12 @@ const jsonToBru = (json) => { http: { method: _.lowerCase(_.get(json, 'request.method')), url: _.get(json, 'request.url'), + auth: _.get(json, 'request.auth.mode', 'none'), body: _.get(json, 'request.body.mode', 'none') }, query: _.get(json, 'request.params', []), headers: _.get(json, 'request.headers', []), + auth: _.get(json, 'request.auth', {}), body: _.get(json, 'request.body', {}), script: _.get(json, 'request.script', {}), vars: { diff --git a/packages/bruno-schema/src/collections/index.js b/packages/bruno-schema/src/collections/index.js index 7076175b5..81cd2528b 100644 --- a/packages/bruno-schema/src/collections/index.js +++ b/packages/bruno-schema/src/collections/index.js @@ -69,6 +69,27 @@ const requestBodySchema = Yup.object({ .noUnknown(true) .strict(); +const authBasicSchema = Yup.object({ + username: Yup.string().nullable(), + password: Yup.string().nullable() +}) + .noUnknown(true) + .strict(); + +const authBearerSchema = Yup.object({ + token: Yup.string().nullable() +}) + .noUnknown(true) + .strict(); + +const authSchema = Yup.object({ + mode: Yup.string().oneOf(['none', 'basic', 'bearer']).required('mode is required'), + basic: authBasicSchema.nullable(), + bearer: authBearerSchema.nullable() +}) + .noUnknown(true) + .strict(); + // Right now, the request schema is very tightly coupled with http request // As we introduce more request types in the future, we will improve the definition to support // schema structure based on other request type @@ -77,6 +98,7 @@ const requestSchema = Yup.object({ method: requestMethodSchema, headers: Yup.array().of(keyValueSchema).required('headers are required'), params: Yup.array().of(keyValueSchema).required('params are required'), + auth: authSchema, body: requestBodySchema, script: Yup.object({ req: Yup.string().nullable(), From 21edfbc25ac50f9e615c8e0ee74f0a958675adf6 Mon Sep 17 00:00:00 2001 From: Brahma Dev Date: Fri, 29 Sep 2023 11:59:01 +0000 Subject: [PATCH 03/55] Use Codemirror Hint feature for autocomplete --- .../src/components/SingleLineEditor/index.js | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/packages/bruno-app/src/components/SingleLineEditor/index.js b/packages/bruno-app/src/components/SingleLineEditor/index.js index 5c4ba12d7..3b786ca93 100644 --- a/packages/bruno-app/src/components/SingleLineEditor/index.js +++ b/packages/bruno-app/src/components/SingleLineEditor/index.js @@ -9,6 +9,35 @@ const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODE if (!SERVER_RENDERED) { CodeMirror = require('codemirror'); + CodeMirror.registerHelper('hint', 'anyword', (editor, options) => { + const word = /[\w$]+/; + const wordlist = (options && options.autocomplete) || []; + let cur = editor.getCursor(), + curLine = editor.getLine(cur.line); + let end = cur.ch, + start = end; + while (start && word.test(curLine.charAt(start - 1))) --start; + let curWord = start != end && curLine.slice(start, end); + + const list = (options && options.list) || []; + const re = new RegExp(word.source, 'g'); + for (let dir = -1; dir <= 1; dir += 2) { + let line = cur.line, + endLine = Math.min(Math.max(line + dir * 500, editor.firstLine()), editor.lastLine()) + dir; + for (; line != endLine; line += dir) { + let text = editor.getLine(line), + m; + while ((m = re.exec(text))) { + if (line == cur.line && curWord.length < 3) continue; + list.push(...wordlist.filter((el) => el.toLowerCase().startsWith(curWord.toLowerCase()))); + } + } + } + return { list: [...new Set(list)], from: CodeMirror.Pos(cur.line, start), to: CodeMirror.Pos(cur.line, end) }; + }); + CodeMirror.commands.autocomplete = (cm, hint, options) => { + cm.showHint({ hint, ...options }); + }; } class SingleLineEditor extends Component { @@ -73,6 +102,14 @@ class SingleLineEditor extends Component { Tab: () => {} } }); + if (this.props.autocomplete) { + this.editor.on('keyup', (cm, event) => { + if (!cm.state.completionActive /*Enables keyboard navigation in autocomplete list*/ && event.keyCode != 13) { + /*Enter - do not open autocomplete list just after item has been selected in it*/ + CodeMirror.commands.autocomplete(cm, CodeMirror.hint.anyword, { autocomplete: this.props.autocomplete }); + } + }); + } this.editor.setValue(this.props.value || ''); this.editor.on('change', this._onEdit); this.addOverlay(); From a02d2b9c5852ac5903d41e25768365485b74a305 Mon Sep 17 00:00:00 2001 From: Brahma Dev Date: Fri, 29 Sep 2023 11:59:09 +0000 Subject: [PATCH 04/55] Add standard http headers for autocomplete --- packages/bruno-app/package.json | 1 + .../src/components/RequestPane/RequestHeaders/index.js | 3 +++ 2 files changed, 4 insertions(+) diff --git a/packages/bruno-app/package.json b/packages/bruno-app/package.json index a0c3cfffa..4845116a5 100644 --- a/packages/bruno-app/package.json +++ b/packages/bruno-app/package.json @@ -32,6 +32,7 @@ "graphql-request": "^3.7.0", "idb": "^7.0.0", "immer": "^9.0.15", + "know-your-http-well": "^0.5.0", "lodash": "^4.17.21", "markdown-it": "^13.0.1", "mousetrap": "^1.6.5", diff --git a/packages/bruno-app/src/components/RequestPane/RequestHeaders/index.js b/packages/bruno-app/src/components/RequestPane/RequestHeaders/index.js index 2cd5ccade..a36004c29 100644 --- a/packages/bruno-app/src/components/RequestPane/RequestHeaders/index.js +++ b/packages/bruno-app/src/components/RequestPane/RequestHeaders/index.js @@ -8,6 +8,8 @@ import { addRequestHeader, updateRequestHeader, deleteRequestHeader } from 'prov import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import SingleLineEditor from 'components/SingleLineEditor'; import StyledWrapper from './StyledWrapper'; +import { headers as StandardHTTPHeaders } from 'know-your-http-well'; +const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header); const RequestHeaders = ({ item, collection }) => { const dispatch = useDispatch(); @@ -91,6 +93,7 @@ const RequestHeaders = ({ item, collection }) => { 'name' ) } + autocomplete={headerAutoCompleteList} onRun={handleRun} collection={collection} /> From 0e3bc62d9dca66bb8ecfd0d541ee1bbb0930479d Mon Sep 17 00:00:00 2001 From: Alex Costinescu Date: Fri, 29 Sep 2023 15:24:13 +0200 Subject: [PATCH 05/55] Changed proxied requests to use https-proxy-agent to handle TCP tunneling. --- package.json | 3 +- .../src/runner/run-single-request.js | 52 +++++---- packages/bruno-electron/package.json | 1 + .../bruno-electron/src/ipc/network/index.js | 104 +++++++++--------- 4 files changed, 77 insertions(+), 83 deletions(-) diff --git a/package.json b/package.json index 0c802c492..9aaaf23f2 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,5 @@ }, "overrides": { "rollup": "3.2.5" - }, - "dependencies": {} + } } diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js index 0d3a99e82..04a478781 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -4,12 +4,12 @@ const fs = require('fs'); const { forOwn, each, extend, get } = require('lodash'); const FormData = require('form-data'); const axios = require('axios'); -const https = require('https'); const prepareRequest = require('./prepare-request'); const interpolateVars = require('./interpolate-vars'); const { ScriptRuntime, TestRuntime, VarsRuntime, AssertRuntime } = require('@usebruno/js'); const { stripExtension } = require('../utils/filesystem'); const { getOptions } = require('../utils/bru'); +const { HttpsProxyAgent } = require('https-proxy-agent'); const runSingleRequest = async function ( filename, @@ -65,31 +65,6 @@ const runSingleRequest = async function ( ); } - // set proxy if enabled - const proxyEnabled = get(brunoConfig, 'proxy.enabled', false); - if (proxyEnabled) { - const proxyProtocol = get(brunoConfig, 'proxy.protocol'); - const proxyHostname = get(brunoConfig, 'proxy.hostname'); - const proxyPort = get(brunoConfig, 'proxy.port'); - const proxyAuthEnabled = get(brunoConfig, 'proxy.auth.enabled', false); - - const proxyConfig = { - protocol: proxyProtocol, - hostname: proxyHostname, - port: proxyPort - }; - if (proxyAuthEnabled) { - const proxyAuthUsername = get(brunoConfig, 'proxy.auth.username'); - const proxyAuthPassword = get(brunoConfig, 'proxy.auth.password'); - proxyConfig.auth = { - username: proxyAuthUsername, - password: proxyAuthPassword - }; - } - - request.proxy = proxyConfig; - } - // interpolate variables inside request interpolateVars(request, envVariables, collectionVariables, processEnvVars); @@ -111,7 +86,30 @@ const runSingleRequest = async function ( } } - if (Object.keys(httpsAgentRequestFields).length > 0) { + // set proxy if enabled + const proxyEnabled = get(brunoConfig, 'proxy.enabled', false); + if (proxyEnabled) { + const proxyProtocol = get(brunoConfig, 'proxy.protocol'); + const proxyHostname = get(brunoConfig, 'proxy.hostname'); + const proxyPort = get(brunoConfig, 'proxy.port'); + const proxyAuthEnabled = get(brunoConfig, 'proxy.auth.enabled', false); + + let proxy; + + if (proxyAuthEnabled) { + const proxyAuthUsername = get(brunoConfig, 'proxy.auth.username'); + const proxyAuthPassword = get(brunoConfig, 'proxy.auth.password'); + + proxy = `${proxyProtocol}://${proxyAuthUsername}:${proxyAuthPassword}@${proxyHostname}:${proxyPort}`; + } else { + proxy = `${proxyProtocol}://${proxyHostname}:${proxyPort}`; + } + + request.httpsAgent = new HttpsProxyAgent( + proxy, + Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined + ); + } else if (Object.keys(httpsAgentRequestFields).length > 0) { request.httpsAgent = new https.Agent({ ...httpsAgentRequestFields }); diff --git a/packages/bruno-electron/package.json b/packages/bruno-electron/package.json index 6ff6e7f43..5f5fe128b 100644 --- a/packages/bruno-electron/package.json +++ b/packages/bruno-electron/package.json @@ -30,6 +30,7 @@ "fs-extra": "^10.1.0", "graphql": "^16.6.0", "handlebars": "^4.7.8", + "https-proxy-agent": "^7.0.2", "is-valid-path": "^0.1.1", "lodash": "^4.17.21", "mustache": "^4.2.0", diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index d4cfa48b1..4b7560446 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -15,6 +15,7 @@ const { sortFolder, getAllRequestsInFolderRecursively } = require('./helper'); const { getPreferences } = require('../../store/preferences'); const { getProcessEnvVars } = require('../../store/process-env'); const { getBrunoConfig } = require('../../store/bruno-config'); +const { HttpsProxyAgent } = require('https-proxy-agent'); // override the default escape function to prevent escaping Mustache.escape = function (value) { @@ -165,32 +166,6 @@ const registerNetworkIpc = (mainWindow) => { }); } - // proxy configuration - const brunoConfig = getBrunoConfig(collectionUid); - const proxyEnabled = get(brunoConfig, 'proxy.enabled', false); - if (proxyEnabled) { - const proxyProtocol = get(brunoConfig, 'proxy.protocol'); - const proxyHostname = get(brunoConfig, 'proxy.hostname'); - const proxyPort = get(brunoConfig, 'proxy.port'); - const proxyAuthEnabled = get(brunoConfig, 'proxy.auth.enabled', false); - - const proxyConfig = { - protocol: proxyProtocol, - hostname: proxyHostname, - port: proxyPort - }; - if (proxyAuthEnabled) { - const proxyAuthUsername = get(brunoConfig, 'proxy.auth.username'); - const proxyAuthPassword = get(brunoConfig, 'proxy.auth.password'); - proxyConfig.auth = { - username: proxyAuthUsername, - password: proxyAuthPassword - }; - } - - request.proxy = proxyConfig; - } - interpolateVars(request, envVars, collectionVariables, processEnvVars); // stringify the request url encoded params @@ -234,7 +209,31 @@ const registerNetworkIpc = (mainWindow) => { } } - if (Object.keys(httpsAgentRequestFields).length > 0) { + // proxy configuration + const brunoConfig = getBrunoConfig(collectionUid); + const proxyEnabled = get(brunoConfig, 'proxy.enabled', false); + if (proxyEnabled) { + const proxyProtocol = get(brunoConfig, 'proxy.protocol'); + const proxyHostname = get(brunoConfig, 'proxy.hostname'); + const proxyPort = get(brunoConfig, 'proxy.port'); + const proxyAuthEnabled = get(brunoConfig, 'proxy.auth.enabled', false); + + let proxy; + + if (proxyAuthEnabled) { + const proxyAuthUsername = get(brunoConfig, 'proxy.auth.username'); + const proxyAuthPassword = get(brunoConfig, 'proxy.auth.password'); + + proxy = `${proxyProtocol}://${proxyAuthUsername}:${proxyAuthPassword}@${proxyHostname}:${proxyPort}`; + } else { + proxy = `${proxyProtocol}://${proxyHostname}:${proxyPort}`; + } + + request.httpsAgent = new HttpsProxyAgent( + proxy, + Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined + ); + } else if (Object.keys(httpsAgentRequestFields).length > 0) { request.httpsAgent = new https.Agent({ ...httpsAgentRequestFields }); @@ -598,32 +597,6 @@ const registerNetworkIpc = (mainWindow) => { }); } - // proxy configuration - const brunoConfig = getBrunoConfig(collectionUid); - const proxyEnabled = get(brunoConfig, 'proxy.enabled', false); - if (proxyEnabled) { - const proxyProtocol = get(brunoConfig, 'proxy.protocol'); - const proxyHostname = get(brunoConfig, 'proxy.hostname'); - const proxyPort = get(brunoConfig, 'proxy.port'); - const proxyAuthEnabled = get(brunoConfig, 'proxy.auth.enabled', false); - - const proxyConfig = { - protocol: proxyProtocol, - hostname: proxyHostname, - port: proxyPort - }; - if (proxyAuthEnabled) { - const proxyAuthUsername = get(brunoConfig, 'proxy.auth.username'); - const proxyAuthPassword = get(brunoConfig, 'proxy.auth.password'); - proxyConfig.auth = { - username: proxyAuthUsername, - password: proxyAuthPassword - }; - } - - request.proxy = proxyConfig; - } - // interpolate variables inside request interpolateVars(request, envVars, collectionVariables, processEnvVars); @@ -644,7 +617,30 @@ const registerNetworkIpc = (mainWindow) => { const preferences = getPreferences(); const sslVerification = get(preferences, 'request.sslVerification', true); - if (!sslVerification) { + // proxy configuration + const brunoConfig = getBrunoConfig(collectionUid); + const proxyEnabled = get(brunoConfig, 'proxy.enabled', false); + if (proxyEnabled) { + const proxyProtocol = get(brunoConfig, 'proxy.protocol'); + const proxyHostname = get(brunoConfig, 'proxy.hostname'); + const proxyPort = get(brunoConfig, 'proxy.port'); + const proxyAuthEnabled = get(brunoConfig, 'proxy.auth.enabled', false); + + let proxy; + + if (proxyAuthEnabled) { + const proxyAuthUsername = get(brunoConfig, 'proxy.auth.username'); + const proxyAuthPassword = get(brunoConfig, 'proxy.auth.password'); + + proxy = `${proxyProtocol}://${proxyAuthUsername}:${proxyAuthPassword}@${proxyHostname}:${proxyPort}`; + } else { + proxy = `${proxyProtocol}://${proxyHostname}:${proxyPort}`; + } + + request.httpsAgent = new HttpsProxyAgent(proxy, { + rejectUnauthorized: sslVerification + }); + } else if (!sslVerification) { request.httpsAgent = new https.Agent({ rejectUnauthorized: false }); From 742d69b03c54cc2a5b1a73a59e9a6291dac77b65 Mon Sep 17 00:00:00 2001 From: Alex Costinescu Date: Fri, 29 Sep 2023 15:37:39 +0200 Subject: [PATCH 06/55] Re-added missing https require statement. --- packages/bruno-cli/src/runner/run-single-request.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js index 04a478781..f50bc67df 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -10,6 +10,7 @@ const { ScriptRuntime, TestRuntime, VarsRuntime, AssertRuntime } = require('@use const { stripExtension } = require('../utils/filesystem'); const { getOptions } = require('../utils/bru'); const { HttpsProxyAgent } = require('https-proxy-agent'); +const https = require('https'); const runSingleRequest = async function ( filename, From 8bb57aa41d5a4e212ad96dd9bd5d299798b4e60f Mon Sep 17 00:00:00 2001 From: Its-treason <39559178+Its-treason@users.noreply.github.com> Date: Fri, 29 Sep 2023 21:20:36 +0200 Subject: [PATCH 07/55] fix: location validation in create collection form --- .../src/components/Sidebar/CreateCollection/index.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js b/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js index 8a65bedb2..97e0241e0 100644 --- a/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js +++ b/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js @@ -28,7 +28,9 @@ const CreateCollection = ({ onClose }) => { .min(1, 'must be atleast 1 characters') .max(50, 'must be 50 characters or less') .required('folder name is required'), - collectionLocation: Yup.string().required('location is required') + collectionLocation: Yup.string() + .min(1, 'location is required') + .required('location is required') }), onSubmit: (values) => { dispatch(createCollection(values.collectionName, values.collectionFolderName, values.collectionLocation)) @@ -43,7 +45,10 @@ const CreateCollection = ({ onClose }) => { const browse = () => { dispatch(browseDirectory()) .then((dirPath) => { - formik.setFieldValue('collectionLocation', dirPath); + // When the user closes the diolog without selecting anything dirPath will be false + if (typeof dirPath === 'string') { + formik.setFieldValue('collectionLocation', dirPath); + } }) .catch((error) => { formik.setFieldValue('collectionLocation', ''); From 0c4ba7192260f92ecddd519c07c6af6833437f5b Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Sat, 30 Sep 2023 01:32:05 +0530 Subject: [PATCH 08/55] feat(#229): added error boundary to capture error trace and allow users to continue using the app --- .../src/pages/ErrorBoundary/index.js | 44 +++++++++++++++++++ packages/bruno-app/src/pages/_app.js | 37 +++++++++------- 2 files changed, 64 insertions(+), 17 deletions(-) create mode 100644 packages/bruno-app/src/pages/ErrorBoundary/index.js diff --git a/packages/bruno-app/src/pages/ErrorBoundary/index.js b/packages/bruno-app/src/pages/ErrorBoundary/index.js new file mode 100644 index 000000000..3b45122ab --- /dev/null +++ b/packages/bruno-app/src/pages/ErrorBoundary/index.js @@ -0,0 +1,44 @@ +import React from 'react'; + +class ErrorBoundary extends React.Component { + constructor(props) { + super(props); + + this.state = { hasError: false }; + } + componentDidMount() { + // Add a global error event listener to capture client-side errors + window.onerror = (message, source, lineno, colno, error) => { + this.setState({ hasError: true, error }); + }; + } + componentDidCatch(error, errorInfo) { + console.log({ error, errorInfo }); + } + render() { + if (this.state.hasError) { + return ( +
+
+

Oops! Something went wrong

+

{this.state.error && this.state.error.toString()}

+ {this.state.error && this.state.error.stack && ( +
{this.state.error.stack}
+ )} + +
+
+ ); + } + return this.props.children; + } +} + +export default ErrorBoundary; diff --git a/packages/bruno-app/src/pages/_app.js b/packages/bruno-app/src/pages/_app.js index 382b95093..ab2692529 100644 --- a/packages/bruno-app/src/pages/_app.js +++ b/packages/bruno-app/src/pages/_app.js @@ -7,6 +7,7 @@ import { PreferencesProvider } from 'providers/Preferences'; import ReduxStore from 'providers/ReduxStore'; import ThemeProvider from 'providers/Theme/index'; +import ErrorBoundary from './ErrorBoundary'; import '../styles/app.scss'; import '../styles/globals.css'; @@ -41,23 +42,25 @@ function MyApp({ Component, pageProps }) { } return ( - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + ); } From 613699fb695671ea7e7ae63ac6a9bda0384a2a46 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Sat, 30 Sep 2023 01:44:54 +0530 Subject: [PATCH 09/55] fix(#248): gracefully abort cm header hint when word match fails --- .../bruno-app/src/components/SingleLineEditor/index.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/bruno-app/src/components/SingleLineEditor/index.js b/packages/bruno-app/src/components/SingleLineEditor/index.js index 3b786ca93..4f1575bca 100644 --- a/packages/bruno-app/src/components/SingleLineEditor/index.js +++ b/packages/bruno-app/src/components/SingleLineEditor/index.js @@ -10,7 +10,7 @@ const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODE if (!SERVER_RENDERED) { CodeMirror = require('codemirror'); CodeMirror.registerHelper('hint', 'anyword', (editor, options) => { - const word = /[\w$]+/; + const word = /[\w$-]+/; const wordlist = (options && options.autocomplete) || []; let cur = editor.getCursor(), curLine = editor.getLine(cur.line); @@ -19,6 +19,11 @@ if (!SERVER_RENDERED) { while (start && word.test(curLine.charAt(start - 1))) --start; let curWord = start != end && curLine.slice(start, end); + // Check if curWord is a valid string before proceeding + if (typeof curWord !== 'string' || curWord.length < 3) { + return null; // Abort the hint + } + const list = (options && options.list) || []; const re = new RegExp(word.source, 'g'); for (let dir = -1; dir <= 1; dir += 2) { From acff0c379e3588bfe52e4bc0df5600b2f792f8b5 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Sat, 30 Sep 2023 02:24:31 +0530 Subject: [PATCH 10/55] fix(#236): insomnia import fix --- packages/bruno-app/src/utils/importers/insomnia-collection.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/bruno-app/src/utils/importers/insomnia-collection.js b/packages/bruno-app/src/utils/importers/insomnia-collection.js index b0fd7195e..1b6af3f7b 100644 --- a/packages/bruno-app/src/utils/importers/insomnia-collection.js +++ b/packages/bruno-app/src/utils/importers/insomnia-collection.js @@ -126,9 +126,7 @@ const parseInsomniaCollection = (data) => { try { const insomniaExport = JSON.parse(data); const insomniaResources = get(insomniaExport, 'resources', []); - const insomniaCollection = insomniaResources.find( - (resource) => resource._type === 'workspace' && resource.scope === 'collection' - ); + const insomniaCollection = insomniaResources.find((resource) => resource._type === 'workspace'); if (!insomniaCollection) { reject(new BrunoError('Collection not found inside Insomnia export')); From 1877dd858ec30df306382eddeabb7684c6f7307c Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Sat, 30 Sep 2023 02:42:10 +0530 Subject: [PATCH 11/55] chore: remove unused var --- packages/bruno-app/src/components/ResponsePane/index.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/bruno-app/src/components/ResponsePane/index.js b/packages/bruno-app/src/components/ResponsePane/index.js index 919922fd1..c8e071625 100644 --- a/packages/bruno-app/src/components/ResponsePane/index.js +++ b/packages/bruno-app/src/components/ResponsePane/index.js @@ -93,10 +93,6 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => { }); }; - const isJson = (headers) => { - return getContentType(headers) === 'application/ld+json'; - }; - return (
From f99918d725bb03902373369aeb0d86a421da9fbf Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Sat, 30 Sep 2023 02:44:30 +0530 Subject: [PATCH 12/55] fix(#229): fixed handling of non-JSON/XML content types --- packages/bruno-app/src/utils/common/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/bruno-app/src/utils/common/index.js b/packages/bruno-app/src/utils/common/index.js index 204cae6bf..84725332f 100644 --- a/packages/bruno-app/src/utils/common/index.js +++ b/packages/bruno-app/src/utils/common/index.js @@ -76,6 +76,8 @@ export const getContentType = (headers) => { } else if (typeof contentType[0] == 'string' && /^[\w\-]+\/([\w\-]+\+)?xml/.test(contentType[0])) { return 'application/xml'; } + + return contentType[0]; } } return ''; From 08ceed86a84f21c1b36f33ae880561115a7e7c56 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Sat, 30 Sep 2023 02:48:20 +0530 Subject: [PATCH 13/55] chore: bump version to v0.16.4 --- packages/bruno-app/src/components/Sidebar/index.js | 2 +- packages/bruno-electron/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bruno-app/src/components/Sidebar/index.js b/packages/bruno-app/src/components/Sidebar/index.js index 217b7e36f..56dad8c48 100644 --- a/packages/bruno-app/src/components/Sidebar/index.js +++ b/packages/bruno-app/src/components/Sidebar/index.js @@ -116,7 +116,7 @@ const Sidebar = () => { )}
-
v0.16.3
+
v0.16.4
diff --git a/packages/bruno-electron/package.json b/packages/bruno-electron/package.json index 6ff6e7f43..accec414f 100644 --- a/packages/bruno-electron/package.json +++ b/packages/bruno-electron/package.json @@ -1,5 +1,5 @@ { - "version": "v0.16.3", + "version": "v0.16.4", "name": "bruno", "description": "Opensource API Client for Exploring and Testing APIs", "homepage": "https://www.usebruno.com", From 0b83fbb7ce3596dcd9a519179e5b5a59be81f75e Mon Sep 17 00:00:00 2001 From: Brahma Dev Date: Fri, 29 Sep 2023 21:38:08 +0000 Subject: [PATCH 14/55] Add a randomly generated classname to each variable so that CodeMirror does not merge adjacent variables into the same SPAN. --- packages/bruno-app/src/utils/common/codemirror.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/bruno-app/src/utils/common/codemirror.js b/packages/bruno-app/src/utils/common/codemirror.js index 59daee837..1226e75dc 100644 --- a/packages/bruno-app/src/utils/common/codemirror.js +++ b/packages/bruno-app/src/utils/common/codemirror.js @@ -25,10 +25,11 @@ export const defineCodeMirrorBrunoVariablesMode = (variables, mode) => { stream.eat('}'); let found = pathFoundInVariables(word, variables); if (found) { - return 'variable-valid'; + return 'variable-valid ' + (Math.random() + 1).toString(36).substring(9); } else { - return 'variable-invalid'; + return 'variable-invalid ' + (Math.random() + 1).toString(36).substring(9); } + // Random classname added so adjacent variables are not rendered in the same SPAN by CodeMirror. } word += ch; } From 1c53ce91f00593235ec821d537bac20cd4f780bc Mon Sep 17 00:00:00 2001 From: Brahma Dev Date: Fri, 29 Sep 2023 21:42:15 +0000 Subject: [PATCH 15/55] Ignore the randomly generated classname. --- packages/bruno-app/src/utils/codemirror/brunoVarInfo.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js b/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js index 50f314dac..d37e10bb6 100644 --- a/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js +++ b/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js @@ -66,8 +66,7 @@ if (!SERVER_RENDERED) { if (target.nodeName !== 'SPAN' || state.hoverTimeout !== undefined) { return; } - - if (target.className !== 'cm-variable-valid') { + if (!target.classList.contains('cm-variable-valid')) { return; } From 4c89f319341d691056803736296755ea4693e406 Mon Sep 17 00:00:00 2001 From: Brahma Dev Date: Fri, 29 Sep 2023 21:58:46 +0000 Subject: [PATCH 16/55] Decrease likelihood of any unintentional classname clash. --- packages/bruno-app/src/utils/common/codemirror.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bruno-app/src/utils/common/codemirror.js b/packages/bruno-app/src/utils/common/codemirror.js index 1226e75dc..b1b60568c 100644 --- a/packages/bruno-app/src/utils/common/codemirror.js +++ b/packages/bruno-app/src/utils/common/codemirror.js @@ -25,9 +25,9 @@ export const defineCodeMirrorBrunoVariablesMode = (variables, mode) => { stream.eat('}'); let found = pathFoundInVariables(word, variables); if (found) { - return 'variable-valid ' + (Math.random() + 1).toString(36).substring(9); + return 'variable-valid random-' + (Math.random() + 1).toString(36).substring(9); } else { - return 'variable-invalid ' + (Math.random() + 1).toString(36).substring(9); + return 'variable-invalid random-' + (Math.random() + 1).toString(36).substring(9); } // Random classname added so adjacent variables are not rendered in the same SPAN by CodeMirror. } From 64019f8ecf5378087050cda15a55edefaec2798a Mon Sep 17 00:00:00 2001 From: Anik Das Date: Sat, 30 Sep 2023 09:49:02 +0530 Subject: [PATCH 17/55] fix(graphql-docs/build): rollup error thrown during build - during the dts transformation, the css import was not recognized, hence marking it as external in the dts transform didn't throw the error - "extract: true" in postcss plugin makes sure it gets extracted to the final bundle as well Signed-off-by: Anik Das --- docs/development.md | 4 +- packages/bruno-graphql-docs/rollup.config.js | 42 ++++++++++---------- packages/bruno-graphql-docs/src/index.ts | 1 - 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/docs/development.md b/docs/development.md index 21bf0cbab..77614d2f6 100644 --- a/docs/development.md +++ b/docs/development.md @@ -3,7 +3,8 @@ Bruno is being developed as a desktop app. You need to load the app by running the nextjs app in one terminal and then run the electron app in another terminal. ### Dependencies -* NodeJS v18 + +- NodeJS v18 ### Local Development @@ -15,7 +16,6 @@ nvm use npm i --legacy-peer-deps # build graphql docs -# note: you can for now ignore the error thrown while building the graphql docs npm run build:graphql-docs # build bruno query diff --git a/packages/bruno-graphql-docs/rollup.config.js b/packages/bruno-graphql-docs/rollup.config.js index dd2424c5f..d289e1df1 100644 --- a/packages/bruno-graphql-docs/rollup.config.js +++ b/packages/bruno-graphql-docs/rollup.config.js @@ -1,46 +1,48 @@ -const { nodeResolve } = require("@rollup/plugin-node-resolve"); -const commonjs = require("@rollup/plugin-commonjs"); -const typescript = require("@rollup/plugin-typescript"); -const dts = require("rollup-plugin-dts"); -const postcss = require("rollup-plugin-postcss"); -const { terser } = require("rollup-plugin-terser"); +const { nodeResolve } = require('@rollup/plugin-node-resolve'); +const commonjs = require('@rollup/plugin-commonjs'); +const typescript = require('@rollup/plugin-typescript'); +const dts = require('rollup-plugin-dts'); +const postcss = require('rollup-plugin-postcss'); +const { terser } = require('rollup-plugin-terser'); const peerDepsExternal = require('rollup-plugin-peer-deps-external'); -const packageJson = require("./package.json"); +const packageJson = require('./package.json'); module.exports = [ { - input: "src/index.ts", + input: 'src/index.ts', output: [ { file: packageJson.main, - format: "cjs", - sourcemap: true, + format: 'cjs', + sourcemap: true }, { file: packageJson.module, - format: "esm", - sourcemap: true, - }, + format: 'esm', + sourcemap: true + } ], plugins: [ postcss({ minimize: true, - extensions: ['.css'] + extensions: ['.css'], + extract: true }), peerDepsExternal(), nodeResolve({ extensions: ['.css'] }), commonjs(), - typescript({ tsconfig: "./tsconfig.json" }), + typescript({ tsconfig: './tsconfig.json' }), terser() ], - external: ["react", "react-dom", "index.css"] + external: ['react', 'react-dom', 'index.css'] }, { - input: "dist/esm/index.d.ts", - output: [{ file: "dist/index.d.ts", format: "esm" }], - plugins: [dts.default()], + input: 'dist/esm/index.d.ts', + external: [/\.css$/], + output: [{ file: 'dist/index.d.ts', format: 'esm' }], + plugins: [dts.default()] } -]; \ No newline at end of file +]; diff --git a/packages/bruno-graphql-docs/src/index.ts b/packages/bruno-graphql-docs/src/index.ts index e38befd3c..b8c6cd039 100644 --- a/packages/bruno-graphql-docs/src/index.ts +++ b/packages/bruno-graphql-docs/src/index.ts @@ -1,6 +1,5 @@ import { DocExplorer } from './components/DocExplorer'; -// Todo: Rollup throws error import './index.css'; export { DocExplorer }; From 5c79282a1bc5ef8cff5561834eafd0d48154a5de Mon Sep 17 00:00:00 2001 From: Its-treason <39559178+Its-treason@users.noreply.github.com> Date: Sat, 30 Sep 2023 14:35:37 +0200 Subject: [PATCH 18/55] feat: Update create collection form - Remove Name tooltip - Update Folder Name tooltip - Move Folder Name input under location - Update Folder Name validation - Now only allow characters for valid system folder names - Update label htmlFor ids to input ids --- .../Sidebar/CreateCollection/index.js | 65 +++++++++---------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js b/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js index 97e0241e0..9b56ca1b8 100644 --- a/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js +++ b/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js @@ -27,10 +27,11 @@ const CreateCollection = ({ onClose }) => { collectionFolderName: Yup.string() .min(1, 'must be atleast 1 characters') .max(50, 'must be 50 characters or less') + .matches(/^[\w\-. ]+$/, 'Folder name contains invalid characters') .required('folder name is required'), collectionLocation: Yup.string() .min(1, 'location is required') - .required('location is required') + .required('location is required'), }), onSubmit: (values) => { dispatch(createCollection(values.collectionName, values.collectionFolderName, values.collectionLocation)) @@ -68,9 +69,8 @@ const CreateCollection = ({ onClose }) => {
-
From 6695d90609cf162331ff9c8450be5ba3d43c0651 Mon Sep 17 00:00:00 2001 From: Andy Piper Date: Sat, 30 Sep 2023 15:37:45 +0100 Subject: [PATCH 19/55] Remove rogue apostrophes and capitalise API in text. Signed-off-by: Andy Piper --- packages/bruno-app/src/components/VariablesEditor/index.js | 2 +- packages/bruno-app/src/components/Welcome/index.js | 2 +- packages/bruno-cli/src/index.js | 2 +- readme.md | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/bruno-app/src/components/VariablesEditor/index.js b/packages/bruno-app/src/components/VariablesEditor/index.js index c8fc36fa1..735b9a543 100644 --- a/packages/bruno-app/src/components/VariablesEditor/index.js +++ b/packages/bruno-app/src/components/VariablesEditor/index.js @@ -87,7 +87,7 @@ const VariablesEditor = ({ collection }) => {
- Note: As of today, collection variables can only be set via the api -{' '} + Note: As of today, collection variables can only be set via the API -{' '} getVar() and setVar().
In the next release, we will add a UI to set and modify collection variables.
diff --git a/packages/bruno-app/src/components/Welcome/index.js b/packages/bruno-app/src/components/Welcome/index.js index 625f18abd..278516538 100644 --- a/packages/bruno-app/src/components/Welcome/index.js +++ b/packages/bruno-app/src/components/Welcome/index.js @@ -54,7 +54,7 @@ const Welcome = () => {
bruno
-
Opensource IDE for exploring and testing api's
+
Opensource IDE for exploring and testing APIs
Collections
diff --git a/packages/bruno-cli/src/index.js b/packages/bruno-cli/src/index.js index d54c6861f..d9bc66550 100644 --- a/packages/bruno-cli/src/index.js +++ b/packages/bruno-cli/src/index.js @@ -20,7 +20,7 @@ const run = async () => { .commandDir('commands') .epilogue(CLI_EPILOGUE) .usage('Usage: $0 [options]') - .demandCommand(1, "Woof !! Let's play with some apis !!") + .demandCommand(1, "Woof !! Let's play with some APIs !!") .help('h') .alias('h', 'help'); }; diff --git a/readme.md b/readme.md index badd9430a..6f902b14d 100644 --- a/readme.md +++ b/readme.md @@ -1,7 +1,7 @@
-### Bruno - Opensource IDE for exploring and testing api's. +### Bruno - Opensource IDE for exploring and testing APIs. [![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno) [![CI](https://github.com/usebruno/bruno/actions/workflows/unit-tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/workflows/unit-tests.yml) @@ -15,7 +15,7 @@ Bruno is a new and innovative API client, aimed at revolutionizing the status qu Bruno stores your collections directly in a folder on your filesystem. We use a plain text markup language, Bru, to save information about API requests. -You can use git or any version control of your choice to collaborate over your api collections. +You can use git or any version control of your choice to collaborate over your API collections. ![bruno](assets/images/landing-2.png)

From f4f093d4db9937fe615f8ae8d49af3870eeeb3c1 Mon Sep 17 00:00:00 2001 From: Mirko Golze Date: Sat, 30 Sep 2023 21:56:21 +0200 Subject: [PATCH 20/55] #203 #254 #231 add icon to asar content, change styling to allow copying the content --- packages/bruno-electron/src/about/256x256.png | Bin 0 -> 25531 bytes packages/bruno-electron/src/about/about.css | 8 ++++++++ .../bruno-electron/src/app/menu-template.js | 3 ++- 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 packages/bruno-electron/src/about/256x256.png create mode 100644 packages/bruno-electron/src/about/about.css diff --git a/packages/bruno-electron/src/about/256x256.png b/packages/bruno-electron/src/about/256x256.png new file mode 100644 index 0000000000000000000000000000000000000000..d1263e9c5dedfc3c95405a1f0aeb64a359bafb32 GIT binary patch literal 25531 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

Y7TS%4z); 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 literal 0 HcmV?d00001 diff --git a/packages/bruno-electron/src/about/about.css b/packages/bruno-electron/src/about/about.css new file mode 100644 index 000000000..dd8b987d1 --- /dev/null +++ b/packages/bruno-electron/src/about/about.css @@ -0,0 +1,8 @@ +.versions { + -webkit-user-select: text; + user-select: text; +} +.title { + -webkit-user-select: text; + user-select: text; +} diff --git a/packages/bruno-electron/src/app/menu-template.js b/packages/bruno-electron/src/app/menu-template.js index 7e0911298..2efd93cde 100644 --- a/packages/bruno-electron/src/app/menu-template.js +++ b/packages/bruno-electron/src/app/menu-template.js @@ -51,7 +51,8 @@ const template = [ click: () => openAboutWindow({ product_name: 'Bruno', - icon_path: join(process.cwd(), '/resources/icons/png/256x256.png'), + icon_path: join(__dirname, '../about/256x256.png'), + css_path: join(__dirname, '../about/about.css'), homepage: 'https://www.usebruno.com/', package_json_dir: join(__dirname, '../..') }) From e83c2da79888d1e841ceb6c215606cce861eb00b Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Sun, 1 Oct 2023 02:17:28 +0530 Subject: [PATCH 21/55] chore: version bumped to v0.16.5 --- packages/bruno-app/src/components/Sidebar/index.js | 2 +- packages/bruno-electron/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bruno-app/src/components/Sidebar/index.js b/packages/bruno-app/src/components/Sidebar/index.js index 56dad8c48..14d92d42f 100644 --- a/packages/bruno-app/src/components/Sidebar/index.js +++ b/packages/bruno-app/src/components/Sidebar/index.js @@ -116,7 +116,7 @@ const Sidebar = () => { )}

-
v0.16.4
+
v0.16.5
diff --git a/packages/bruno-electron/package.json b/packages/bruno-electron/package.json index accec414f..ce46ee3c3 100644 --- a/packages/bruno-electron/package.json +++ b/packages/bruno-electron/package.json @@ -1,5 +1,5 @@ { - "version": "v0.16.4", + "version": "v0.16.5", "name": "bruno", "description": "Opensource API Client for Exploring and Testing APIs", "homepage": "https://www.usebruno.com", From ce9cdc5293013903053b95ce28c87c6bdc47d519 Mon Sep 17 00:00:00 2001 From: Beedhan Date: Sun, 1 Oct 2023 23:55:39 +0545 Subject: [PATCH 22/55] fix: folder showing after deleting --- .../src/providers/ReduxStore/slices/collections/actions.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index cfede2a40..4e0a7724e 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -339,11 +339,10 @@ export const deleteItem = (itemUid, collectionUid) => (dispatch, getState) => { if (!collection) { return reject(new Error('Collection not found')); } - const item = findItemInCollection(collection, itemUid); if (item) { const { ipcRenderer } = window; - + dispatch(_deleteItem({ itemUid, collectionUid })); ipcRenderer .invoke('renderer:delete-item', item.pathname, item.type) .then(() => resolve()) From cedcd2cf35fd50f8400185921f9a45a3c2a2cdfd Mon Sep 17 00:00:00 2001 From: Beedhan Date: Mon, 2 Oct 2023 00:00:03 +0545 Subject: [PATCH 23/55] Revert "fix: folder showing after deleting" This reverts commit ce9cdc5293013903053b95ce28c87c6bdc47d519. --- .../src/providers/ReduxStore/slices/collections/actions.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index 4e0a7724e..cfede2a40 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -339,10 +339,11 @@ export const deleteItem = (itemUid, collectionUid) => (dispatch, getState) => { if (!collection) { return reject(new Error('Collection not found')); } + const item = findItemInCollection(collection, itemUid); if (item) { const { ipcRenderer } = window; - dispatch(_deleteItem({ itemUid, collectionUid })); + ipcRenderer .invoke('renderer:delete-item', item.pathname, item.type) .then(() => resolve()) From 978d810473f53e4bb63889657804bc60d98da1a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Szymborski?= Date: Sun, 1 Oct 2023 20:57:03 +0200 Subject: [PATCH 24/55] fix(#154): correct ordering during drag-and-drop When dragging and dropping items, to delay changing sequence numbers until after all the resource-dependent logic has completed, we were relying on the order of children in redux store (which we then converted into new seq numbers). This order of children was however not updated when sequence numbers changed (for example due to file watch changes). This resulted in a seemingly random drag-and-drop ordering, which in fact was linked to the initial order when the collection was loaded. This change sorts all the items by sequence number prior to reordering, so that those random jumps no longer happen. As this happens on a deep clone of the collection, no data gets hurt in the process. fixes #154 --- packages/bruno-app/src/utils/collections/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js index 80fe41dd3..0a20cb448 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -129,9 +129,11 @@ export const moveCollectionItem = (collection, draggedItem, targetItem) => { let draggedItemParent = findParentItemInCollection(collection, draggedItem.uid); if (draggedItemParent) { + draggedItemParent.items = sortBy(draggedItemParent.items, (item) => item.seq); draggedItemParent.items = filter(draggedItemParent.items, (i) => i.uid !== draggedItem.uid); draggedItem.pathname = path.join(draggedItemParent.pathname, draggedItem.filename); } else { + collection.items = sortBy(collection.items, (item) => item.seq); collection.items = filter(collection.items, (i) => i.uid !== draggedItem.uid); } @@ -143,10 +145,12 @@ export const moveCollectionItem = (collection, draggedItem, targetItem) => { let targetItemParent = findParentItemInCollection(collection, targetItem.uid); if (targetItemParent) { + targetItemParent.items = sortBy(targetItemParent.items, (item) => item.seq); let targetItemIndex = findIndex(targetItemParent.items, (i) => i.uid === targetItem.uid); targetItemParent.items.splice(targetItemIndex + 1, 0, draggedItem); draggedItem.pathname = path.join(targetItemParent.pathname, draggedItem.filename); } else { + collection.items = sortBy(collection.items, (item) => item.seq); let targetItemIndex = findIndex(collection.items, (i) => i.uid === targetItem.uid); collection.items.splice(targetItemIndex + 1, 0, draggedItem); draggedItem.pathname = path.join(collection.pathname, draggedItem.filename); From cf6ec4e84fdd5cb3a51f69db972b233e7d48cdc1 Mon Sep 17 00:00:00 2001 From: not-known-person Date: Sun, 1 Oct 2023 15:22:05 -0400 Subject: [PATCH 25/55] fixed issue-#251-&-#265 --- .../providers/ReduxStore/slices/collections/actions.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index cfede2a40..642d7cd92 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -262,7 +262,10 @@ export const renameItem = (newName, itemUid, collectionUid) => (dispatch, getSta } const { ipcRenderer } = window; - ipcRenderer.invoke('renderer:rename-item', item.pathname, newPathname, newName).then(resolve).catch(reject); + ipcRenderer.invoke('renderer:rename-item', item.pathname, newPathname, newName).then(() => { + dispatch(_renameItem({ newName, itemUid, collectionUid })) + resolve() + }).catch(reject); }); }; @@ -346,7 +349,10 @@ export const deleteItem = (itemUid, collectionUid) => (dispatch, getState) => { ipcRenderer .invoke('renderer:delete-item', item.pathname, item.type) - .then(() => resolve()) + .then(() => { + dispatch(_deleteItem({ itemUid, collectionUid })) + resolve() + }) .catch((error) => reject(error)); } return; From b83da46f124a40aa455389e69bed8e7c8a1385cc Mon Sep 17 00:00:00 2001 From: not-known-person Date: Sun, 1 Oct 2023 21:01:39 -0400 Subject: [PATCH 26/55] added feat/-#220 --- .../components/Sidebar/Collections/index.js | 33 +++++++++++-------- .../ReduxStore/slices/collections/actions.js | 4 +++ .../ReduxStore/slices/collections/index.js | 4 +++ 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/packages/bruno-app/src/components/Sidebar/Collections/index.js b/packages/bruno-app/src/components/Sidebar/Collections/index.js index 496f01456..57b14a0d7 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/index.js @@ -1,21 +1,28 @@ import React, { useState } from 'react'; -import { useSelector } from 'react-redux'; -import { IconSearch, IconFolders } from '@tabler/icons'; +import { useDispatch, useSelector } from 'react-redux'; +import { IconSearch, IconFolders, IconSortAZ } from '@tabler/icons'; import Collection from '../Collections/Collection'; import CreateCollection from '../CreateCollection'; import StyledWrapper from './StyledWrapper'; import CreateOrOpenCollection from './CreateOrOpenCollection'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; +import { sortCollections } from 'providers/ReduxStore/slices/collections/actions'; const CollectionsBadge = () => { + const dispatch = useDispatch() return (
-
- - - - Collections +
+
+ + + + Collections +
+
); @@ -64,12 +71,12 @@ const Collections = () => {
{collections && collections.length ? collections.map((c) => { - return ( - - - - ); - }) + return ( + + + + ); + }) : null}
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index cfede2a40..9cd588e31 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -34,6 +34,7 @@ import { renameItem as _renameItem, cloneItem as _cloneItem, deleteItem as _deleteItem, + sortCollections as _sortCollections, saveRequest as _saveRequest, selectEnvironment as _selectEnvironment, createCollection as _createCollection, @@ -353,6 +354,9 @@ export const deleteItem = (itemUid, collectionUid) => (dispatch, getState) => { }); }; +export const sortCollections = () => (dispatch) => { + dispatch(_sortCollections()) +} export const moveItem = (collectionUid, draggedItemUid, targetItemUid) => (dispatch, getState) => { const state = getState(); const collection = findCollectionByUid(state.collections.collections, collectionUid); diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js index 495989eb1..8227efc6b 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -70,6 +70,9 @@ export const collectionsSlice = createSlice({ removeCollection: (state, action) => { state.collections = filter(state.collections, (c) => c.uid !== action.payload.collectionUid); }, + sortCollections: (state) => { + state.collections = state.collections.sort((a, b) => a.name.localeCompare(b.name)) + }, updateLastAction: (state, action) => { const { collectionUid, lastAction } = action.payload; const collection = findCollectionByUid(state.collections, collectionUid); @@ -1141,6 +1144,7 @@ export const { brunoConfigUpdateEvent, renameCollection, removeCollection, + sortCollections, updateLastAction, collectionUnlinkEnvFileEvent, saveEnvironment, From 26d99c7aeed1af797184c3990ba658014646143c Mon Sep 17 00:00:00 2001 From: Its-treason <39559178+Its-treason@users.noreply.github.com> Date: Sun, 1 Oct 2023 02:01:48 +0200 Subject: [PATCH 27/55] fix(#257): Name collision during Insomnia collection import --- .../utils/importers/insomnia-collection.js | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/bruno-app/src/utils/importers/insomnia-collection.js b/packages/bruno-app/src/utils/importers/insomnia-collection.js index 1b6af3f7b..03876e62d 100644 --- a/packages/bruno-app/src/utils/importers/insomnia-collection.js +++ b/packages/bruno-app/src/utils/importers/insomnia-collection.js @@ -30,10 +30,23 @@ const parseGraphQL = (text) => { } }; -const transformInsomniaRequestItem = (request) => { +const addSuffixToDuplicateName = (item, index, allItems) => { + // Check if the request name already exist and if so add a number suffix + const nameSuffix = allItems.reduce((nameSuffix, otherItem, otherIndex) => { + if (otherItem.name === item.name && otherIndex < index) { + nameSuffix++; + } + return nameSuffix; + }, 0); + return nameSuffix !== 0 ? `${item.name}_${nameSuffix}` : item.name; +} + +const transformInsomniaRequestItem = (request, index, allRequests) => { + const name = addSuffixToDuplicateName(request, index, allRequests); + const brunoRequestItem = { uid: uuid(), - name: request.name, + name, type: 'http-request', request: { url: request.url, @@ -143,14 +156,15 @@ const parseInsomniaCollection = (data) => { resources.filter((resource) => resource._type === 'request_group' && resource.parentId === parentId) || []; const requests = resources.filter((resource) => resource._type === 'request' && resource.parentId === parentId); - const folders = requestGroups.map((folder) => { + const folders = requestGroups.map((folder, index, allFolder) => { + const name = addSuffixToDuplicateName(folder, index, allFolder); const requests = resources.filter( (resource) => resource._type === 'request' && resource.parentId === folder._id ); return { uid: uuid(), - name: folder.name, + name, type: 'folder', items: createFolderStructure(resources, folder._id).concat(requests.map(transformInsomniaRequestItem)) }; From fcc12fb089472716328054665d767d7b0ae48e04 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 2 Oct 2023 04:17:35 +0530 Subject: [PATCH 28/55] fix(#251, #265): phantoms folders fix on rename/delete needs to be run only on windows --- .../ReduxStore/slices/collections/actions.js | 33 ++++++++++++++----- .../bruno-app/src/utils/common/platform.js | 8 +++++ 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index 9ceb90ed5..b91c7c8fe 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -22,7 +22,7 @@ import { } from 'utils/collections'; import { collectionSchema, itemSchema, environmentSchema, environmentsSchema } from '@usebruno/schema'; import { waitForNextTick } from 'utils/common'; -import { getDirectoryName } from 'utils/common/platform'; +import { getDirectoryName, isWindowsOS } from 'utils/common/platform'; import { sendNetworkRequest, cancelNetworkRequest } from 'utils/network'; import { @@ -263,10 +263,19 @@ export const renameItem = (newName, itemUid, collectionUid) => (dispatch, getSta } const { ipcRenderer } = window; - ipcRenderer.invoke('renderer:rename-item', item.pathname, newPathname, newName).then(() => { - dispatch(_renameItem({ newName, itemUid, collectionUid })) - resolve() - }).catch(reject); + ipcRenderer + .invoke('renderer:rename-item', item.pathname, newPathname, newName) + .then(() => { + // In case of Mac and Linux, we get the unlinkDir and addDir IPC events from electron which takes care of updating the state + // But in windows we don't get those events, so we need to update the state manually + // This looks like an issue in our watcher library chokidar + // GH: https://github.com/usebruno/bruno/issues/251 + if (isWindowsOS()) { + dispatch(_renameItem({ newName, itemUid, collectionUid })); + } + resolve(); + }) + .catch(reject); }); }; @@ -351,8 +360,14 @@ export const deleteItem = (itemUid, collectionUid) => (dispatch, getState) => { ipcRenderer .invoke('renderer:delete-item', item.pathname, item.type) .then(() => { - dispatch(_deleteItem({ itemUid, collectionUid })) - resolve() + // In case of Mac and Linux, we get the unlinkDir IPC event from electron which takes care of updating the state + // But in windows we don't get those events, so we need to update the state manually + // This looks like an issue in our watcher library chokidar + // GH: https://github.com/usebruno/bruno/issues/265 + if (isWindowsOS()) { + dispatch(_deleteItem({ itemUid, collectionUid })); + } + resolve(); }) .catch((error) => reject(error)); } @@ -361,8 +376,8 @@ export const deleteItem = (itemUid, collectionUid) => (dispatch, getState) => { }; export const sortCollections = () => (dispatch) => { - dispatch(_sortCollections()) -} + dispatch(_sortCollections()); +}; export const moveItem = (collectionUid, draggedItemUid, targetItemUid) => (dispatch, getState) => { const state = getState(); const collection = findCollectionByUid(state.collections.collections, collectionUid); diff --git a/packages/bruno-app/src/utils/common/platform.js b/packages/bruno-app/src/utils/common/platform.js index d144796e7..e49a66ec9 100644 --- a/packages/bruno-app/src/utils/common/platform.js +++ b/packages/bruno-app/src/utils/common/platform.js @@ -1,6 +1,7 @@ import trim from 'lodash/trim'; import path from 'path'; import slash from './slash'; +import platform from 'platform'; export const isElectron = () => { if (!window) { @@ -33,3 +34,10 @@ export const getDirectoryName = (pathname) => { return path.dirname(pathname); }; + +export const isWindowsOS = () => { + const os = platform.os; + const osFamily = os.family.toLowerCase(); + + return osFamily.includes('windows'); +}; From 1804454ff021eda6f17e15a904abc42982a0d21c Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 2 Oct 2023 05:26:47 +0530 Subject: [PATCH 29/55] chore: bump version v0.16.6 --- packages/bruno-app/src/components/Sidebar/index.js | 2 +- packages/bruno-electron/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bruno-app/src/components/Sidebar/index.js b/packages/bruno-app/src/components/Sidebar/index.js index 14d92d42f..cb0f0ffb5 100644 --- a/packages/bruno-app/src/components/Sidebar/index.js +++ b/packages/bruno-app/src/components/Sidebar/index.js @@ -116,7 +116,7 @@ const Sidebar = () => { )}
-
v0.16.5
+
v0.16.6
diff --git a/packages/bruno-electron/package.json b/packages/bruno-electron/package.json index ce46ee3c3..2807e2f97 100644 --- a/packages/bruno-electron/package.json +++ b/packages/bruno-electron/package.json @@ -1,5 +1,5 @@ { - "version": "v0.16.5", + "version": "v0.16.6", "name": "bruno", "description": "Opensource API Client for Exploring and Testing APIs", "homepage": "https://www.usebruno.com", From 3d0c9cc0ae045939a31eab4134b6eff3016ba271 Mon Sep 17 00:00:00 2001 From: Beedhan Date: Mon, 2 Oct 2023 13:58:25 +0545 Subject: [PATCH 30/55] feat:added code generator to http-requests --- packages/bruno-app/package.json | 1 + .../src/components/CodeEditor/index.js | 2 +- .../GenerateCodeItem/CodeView/index.js | 21 +++++ .../GenerateCodeItem/StyledWrapper.js | 39 ++++++++ .../CollectionItem/GenerateCodeItem/index.js | 94 +++++++++++++++++++ .../Collection/CollectionItem/index.js | 17 ++++ .../bruno-app/src/utils/codegenerator/har.js | 72 ++++++++++++++ packages/bruno-app/src/utils/url/index.js | 9 ++ 8 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/index.js create mode 100644 packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/index.js create mode 100644 packages/bruno-app/src/utils/codegenerator/har.js diff --git a/packages/bruno-app/package.json b/packages/bruno-app/package.json index 4845116a5..7be069c17 100644 --- a/packages/bruno-app/package.json +++ b/packages/bruno-app/package.json @@ -30,6 +30,7 @@ "graphiql": "^1.5.9", "graphql": "^16.6.0", "graphql-request": "^3.7.0", + "httpsnippet": "^3.0.1", "idb": "^7.0.0", "immer": "^9.0.15", "know-your-http-well": "^0.5.0", diff --git a/packages/bruno-app/src/components/CodeEditor/index.js b/packages/bruno-app/src/components/CodeEditor/index.js index bcc13b5c2..96d5bb48a 100644 --- a/packages/bruno-app/src/components/CodeEditor/index.js +++ b/packages/bruno-app/src/components/CodeEditor/index.js @@ -119,7 +119,7 @@ export default class CodeEditor extends React.Component { render() { return ( { this._node = node; diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/index.js new file mode 100644 index 000000000..ba100b4fe --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/index.js @@ -0,0 +1,21 @@ +import CodeEditor from 'components/CodeEditor/index'; +import HTTPSnippet from 'httpsnippet'; +import { useTheme } from 'providers/Theme/index'; +import { buildHarRequest } from 'utils/codegenerator/har'; + +const index = ({ language, item }) => { + const { target, client, language: lang } = language; + const snippet = new HTTPSnippet(buildHarRequest(item.request)).convert(target, client); + const { storedTheme } = useTheme(); + return ( + + ); +}; + +export default index; diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/StyledWrapper.js new file mode 100644 index 000000000..86772b5e1 --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/StyledWrapper.js @@ -0,0 +1,39 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + margin-inline: -1rem; + margin-block: -1.5rem; + + background-color: ${(props) => props.theme.collection.environment.settings.bg}; + + .generate-code-sidebar { + background-color: ${(props) => props.theme.collection.environment.settings.sidebar.bg}; + border-right: solid 1px ${(props) => props.theme.collection.environment.settings.sidebar.borderRight}; + min-height: 400px; + } + + .generate-code-item { + min-width: 150px; + display: block; + position: relative; + cursor: pointer; + padding: 8px 10px; + border-left: solid 2px transparent; + text-decoration: none; + + &:hover { + text-decoration: none; + background-color: ${(props) => props.theme.collection.environment.settings.item.hoverBg}; + } + } + + .active { + background-color: ${(props) => props.theme.collection.environment.settings.item.active.bg} !important; + border-left: solid 2px ${(props) => props.theme.collection.environment.settings.item.border}; + &:hover { + background-color: ${(props) => props.theme.collection.environment.settings.item.active.hoverBg} !important; + } + } +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/index.js new file mode 100644 index 000000000..35f01972f --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/index.js @@ -0,0 +1,94 @@ +import Modal from 'components/Modal/index'; +import { useState } from 'react'; +import CodeView from './CodeView'; +import StyledWrapper from './StyledWrapper'; +import ErrorBoundary from 'src/pages/ErrorBoundary/index'; +import { isValidUrl } from 'utils/url/index'; + +const languages = [ + { + name: 'HTTP', + target: 'http', + client: 'http1.1' + }, + { + name: 'JavaScript-Fetch', + target: 'javascript', + client: 'fetch' + }, + { + name: 'Javascript-jQuery', + target: 'javascript', + client: 'jquery' + }, + { + name: 'Javascript-axios', + target: 'javascript', + client: 'axios' + }, + { + name: 'Python-Python3', + target: 'python', + client: 'python3' + }, + { + name: 'Python-Requests', + target: 'python', + client: 'requests' + }, + { + name: 'PHP', + target: 'php', + client: 'curl' + }, + { + name: 'Shell-curl', + target: 'shell', + client: 'curl' + }, + { + name: 'Shell-httpie', + target: 'shell', + client: 'httpie' + } +]; +const index = ({ item, onClose }) => { + const [selectedLanguage, setSelectedLanguage] = useState(languages[0]); + return ( + + +
+
+
+ {languages && + languages.length && + languages.map((language) => ( +
setSelectedLanguage(language)} + > + {language.name} +
+ ))} +
+
+ {isValidUrl(item.request.url) ? ( + + ) : ( +
+
+

Invalid URL

+

Please check the URL and try again

+
+
+ )} +
+
+
+ ); +}; + +export default index; diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js index 3916cc2e6..a59503be0 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js @@ -16,6 +16,7 @@ import RenameCollectionItem from './RenameCollectionItem'; import CloneCollectionItem from './CloneCollectionItem'; import DeleteCollectionItem from './DeleteCollectionItem'; import RunCollectionItem from './RunCollectionItem'; +import GenerateCodeItem from './GenerateCodeItem'; import { isItemARequest, isItemAFolder, itemIsOpenedInTabs } from 'utils/tabs'; import { doesRequestMatchSearchText, doesFolderHaveItemsMatchSearchText } from 'utils/collections/search'; import { getDefaultRequestPaneTab } from 'utils/collections'; @@ -32,6 +33,7 @@ const CollectionItem = ({ item, collection, searchText }) => { const [renameItemModalOpen, setRenameItemModalOpen] = useState(false); const [cloneItemModalOpen, setCloneItemModalOpen] = useState(false); const [deleteItemModalOpen, setDeleteItemModalOpen] = useState(false); + const [generateCodeItemModalOpen, setGenerateCodeItemModalOpen] = useState(false); const [newRequestModalOpen, setNewRequestModalOpen] = useState(false); const [newFolderModalOpen, setNewFolderModalOpen] = useState(false); const [runCollectionModalOpen, setRunCollectionModalOpen] = useState(false); @@ -166,6 +168,9 @@ const CollectionItem = ({ item, collection, searchText }) => { {runCollectionModalOpen && ( setRunCollectionModalOpen(false)} /> )} + {generateCodeItemModalOpen && ( + setGenerateCodeItemModalOpen(false)} /> + )}
drag(drop(node))}>
{indents && indents.length @@ -264,6 +269,18 @@ const CollectionItem = ({ item, collection, searchText }) => { Clone
)} + {!isFolder && item.type === 'http-request' && ( +
{ + e.stopPropagation(); + dropdownTippyRef.current.hide(); + setGenerateCodeItemModalOpen(true); + }} + > + Generate Code +
+ )}
{ diff --git a/packages/bruno-app/src/utils/codegenerator/har.js b/packages/bruno-app/src/utils/codegenerator/har.js new file mode 100644 index 000000000..241c1a64a --- /dev/null +++ b/packages/bruno-app/src/utils/codegenerator/har.js @@ -0,0 +1,72 @@ +const createContentType = (mode) => { + switch (mode) { + case 'json': + return 'application/json'; + case 'xml': + return 'application/xml'; + case 'formUrlEncoded': + return 'application/x-www-form-urlencoded'; + case 'multipartForm': + return 'multipart/form-data'; + default: + return 'application/json'; + } +}; + +const createHeaders = (headers, mode) => { + const contentType = createContentType(mode); + const headersArray = headers + .filter((header) => header.enabled) + .map((header) => { + return { + name: header.name, + value: header.value + }; + }); + const headerNames = headersArray.map((header) => header.name); + if (!headerNames.includes('Content-Type')) { + return [...headersArray, { name: 'Content-Type', value: contentType }]; + } + return headersArray; +}; + +const createQuery = (url) => { + const params = new URLSearchParams(url); + return params.forEach((value, name) => { + return { + name, + value + }; + }); +}; + +const createPostData = (body) => { + const contentType = createContentType(body.mode); + if (body.mode === 'formUrlEncoded' || body.mode === 'multipartForm') { + return { + mimeType: contentType, + params: body[body.mode] + .filter((param) => param.enabled) + .map((param) => ({ name: param.name, value: param.value })) + }; + } else { + return { + mimeType: contentType, + text: body[body.mode] + }; + } +}; + +export const buildHarRequest = (request) => { + return { + method: request.method, + url: request.url, + httpVersion: 'HTTP/1.1', + cookies: [], + headers: createHeaders(request.headers, request.body.mode), + queryString: createQuery(request.url), + postData: createPostData(request.body), + headersSize: 0, + bodySize: 0 + }; +}; diff --git a/packages/bruno-app/src/utils/url/index.js b/packages/bruno-app/src/utils/url/index.js index b28cc019e..7f5a8e825 100644 --- a/packages/bruno-app/src/utils/url/index.js +++ b/packages/bruno-app/src/utils/url/index.js @@ -53,3 +53,12 @@ export const splitOnFirst = (str, char) => { return [str.slice(0, index), str.slice(index + 1)]; }; + +export const isValidUrl = (url) => { + try { + new URL(url); + return true; + } catch (err) { + return false; + } +}; From ce0827308f621c9aa0c530db24aa8ba84f7976c1 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 2 Oct 2023 13:47:14 +0530 Subject: [PATCH 31/55] chore: updated readme --- readme.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 6f902b14d..8d28bb6ec 100644 --- a/readme.md +++ b/readme.md @@ -1,7 +1,7 @@
-### Bruno - Opensource IDE for exploring and testing APIs. +### Bruno - Opensource IDE for exploring and testing APIs. [![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno) [![CI](https://github.com/usebruno/bruno/actions/workflows/unit-tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/workflows/unit-tests.yml) @@ -10,36 +10,43 @@ [![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com) [![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads) - Bruno is a new and innovative API client, aimed at revolutionizing the status quo represented by Postman and similar tools out there. Bruno stores your collections directly in a folder on your filesystem. We use a plain text markup language, Bru, to save information about API requests. You can use git or any version control of your choice to collaborate over your API collections. +Bruno is offline-only. There are no plans to add cloud-sync to Bruno, Ever. We believe that your data is yours and should be stored on your machine. +If you are interested to know more about our long term vision, we recently penned some thoughts [here](https://github.com/usebruno/bruno/discussions/269) ![bruno](assets/images/landing-2.png)

### Run across multiple platforms 🖥️ + ![bruno](assets/images/run-anywhere.png)

### Collaborate via Git 👩‍💻🧑‍💻 + Or any version control system of your choice ![bruno](assets/images/version-control.png)

### Website 📄 + Please visit [here](https://www.usebruno.com) to checkout our website and download the app ### Documentation 📄 + Please visit [here](https://docs.usebruno.com) for documentation ### Contribute 👩‍💻🧑‍💻 + I am happy that you are looking to improve bruno. Please checkout the [contributing guide](contributing.md) Even if you are not able to make contributions via code, please don't hesitate to file bugs and feature requests that needs to be implemented to solve your use case. -### Support ❤️ +### Support ❤️ + Woof! If you like project, hit that ⭐ button !! ### Authors @@ -51,9 +58,11 @@ Woof! If you like project, hit that ⭐ button !!
### Stay in touch 🌐 + [Twitter](https://twitter.com/use_bruno)
[Website](https://www.usebruno.com)
[Discord](https://discord.com/invite/KgcZUncpjq) ### License 📄 + [MIT](license.md) From b74922c8f3940b4a0146973c5536abf8d96ca825 Mon Sep 17 00:00:00 2001 From: Beedhan Date: Mon, 2 Oct 2023 14:02:54 +0545 Subject: [PATCH 32/55] bugfix:sidebar moving when code generator modal is opened --- .../Collection/CollectionItem/GenerateCodeItem/StyledWrapper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/StyledWrapper.js index 86772b5e1..4b6eec556 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/StyledWrapper.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/StyledWrapper.js @@ -3,7 +3,7 @@ import styled from 'styled-components'; const StyledWrapper = styled.div` margin-inline: -1rem; margin-block: -1.5rem; - + position: absolute; background-color: ${(props) => props.theme.collection.environment.settings.bg}; .generate-code-sidebar { From 7297bb184ede2628bfdb9d890bf92e1d20502d54 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 2 Oct 2023 13:58:09 +0530 Subject: [PATCH 33/55] chore: updated readme --- readme.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 8d28bb6ec..af60a1888 100644 --- a/readme.md +++ b/readme.md @@ -16,8 +16,7 @@ Bruno stores your collections directly in a folder on your filesystem. We use a You can use git or any version control of your choice to collaborate over your API collections. -Bruno is offline-only. There are no plans to add cloud-sync to Bruno, Ever. We believe that your data is yours and should be stored on your machine. -If you are interested to know more about our long term vision, we recently penned some thoughts [here](https://github.com/usebruno/bruno/discussions/269) +Bruno is offline-only. There are no plans to add cloud-sync to Bruno, ever. We value your data privacy and believe it should stay on your device. Read our long-term vision [here](https://github.com/usebruno/bruno/discussions/269) ![bruno](assets/images/landing-2.png)

From 97200961eb68cd721932b0690c3f2018c98a052d Mon Sep 17 00:00:00 2001 From: Beedhan Date: Mon, 2 Oct 2023 14:24:13 +0545 Subject: [PATCH 34/55] feat: support for variables in url --- .../bruno-app/src/utils/codegenerator/har.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/bruno-app/src/utils/codegenerator/har.js b/packages/bruno-app/src/utils/codegenerator/har.js index 241c1a64a..ada2854d7 100644 --- a/packages/bruno-app/src/utils/codegenerator/har.js +++ b/packages/bruno-app/src/utils/codegenerator/har.js @@ -57,10 +57,24 @@ const createPostData = (body) => { } }; +const createUrl = (request) => { + let url = request.url; + const variablePattern = /\{\{([^}]+)\}\}/g; + const variables = request.url.match(variablePattern); + if (variables) { + variables.forEach((variable) => { + const variableName = variable.replaceAll('{', '').replaceAll('}', ''); + const variableValue = request.vars.req.find((v) => v.name === variableName).value; + url = url.replace(variable, variableValue); + }); + } + return url; +}; + export const buildHarRequest = (request) => { return { method: request.method, - url: request.url, + url: createUrl(request), httpVersion: 'HTTP/1.1', cookies: [], headers: createHeaders(request.headers, request.body.mode), From bfc03f5ae4fc7dabd7fee8329fd7b74c104f1f9f Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 2 Oct 2023 15:25:59 +0530 Subject: [PATCH 35/55] fix(#279): fixed codemirror crashes resulting in white screen --- .../ResponsePane/QueryResult/index.js | 53 +++++++++++++++---- .../src/components/ResponsePane/index.js | 5 +- .../RunnerResults/ResponsePane/index.js | 3 +- .../bruno-app/src/utils/common/codemirror.js | 22 ++++++++ packages/bruno-app/src/utils/common/index.js | 23 ++++---- 5 files changed, 82 insertions(+), 24 deletions(-) diff --git a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js index e6d6f94a8..4093dfcf9 100644 --- a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js +++ b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js @@ -3,10 +3,12 @@ import CodeEditor from 'components/CodeEditor'; import { useTheme } from 'providers/Theme'; import { useDispatch } from 'react-redux'; import { sendRequest } from 'providers/ReduxStore/slices/collections/actions'; +import { getContentType, safeStringifyJSON, safeParseXML } from 'utils/common'; +import { getCodeMirrorModeBasedOnContentType } from 'utils/common/codemirror'; import StyledWrapper from './StyledWrapper'; -const QueryResult = ({ item, collection, value, width, disableRunEventListener, mode }) => { +const QueryResult = ({ item, collection, data, width, disableRunEventListener, headers }) => { const { storedTheme } = useTheme(); const dispatch = useDispatch(); @@ -17,17 +19,50 @@ const QueryResult = ({ item, collection, value, width, disableRunEventListener, dispatch(sendRequest(item, collection.uid)); }; + const contentType = getContentType(headers); + const mode = getCodeMirrorModeBasedOnContentType(contentType); + + const formatResponse = (data, mode) => { + if (!data) { + return ''; + } + + if (mode.includes('json')) { + return safeStringifyJSON(data, true); + } + + if (mode.includes('xml')) { + let parsed = safeParseXML(data, { collapseContent: true }); + + if (typeof parsed === 'string') { + return parsed; + } + + return safeStringifyJSON(parsed, true); + } + + if (['text', 'html'].includes(mode)) { + if (typeof data === 'string') { + return data; + } + + return safeStringifyJSON(data); + } + + // final fallback + if (typeof data === 'string') { + return data; + } + + return safeStringifyJSON(data); + }; + + const value = formatResponse(data, mode); + return (
- +
); diff --git a/packages/bruno-app/src/components/ResponsePane/index.js b/packages/bruno-app/src/components/ResponsePane/index.js index c8e071625..0b56840fa 100644 --- a/packages/bruno-app/src/components/ResponsePane/index.js +++ b/packages/bruno-app/src/components/ResponsePane/index.js @@ -2,7 +2,6 @@ import React from 'react'; import find from 'lodash/find'; import classnames from 'classnames'; import { useDispatch, useSelector } from 'react-redux'; -import { getContentType, formatResponse } from 'utils/common'; import { updateResponsePaneTab } from 'providers/ReduxStore/slices/tabs'; import QueryResult from './QueryResult'; import Overlay from './Overlay'; @@ -41,8 +40,8 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => { item={item} collection={collection} width={rightPaneWidth} - value={response.data ? formatResponse(response) : ''} - mode={getContentType(response.headers)} + data={response.data} + headers={response.headers} /> ); } diff --git a/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js b/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js index ba87fcaf9..2c4f28b20 100644 --- a/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js +++ b/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js @@ -33,7 +33,8 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => { collection={collection} width={rightPaneWidth} disableRunEventListener={true} - value={responseReceived && responseReceived.data ? safeStringifyJSON(responseReceived.data, true) : ''} + data={responseReceived.data} + headers={responseReceived.headers} /> ); } diff --git a/packages/bruno-app/src/utils/common/codemirror.js b/packages/bruno-app/src/utils/common/codemirror.js index b1b60568c..aa4ba0519 100644 --- a/packages/bruno-app/src/utils/common/codemirror.js +++ b/packages/bruno-app/src/utils/common/codemirror.js @@ -42,3 +42,25 @@ export const defineCodeMirrorBrunoVariablesMode = (variables, mode) => { return CodeMirror.overlayMode(CodeMirror.getMode(config, parserConfig.backdrop || mode), variablesOverlay); }); }; + +export const getCodeMirrorModeBasedOnContentType = (contentType) => { + if (!contentType || typeof contentType !== 'string') { + return 'application/text'; + } + + if (contentType.includes('json')) { + return 'application/ld+json'; + } else if (contentType.includes('xml')) { + return 'application/xml'; + } else if (contentType.includes('html')) { + return 'application/html'; + } else if (contentType.includes('text')) { + return 'application/text'; + } else if (contentType.includes('application/edn')) { + return 'application/xml'; + } else if (mimeType.includes('yaml')) { + return 'application/yaml'; + } else { + return 'application/text'; + } +}; diff --git a/packages/bruno-app/src/utils/common/index.js b/packages/bruno-app/src/utils/common/index.js index 84725332f..c5eaa93ab 100644 --- a/packages/bruno-app/src/utils/common/index.js +++ b/packages/bruno-app/src/utils/common/index.js @@ -51,6 +51,17 @@ export const safeStringifyJSON = (obj, indent = false) => { } }; +export const safeParseXML = (str) => { + if (!str || !str.length || typeof str !== 'string') { + return str; + } + try { + return xmlFormat(str); + } catch (e) { + return str; + } +}; + // Remove any characters that are not alphanumeric, spaces, hyphens, or underscores export const normalizeFileName = (name) => { if (!name) { @@ -80,16 +91,6 @@ export const getContentType = (headers) => { return contentType[0]; } } + return ''; }; - -export const formatResponse = (response) => { - let type = getContentType(response.headers); - if (type.includes('json')) { - return safeStringifyJSON(response.data, true); - } - if (type.includes('xml')) { - return xmlFormat(response.data, { collapseContent: true }); - } - return response.data; -}; From 636e14a3852f11df0c82c2b5a8bcc1f843e673b4 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 2 Oct 2023 15:28:54 +0530 Subject: [PATCH 36/55] feat(#253): released cmd+h key back to native mac hide behaviour --- .../components/ResponsePane/Placeholder/index.js | 2 -- packages/bruno-app/src/providers/Hotkeys/index.js | 15 --------------- 2 files changed, 17 deletions(-) diff --git a/packages/bruno-app/src/components/ResponsePane/Placeholder/index.js b/packages/bruno-app/src/components/ResponsePane/Placeholder/index.js index 0879ba520..2e7cd8621 100644 --- a/packages/bruno-app/src/components/ResponsePane/Placeholder/index.js +++ b/packages/bruno-app/src/components/ResponsePane/Placeholder/index.js @@ -13,13 +13,11 @@ const Placeholder = () => {
Send Request
New Request
Edit Environments
-
Help
Cmd + Enter
Cmd + B
Cmd + E
-
Cmd + H
diff --git a/packages/bruno-app/src/providers/Hotkeys/index.js b/packages/bruno-app/src/providers/Hotkeys/index.js index a50e71dfb..522fa0d46 100644 --- a/packages/bruno-app/src/providers/Hotkeys/index.js +++ b/packages/bruno-app/src/providers/Hotkeys/index.js @@ -7,7 +7,6 @@ import SaveRequest from 'components/RequestPane/SaveRequest'; import EnvironmentSettings from 'components/Environments/EnvironmentSettings'; import NetworkError from 'components/ResponsePane/NetworkError'; import NewRequest from 'components/Sidebar/NewRequest'; -import BrunoSupport from 'components/BrunoSupport'; import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import { findCollectionByUid, findItemInCollection } from 'utils/collections'; import { closeTabs } from 'providers/ReduxStore/slices/tabs'; @@ -22,7 +21,6 @@ export const HotkeysProvider = (props) => { const [showSaveRequestModal, setShowSaveRequestModal] = useState(false); const [showEnvSettingsModal, setShowEnvSettingsModal] = useState(false); const [showNewRequestModal, setShowNewRequestModal] = useState(false); - const [showBrunoSupportModal, setShowBrunoSupportModal] = useState(false); const getCurrentCollectionItems = () => { const activeTab = find(tabs, (t) => t.uid === activeTabUid); @@ -133,18 +131,6 @@ export const HotkeysProvider = (props) => { }; }, [activeTabUid, tabs, collections, setShowNewRequestModal]); - // help (ctrl/cmd + h) - useEffect(() => { - Mousetrap.bind(['command+h', 'ctrl+h'], (e) => { - setShowBrunoSupportModal(true); - return false; // this stops the event bubbling - }); - - return () => { - Mousetrap.unbind(['command+h', 'ctrl+h']); - }; - }, [setShowNewRequestModal]); - // close tab hotkey useEffect(() => { Mousetrap.bind(['command+w', 'ctrl+w'], (e) => { @@ -164,7 +150,6 @@ export const HotkeysProvider = (props) => { return ( - {showBrunoSupportModal && setShowBrunoSupportModal(false)} />} {showSaveRequestModal && ( setShowSaveRequestModal(false)} /> )} From 336ad38cb9f4126de0ddfa5bfdc652876be2c485 Mon Sep 17 00:00:00 2001 From: not-known-person Date: Mon, 2 Oct 2023 08:34:39 -0400 Subject: [PATCH 37/55] Improved feat-/#220 --- .../components/Sidebar/Collections/index.js | 32 ++++++++++++++++--- .../ReduxStore/slices/collections/actions.js | 10 +++--- .../ReduxStore/slices/collections/index.js | 21 +++++++++--- 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/packages/bruno-app/src/components/Sidebar/Collections/index.js b/packages/bruno-app/src/components/Sidebar/Collections/index.js index 57b14a0d7..4d427ee35 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/index.js @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { IconSearch, IconFolders, IconSortAZ } from '@tabler/icons'; +import { IconSearch, IconFolders, IconArrowsSort, IconSortAscendingLetters, IconSortDescendingLetters } from '@tabler/icons'; import Collection from '../Collections/Collection'; import CreateCollection from '../CreateCollection'; import StyledWrapper from './StyledWrapper'; @@ -11,6 +11,23 @@ import { sortCollections } from 'providers/ReduxStore/slices/collections/actions const CollectionsBadge = () => { const dispatch = useDispatch() + const { collections } = useSelector((state) => state.collections); + const { collectionSortOrder } = useSelector((state) => state.collections); + const sortCollectionOrder = () => { + let order; + switch (collectionSortOrder) { + case 'default': + order = 'alphabetical' + break; + case 'alphabetical': + order = 'reverseAlphabetical' + break; + case 'reverseAlphabetical': + order = 'default' + break; + } + dispatch(sortCollections({ order })) + } return (
@@ -20,9 +37,16 @@ const CollectionsBadge = () => { Collections
- + { + collections.length >= 1 && + } +
); diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index b91c7c8fe..856431e9b 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -34,12 +34,12 @@ import { renameItem as _renameItem, cloneItem as _cloneItem, deleteItem as _deleteItem, - sortCollections as _sortCollections, saveRequest as _saveRequest, selectEnvironment as _selectEnvironment, createCollection as _createCollection, renameCollection as _renameCollection, removeCollection as _removeCollection, + sortCollections as _sortCollections, collectionAddEnvFileEvent as _collectionAddEnvFileEvent } from './index'; @@ -145,7 +145,10 @@ export const cancelRequest = (cancelTokenUid, item, collection) => (dispatch) => }) .catch((err) => console.log(err)); }; - +export const sortCollections = (order) => (dispatch) => { + console.log("working") + dispatch(_sortCollections(order)) +} export const runCollectionFolder = (collectionUid, folderUid, recursive) => (dispatch, getState) => { const state = getState(); const collection = findCollectionByUid(state.collections.collections, collectionUid); @@ -375,9 +378,6 @@ export const deleteItem = (itemUid, collectionUid) => (dispatch, getState) => { }); }; -export const sortCollections = () => (dispatch) => { - dispatch(_sortCollections()); -}; export const moveItem = (collectionUid, draggedItemUid, targetItemUid) => (dispatch, getState) => { const state = getState(); const collection = findCollectionByUid(state.collections.collections, collectionUid); diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js index 8227efc6b..24ad23a8d 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -28,7 +28,8 @@ import { getSubdirectoriesFromRoot, getDirectoryName } from 'utils/common/platfo const PATH_SEPARATOR = path.sep; const initialState = { - collections: [] + collections: [], + collectionSortOrder: 'default' }; export const collectionsSlice = createSlice({ @@ -38,13 +39,14 @@ export const collectionsSlice = createSlice({ createCollection: (state, action) => { const collectionUids = map(state.collections, (c) => c.uid); const collection = action.payload; - // last action is used to track the last action performed on the collection // this is optional // this is used in scenarios where we want to know the last action performed on the collection // and take some extra action based on that // for example, when a env is created, we want to auto select it the env modal + collection.importedAt = new Date().getTime() collection.lastAction = null; + console.log(collection) collapseCollection(collection); addDepth(collection.items); @@ -70,8 +72,19 @@ export const collectionsSlice = createSlice({ removeCollection: (state, action) => { state.collections = filter(state.collections, (c) => c.uid !== action.payload.collectionUid); }, - sortCollections: (state) => { - state.collections = state.collections.sort((a, b) => a.name.localeCompare(b.name)) + sortCollections: (state, action) => { + state.collectionSortOrder = action.payload.order + switch (action.payload.order) { + case 'default': + state.collections = state.collections.sort((a, b) => a.importedAt - b.importedAt) + break; + case 'alphabetical': + state.collections = state.collections.sort((a, b) => a.name.localeCompare(b.name)) + break; + case 'reverseAlphabetical': + state.collections = state.collections.sort((a, b) => b.name.localeCompare(a.name)) + break; + } }, updateLastAction: (state, action) => { const { collectionUid, lastAction } = action.payload; From cbdd56e57783442c4a0ef2dbbceb65df30c9f146 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 2 Oct 2023 15:38:50 +0530 Subject: [PATCH 38/55] chore: added some todo's for future code cleanup --- .../components/Sidebar/Collections/index.js | 55 +++++++++++-------- .../ReduxStore/slices/collections/actions.js | 8 ++- 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/packages/bruno-app/src/components/Sidebar/Collections/index.js b/packages/bruno-app/src/components/Sidebar/Collections/index.js index 4d427ee35..af54350e4 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/index.js @@ -1,6 +1,12 @@ import React, { useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { IconSearch, IconFolders, IconArrowsSort, IconSortAscendingLetters, IconSortDescendingLetters } from '@tabler/icons'; +import { + IconSearch, + IconFolders, + IconArrowsSort, + IconSortAscendingLetters, + IconSortDescendingLetters +} from '@tabler/icons'; import Collection from '../Collections/Collection'; import CreateCollection from '../CreateCollection'; import StyledWrapper from './StyledWrapper'; @@ -9,44 +15,47 @@ import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; import { sortCollections } from 'providers/ReduxStore/slices/collections/actions'; +// todo: move this to a separate folder +// the coding convention is to keep all the components in a folder named after the component const CollectionsBadge = () => { - const dispatch = useDispatch() + const dispatch = useDispatch(); const { collections } = useSelector((state) => state.collections); const { collectionSortOrder } = useSelector((state) => state.collections); const sortCollectionOrder = () => { let order; switch (collectionSortOrder) { case 'default': - order = 'alphabetical' + order = 'alphabetical'; break; case 'alphabetical': - order = 'reverseAlphabetical' + order = 'reverseAlphabetical'; break; case 'reverseAlphabetical': - order = 'default' + order = 'default'; break; } - dispatch(sortCollections({ order })) - } + dispatch(sortCollections({ order })); + }; return (
-
+
Collections
- { - collections.length >= 1 && - } - + )}
); @@ -95,12 +104,12 @@ const Collections = () => {
{collections && collections.length ? collections.map((c) => { - return ( - - - - ); - }) + return ( + + + + ); + }) : null}
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index 856431e9b..0c6945ae9 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -145,10 +145,12 @@ export const cancelRequest = (cancelTokenUid, item, collection) => (dispatch) => }) .catch((err) => console.log(err)); }; + +// todo: this can be directly put inside the collections/index.js file +// the coding convention is to put only actions that need ipc in this file export const sortCollections = (order) => (dispatch) => { - console.log("working") - dispatch(_sortCollections(order)) -} + dispatch(_sortCollections(order)); +}; export const runCollectionFolder = (collectionUid, folderUid, recursive) => (dispatch, getState) => { const state = getState(); const collection = findCollectionByUid(state.collections.collections, collectionUid); From 95532102baf9e5945f964db0b8b71f905af17a44 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 2 Oct 2023 16:52:02 +0530 Subject: [PATCH 39/55] feat(#280): code generator polishing and support url interpolation --- packages/bruno-app/package.json | 1 + .../GenerateCodeItem/CodeView/index.js | 28 +++--- .../GenerateCodeItem/StyledWrapper.js | 1 - .../CollectionItem/GenerateCodeItem/index.js | 85 +++++++++++++++---- .../Collection/CollectionItem/index.js | 2 +- .../ReduxStore/slices/collections/index.js | 11 ++- .../bruno-app/src/utils/codegenerator/har.js | 27 ++---- 7 files changed, 95 insertions(+), 60 deletions(-) diff --git a/packages/bruno-app/package.json b/packages/bruno-app/package.json index 7be069c17..625e9dc0d 100644 --- a/packages/bruno-app/package.json +++ b/packages/bruno-app/package.json @@ -30,6 +30,7 @@ "graphiql": "^1.5.9", "graphql": "^16.6.0", "graphql-request": "^3.7.0", + "handlebars": "^4.7.8", "httpsnippet": "^3.0.1", "idb": "^7.0.0", "immer": "^9.0.15", diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/index.js index ba100b4fe..79d636daf 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/index.js @@ -1,21 +1,21 @@ import CodeEditor from 'components/CodeEditor/index'; -import HTTPSnippet from 'httpsnippet'; +import { HTTPSnippet } from 'httpsnippet'; import { useTheme } from 'providers/Theme/index'; import { buildHarRequest } from 'utils/codegenerator/har'; -const index = ({ language, item }) => { - const { target, client, language: lang } = language; - const snippet = new HTTPSnippet(buildHarRequest(item.request)).convert(target, client); +const CodeView = ({ language, item }) => { const { storedTheme } = useTheme(); - return ( - - ); + const { target, client, language: lang } = language; + let snippet = ''; + + try { + snippet = new HTTPSnippet(buildHarRequest(item.request)).convert(target, client); + } catch (e) { + console.error(e); + snippet = 'Error generating code snippet'; + } + + return ; }; -export default index; +export default CodeView; diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/StyledWrapper.js index 4b6eec556..f1c1c33e4 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/StyledWrapper.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/StyledWrapper.js @@ -3,7 +3,6 @@ import styled from 'styled-components'; const StyledWrapper = styled.div` margin-inline: -1rem; margin-block: -1.5rem; - position: absolute; background-color: ${(props) => props.theme.collection.environment.settings.bg}; .generate-code-sidebar { diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/index.js index 35f01972f..509a529bd 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/index.js @@ -2,8 +2,28 @@ import Modal from 'components/Modal/index'; import { useState } from 'react'; import CodeView from './CodeView'; import StyledWrapper from './StyledWrapper'; -import ErrorBoundary from 'src/pages/ErrorBoundary/index'; import { isValidUrl } from 'utils/url/index'; +import get from 'lodash/get'; +import handlebars from 'handlebars'; +import { findEnvironmentInCollection } from 'utils/collections'; + +const interpolateUrl = ({ url, envVars, collectionVariables, processEnvVars }) => { + if (!url || !url.length || typeof url !== 'string') { + return str; + } + + const template = handlebars.compile(url, { noEscape: true }); + + return template({ + ...envVars, + ...collectionVariables, + process: { + env: { + ...processEnvVars + } + } + }); +}; const languages = [ { @@ -52,12 +72,32 @@ const languages = [ client: 'httpie' } ]; -const index = ({ item, onClose }) => { + +const GenerateCodeItem = ({ collection, item, onClose }) => { + const url = get(item, 'request.url') || ''; + const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid); + + let envVars = {}; + if (environment) { + const vars = get(environment, 'variables', []); + envVars = vars.reduce((acc, curr) => { + acc[curr.name] = curr.value; + return acc; + }, {}); + } + + const interpolatedUrl = interpolateUrl({ + url, + envVars, + collectionVariables: collection.collectionVariables, + processEnvVars: collection.processEnvVariables + }); + const [selectedLanguage, setSelectedLanguage] = useState(languages[0]); return ( - - -
+ + +
{languages && @@ -75,20 +115,31 @@ const index = ({ item, onClose }) => { ))}
- {isValidUrl(item.request.url) ? ( - - ) : ( -
-
-

Invalid URL

-

Please check the URL and try again

+
+ {isValidUrl(interpolatedUrl) ? ( + + ) : ( +
+
+

Invalid URL: {interpolatedUrl}

+

Please check the URL and try again

+
-
- )} + )} +
- - + + ); }; -export default index; +export default GenerateCodeItem; diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js index a59503be0..daed44279 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js @@ -169,7 +169,7 @@ const CollectionItem = ({ item, collection, searchText }) => { setRunCollectionModalOpen(false)} /> )} {generateCodeItemModalOpen && ( - setGenerateCodeItemModalOpen(false)} /> + setGenerateCodeItemModalOpen(false)} /> )}
drag(drop(node))}>
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js index 24ad23a8d..213761029 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -44,9 +44,8 @@ export const collectionsSlice = createSlice({ // this is used in scenarios where we want to know the last action performed on the collection // and take some extra action based on that // for example, when a env is created, we want to auto select it the env modal - collection.importedAt = new Date().getTime() + collection.importedAt = new Date().getTime(); collection.lastAction = null; - console.log(collection) collapseCollection(collection); addDepth(collection.items); @@ -73,16 +72,16 @@ export const collectionsSlice = createSlice({ state.collections = filter(state.collections, (c) => c.uid !== action.payload.collectionUid); }, sortCollections: (state, action) => { - state.collectionSortOrder = action.payload.order + state.collectionSortOrder = action.payload.order; switch (action.payload.order) { case 'default': - state.collections = state.collections.sort((a, b) => a.importedAt - b.importedAt) + state.collections = state.collections.sort((a, b) => a.importedAt - b.importedAt); break; case 'alphabetical': - state.collections = state.collections.sort((a, b) => a.name.localeCompare(b.name)) + state.collections = state.collections.sort((a, b) => a.name.localeCompare(b.name)); break; case 'reverseAlphabetical': - state.collections = state.collections.sort((a, b) => b.name.localeCompare(a.name)) + state.collections = state.collections.sort((a, b) => b.name.localeCompare(a.name)); break; } }, diff --git a/packages/bruno-app/src/utils/codegenerator/har.js b/packages/bruno-app/src/utils/codegenerator/har.js index ada2854d7..b48fbc3c7 100644 --- a/packages/bruno-app/src/utils/codegenerator/har.js +++ b/packages/bruno-app/src/utils/codegenerator/har.js @@ -30,12 +30,11 @@ const createHeaders = (headers, mode) => { return headersArray; }; -const createQuery = (url) => { - const params = new URLSearchParams(url); - return params.forEach((value, name) => { +const createQuery = (queryParams = []) => { + return queryParams.map((param) => { return { - name, - value + name: param.name, + value: param.value }; }); }; @@ -57,28 +56,14 @@ const createPostData = (body) => { } }; -const createUrl = (request) => { - let url = request.url; - const variablePattern = /\{\{([^}]+)\}\}/g; - const variables = request.url.match(variablePattern); - if (variables) { - variables.forEach((variable) => { - const variableName = variable.replaceAll('{', '').replaceAll('}', ''); - const variableValue = request.vars.req.find((v) => v.name === variableName).value; - url = url.replace(variable, variableValue); - }); - } - return url; -}; - export const buildHarRequest = (request) => { return { method: request.method, - url: createUrl(request), + url: request.url, httpVersion: 'HTTP/1.1', cookies: [], headers: createHeaders(request.headers, request.body.mode), - queryString: createQuery(request.url), + queryString: createQuery(request.params), postData: createPostData(request.body), headersSize: 0, bodySize: 0 From 43f7c2ab86eeec5342f9f1e9972ccadc28406298 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 2 Oct 2023 16:55:22 +0530 Subject: [PATCH 40/55] chore: version bump to v0.17.0 --- packages/bruno-app/src/components/Sidebar/index.js | 2 +- packages/bruno-electron/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bruno-app/src/components/Sidebar/index.js b/packages/bruno-app/src/components/Sidebar/index.js index cb0f0ffb5..297258389 100644 --- a/packages/bruno-app/src/components/Sidebar/index.js +++ b/packages/bruno-app/src/components/Sidebar/index.js @@ -116,7 +116,7 @@ const Sidebar = () => { )}
-
v0.16.6
+
v0.17.0
diff --git a/packages/bruno-electron/package.json b/packages/bruno-electron/package.json index 2807e2f97..a0746f854 100644 --- a/packages/bruno-electron/package.json +++ b/packages/bruno-electron/package.json @@ -1,5 +1,5 @@ { - "version": "v0.16.6", + "version": "v0.17.0", "name": "bruno", "description": "Opensource API Client for Exploring and Testing APIs", "homepage": "https://www.usebruno.com", From e720fed63b0656b317751cccfe85865c67494c5a Mon Sep 17 00:00:00 2001 From: Its-treason <39559178+Its-treason@users.noreply.github.com> Date: Mon, 2 Oct 2023 14:26:24 +0200 Subject: [PATCH 41/55] feat(#245): Add HTML preview to response --- .../ResponsePane/QueryResult/StyledWrapper.js | 23 ++++++- .../ResponsePane/QueryResult/index.js | 66 +++++++++++++++---- .../src/components/ResponsePane/index.js | 2 +- packages/bruno-electron/src/index.js | 3 +- 4 files changed, 79 insertions(+), 15 deletions(-) diff --git a/packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js b/packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js index 9f7583222..18c1c5314 100644 --- a/packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js +++ b/packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js @@ -1,9 +1,28 @@ import styled from 'styled-components'; const StyledWrapper = styled.div` + display: grid; + grid-template-columns: 100%; + grid-template-rows: 50px calc(100% - 50px); + + /* If there is only one element (the preview, not tabs) make it span over both grid rows */ + > *:last-child:first-child { + grid-row: 1 / 3; + margin-top: 1.25rem; + height: calc(100% - 1.25rem); + } + + /* This is a hack to force Codemirror to use all available space */ + > div { + position: relative; + } + div.CodeMirror { - /* todo: find a better way */ - height: calc(100vh - 220px); + position: absolute; + top: 0; + bottom: 0; + height: 100%; + width: 100%; } `; diff --git a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js index e6d6f94a8..f6698df22 100644 --- a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js +++ b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js @@ -3,11 +3,15 @@ import CodeEditor from 'components/CodeEditor'; import { useTheme } from 'providers/Theme'; import { useDispatch } from 'react-redux'; import { sendRequest } from 'providers/ReduxStore/slices/collections/actions'; +import classnames from 'classnames'; import StyledWrapper from './StyledWrapper'; +import { useState } from 'react'; +import { useMemo } from 'react'; const QueryResult = ({ item, collection, value, width, disableRunEventListener, mode }) => { const { storedTheme } = useTheme(); + const [tab, setTab] = useState('raw'); const dispatch = useDispatch(); const onRun = () => { @@ -17,18 +21,58 @@ const QueryResult = ({ item, collection, value, width, disableRunEventListener, dispatch(sendRequest(item, collection.uid)); }; - return ( - -
- + const getTabClassname = (tabName) => { + return classnames(`tab select-none ${tabName}`, { + active: tabName === tab + }); + }; + + const tabs = [( +
setTab('raw')}> + Raw +
+ )]; + if (mode.includes('text/html')) { + tabs.push( +
setTab('preview')}> + Preview
+ ); + } + + const activeResult = useMemo(() => { + if (tab === 'preview') { + // Add the Base tag to the head so content loads proparly. This also needs the correct CSP settings + const webViewSrc = value.replace('', ``); + return ( + + ); + } + + return ( + + ); + }, [tab, collection, storedTheme, onRun, value, mode]); + + return ( + + {tabs.length > 1 ? ( +
+ {tabs} +
+ ) : null} + {activeResult}
); }; diff --git a/packages/bruno-app/src/components/ResponsePane/index.js b/packages/bruno-app/src/components/ResponsePane/index.js index c8e071625..64ff9eb25 100644 --- a/packages/bruno-app/src/components/ResponsePane/index.js +++ b/packages/bruno-app/src/components/ResponsePane/index.js @@ -116,7 +116,7 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
) : null}
-
{getTabPanel(focusedTab.responsePaneTab)}
+
{getTabPanel(focusedTab.responsePaneTab)}
); }; diff --git a/packages/bruno-electron/src/index.js b/packages/bruno-electron/src/index.js index 2a62dd969..8f56d6461 100644 --- a/packages/bruno-electron/src/index.js +++ b/packages/bruno-electron/src/index.js @@ -35,7 +35,8 @@ app.on('ready', async () => { webPreferences: { nodeIntegration: true, contextIsolation: true, - preload: path.join(__dirname, 'preload.js') + preload: path.join(__dirname, 'preload.js'), + webviewTag: true, } }); From d63e7371fe0df7e0d8b60b58815a608d5ca5c4de Mon Sep 17 00:00:00 2001 From: pedward99 Date: Mon, 2 Oct 2023 10:39:29 -0400 Subject: [PATCH 42/55] Fix for XML Formatting Options --- packages/bruno-app/src/utils/common/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bruno-app/src/utils/common/index.js b/packages/bruno-app/src/utils/common/index.js index c5eaa93ab..992ec233e 100644 --- a/packages/bruno-app/src/utils/common/index.js +++ b/packages/bruno-app/src/utils/common/index.js @@ -51,12 +51,12 @@ export const safeStringifyJSON = (obj, indent = false) => { } }; -export const safeParseXML = (str) => { +export const safeParseXML = (str, options) => { if (!str || !str.length || typeof str !== 'string') { return str; } try { - return xmlFormat(str); + return xmlFormat(str, options); } catch (e) { return str; } From ea9e294c546f640b1bd9ecb9079ef0295447b68c Mon Sep 17 00:00:00 2001 From: Its-treason <39559178+Its-treason@users.noreply.github.com> Date: Mon, 2 Oct 2023 19:50:01 +0200 Subject: [PATCH 43/55] feat(#245): Update tab design + Remove CSP --- .../ResponsePane/QueryResult/StyledWrapper.js | 9 +---- .../ResponsePane/QueryResult/index.js | 39 ++++++++++--------- packages/bruno-electron/src/index.js | 2 - 3 files changed, 22 insertions(+), 28 deletions(-) diff --git a/packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js b/packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js index 18c1c5314..df65244d6 100644 --- a/packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js +++ b/packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js @@ -3,14 +3,7 @@ import styled from 'styled-components'; const StyledWrapper = styled.div` display: grid; grid-template-columns: 100%; - grid-template-rows: 50px calc(100% - 50px); - - /* If there is only one element (the preview, not tabs) make it span over both grid rows */ - > *:last-child:first-child { - grid-row: 1 / 3; - margin-top: 1.25rem; - height: calc(100% - 1.25rem); - } + grid-template-rows: 1.25rem calc(100% - 1.25rem); /* This is a hack to force Codemirror to use all available space */ > div { diff --git a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js index dd4c8a502..faa0e1e14 100644 --- a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js +++ b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js @@ -63,23 +63,28 @@ const QueryResult = ({ item, collection, data, width, disableRunEventListener, h }; const getTabClassname = (tabName) => { - return classnames(`tab select-none ${tabName}`, { - active: tabName === tab + return classnames(`select-none ${tabName}`, { + 'text-yellow-500': tabName === tab, + 'cursor-pointer': tabName !== tab, }); }; - const tabs = [( -
setTab('raw')}> - Raw -
- )]; - if (mode.includes('html')) { - tabs.push( -
setTab('preview')}> - Preview -
+ const getTabs = () => { + if (!mode.includes('html')) { + return null; + } + + return ( + <> +
setTab('raw')}> + Raw +
+
setTab('preview')}> + Preview +
+ ); - } + }; const activeResult = useMemo(() => { if (tab === 'preview') { @@ -108,11 +113,9 @@ const QueryResult = ({ item, collection, data, width, disableRunEventListener, h return ( - {tabs.length > 1 ? ( -
- {tabs} -
- ) : null} +
+ {getTabs()} +
{activeResult}
); diff --git a/packages/bruno-electron/src/index.js b/packages/bruno-electron/src/index.js index 8f56d6461..ac2e92208 100644 --- a/packages/bruno-electron/src/index.js +++ b/packages/bruno-electron/src/index.js @@ -16,9 +16,7 @@ setContentSecurityPolicy(` default-src * 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; - base-uri 'none'; form-action 'none'; - img-src 'self' data:image/svg+xml; `); const menu = Menu.buildFromTemplate(menuTemplate); From 7a80247fc52c6ba87691f6a85d8db2ff88ec1c94 Mon Sep 17 00:00:00 2001 From: Its-treason <39559178+Its-treason@users.noreply.github.com> Date: Tue, 3 Oct 2023 00:48:49 +0200 Subject: [PATCH 44/55] fix: Color in environment delete modal --- packages/bruno-app/src/components/Modal/StyledWrapper.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/bruno-app/src/components/Modal/StyledWrapper.js b/packages/bruno-app/src/components/Modal/StyledWrapper.js index f0cb79353..4ec5d4a25 100644 --- a/packages/bruno-app/src/components/Modal/StyledWrapper.js +++ b/packages/bruno-app/src/components/Modal/StyledWrapper.js @@ -1,6 +1,8 @@ import styled from 'styled-components'; const Wrapper = styled.div` + color: ${(props) => props.theme.text}; + &.modal--animate-out { animation: fade-out 0.5s forwards cubic-bezier(0.19, 1, 0.22, 1); From 26853787da6863184848bac85ed33cea37c26366 Mon Sep 17 00:00:00 2001 From: O Teerasak Vichadee Date: Tue, 3 Oct 2023 11:28:27 +0700 Subject: [PATCH 45/55] feat(291): add double click request to open rename modal --- .../Sidebar/Collections/Collection/CollectionItem/index.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js index daed44279..a08b4fe1f 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js @@ -115,6 +115,10 @@ const CollectionItem = ({ item, collection, searchText }) => { } }; + const handleDoubleClick = (event) => { + setRenameItemModalOpen(true); + }; + let indents = range(item.depth); const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); const isFolder = isItemAFolder(item); @@ -178,6 +182,7 @@ const CollectionItem = ({ item, collection, searchText }) => { return (
{ : null}
Date: Tue, 3 Oct 2023 10:39:38 +0200 Subject: [PATCH 46/55] Fix rounding response size I noticed that sometimes the response size is weirdly displayed. - size `112932` is displayed as `110.28.999999999999996KB` - size `112990` is displayed as `110.34KB` The problem is in the decimal calculation. Rounding the value ensure we don't have two `.` in the displayed size. Also, update the `contributing` to increase the minimum Node version required. --- contributing.md | 2 +- .../bruno-app/src/components/ResponsePane/ResponseSize/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contributing.md b/contributing.md index 6b70dc028..a538f1bd7 100644 --- a/contributing.md +++ b/contributing.md @@ -19,7 +19,7 @@ Libraries we use ### Dependencies -You would need [Node v14.x or the latest LTS version](https://nodejs.org/en/) and npm 8.x. We use npm workspaces in the project +You would need [Node v18.x or the latest LTS version](https://nodejs.org/en/) and npm 8.x. We use npm workspaces in the project ### Lets start coding diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseSize/index.js b/packages/bruno-app/src/components/ResponsePane/ResponseSize/index.js index c3c2f1fe3..2500474cb 100644 --- a/packages/bruno-app/src/components/ResponsePane/ResponseSize/index.js +++ b/packages/bruno-app/src/components/ResponsePane/ResponseSize/index.js @@ -7,7 +7,7 @@ const ResponseSize = ({ size }) => { if (size > 1024) { // size is greater than 1kb let kb = Math.floor(size / 1024); - let decimal = ((size % 1024) / 1024).toFixed(2) * 100; + let decimal = Math.round(((size % 1024) / 1024).toFixed(2) * 100); sizeToDisplay = kb + '.' + decimal + 'KB'; } else { sizeToDisplay = size + 'B'; From 2734f26c78fac7da9e2ee16f1dd9420e865abecb Mon Sep 17 00:00:00 2001 From: Its-treason <39559178+Its-treason@users.noreply.github.com> Date: Tue, 3 Oct 2023 13:04:34 +0200 Subject: [PATCH 47/55] fix(#270): Fix deleted assertions & tests shown in test tab After all tests or assertions were deleted the store was not updated because of the length checks. Now the assertions/tests will always try to run, to ensure the data is always updated. --- .../bruno-cli/src/runner/run-single-request.js | 8 ++++---- packages/bruno-electron/src/ipc/network/index.js | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js index 0d3a99e82..1f9b6ba41 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -161,7 +161,7 @@ const runSingleRequest = async function ( // run assertions let assertionResults = []; const assertions = get(bruJson, 'request.assertions'); - if (assertions && assertions.length) { + if (assertions) { const assertRuntime = new AssertRuntime(); assertionResults = assertRuntime.runAssertions( assertions, @@ -185,7 +185,7 @@ const runSingleRequest = async function ( // run tests let testResults = []; const testFile = get(bruJson, 'request.tests'); - if (testFile && testFile.length) { + if (typeof testFile === 'string') { const testRuntime = new TestRuntime(); const result = await testRuntime.runTests( testFile, @@ -266,7 +266,7 @@ const runSingleRequest = async function ( // run assertions let assertionResults = []; const assertions = get(bruJson, 'request.assertions'); - if (assertions && assertions.length) { + if (assertions) { const assertRuntime = new AssertRuntime(); assertionResults = assertRuntime.runAssertions( assertions, @@ -290,7 +290,7 @@ const runSingleRequest = async function ( // run tests let testResults = []; const testFile = get(bruJson, 'request.tests'); - if (testFile && testFile.length) { + if (typeof testFile === 'string') { const testRuntime = new TestRuntime(); const result = await testRuntime.runTests( testFile, diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index d4cfa48b1..6c1bec01a 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -291,7 +291,7 @@ const registerNetworkIpc = (mainWindow) => { // run assertions const assertions = get(request, 'assertions'); - if (assertions && assertions.length) { + if (assertions) { const assertRuntime = new AssertRuntime(); const results = assertRuntime.runAssertions( assertions, @@ -313,7 +313,7 @@ const registerNetworkIpc = (mainWindow) => { // run tests const testFile = item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests'); - if (testFile && testFile.length) { + if (typeof testFile === 'string') { const testRuntime = new TestRuntime(); const testResults = await testRuntime.runTests( testFile, @@ -365,7 +365,7 @@ const registerNetworkIpc = (mainWindow) => { if (error && error.response) { // run assertions const assertions = get(request, 'assertions'); - if (assertions && assertions.length) { + if (assertions) { const assertRuntime = new AssertRuntime(); const results = assertRuntime.runAssertions( assertions, @@ -387,7 +387,7 @@ const registerNetworkIpc = (mainWindow) => { // run tests const testFile = item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests'); - if (testFile && testFile.length) { + if (typeof testFile === 'string') { const testRuntime = new TestRuntime(); const testResults = await testRuntime.runTests( testFile, @@ -702,7 +702,7 @@ const registerNetworkIpc = (mainWindow) => { // run assertions const assertions = get(item, 'request.assertions'); - if (assertions && assertions.length) { + if (assertions) { const assertRuntime = new AssertRuntime(); const results = assertRuntime.runAssertions( assertions, @@ -723,7 +723,7 @@ const registerNetworkIpc = (mainWindow) => { // run tests const testFile = item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests'); - if (testFile && testFile.length) { + if (typeof testFile === 'string') { const testRuntime = new TestRuntime(); const testResults = await testRuntime.runTests( testFile, @@ -781,7 +781,7 @@ const registerNetworkIpc = (mainWindow) => { // run assertions const assertions = get(item, 'request.assertions'); - if (assertions && assertions.length) { + if (assertions) { const assertRuntime = new AssertRuntime(); const results = assertRuntime.runAssertions( assertions, @@ -802,7 +802,7 @@ const registerNetworkIpc = (mainWindow) => { // run tests const testFile = item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests'); - if (testFile && testFile.length) { + if (typeof testFile === 'string') { const testRuntime = new TestRuntime(); const testResults = await testRuntime.runTests( testFile, From 48e4e606961ff30cbadfdef37d50b69e31691e4e Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Wed, 4 Oct 2023 00:36:52 +0530 Subject: [PATCH 48/55] feat(#119): bearer auth support completed --- .../Auth/AuthMode/StyledWrapper.js | 3 +- .../RequestPane/Auth/AuthMode/index.js | 4 +- .../Auth/BearerAuth/StyledWrapper.js | 10 ++++ .../RequestPane/Auth/BearerAuth/index.js | 52 +++++++++++++++++++ .../src/components/RequestPane/Auth/index.js | 44 ++++++++-------- .../RequestPane/HttpRequestPane/index.js | 9 ++-- .../ReduxStore/slices/collections/actions.js | 1 - .../ReduxStore/slices/collections/index.js | 21 ++++++++ .../bruno-app/src/utils/collections/index.js | 1 + .../src/ipc/network/prepare-request.js | 14 +++++ packages/bruno-lang/v2/src/jsonToBru.js | 5 ++ .../bruno-lang/v2/tests/fixtures/request.bru | 1 + .../bruno-lang/v2/tests/fixtures/request.json | 3 +- packages/bruno-lang/v2/tests/index.spec.js | 2 +- 14 files changed, 134 insertions(+), 36 deletions(-) create mode 100644 packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/index.js diff --git a/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/StyledWrapper.js index 6613dd1f3..e1d0530c6 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/StyledWrapper.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/StyledWrapper.js @@ -4,8 +4,7 @@ const Wrapper = styled.div` font-size: 0.8125rem; .auth-mode-selector { - background: ${(props) => props.theme.requestTabPanel.bodyModeSelect.color}; - border-radius: 3px; + background: transparent; .dropdown-item { padding: 0.2rem 0.6rem !important; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js b/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js index 3ad80ce13..f1d2e443f 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js @@ -15,8 +15,8 @@ const AuthMode = ({ item, collection }) => { const Icon = forwardRef((props, ref) => { return ( -
- {humanizeRequestAuthMode(authMode)} +
+ {humanizeRequestAuthMode(authMode)}
); }); diff --git a/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/StyledWrapper.js new file mode 100644 index 000000000..71d3bdf1d --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/StyledWrapper.js @@ -0,0 +1,10 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + .single-line-editor-wrapper { + padding: 0.15rem 0.4rem; + border: ${(props) => props.theme.sidebar.search.border}; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/index.js new file mode 100644 index 000000000..90347dc5a --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/index.js @@ -0,0 +1,52 @@ +import React, { useState } from 'react'; +import get from 'lodash/get'; +import { useTheme } from 'providers/Theme'; +import { useDispatch } from 'react-redux'; +import SingleLineEditor from 'components/SingleLineEditor'; +import { updateBearerToken } from 'providers/ReduxStore/slices/collections'; +import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; +import StyledWrapper from './StyledWrapper'; + +const BearerAuth = ({ onTokenChange, item, collection }) => { + const dispatch = useDispatch(); + const { storedTheme } = useTheme(); + + const bearerToken = item.draft + ? get(item, 'draft.request.auth.bearer.token') + : get(item, 'request.auth.bearer.token'); + + const handleRun = () => dispatch(sendRequest(item, collection.uid)); + const handleSave = () => dispatch(saveRequest(item.uid, collection.uid)); + + const handleTokenChange = (token) => { + dispatch( + updateBearerToken({ + collectionUid: collection.uid, + itemUid: item.uid, + content: { + token: token + } + }) + ); + }; + + return ( + + +
+ handleTokenChange(val)} + onRun={handleRun} + collection={collection} + /> +
+
+ ); +}; + +export default BearerAuth; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/index.js index e2f345d23..1210b0251 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/index.js @@ -1,32 +1,30 @@ import React from 'react'; import get from 'lodash/get'; -import { useDispatch } from 'react-redux'; -import { updateRequestBody } from 'providers/ReduxStore/slices/collections'; -import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; +import AuthMode from './AuthMode'; +import BearerAuth from './BearerAuth'; import StyledWrapper from './StyledWrapper'; -const RequestBody = ({ item, collection }) => { - const dispatch = useDispatch(); +const Auth = ({ item, collection }) => { const authMode = item.draft ? get(item, 'draft.request.auth.mode') : get(item, 'request.auth.mode'); - const onEdit = (value) => { - // dispatch( - // updateRequestBody({ - // content: value, - // itemUid: item.uid, - // collectionUid: collection.uid - // }) - // ); + const getAuthView = () => { + switch (authMode) { + case 'basic': { + return
Basic Auth
; + } + case 'bearer': { + return ; + } + } }; - if (authMode === 'basic') { - return
Basic Auth
; - } - - if (authMode === 'bearer') { - return
Bearer Token
; - } - - return No Auth; + return ( + +
+ +
+ {getAuthView()} +
+ ); }; -export default RequestBody; +export default Auth; diff --git a/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js b/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js index 652414a62..ec20514fe 100644 --- a/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js +++ b/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js @@ -108,13 +108,10 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
) : null} - {focusedTab.requestPaneTab === 'auth' ? ( -
- -
- ) : null}
-
+
{getTabPanel(focusedTab.requestPaneTab)}
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index 0c6945ae9..ccdd7fe1a 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -12,7 +12,6 @@ import { getItemsToResequence, moveCollectionItemToRootOfCollection, findCollectionByUid, - recursivelyGetAllItemUids, transformRequestToSaveToFilesystem, findParentItemInCollection, findEnvironmentInCollection, diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js index 176926e19..84762aa54 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -322,6 +322,26 @@ export const collectionsSlice = createSlice({ } } }, + updateBearerToken: (state, action) => { + const collection = findCollectionByUid(state.collections, action.payload.collectionUid); + + if (collection) { + const item = findItemInCollection(collection, action.payload.itemUid); + + if (item && isItemARequest(item)) { + if (!item.draft) { + item.draft = cloneDeep(item); + } + + item.draft.request.auth = item.draft.request.auth || {}; + switch (item.draft.request.auth.mode) { + case 'bearer': + item.draft.request.auth.bearer = action.payload.content; + break; + } + } + } + }, addQueryParam: (state, action) => { const collection = findCollectionByUid(state.collections, action.payload.collectionUid); @@ -1188,6 +1208,7 @@ export const { collectionClicked, collectionFolderClicked, requestUrlChanged, + updateBearerToken, addQueryParam, updateQueryParam, deleteQueryParam, diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js index a1688890a..a2b63966b 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -355,6 +355,7 @@ export const transformRequestToSaveToFilesystem = (item) => { url: _item.request.url, params: [], headers: [], + auth: _item.request.auth, body: _item.request.body, script: _item.request.script, vars: _item.request.vars, diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js index f07331c55..5a8512910 100644 --- a/packages/bruno-electron/src/ipc/network/prepare-request.js +++ b/packages/bruno-electron/src/ipc/network/prepare-request.js @@ -18,6 +18,20 @@ const prepareRequest = (request) => { headers: headers }; + // Authentication + if (request.auth) { + if (request.auth.mode === 'basic') { + axiosRequest.auth = { + username: get(request, 'auth.basic.username'), + password: get(request, 'auth.basic.password') + }; + } + + if (request.auth.mode === 'bearer') { + axiosRequest.headers['authorization'] = `Bearer ${get(request, 'auth.bearer.token')}`; + } + } + if (request.body.mode === 'json') { if (!contentTypeDefined) { axiosRequest.headers['content-type'] = 'application/json'; diff --git a/packages/bruno-lang/v2/src/jsonToBru.js b/packages/bruno-lang/v2/src/jsonToBru.js index b9a5779fa..8ef44d7ad 100644 --- a/packages/bruno-lang/v2/src/jsonToBru.js +++ b/packages/bruno-lang/v2/src/jsonToBru.js @@ -34,6 +34,11 @@ const jsonToBru = (json) => { body: ${http.body}`; } + if (http.auth && http.auth.length) { + bru += ` + auth: ${http.auth}`; + } + bru += ` } diff --git a/packages/bruno-lang/v2/tests/fixtures/request.bru b/packages/bruno-lang/v2/tests/fixtures/request.bru index f340bb9a4..c4ae4b058 100644 --- a/packages/bruno-lang/v2/tests/fixtures/request.bru +++ b/packages/bruno-lang/v2/tests/fixtures/request.bru @@ -7,6 +7,7 @@ meta { get { url: https://api.textlocal.in/send body: json + auth: bearer } query { diff --git a/packages/bruno-lang/v2/tests/fixtures/request.json b/packages/bruno-lang/v2/tests/fixtures/request.json index 47995832d..7a00f5bb3 100644 --- a/packages/bruno-lang/v2/tests/fixtures/request.json +++ b/packages/bruno-lang/v2/tests/fixtures/request.json @@ -7,7 +7,8 @@ "http": { "method": "get", "url": "https://api.textlocal.in/send", - "body": "json" + "body": "json", + "auth": "bearer" }, "query": [ { diff --git a/packages/bruno-lang/v2/tests/index.spec.js b/packages/bruno-lang/v2/tests/index.spec.js index ecc970670..e753ee962 100644 --- a/packages/bruno-lang/v2/tests/index.spec.js +++ b/packages/bruno-lang/v2/tests/index.spec.js @@ -14,7 +14,7 @@ describe('bruToJson', () => { }); describe('jsonToBru', () => { - it('should parse the bru file', () => { + it('should parse the json file', () => { const input = require('./fixtures/request.json'); const expected = fs.readFileSync(path.join(__dirname, 'fixtures', 'request.bru'), 'utf8'); const output = jsonToBru(input); From 494b484cd949fc3cbee9beaf1492931baec52c71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Szymborski?= Date: Tue, 3 Oct 2023 21:24:59 +0200 Subject: [PATCH 49/55] fix(#312): tabbing order through tables (variables etc) was inconsistent or impossible resolves #312 --- .../components/RequestPane/Assertions/AssertionRow/index.js | 3 ++- .../src/components/RequestPane/FormUrlEncodedParams/index.js | 3 ++- .../src/components/RequestPane/MultipartFormParams/index.js | 3 ++- .../src/components/RequestPane/QueryParams/index.js | 3 ++- .../src/components/RequestPane/RequestHeaders/index.js | 3 ++- .../src/components/RequestPane/Vars/VarsTable/index.js | 3 ++- packages/bruno-app/src/components/SingleLineEditor/index.js | 5 ++++- 7 files changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/bruno-app/src/components/RequestPane/Assertions/AssertionRow/index.js b/packages/bruno-app/src/components/RequestPane/Assertions/AssertionRow/index.js index d4d6eeff8..61293b616 100644 --- a/packages/bruno-app/src/components/RequestPane/Assertions/AssertionRow/index.js +++ b/packages/bruno-app/src/components/RequestPane/Assertions/AssertionRow/index.js @@ -197,10 +197,11 @@ const AssertionRow = ({ handleAssertionChange(e, assertion, 'enabled')} /> -
diff --git a/packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js b/packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js index 4f28cc579..3cf5842e8 100644 --- a/packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js +++ b/packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js @@ -116,10 +116,11 @@ const FormUrlEncodedParams = ({ item, collection }) => { handleParamChange(e, param, 'enabled')} /> - diff --git a/packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js b/packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js index 9cbfd67fd..f8bd7ebbd 100644 --- a/packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js +++ b/packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js @@ -116,10 +116,11 @@ const MultipartFormParams = ({ item, collection }) => { handleParamChange(e, param, 'enabled')} /> - diff --git a/packages/bruno-app/src/components/RequestPane/QueryParams/index.js b/packages/bruno-app/src/components/RequestPane/QueryParams/index.js index 0cb92f8e1..54e3ee0b3 100644 --- a/packages/bruno-app/src/components/RequestPane/QueryParams/index.js +++ b/packages/bruno-app/src/components/RequestPane/QueryParams/index.js @@ -115,10 +115,11 @@ const QueryParams = ({ item, collection }) => { handleParamChange(e, param, 'enabled')} /> - diff --git a/packages/bruno-app/src/components/RequestPane/RequestHeaders/index.js b/packages/bruno-app/src/components/RequestPane/RequestHeaders/index.js index a36004c29..db8b20f5f 100644 --- a/packages/bruno-app/src/components/RequestPane/RequestHeaders/index.js +++ b/packages/bruno-app/src/components/RequestPane/RequestHeaders/index.js @@ -123,10 +123,11 @@ const RequestHeaders = ({ item, collection }) => { handleHeaderValueChange(e, header, 'enabled')} /> - diff --git a/packages/bruno-app/src/components/RequestPane/Vars/VarsTable/index.js b/packages/bruno-app/src/components/RequestPane/Vars/VarsTable/index.js index 7333acc62..6ee760978 100644 --- a/packages/bruno-app/src/components/RequestPane/Vars/VarsTable/index.js +++ b/packages/bruno-app/src/components/RequestPane/Vars/VarsTable/index.js @@ -128,10 +128,11 @@ const VarsTable = ({ item, collection, vars, varType }) => { handleVarChange(e, _var, 'enabled')} /> - diff --git a/packages/bruno-app/src/components/SingleLineEditor/index.js b/packages/bruno-app/src/components/SingleLineEditor/index.js index 4f1575bca..bee59b2d9 100644 --- a/packages/bruno-app/src/components/SingleLineEditor/index.js +++ b/packages/bruno-app/src/components/SingleLineEditor/index.js @@ -66,6 +66,7 @@ class SingleLineEditor extends Component { variables: getAllVariables(this.props.collection) }, scrollbarStyle: null, + tabindex: 0, extraKeys: { Enter: () => { if (this.props.onRun) { @@ -104,7 +105,9 @@ class SingleLineEditor extends Component { }, 'Cmd-F': () => {}, 'Ctrl-F': () => {}, - Tab: () => {} + // Tabbing disabled to make tabindex work + Tab: false, + 'Shift-Tab': false } }); if (this.props.autocomplete) { From 3f4f7fb24ca8dc78e31f16e743b9e39acd128296 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Wed, 4 Oct 2023 02:58:22 +0530 Subject: [PATCH 50/55] feat(#119): basic auth support completed --- .../Auth/BasicAuth/StyledWrapper.js | 10 +++ .../RequestPane/Auth/BasicAuth/index.js | 76 +++++++++++++++++++ .../RequestPane/Auth/BearerAuth/index.js | 13 ++-- .../src/components/RequestPane/Auth/index.js | 3 +- .../Sidebar/Collections/Collection/index.js | 4 +- .../ReduxStore/slices/collections/index.js | 11 ++- .../bruno-app/src/utils/collections/index.js | 22 +++++- 7 files changed, 125 insertions(+), 14 deletions(-) create mode 100644 packages/bruno-app/src/components/RequestPane/Auth/BasicAuth/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/RequestPane/Auth/BasicAuth/index.js diff --git a/packages/bruno-app/src/components/RequestPane/Auth/BasicAuth/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Auth/BasicAuth/StyledWrapper.js new file mode 100644 index 000000000..71d3bdf1d --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/BasicAuth/StyledWrapper.js @@ -0,0 +1,10 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + .single-line-editor-wrapper { + padding: 0.15rem 0.4rem; + border: ${(props) => props.theme.sidebar.search.border}; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/BasicAuth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/BasicAuth/index.js new file mode 100644 index 000000000..845dae273 --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/BasicAuth/index.js @@ -0,0 +1,76 @@ +import React from 'react'; +import get from 'lodash/get'; +import { useTheme } from 'providers/Theme'; +import { useDispatch } from 'react-redux'; +import SingleLineEditor from 'components/SingleLineEditor'; +import { updateAuth } from 'providers/ReduxStore/slices/collections'; +import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; +import StyledWrapper from './StyledWrapper'; + +const BasicAuth = ({ item, collection }) => { + const dispatch = useDispatch(); + const { storedTheme } = useTheme(); + + const basicAuth = item.draft ? get(item, 'draft.request.auth.basic', {}) : get(item, 'request.auth.basic', {}); + + const handleRun = () => dispatch(sendRequest(item, collection.uid)); + const handleSave = () => dispatch(saveRequest(item.uid, collection.uid)); + + const handleUsernameChange = (username) => { + dispatch( + updateAuth({ + mode: 'basic', + collectionUid: collection.uid, + itemUid: item.uid, + content: { + username: username, + password: basicAuth.password + } + }) + ); + }; + + const handlePasswordChange = (password) => { + dispatch( + updateAuth({ + mode: 'basic', + collectionUid: collection.uid, + itemUid: item.uid, + content: { + username: basicAuth.username, + password: password + } + }) + ); + }; + + return ( + + +
+ handleUsernameChange(val)} + onRun={handleRun} + collection={collection} + /> +
+ + +
+ handlePasswordChange(val)} + onRun={handleRun} + collection={collection} + /> +
+
+ ); +}; + +export default BasicAuth; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/index.js index 90347dc5a..d839d6206 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/index.js @@ -1,13 +1,13 @@ -import React, { useState } from 'react'; +import React from 'react'; import get from 'lodash/get'; import { useTheme } from 'providers/Theme'; import { useDispatch } from 'react-redux'; import SingleLineEditor from 'components/SingleLineEditor'; -import { updateBearerToken } from 'providers/ReduxStore/slices/collections'; +import { updateAuth } from 'providers/ReduxStore/slices/collections'; import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import StyledWrapper from './StyledWrapper'; -const BearerAuth = ({ onTokenChange, item, collection }) => { +const BearerAuth = ({ item, collection }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); @@ -20,7 +20,8 @@ const BearerAuth = ({ onTokenChange, item, collection }) => { const handleTokenChange = (token) => { dispatch( - updateBearerToken({ + updateAuth({ + mode: 'bearer', collectionUid: collection.uid, itemUid: item.uid, content: { @@ -32,9 +33,7 @@ const BearerAuth = ({ onTokenChange, item, collection }) => { return ( - +
{ @@ -10,7 +11,7 @@ const Auth = ({ item, collection }) => { const getAuthView = () => { switch (authMode) { case 'basic': { - return
Basic Auth
; + return ; } case 'bearer': { return ; diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js index b015df0aa..b150ade8c 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js @@ -16,7 +16,7 @@ import CollectionItem from './CollectionItem'; import RemoveCollection from './RemoveCollection'; import CollectionProperties from './CollectionProperties'; import { doesCollectionHaveItemsMatchingSearchText } from 'utils/collections/search'; -import { isItemAFolder, isItemARequest, transformCollectionToSaveToIdb } from 'utils/collections'; +import { isItemAFolder, isItemARequest, transformCollectionToSaveToExportAsFile } from 'utils/collections'; import exportCollection from 'utils/collections/export'; import RenameCollection from './RenameCollection'; @@ -69,7 +69,7 @@ const Collection = ({ collection, searchText }) => { const handleExportClick = () => { const collectionCopy = cloneDeep(collection); - exportCollection(transformCollectionToSaveToIdb(collectionCopy)); + exportCollection(transformCollectionToSaveToExportAsFile(collectionCopy)); }; const [{ isOver }, drop] = useDrop({ diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js index 84762aa54..2cb1bdea5 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -322,7 +322,7 @@ export const collectionsSlice = createSlice({ } } }, - updateBearerToken: (state, action) => { + updateAuth: (state, action) => { const collection = findCollectionByUid(state.collections, action.payload.collectionUid); if (collection) { @@ -334,10 +334,15 @@ export const collectionsSlice = createSlice({ } item.draft.request.auth = item.draft.request.auth || {}; - switch (item.draft.request.auth.mode) { + switch (action.payload.mode) { case 'bearer': + item.draft.request.auth.mode = 'bearer'; item.draft.request.auth.bearer = action.payload.content; break; + case 'basic': + item.draft.request.auth.mode = 'basic'; + item.draft.request.auth.basic = action.payload.content; + break; } } } @@ -1208,7 +1213,7 @@ export const { collectionClicked, collectionFolderClicked, requestUrlChanged, - updateBearerToken, + updateAuth, addQueryParam, updateQueryParam, deleteQueryParam, diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js index a2b63966b..bf0fe6879 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -207,7 +207,7 @@ export const getItemsToResequence = (parent, collection) => { return itemsToResequence; }; -export const transformCollectionToSaveToIdb = (collection, options = {}) => { +export const transformCollectionToSaveToExportAsFile = (collection, options = {}) => { const copyHeaders = (headers) => { return map(headers, (header) => { return { @@ -285,6 +285,16 @@ export const transformCollectionToSaveToIdb = (collection, options = {}) => { formUrlEncoded: copyFormUrlEncodedParams(si.draft.request.body.formUrlEncoded), multipartForm: copyMultipartFormParams(si.draft.request.body.multipartForm) }, + auth: { + mode: get(si.draft.request, 'auth.mode', 'none'), + basic: { + username: get(si.draft.request, 'auth.basic.username', ''), + password: get(si.draft.request, 'auth.basic.password', '') + }, + bearer: { + token: get(si.draft.request, 'auth.bearer.token', '') + } + }, script: si.draft.request.script, vars: si.draft.request.vars, assertions: si.draft.request.assertions, @@ -307,6 +317,16 @@ export const transformCollectionToSaveToIdb = (collection, options = {}) => { formUrlEncoded: copyFormUrlEncodedParams(si.request.body.formUrlEncoded), multipartForm: copyMultipartFormParams(si.request.body.multipartForm) }, + auth: { + mode: get(si.request, 'auth.mode', 'none'), + basic: { + username: get(si.request, 'auth.basic.username', ''), + password: get(si.request, 'auth.basic.password', '') + }, + bearer: { + token: get(si.request, 'auth.bearer.token', '') + } + }, script: si.request.script, vars: si.request.vars, assertions: si.request.assertions, From bdb7ff9947fa769c8dc2d5db3a28783d73015069 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Wed, 4 Oct 2023 03:07:50 +0530 Subject: [PATCH 51/55] chore: style updates --- .../components/RequestPane/Auth/BasicAuth/StyledWrapper.js | 7 ++++++- .../RequestPane/Auth/BearerAuth/StyledWrapper.js | 7 ++++++- packages/bruno-app/src/themes/dark.js | 6 ++++++ packages/bruno-app/src/themes/light.js | 6 ++++++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/bruno-app/src/components/RequestPane/Auth/BasicAuth/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Auth/BasicAuth/StyledWrapper.js index 71d3bdf1d..0b4514e15 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/BasicAuth/StyledWrapper.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/BasicAuth/StyledWrapper.js @@ -1,9 +1,14 @@ import styled from 'styled-components'; const Wrapper = styled.div` + label { + font-size: 0.8125rem; + } + .single-line-editor-wrapper { padding: 0.15rem 0.4rem; - border: ${(props) => props.theme.sidebar.search.border}; + border: ${(props) => props.theme.input.border}; + background-color: ${(props) => props.theme.input.bg}; } `; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/StyledWrapper.js index 71d3bdf1d..0b4514e15 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/StyledWrapper.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/StyledWrapper.js @@ -1,9 +1,14 @@ import styled from 'styled-components'; const Wrapper = styled.div` + label { + font-size: 0.8125rem; + } + .single-line-editor-wrapper { padding: 0.15rem 0.4rem; - border: ${(props) => props.theme.sidebar.search.border}; + border: ${(props) => props.theme.input.border}; + background-color: ${(props) => props.theme.input.bg}; } `; diff --git a/packages/bruno-app/src/themes/dark.js b/packages/bruno-app/src/themes/dark.js index 122d16252..e6bb60815 100644 --- a/packages/bruno-app/src/themes/dark.js +++ b/packages/bruno-app/src/themes/dark.js @@ -16,6 +16,12 @@ const darkTheme = { } }, + input: { + bg: 'rgb(65, 65, 65)', + border: 'rgb(65, 65, 65)', + focusBorder: 'rgb(65, 65, 65)' + }, + variables: { bg: 'rgb(48, 48, 49)', diff --git a/packages/bruno-app/src/themes/light.js b/packages/bruno-app/src/themes/light.js index 846940cdb..d2c42a7e3 100644 --- a/packages/bruno-app/src/themes/light.js +++ b/packages/bruno-app/src/themes/light.js @@ -16,6 +16,12 @@ const lightTheme = { } }, + input: { + bg: 'white', + border: '#ccc', + focusBorder: '#8b8b8b' + }, + menubar: { bg: 'rgb(44, 44, 44)' }, From 9b2e2ed3d8c7784b22602c5f325cf0f385c572d1 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Wed, 4 Oct 2023 04:11:23 +0530 Subject: [PATCH 52/55] chore: style updates --- .../RequestPane/Auth/AuthMode/StyledWrapper.js | 4 ++++ .../RequestPane/Auth/AuthMode/index.js | 2 +- .../RequestPane/Auth/BasicAuth/StyledWrapper.js | 3 ++- .../Auth/BearerAuth/StyledWrapper.js | 3 ++- .../ResponsePane/QueryResult/StyledWrapper.js | 6 ++++++ .../ResponsePane/QueryResult/index.js | 17 ++++------------- packages/bruno-app/src/themes/dark.js | 3 ++- packages/bruno-app/src/themes/light.js | 3 ++- 8 files changed, 23 insertions(+), 18 deletions(-) diff --git a/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/StyledWrapper.js index e1d0530c6..cdbdf8424 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/StyledWrapper.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/StyledWrapper.js @@ -6,6 +6,10 @@ const Wrapper = styled.div` .auth-mode-selector { background: transparent; + .auth-mode-label { + color: ${(props) => props.theme.colors.text.yellow}; + } + .dropdown-item { padding: 0.2rem 0.6rem !important; } diff --git a/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js b/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js index f1d2e443f..43c90e2ce 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js @@ -15,7 +15,7 @@ const AuthMode = ({ item, collection }) => { const Icon = forwardRef((props, ref) => { return ( -
+
{humanizeRequestAuthMode(authMode)}
); diff --git a/packages/bruno-app/src/components/RequestPane/Auth/BasicAuth/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Auth/BasicAuth/StyledWrapper.js index 0b4514e15..c2bb5d207 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/BasicAuth/StyledWrapper.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/BasicAuth/StyledWrapper.js @@ -7,7 +7,8 @@ const Wrapper = styled.div` .single-line-editor-wrapper { padding: 0.15rem 0.4rem; - border: ${(props) => props.theme.input.border}; + border-radius: 3px; + border: solid 1px ${(props) => props.theme.input.border}; background-color: ${(props) => props.theme.input.bg}; } `; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/StyledWrapper.js index 0b4514e15..c2bb5d207 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/StyledWrapper.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/StyledWrapper.js @@ -7,7 +7,8 @@ const Wrapper = styled.div` .single-line-editor-wrapper { padding: 0.15rem 0.4rem; - border: ${(props) => props.theme.input.border}; + border-radius: 3px; + border: solid 1px ${(props) => props.theme.input.border}; background-color: ${(props) => props.theme.input.bg}; } `; diff --git a/packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js b/packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js index df65244d6..5429ef440 100644 --- a/packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js +++ b/packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js @@ -17,6 +17,12 @@ const StyledWrapper = styled.div` height: 100%; width: 100%; } + + div[role='tablist'] { + .active { + color: ${(props) => props.theme.colors.text.yellow}; + } + } `; export default StyledWrapper; diff --git a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js index faa0e1e14..96624a02b 100644 --- a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js +++ b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js @@ -13,7 +13,7 @@ import { useMemo } from 'react'; const QueryResult = ({ item, collection, data, width, disableRunEventListener, headers }) => { const { storedTheme } = useTheme(); - const [tab, setTab] = useState('raw'); + const [tab, setTab] = useState('preview'); const dispatch = useDispatch(); const contentType = getContentType(headers); const mode = getCodeMirrorModeBasedOnContentType(contentType); @@ -64,8 +64,8 @@ const QueryResult = ({ item, collection, data, width, disableRunEventListener, h const getTabClassname = (tabName) => { return classnames(`select-none ${tabName}`, { - 'text-yellow-500': tabName === tab, - 'cursor-pointer': tabName !== tab, + active: tabName === tab, + 'cursor-pointer': tabName !== tab }); }; @@ -99,16 +99,7 @@ const QueryResult = ({ item, collection, data, width, disableRunEventListener, h ); } - return ( - - ); + return ; }, [tab, collection, storedTheme, onRun, value, mode]); return ( diff --git a/packages/bruno-app/src/themes/dark.js b/packages/bruno-app/src/themes/dark.js index e6bb60815..7a4ad64de 100644 --- a/packages/bruno-app/src/themes/dark.js +++ b/packages/bruno-app/src/themes/dark.js @@ -9,7 +9,8 @@ const darkTheme = { green: 'rgb(11 178 126)', danger: '#f06f57', muted: '#9d9d9d', - purple: '#cd56d6' + purple: '#cd56d6', + yellow: '#f59e0b' }, bg: { danger: '#d03544' diff --git a/packages/bruno-app/src/themes/light.js b/packages/bruno-app/src/themes/light.js index d2c42a7e3..b014c2f38 100644 --- a/packages/bruno-app/src/themes/light.js +++ b/packages/bruno-app/src/themes/light.js @@ -9,7 +9,8 @@ const lightTheme = { green: '#047857', danger: 'rgb(185, 28, 28)', muted: '#4b5563', - purple: '#8e44ad' + purple: '#8e44ad', + yellow: '#d97706' }, bg: { danger: '#dc3545' From 5e2640fe7392656ca0c1808090739d8926cc7dd1 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Wed, 4 Oct 2023 05:48:05 +0530 Subject: [PATCH 53/55] fix: fixed query result issue --- .../src/components/ResponsePane/QueryResult/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js index 96624a02b..5729e0b2d 100644 --- a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js +++ b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js @@ -87,8 +87,8 @@ const QueryResult = ({ item, collection, data, width, disableRunEventListener, h }; const activeResult = useMemo(() => { - if (tab === 'preview') { - // Add the Base tag to the head so content loads proparly. This also needs the correct CSP settings + if (tab === 'preview' && mode.includes('html')) { + // Add the Base tag to the head so content loads properly. This also needs the correct CSP settings const webViewSrc = data.replace('', ``); return ( Date: Wed, 4 Oct 2023 05:52:59 +0530 Subject: [PATCH 54/55] revert pr #252 --- .../src/runner/run-single-request.js | 53 ++++----- .../bruno-electron/src/ipc/network/index.js | 104 +++++++++--------- 2 files changed, 81 insertions(+), 76 deletions(-) diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js index 7041a1396..1f9b6ba41 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -4,13 +4,12 @@ const fs = require('fs'); const { forOwn, each, extend, get } = require('lodash'); const FormData = require('form-data'); const axios = require('axios'); +const https = require('https'); const prepareRequest = require('./prepare-request'); const interpolateVars = require('./interpolate-vars'); const { ScriptRuntime, TestRuntime, VarsRuntime, AssertRuntime } = require('@usebruno/js'); const { stripExtension } = require('../utils/filesystem'); const { getOptions } = require('../utils/bru'); -const { HttpsProxyAgent } = require('https-proxy-agent'); -const https = require('https'); const runSingleRequest = async function ( filename, @@ -66,6 +65,31 @@ const runSingleRequest = async function ( ); } + // set proxy if enabled + const proxyEnabled = get(brunoConfig, 'proxy.enabled', false); + if (proxyEnabled) { + const proxyProtocol = get(brunoConfig, 'proxy.protocol'); + const proxyHostname = get(brunoConfig, 'proxy.hostname'); + const proxyPort = get(brunoConfig, 'proxy.port'); + const proxyAuthEnabled = get(brunoConfig, 'proxy.auth.enabled', false); + + const proxyConfig = { + protocol: proxyProtocol, + hostname: proxyHostname, + port: proxyPort + }; + if (proxyAuthEnabled) { + const proxyAuthUsername = get(brunoConfig, 'proxy.auth.username'); + const proxyAuthPassword = get(brunoConfig, 'proxy.auth.password'); + proxyConfig.auth = { + username: proxyAuthUsername, + password: proxyAuthPassword + }; + } + + request.proxy = proxyConfig; + } + // interpolate variables inside request interpolateVars(request, envVariables, collectionVariables, processEnvVars); @@ -87,30 +111,7 @@ const runSingleRequest = async function ( } } - // set proxy if enabled - const proxyEnabled = get(brunoConfig, 'proxy.enabled', false); - if (proxyEnabled) { - const proxyProtocol = get(brunoConfig, 'proxy.protocol'); - const proxyHostname = get(brunoConfig, 'proxy.hostname'); - const proxyPort = get(brunoConfig, 'proxy.port'); - const proxyAuthEnabled = get(brunoConfig, 'proxy.auth.enabled', false); - - let proxy; - - if (proxyAuthEnabled) { - const proxyAuthUsername = get(brunoConfig, 'proxy.auth.username'); - const proxyAuthPassword = get(brunoConfig, 'proxy.auth.password'); - - proxy = `${proxyProtocol}://${proxyAuthUsername}:${proxyAuthPassword}@${proxyHostname}:${proxyPort}`; - } else { - proxy = `${proxyProtocol}://${proxyHostname}:${proxyPort}`; - } - - request.httpsAgent = new HttpsProxyAgent( - proxy, - Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined - ); - } else if (Object.keys(httpsAgentRequestFields).length > 0) { + if (Object.keys(httpsAgentRequestFields).length > 0) { request.httpsAgent = new https.Agent({ ...httpsAgentRequestFields }); diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index db286089b..6c1bec01a 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -15,7 +15,6 @@ const { sortFolder, getAllRequestsInFolderRecursively } = require('./helper'); const { getPreferences } = require('../../store/preferences'); const { getProcessEnvVars } = require('../../store/process-env'); const { getBrunoConfig } = require('../../store/bruno-config'); -const { HttpsProxyAgent } = require('https-proxy-agent'); // override the default escape function to prevent escaping Mustache.escape = function (value) { @@ -166,6 +165,32 @@ const registerNetworkIpc = (mainWindow) => { }); } + // proxy configuration + const brunoConfig = getBrunoConfig(collectionUid); + const proxyEnabled = get(brunoConfig, 'proxy.enabled', false); + if (proxyEnabled) { + const proxyProtocol = get(brunoConfig, 'proxy.protocol'); + const proxyHostname = get(brunoConfig, 'proxy.hostname'); + const proxyPort = get(brunoConfig, 'proxy.port'); + const proxyAuthEnabled = get(brunoConfig, 'proxy.auth.enabled', false); + + const proxyConfig = { + protocol: proxyProtocol, + hostname: proxyHostname, + port: proxyPort + }; + if (proxyAuthEnabled) { + const proxyAuthUsername = get(brunoConfig, 'proxy.auth.username'); + const proxyAuthPassword = get(brunoConfig, 'proxy.auth.password'); + proxyConfig.auth = { + username: proxyAuthUsername, + password: proxyAuthPassword + }; + } + + request.proxy = proxyConfig; + } + interpolateVars(request, envVars, collectionVariables, processEnvVars); // stringify the request url encoded params @@ -209,31 +234,7 @@ const registerNetworkIpc = (mainWindow) => { } } - // proxy configuration - const brunoConfig = getBrunoConfig(collectionUid); - const proxyEnabled = get(brunoConfig, 'proxy.enabled', false); - if (proxyEnabled) { - const proxyProtocol = get(brunoConfig, 'proxy.protocol'); - const proxyHostname = get(brunoConfig, 'proxy.hostname'); - const proxyPort = get(brunoConfig, 'proxy.port'); - const proxyAuthEnabled = get(brunoConfig, 'proxy.auth.enabled', false); - - let proxy; - - if (proxyAuthEnabled) { - const proxyAuthUsername = get(brunoConfig, 'proxy.auth.username'); - const proxyAuthPassword = get(brunoConfig, 'proxy.auth.password'); - - proxy = `${proxyProtocol}://${proxyAuthUsername}:${proxyAuthPassword}@${proxyHostname}:${proxyPort}`; - } else { - proxy = `${proxyProtocol}://${proxyHostname}:${proxyPort}`; - } - - request.httpsAgent = new HttpsProxyAgent( - proxy, - Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined - ); - } else if (Object.keys(httpsAgentRequestFields).length > 0) { + if (Object.keys(httpsAgentRequestFields).length > 0) { request.httpsAgent = new https.Agent({ ...httpsAgentRequestFields }); @@ -597,6 +598,32 @@ const registerNetworkIpc = (mainWindow) => { }); } + // proxy configuration + const brunoConfig = getBrunoConfig(collectionUid); + const proxyEnabled = get(brunoConfig, 'proxy.enabled', false); + if (proxyEnabled) { + const proxyProtocol = get(brunoConfig, 'proxy.protocol'); + const proxyHostname = get(brunoConfig, 'proxy.hostname'); + const proxyPort = get(brunoConfig, 'proxy.port'); + const proxyAuthEnabled = get(brunoConfig, 'proxy.auth.enabled', false); + + const proxyConfig = { + protocol: proxyProtocol, + hostname: proxyHostname, + port: proxyPort + }; + if (proxyAuthEnabled) { + const proxyAuthUsername = get(brunoConfig, 'proxy.auth.username'); + const proxyAuthPassword = get(brunoConfig, 'proxy.auth.password'); + proxyConfig.auth = { + username: proxyAuthUsername, + password: proxyAuthPassword + }; + } + + request.proxy = proxyConfig; + } + // interpolate variables inside request interpolateVars(request, envVars, collectionVariables, processEnvVars); @@ -617,30 +644,7 @@ const registerNetworkIpc = (mainWindow) => { const preferences = getPreferences(); const sslVerification = get(preferences, 'request.sslVerification', true); - // proxy configuration - const brunoConfig = getBrunoConfig(collectionUid); - const proxyEnabled = get(brunoConfig, 'proxy.enabled', false); - if (proxyEnabled) { - const proxyProtocol = get(brunoConfig, 'proxy.protocol'); - const proxyHostname = get(brunoConfig, 'proxy.hostname'); - const proxyPort = get(brunoConfig, 'proxy.port'); - const proxyAuthEnabled = get(brunoConfig, 'proxy.auth.enabled', false); - - let proxy; - - if (proxyAuthEnabled) { - const proxyAuthUsername = get(brunoConfig, 'proxy.auth.username'); - const proxyAuthPassword = get(brunoConfig, 'proxy.auth.password'); - - proxy = `${proxyProtocol}://${proxyAuthUsername}:${proxyAuthPassword}@${proxyHostname}:${proxyPort}`; - } else { - proxy = `${proxyProtocol}://${proxyHostname}:${proxyPort}`; - } - - request.httpsAgent = new HttpsProxyAgent(proxy, { - rejectUnauthorized: sslVerification - }); - } else if (!sslVerification) { + if (!sslVerification) { request.httpsAgent = new https.Agent({ rejectUnauthorized: false }); From bf2d1220a12079c757bcdcd93fcf8bec6fb5b910 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Wed, 4 Oct 2023 05:53:59 +0530 Subject: [PATCH 55/55] chore: version bumped to v0.18.0 --- packages/bruno-app/src/components/Sidebar/index.js | 2 +- packages/bruno-electron/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bruno-app/src/components/Sidebar/index.js b/packages/bruno-app/src/components/Sidebar/index.js index 297258389..8b8c7dbb0 100644 --- a/packages/bruno-app/src/components/Sidebar/index.js +++ b/packages/bruno-app/src/components/Sidebar/index.js @@ -116,7 +116,7 @@ const Sidebar = () => { )}
-
v0.17.0
+
v0.18.0
diff --git a/packages/bruno-electron/package.json b/packages/bruno-electron/package.json index 0f0674144..af0982c90 100644 --- a/packages/bruno-electron/package.json +++ b/packages/bruno-electron/package.json @@ -1,5 +1,5 @@ { - "version": "v0.17.0", + "version": "v0.18.0", "name": "bruno", "description": "Opensource API Client for Exploring and Testing APIs", "homepage": "https://www.usebruno.com",