From 289625c851fb3002351e50050687c0904ad1d8ba Mon Sep 17 00:00:00 2001 From: Qweme Dev <99718350+qweme32@users.noreply.github.com> Date: Fri, 6 Oct 2023 17:53:05 +0300 Subject: [PATCH 01/65] Added a setting that allows you to change the font of the code --- .../components/CodeEditor/StyledWrapper.js | 1 + .../src/components/CodeEditor/index.js | 1 + .../Preferences/Font/StyledWrapper.js | 7 ++++ .../src/components/Preferences/Font/index.js | 33 +++++++++++++++++++ .../src/components/Preferences/index.js | 8 +++++ .../RequestPane/GraphQLVariables/index.js | 3 ++ .../RequestPane/RequestBody/index.js | 3 ++ .../components/RequestPane/Script/index.js | 4 +++ .../src/components/RequestPane/Tests/index.js | 3 ++ .../ResponsePane/QueryResult/index.js | 4 ++- .../GenerateCodeItem/CodeView/index.js | 4 ++- 11 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 packages/bruno-app/src/components/Preferences/Font/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/Preferences/Font/index.js diff --git a/packages/bruno-app/src/components/CodeEditor/StyledWrapper.js b/packages/bruno-app/src/components/CodeEditor/StyledWrapper.js index 09de00ddc2..9853280a16 100644 --- a/packages/bruno-app/src/components/CodeEditor/StyledWrapper.js +++ b/packages/bruno-app/src/components/CodeEditor/StyledWrapper.js @@ -4,6 +4,7 @@ const StyledWrapper = styled.div` div.CodeMirror { background: ${(props) => props.theme.codemirror.bg}; border: solid 1px ${(props) => props.theme.codemirror.border}; + font-family: ${(props) => props.font ? props.font : "default"}; } .CodeMirror-overlayscroll-horizontal div, diff --git a/packages/bruno-app/src/components/CodeEditor/index.js b/packages/bruno-app/src/components/CodeEditor/index.js index 96d5bb48a8..6ad999e6ba 100644 --- a/packages/bruno-app/src/components/CodeEditor/index.js +++ b/packages/bruno-app/src/components/CodeEditor/index.js @@ -121,6 +121,7 @@ export default class CodeEditor extends React.Component { { this._node = node; }} diff --git a/packages/bruno-app/src/components/Preferences/Font/StyledWrapper.js b/packages/bruno-app/src/components/Preferences/Font/StyledWrapper.js new file mode 100644 index 0000000000..d45eda5b68 --- /dev/null +++ b/packages/bruno-app/src/components/Preferences/Font/StyledWrapper.js @@ -0,0 +1,7 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + color: ${(props) => props.theme.text}; +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/Preferences/Font/index.js b/packages/bruno-app/src/components/Preferences/Font/index.js new file mode 100644 index 0000000000..4694aaf698 --- /dev/null +++ b/packages/bruno-app/src/components/Preferences/Font/index.js @@ -0,0 +1,33 @@ +import React, { useState } from 'react'; +import { usePreferences } from 'providers/Preferences'; +import StyledWrapper from './StyledWrapper'; + +const Font = () => { + const { preferences, setPreferences } = usePreferences(); + + const [codeFont, setCodeFont] = useState(preferences.codeFont); + + const handleInputChange = (event) => { + const updatedPreferences = { + ...preferences, + codeFont: event.target.value + }; + + setPreferences(updatedPreferences) + .then(() => { + setCodeFont(event.target.value); + }) + .catch((err) => { + console.error(err); + }); + }; + + return ( + +

Font in code area

+ +
+ ); +}; + +export default Font; diff --git a/packages/bruno-app/src/components/Preferences/index.js b/packages/bruno-app/src/components/Preferences/index.js index 455a6748fb..a48f5e66e8 100644 --- a/packages/bruno-app/src/components/Preferences/index.js +++ b/packages/bruno-app/src/components/Preferences/index.js @@ -3,6 +3,7 @@ import classnames from 'classnames'; import React, { useState } from 'react'; import Support from './Support'; import General from './General'; +import Font from './Font'; import Theme from './Theme'; import StyledWrapper from './StyledWrapper'; @@ -28,6 +29,10 @@ const Preferences = ({ onClose }) => { case 'support': { return ; } + + case 'font': { + return ; + } } }; @@ -41,6 +46,9 @@ const Preferences = ({ onClose }) => {
setTab('theme')}> Theme
+
setTab('font')}> + Font +
setTab('support')}> Support
diff --git a/packages/bruno-app/src/components/RequestPane/GraphQLVariables/index.js b/packages/bruno-app/src/components/RequestPane/GraphQLVariables/index.js index 59b1320447..0e213442bd 100644 --- a/packages/bruno-app/src/components/RequestPane/GraphQLVariables/index.js +++ b/packages/bruno-app/src/components/RequestPane/GraphQLVariables/index.js @@ -4,12 +4,14 @@ import CodeEditor from 'components/CodeEditor'; import { updateRequestGraphqlVariables } from 'providers/ReduxStore/slices/collections'; import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import { useTheme } from 'providers/Theme'; +import { usePreferences } from 'providers/Preferences'; import StyledWrapper from './StyledWrapper'; const GraphQLVariables = ({ variables, item, collection }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); + const { preferences } = usePreferences(); const onEdit = (value) => { dispatch( @@ -30,6 +32,7 @@ const GraphQLVariables = ({ variables, item, collection }) => { collection={collection} value={variables || ''} theme={storedTheme} + font={preferences.codeFont} onEdit={onEdit} mode="javascript" onRun={onRun} diff --git a/packages/bruno-app/src/components/RequestPane/RequestBody/index.js b/packages/bruno-app/src/components/RequestPane/RequestBody/index.js index 7ac178f795..fa004d1dcd 100644 --- a/packages/bruno-app/src/components/RequestPane/RequestBody/index.js +++ b/packages/bruno-app/src/components/RequestPane/RequestBody/index.js @@ -5,6 +5,7 @@ import FormUrlEncodedParams from 'components/RequestPane/FormUrlEncodedParams'; import MultipartFormParams from 'components/RequestPane/MultipartFormParams'; import { useDispatch } from 'react-redux'; import { useTheme } from 'providers/Theme'; +import { usePreferences } from 'providers/Preferences'; import { updateRequestBody } from 'providers/ReduxStore/slices/collections'; import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import StyledWrapper from './StyledWrapper'; @@ -14,6 +15,7 @@ const RequestBody = ({ item, collection }) => { const body = item.draft ? get(item, 'draft.request.body') : get(item, 'request.body'); const bodyMode = item.draft ? get(item, 'draft.request.body.mode') : get(item, 'request.body.mode'); const { storedTheme } = useTheme(); + const { preferences } = usePreferences(); const onEdit = (value) => { dispatch( @@ -46,6 +48,7 @@ const RequestBody = ({ item, collection }) => { { @@ -13,6 +14,7 @@ const Script = ({ item, collection }) => { const responseScript = item.draft ? get(item, 'draft.request.script.res') : get(item, 'request.script.res'); const { storedTheme } = useTheme(); + const { preferences } = usePreferences(); const onRequestScriptEdit = (value) => { dispatch( @@ -45,6 +47,7 @@ const Script = ({ item, collection }) => { collection={collection} value={requestScript || ''} theme={storedTheme} + font={preferences.codeFont} onEdit={onRequestScriptEdit} mode="javascript" onRun={onRun} @@ -57,6 +60,7 @@ const Script = ({ item, collection }) => { collection={collection} value={responseScript || ''} theme={storedTheme} + font={preferences.codeFont} onEdit={onResponseScriptEdit} mode="javascript" onRun={onRun} diff --git a/packages/bruno-app/src/components/RequestPane/Tests/index.js b/packages/bruno-app/src/components/RequestPane/Tests/index.js index 351afd3d32..dce6547f68 100644 --- a/packages/bruno-app/src/components/RequestPane/Tests/index.js +++ b/packages/bruno-app/src/components/RequestPane/Tests/index.js @@ -5,6 +5,7 @@ import CodeEditor from 'components/CodeEditor'; import { updateRequestTests } from 'providers/ReduxStore/slices/collections'; import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import { useTheme } from 'providers/Theme'; +import { usePreferences } from 'providers/Preferences'; import StyledWrapper from './StyledWrapper'; const Tests = ({ item, collection }) => { @@ -12,6 +13,7 @@ const Tests = ({ item, collection }) => { const tests = item.draft ? get(item, 'draft.request.tests') : get(item, 'request.tests'); const { storedTheme } = useTheme(); + const { preferences } = usePreferences(); const onEdit = (value) => { dispatch( @@ -32,6 +34,7 @@ const Tests = ({ item, collection }) => { collection={collection} value={tests || ''} theme={storedTheme} + font={preferences.codeFont} onEdit={onEdit} mode="javascript" onRun={onRun} diff --git a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js index 5729e0b2dd..28a1a0af52 100644 --- a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js +++ b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js @@ -1,6 +1,7 @@ import React from 'react'; import CodeEditor from 'components/CodeEditor'; import { useTheme } from 'providers/Theme'; +import { usePreferences } from 'providers/Preferences'; import { useDispatch } from 'react-redux'; import { sendRequest } from 'providers/ReduxStore/slices/collections/actions'; import classnames from 'classnames'; @@ -13,6 +14,7 @@ import { useMemo } from 'react'; const QueryResult = ({ item, collection, data, width, disableRunEventListener, headers }) => { const { storedTheme } = useTheme(); + const { preferences } = usePreferences(); const [tab, setTab] = useState('preview'); const dispatch = useDispatch(); const contentType = getContentType(headers); @@ -99,7 +101,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/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/index.js index 79d636dafb..f3223bab4d 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,10 +1,12 @@ import CodeEditor from 'components/CodeEditor/index'; import { HTTPSnippet } from 'httpsnippet'; import { useTheme } from 'providers/Theme/index'; +import { usePreferences } from 'providers/Preferences/index'; import { buildHarRequest } from 'utils/codegenerator/har'; const CodeView = ({ language, item }) => { const { storedTheme } = useTheme(); + const { preferences } = usePreferences(); const { target, client, language: lang } = language; let snippet = ''; @@ -15,7 +17,7 @@ const CodeView = ({ language, item }) => { snippet = 'Error generating code snippet'; } - return ; + return ; }; export default CodeView; From 094bf7bc60122c94ff16caf51a6df58125a7a3af Mon Sep 17 00:00:00 2001 From: Jack Scotson Date: Sun, 8 Oct 2023 10:47:34 +0100 Subject: [PATCH 02/65] fix(#251): Uses os.release to get correct pathsep for windows --- .../src/providers/ReduxStore/slices/collections/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 495989eb16..72c82f8ce0 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -1,4 +1,3 @@ -import path from 'path'; import { uuid } from 'utils/common'; import find from 'lodash/find'; import map from 'lodash/map'; @@ -24,8 +23,9 @@ import { } from 'utils/collections'; import { parseQueryParams, stringifyQueryParams } from 'utils/url'; import { getSubdirectoriesFromRoot, getDirectoryName } from 'utils/common/platform'; +import os from 'os'; -const PATH_SEPARATOR = path.sep; +const PATH_SEPARATOR = /Windows/i.test(os.release()) ? '\\' : '/'; const initialState = { collections: [] From 1a366893ec6cee0f0cecba0eaf67294a5e6191e1 Mon Sep 17 00:00:00 2001 From: Qweme Dev <99718350+qweme32@users.noreply.github.com> Date: Sun, 8 Oct 2023 21:53:29 +0300 Subject: [PATCH 03/65] Fix styles for theme --- .../Preferences/Font/StyledWrapper.js | 20 +++++++++++++++++++ .../src/components/Preferences/Font/index.js | 4 +++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/bruno-app/src/components/Preferences/Font/StyledWrapper.js b/packages/bruno-app/src/components/Preferences/Font/StyledWrapper.js index d45eda5b68..5bd4e46932 100644 --- a/packages/bruno-app/src/components/Preferences/Font/StyledWrapper.js +++ b/packages/bruno-app/src/components/Preferences/Font/StyledWrapper.js @@ -2,6 +2,26 @@ import styled from 'styled-components'; const StyledWrapper = styled.div` color: ${(props) => props.theme.text}; + div.input-container { + background-color: ${(props) => props.theme.modal.input.bg}; + height: 2.3rem; + } + div.input-container { + border: solid 1px ${(props) => props.theme.modal.input.border}; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + + input { + background-color: ${(props) => props.theme.modal.input.bg}; + outline: none; + box-shadow: none; + + &:focus { + outline: none !important; + box-shadow: none !important; + } + } + } `; export default StyledWrapper; diff --git a/packages/bruno-app/src/components/Preferences/Font/index.js b/packages/bruno-app/src/components/Preferences/Font/index.js index 4694aaf698..4505b5958a 100644 --- a/packages/bruno-app/src/components/Preferences/Font/index.js +++ b/packages/bruno-app/src/components/Preferences/Font/index.js @@ -25,7 +25,9 @@ const Font = () => { return (

Font in code area

- +
+ +
); }; From b854e66a24107561e59c9db1d85c7d01f2404d4f Mon Sep 17 00:00:00 2001 From: Mirko Golze Date: Sun, 8 Oct 2023 23:12:03 +0200 Subject: [PATCH 04/65] #224 refactor preferences store, add global proxy settings --- package-lock.json | 82 +++--- .../CollectionSettings/ProxySettings/index.js | 90 ++++++- .../components/CollectionSettings/index.js | 2 +- .../components/Preferences/General/index.js | 12 +- .../ProxySettings/StyledWrapper.js | 25 ++ .../Preferences/ProxySettings/index.js | 243 ++++++++++++++++++ .../src/components/Preferences/index.js | 8 + .../ResponsePane/Placeholder/index.js | 12 +- .../providers/App/useCollectionTreeSync.js | 5 +- .../src/providers/Preferences/index.js | 80 ++++-- packages/bruno-electron/src/index.js | 5 +- .../bruno-electron/src/ipc/application.js | 72 ++++++ packages/bruno-electron/src/ipc/collection.js | 17 +- .../bruno-electron/src/ipc/network/index.js | 25 +- packages/bruno-electron/src/store/index.js | 7 + .../bruno-electron/src/store/preferences.js | 105 +++++++- 16 files changed, 675 insertions(+), 115 deletions(-) create mode 100644 packages/bruno-app/src/components/Preferences/ProxySettings/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/Preferences/ProxySettings/index.js create mode 100644 packages/bruno-electron/src/ipc/application.js create mode 100644 packages/bruno-electron/src/store/index.js diff --git a/package-lock.json b/package-lock.json index 332917b376..428b2b9856 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13507,8 +13507,7 @@ "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/react-redux": { "version": "7.2.9", @@ -16647,7 +16646,7 @@ }, "packages/bruno-cli": { "name": "@usebruno/cli", - "version": "0.12.0", + "version": "0.13.0", "license": "MIT", "dependencies": { "@usebruno/js": "0.8.0", @@ -16730,7 +16729,7 @@ }, "packages/bruno-electron": { "name": "bruno", - "version": "v0.20.0", + "version": "v0.21.1", "dependencies": { "@usebruno/js": "0.8.0", "@usebruno/lang": "0.5.0", @@ -19496,7 +19495,8 @@ "@tabler/icons": { "version": "1.119.0", "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-1.119.0.tgz", - "integrity": "sha512-Fk3Qq4w2SXcTjc/n1cuL5bccPkylrOMo7cYpQIf/yw6zP76LQV9dtLcHQUjFiUnaYuswR645CnURIhlafyAh9g==" + "integrity": "sha512-Fk3Qq4w2SXcTjc/n1cuL5bccPkylrOMo7cYpQIf/yw6zP76LQV9dtLcHQUjFiUnaYuswR645CnURIhlafyAh9g==", + "requires": {} }, "@tauri-apps/cli": { "version": "1.2.2", @@ -20117,7 +20117,8 @@ } }, "@usebruno/schema": { - "version": "file:packages/bruno-schema" + "version": "file:packages/bruno-schema", + "requires": {} }, "@usebruno/testbench": { "version": "file:packages/bruno-testbench", @@ -20293,7 +20294,8 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", - "dev": true + "dev": true, + "requires": {} }, "@webpack-cli/info": { "version": "1.5.0", @@ -20308,7 +20310,8 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", - "dev": true + "dev": true, + "requires": {} }, "@xtuc/ieee754": { "version": "1.2.0", @@ -20413,7 +20416,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "amdefine": { "version": "0.0.8", @@ -22011,7 +22015,8 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz", "integrity": "sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==", - "dev": true + "dev": true, + "requires": {} }, "css-loader": { "version": "6.7.3", @@ -22156,7 +22161,8 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", - "dev": true + "dev": true, + "requires": {} }, "csso": { "version": "4.2.0", @@ -23617,7 +23623,8 @@ "goober": { "version": "2.1.11", "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.11.tgz", - "integrity": "sha512-5SS2lmxbhqH0u9ABEWq7WPU69a4i2pYcHeCxqaNq6Cw3mnrF0ghWNM4tEGid4dKy8XNIAUbuThuozDHHKJVh3A==" + "integrity": "sha512-5SS2lmxbhqH0u9ABEWq7WPU69a4i2pYcHeCxqaNq6Cw3mnrF0ghWNM4tEGid4dKy8XNIAUbuThuozDHHKJVh3A==", + "requires": {} }, "got": { "version": "9.6.0", @@ -24090,7 +24097,8 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true + "dev": true, + "requires": {} }, "idb": { "version": "7.1.1", @@ -24869,7 +24877,8 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true + "dev": true, + "requires": {} }, "jest-regex-util": { "version": "29.2.0", @@ -25631,7 +25640,8 @@ "meros": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/meros/-/meros-1.2.1.tgz", - "integrity": "sha512-R2f/jxYqCAGI19KhAvaxSOxALBMkaXWH2a7rOyqQw+ZmizX5bKkEYWLzdhC+U82ZVVPVp6MCXe3EkVligh+12g==" + "integrity": "sha512-R2f/jxYqCAGI19KhAvaxSOxALBMkaXWH2a7rOyqQw+ZmizX5bKkEYWLzdhC+U82ZVVPVp6MCXe3EkVligh+12g==", + "requires": {} }, "methods": { "version": "1.1.2", @@ -26661,25 +26671,29 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", - "dev": true + "dev": true, + "requires": {} }, "postcss-discard-duplicates": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", - "dev": true + "dev": true, + "requires": {} }, "postcss-discard-empty": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", - "dev": true + "dev": true, + "requires": {} }, "postcss-discard-overridden": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", - "dev": true + "dev": true, + "requires": {} }, "postcss-js": { "version": "3.0.3", @@ -26781,7 +26795,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true + "dev": true, + "requires": {} }, "postcss-modules-local-by-default": { "version": "4.0.0", @@ -26824,7 +26839,8 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", - "dev": true + "dev": true, + "requires": {} }, "postcss-normalize-display-values": { "version": "5.1.0", @@ -27345,13 +27361,13 @@ "react-inspector": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/react-inspector/-/react-inspector-6.0.2.tgz", - "integrity": "sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==" + "integrity": "sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==", + "requires": {} }, "react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "react-redux": { "version": "7.2.9", @@ -27538,7 +27554,8 @@ "redux-thunk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", - "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==" + "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", + "requires": {} }, "regenerate": { "version": "1.4.2", @@ -27840,7 +27857,8 @@ "version": "2.2.4", "resolved": "https://registry.npmjs.org/rollup-plugin-peer-deps-external/-/rollup-plugin-peer-deps-external-2.2.4.tgz", "integrity": "sha512-AWdukIM1+k5JDdAqV/Cxd+nejvno2FVLVeZ74NKggm3Q5s9cbbcOgUPGdbxPi4BXu7xGaZ8HG12F+thImYu/0g==", - "dev": true + "dev": true, + "requires": {} }, "rollup-plugin-postcss": { "version": "4.0.2", @@ -28453,7 +28471,8 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", - "dev": true + "dev": true, + "requires": {} }, "styled-components": { "version": "5.3.6", @@ -28490,7 +28509,8 @@ "styled-jsx": { "version": "5.0.7", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.0.7.tgz", - "integrity": "sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA==" + "integrity": "sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA==", + "requires": {} }, "stylehacks": { "version": "5.1.1", @@ -29228,7 +29248,8 @@ "use-sync-external-store": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==" + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "requires": {} }, "utf8-byte-length": { "version": "1.0.4", @@ -29441,7 +29462,8 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true + "dev": true, + "requires": {} }, "schema-utils": { "version": "3.1.1", diff --git a/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js b/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js index c3746f566f..da9c0fa378 100644 --- a/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js @@ -7,7 +7,7 @@ import StyledWrapper from './StyledWrapper'; const ProxySettings = ({ proxyConfig, onUpdate }) => { const formik = useFormik({ initialValues: { - enabled: proxyConfig.enabled || false, + enabled: proxyConfig.enabled || 'global', protocol: proxyConfig.protocol || 'http', hostname: proxyConfig.hostname || '', port: proxyConfig.port || '', @@ -15,18 +15,20 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { enabled: proxyConfig.auth ? proxyConfig.auth.enabled || false : false, username: proxyConfig.auth ? proxyConfig.auth.username || '' : '', password: proxyConfig.auth ? proxyConfig.auth.password || '' : '' - } + }, + noProxy: proxyConfig.noProxy || '' }, validationSchema: Yup.object({ - enabled: Yup.boolean(), - protocol: Yup.string().oneOf(['http', 'https']), + enabled: Yup.string().oneOf(['global', 'enabled', 'disabled']), + protocol: Yup.string().oneOf(['http', 'https', 'socks5']), hostname: Yup.string().max(1024), port: Yup.number().min(0).max(65535), auth: Yup.object({ enabled: Yup.boolean(), username: Yup.string().max(1024), password: Yup.string().max(1024) - }) + }), + noProxy: Yup.string().max(1024) }), onSubmit: (values) => { onUpdate(values); @@ -35,7 +37,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { useEffect(() => { formik.setValues({ - enabled: proxyConfig.enabled || false, + enabled: proxyConfig.enabled || 'global', protocol: proxyConfig.protocol || 'http', hostname: proxyConfig.hostname || '', port: proxyConfig.port || '', @@ -43,7 +45,8 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { enabled: proxyConfig.auth ? proxyConfig.auth.enabled || false : false, username: proxyConfig.auth ? proxyConfig.auth.username || '' : '', password: proxyConfig.auth ? proxyConfig.auth.password || '' : '' - } + }, + noProxy: proxyConfig.noProxy || '' }); }, [proxyConfig]); @@ -53,16 +56,50 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
- +
+ + + +
-
@@ -177,6 +225,26 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { ) : null}
+
+ + + {formik.touched.noProxy && formik.errors.noProxy ? ( +
{formik.errors.noProxy}
+ ) : null} +
+
+
+
+ ); +}; + +export default ProxySettings; diff --git a/packages/bruno-app/src/components/Preferences/index.js b/packages/bruno-app/src/components/Preferences/index.js index 455a6748fb..0f552edf06 100644 --- a/packages/bruno-app/src/components/Preferences/index.js +++ b/packages/bruno-app/src/components/Preferences/index.js @@ -4,6 +4,7 @@ import React, { useState } from 'react'; import Support from './Support'; import General from './General'; import Theme from './Theme'; +import Proxy from './ProxySettings'; import StyledWrapper from './StyledWrapper'; const Preferences = ({ onClose }) => { @@ -21,6 +22,10 @@ const Preferences = ({ onClose }) => { return ; } + case 'proxy': { + return ; + } + case 'theme': { return ; } @@ -41,6 +46,9 @@ const Preferences = ({ onClose }) => {
setTab('theme')}> Theme
+
setTab('proxy')}> + Proxy +
setTab('support')}> Support
diff --git a/packages/bruno-app/src/components/ResponsePane/Placeholder/index.js b/packages/bruno-app/src/components/ResponsePane/Placeholder/index.js index 2e7cd86211..edde26b39c 100644 --- a/packages/bruno-app/src/components/ResponsePane/Placeholder/index.js +++ b/packages/bruno-app/src/components/ResponsePane/Placeholder/index.js @@ -1,6 +1,12 @@ import React from 'react'; import { IconSend } from '@tabler/icons'; import StyledWrapper from './StyledWrapper'; +import { isMacOS } from 'utils/common/platform'; + +const isMac = isMacOS(); +const sendShortcut = isMac ? 'Cmd + Enter' : 'Ctrl + Enter'; +const newShortcut = isMac ? 'Cmd + B' : 'Ctrl + B'; +const editEnvShortcut = isMac ? 'Cmd + E' : 'Ctrl + E'; const Placeholder = () => { return ( @@ -15,9 +21,9 @@ const Placeholder = () => {
Edit Environments
-
Cmd + Enter
-
Cmd + B
-
Cmd + E
+
{sendShortcut}
+
{newShortcut}
+
{editEnvShortcut}
diff --git a/packages/bruno-app/src/providers/App/useCollectionTreeSync.js b/packages/bruno-app/src/providers/App/useCollectionTreeSync.js index caf057d5b8..421702be98 100644 --- a/packages/bruno-app/src/providers/App/useCollectionTreeSync.js +++ b/packages/bruno-app/src/providers/App/useCollectionTreeSync.js @@ -82,7 +82,7 @@ const useCollectionTreeSync = () => { } }; - const _collectionAlreadyOpened = (pathname) => { + const _collectionAlreadyOpened = () => { toast.success('Collection is already opened'); }; @@ -115,7 +115,8 @@ const useCollectionTreeSync = () => { dispatch(runRequestEvent(val)); }; - ipcRenderer.invoke('renderer:ready'); + ipcRenderer.invoke('renderer:ready-application'); + ipcRenderer.invoke('renderer:ready-collection'); const removeListener1 = ipcRenderer.on('main:collection-opened', _openCollection); const removeListener2 = ipcRenderer.on('main:collection-tree-updated', _collectionTreeUpdated); diff --git a/packages/bruno-app/src/providers/Preferences/index.js b/packages/bruno-app/src/providers/Preferences/index.js index 9b03450046..14fd30aafd 100644 --- a/packages/bruno-app/src/providers/Preferences/index.js +++ b/packages/bruno-app/src/providers/Preferences/index.js @@ -7,32 +7,64 @@ * On start, an IPC event is published to the main process to set the preferences in the electron process. */ -import { useEffect, createContext, useContext } from 'react'; +import { useEffect, createContext, useContext, useMemo } from 'react'; import * as Yup from 'yup'; import useLocalStorage from 'hooks/useLocalStorage/index'; import toast from 'react-hot-toast'; -const defaultPreferences = { - request: { - sslVerification: true - } -}; - -const preferencesSchema = Yup.object().shape({ - request: Yup.object().shape({ - sslVerification: Yup.boolean() +const preferencesSchema = Yup.object({ + request: Yup.object({ + sslVerification: Yup.boolean(), + caCert: Yup.string().max(1024) + }), + proxy: Yup.object({ + enabled: Yup.boolean(), + protocol: Yup.string().oneOf(['http', 'https', 'socks5']), + hostname: Yup.string().max(1024), + port: Yup.number().min(0).max(65535), + auth: Yup.object({ + enabled: Yup.boolean(), + username: Yup.string().max(1024), + password: Yup.string().max(1024) + }), + noProxy: Yup.string().max(1024) }) }); export const PreferencesContext = createContext(); export const PreferencesProvider = (props) => { - const [preferences, setPreferences] = useLocalStorage('bruno.preferences', defaultPreferences); + // TODO: Remove migration later + const [localStorePreferences] = useLocalStorage('bruno.preferences'); + + const preferences = {}; const { ipcRenderer } = window; useEffect(() => { - ipcRenderer.invoke('renderer:set-preferences', preferences).catch((err) => { - toast.error(err.message || 'Preferences sync error'); + // TODO: Remove migration later + if (localStorePreferences?.request) { + console.log('migrate prefs from localStorage ' + JSON.stringify(localStorePreferences)); + ipcRenderer + .invoke('renderer:migrate-preferences', localStorePreferences.request.sslVerification) + .then(() => { + localStorage.removeItem('bruno.preferences'); + }) + .catch((err) => { + toast.error(err.message || 'Preferences sync error'); + }); + } + + const removeListener = ipcRenderer.on('main:preferences-read', (currentPreferences) => { + if (currentPreferences.request) { + preferences.request = currentPreferences.request; + } + if (currentPreferences.proxy) { + preferences.proxy = currentPreferences.proxy; + } }); + + return () => { + removeListener(); + }; }, [preferences, toast]); const validatedSetPreferences = (newPreferences) => { @@ -40,7 +72,15 @@ export const PreferencesProvider = (props) => { preferencesSchema .validate(newPreferences, { abortEarly: true }) .then((validatedPreferences) => { - setPreferences(validatedPreferences); + ipcRenderer + .invoke('renderer:set-preferences', validatedPreferences) + .then(() => { + preferences.request = validatedPreferences.request; + preferences.proxy = validatedPreferences.proxy; + }) + .catch((err) => { + toast.error(err.message || 'Preferences sync error'); + }); resolve(validatedPreferences); }) .catch((error) => { @@ -51,11 +91,13 @@ export const PreferencesProvider = (props) => { }); }; - // todo: setPreferences must validate the preferences object against a schema - const value = { - preferences, - setPreferences: validatedSetPreferences - }; + const value = useMemo( + () => ({ + preferences, + setPreferences: validatedSetPreferences + }), + [preferences, validatedSetPreferences] + ); return ( diff --git a/packages/bruno-electron/src/index.js b/packages/bruno-electron/src/index.js index 5e9916ef03..be7a1c389a 100644 --- a/packages/bruno-electron/src/index.js +++ b/packages/bruno-electron/src/index.js @@ -8,8 +8,10 @@ const menuTemplate = require('./app/menu-template'); const LastOpenedCollections = require('./store/last-opened-collections'); const registerNetworkIpc = require('./ipc/network'); const registerCollectionsIpc = require('./ipc/collection'); +const registerApplicationIpc = require('./ipc/application'); const Watcher = require('./app/watcher'); const { loadWindowState, saveWindowState } = require('./utils/window'); +const preferences = require('./store/preferences'); const lastOpenedCollections = new LastOpenedCollections(); @@ -68,8 +70,9 @@ app.on('ready', async () => { }); // register all ipc handlers - registerNetworkIpc(mainWindow, watcher, lastOpenedCollections); + registerNetworkIpc(mainWindow); registerCollectionsIpc(mainWindow, watcher, lastOpenedCollections); + registerApplicationIpc(mainWindow, preferences); }); // Quit the app once all windows are closed diff --git a/packages/bruno-electron/src/ipc/application.js b/packages/bruno-electron/src/ipc/application.js new file mode 100644 index 0000000000..396de498ab --- /dev/null +++ b/packages/bruno-electron/src/ipc/application.js @@ -0,0 +1,72 @@ +const { ipcMain } = require('electron'); +const chokidar = require('chokidar'); +const stores = require('../store'); + +const registerApplicationIpc = (mainWindow, preferences) => { + const change = async (pathname, store) => { + if (store === stores.PREFERENCES) { + mainWindow.webContents.send('main:preferences-read', preferences.getAll()); + } + }; + + class StoreWatcher { + constructor() { + this.watchers = {}; + } + + addWatcher(watchPath, store) { + console.log(`watcher add: ${watchPath} for store ${store}`); + + if (this.watchers[watchPath]) { + this.watchers[watchPath].close(); + } + + const self = this; + setTimeout(() => { + const watcher = chokidar.watch(watchPath, { + ignoreInitial: false, + usePolling: false, + persistent: true, + ignorePermissionErrors: true, + awaitWriteFinish: { + stabilityThreshold: 80, + pollInterval: 10 + }, + depth: 20 + }); + + watcher.on('change', (pathname) => change(pathname, store)); + + self.watchers[watchPath] = watcher; + }, 100); + } + + hasWatcher(watchPath) { + return this.watchers[watchPath]; + } + + removeWatcher(watchPath) { + if (this.watchers[watchPath]) { + this.watchers[watchPath].close(); + this.watchers[watchPath] = null; + } + } + } + + const storeWatcher = new StoreWatcher(); + storeWatcher.addWatcher(preferences.getPath(), stores.PREFERENCES); + + ipcMain.handle('renderer:ready-application', async () => { + mainWindow.webContents.send('main:preferences-read', preferences.getAll()); + }); + + ipcMain.handle('renderer:set-preferences', async (event, newPreferences) => { + preferences.setPreferences(newPreferences); + }); + + ipcMain.handle('renderer:migrate-preferences', async (event, sslVerification) => { + preferences.migrateSslVerification(sslVerification); + }); +}; + +module.exports = registerApplicationIpc; diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index 03a15305b4..04ef3a300b 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -18,7 +18,6 @@ const { stringifyJson } = require('../utils/common'); const { openCollectionDialog, openCollection } = require('../app/collections'); const { generateUidBasedOnHash } = require('../utils/common'); const { moveRequestUid, deleteRequestUid } = require('../cache/requestUids'); -const { setPreferences } = require('../store/preferences'); const EnvironmentSecretsStore = require('../store/env-secrets'); const environmentSecretsStore = new EnvironmentSecretsStore(); @@ -33,9 +32,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection // browse directory ipcMain.handle('renderer:browse-directory', async (event, pathname, request) => { try { - const dirPath = await browseDirectory(mainWindow); - - return dirPath; + return await browseDirectory(mainWindow); } catch (error) { return Promise.reject(error); } @@ -68,8 +65,6 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection mainWindow.webContents.send('main:collection-opened', dirPath, uid, brunoConfig); ipcMain.emit('main:collection-opened', mainWindow, dirPath, uid); - - return; } catch (error) { return Promise.reject(error); } @@ -94,8 +89,6 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection collectionPathname, newName }); - - return; } catch (error) { return Promise.reject(error); } @@ -315,7 +308,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection fs.unlinkSync(pathname); } else { - return Promise.reject(error); + return Promise.reject(); } } catch (error) { return Promise.reject(error); @@ -458,7 +451,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection } }); - ipcMain.handle('renderer:ready', async (event) => { + ipcMain.handle('renderer:ready-collection', async (event) => { // reload last opened collections const lastOpened = lastOpenedCollections.getAll(); @@ -473,10 +466,6 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection } }); - ipcMain.handle('renderer:set-preferences', async (event, preferences) => { - setPreferences(preferences); - }); - ipcMain.handle('renderer:update-bruno-config', async (event, brunoConfig, collectionPath, collectionUid) => { try { const brunoConfigPath = path.join(collectionPath, 'bruno.json'); diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index a0b66099c5..e3843c2589 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -14,7 +14,7 @@ const { uuid } = require('../../utils/common'); const interpolateVars = require('./interpolate-vars'); const { interpolateString } = require('./interpolate-string'); const { sortFolder, getAllRequestsInFolderRecursively } = require('./helper'); -const { getPreferences } = require('../../store/preferences'); +const { preferences } = require('../../store/preferences'); const { getProcessEnvVars } = require('../../store/process-env'); const { getBrunoConfig } = require('../../store/bruno-config'); const { HttpsProxyAgent } = require('https-proxy-agent'); @@ -197,19 +197,16 @@ const registerNetworkIpc = (mainWindow) => { cancelTokenUid }); - const preferences = getPreferences(); - const sslVerification = get(preferences, 'request.sslVerification', true); const httpsAgentRequestFields = {}; - if (!sslVerification) { + if (!preferences.isTlsVerification()) { httpsAgentRequestFields['rejectUnauthorized'] = false; } else { - const cacertArray = [preferences['cacert'], process.env.SSL_CERT_FILE, process.env.NODE_EXTRA_CA_CERTS]; - cacertFile = cacertArray.find((el) => el); + const cacertArray = [preferences.getCaCert(), process.env.SSL_CERT_FILE, process.env.NODE_EXTRA_CA_CERTS]; + const cacertFile = cacertArray.find((el) => el); if (cacertFile && cacertFile.length > 1) { try { const fs = require('fs'); - caCrt = fs.readFileSync(cacertFile); - httpsAgentRequestFields['ca'] = caCrt; + httpsAgentRequestFields['ca'] = fs.readFileSync(cacertFile); } catch (err) { console.log('Error reading CA cert file:' + cacertFile, err); } @@ -474,10 +471,7 @@ const registerNetworkIpc = (mainWindow) => { const envVars = getEnvVars(environment); const preparedRequest = prepareGqlIntrospectionRequest(endpoint, envVars, request); - const preferences = getPreferences(); - const sslVerification = get(preferences, 'request.sslVerification', true); - - if (!sslVerification) { + if (!preferences.isTlsVerification()) { request.httpsAgent = new https.Agent({ rejectUnauthorized: false }); @@ -649,9 +643,6 @@ const registerNetworkIpc = (mainWindow) => { ...eventData }); - const preferences = getPreferences(); - const sslVerification = get(preferences, 'request.sslVerification', true); - // proxy configuration const brunoConfig = getBrunoConfig(collectionUid); const proxyEnabled = get(brunoConfig, 'proxy.enabled', false); @@ -685,11 +676,11 @@ const registerNetworkIpc = (mainWindow) => { } request.httpsAgent = new HttpsProxyAgent(proxy, { - rejectUnauthorized: sslVerification + rejectUnauthorized: preferences.isTlsVerification() }); request.httpAgent = new HttpProxyAgent(proxy); - } else if (!sslVerification) { + } else if (!preferences.isTlsVerification()) { request.httpsAgent = new https.Agent({ rejectUnauthorized: false }); diff --git a/packages/bruno-electron/src/store/index.js b/packages/bruno-electron/src/store/index.js new file mode 100644 index 0000000000..40d62b0c59 --- /dev/null +++ b/packages/bruno-electron/src/store/index.js @@ -0,0 +1,7 @@ +const PREFERENCES = 'PREFERENCES'; + +const stores = { + PREFERENCES +}; + +module.exports = stores; diff --git a/packages/bruno-electron/src/store/preferences.js b/packages/bruno-electron/src/store/preferences.js index f1b86b0f36..cb22f3cd19 100644 --- a/packages/bruno-electron/src/store/preferences.js +++ b/packages/bruno-electron/src/store/preferences.js @@ -1,26 +1,107 @@ +const Store = require('electron-store'); +const { get } = require('lodash'); + /** - * The preferences are stored in the browser local storage. - * When the app is started, an IPC message is published from the renderer process to set the preferences. + * The preferences are stored in the electron store 'preferences.json'. * The electron process uses this module to get the preferences. * * { - * request: { - * sslVerification: boolean + * preferences { + * request: { + * tlsVerification: boolean, + * cacert: String (yet not implemented in front end) + * } + * proxy: { (yet not implemented in front end) + * ... + * } * } * } */ -let preferences = {}; +const defaultPreferences = { + request: { + tlsVerification: true, + caCert: '' + }, + proxy: { + enabled: false, + protocol: 'http', + hostnameHttp: '', + portHttp: '', + auth: { + enabled: false, + username: '', + password: '' + }, + noProxy: '' + } +}; + +class PreferencesStore { + constructor() { + this.store = new Store({ + name: 'preferences', + clearInvalidConfig: true + }); + } + + get(key) { + return this.store.get(key); + } + + set(key, value) { + this.store.set(key, value); + } + + getPath() { + return this.store.path; + } +} +const preferencesStore = new PreferencesStore(); const getPreferences = () => { - return preferences; + return { + ...defaultPreferences, + ...(preferencesStore.get('preferences') || {}) + }; }; -const setPreferences = (newPreferences) => { - preferences = newPreferences; -}; +const preferences = { + getAll() { + return getPreferences(); + }, + + getPath() { + return preferencesStore.getPath(); + }, + + isTlsVerification: () => { + return get(getPreferences(), ['request.tlsVerification'], true); + }, + getCaCert: () => { + return get(getPreferences(), 'request.cacert'); + }, -module.exports = { - getPreferences, - setPreferences + setPreferences: (validatedPreferences) => { + const updatedPreferences = { + ...getPreferences(), + ...validatedPreferences + }; + preferencesStore.set('preferences', updatedPreferences); + }, + + migrateSslVerification: (sslVerification) => { + let preferences = getPreferences(); + if (!preferences.request) { + const updatedPreferences = { + ...preferences, + request: { + tlsVerification: sslVerification + } + }; + preferencesStore.set('preferences', updatedPreferences); + } + } }; + +module.exports = preferences; From 71e8ea457c164e4053bfacbc97b8a82a0a112d8f Mon Sep 17 00:00:00 2001 From: Mirko Golze Date: Mon, 9 Oct 2023 21:09:52 +0200 Subject: [PATCH 05/65] try add ca file to global root caas --- package-lock.json | 30 ++++++++++++++++++- packages/bruno-electron/package.json | 1 + .../bruno-electron/src/ipc/network/index.js | 29 ++++++++---------- 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index 162eaae6e3..4aca0e993a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1826,6 +1826,11 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@coolaj86/urequest": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@coolaj86/urequest/-/urequest-1.3.7.tgz", + "integrity": "sha512-PPrVYra9aWvZjSCKl/x1pJ9ZpXda1652oJrPBYy5rQumJJMkmTBN3ux+sK2xAUwVvv2wnewDlaQaHLxLwSHnIA==" + }, "node_modules/@develar/schema-utils": { "version": "2.6.5", "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", @@ -14749,6 +14754,14 @@ "node": ">=0.10.0" } }, + "node_modules/ssl-root-cas": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/ssl-root-cas/-/ssl-root-cas-1.3.1.tgz", + "integrity": "sha512-KR8J210Wfvjh+iNE9jcQEgbG0VG2713PHreItx6aNCPnkFO8XChz1cJ4iuCGeBj0+8wukLmgHgJqX+O5kRjPkQ==", + "dependencies": { + "@coolaj86/urequest": "^1.3.6" + } + }, "node_modules/stable": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", @@ -16687,7 +16700,7 @@ }, "packages/bruno-cli": { "name": "@usebruno/cli", - "version": "0.13.0", + "version": "0.14.0", "license": "MIT", "dependencies": { "@usebruno/js": "0.8.0", @@ -16799,6 +16812,7 @@ "node-machine-id": "^1.1.12", "qs": "^6.11.0", "socks-proxy-agent": "^8.0.2", + "ssl-root-cas": "^1.3.1", "uuid": "^9.0.0", "vm2": "^3.9.13", "yup": "^0.32.11" @@ -18341,6 +18355,11 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "@coolaj86/urequest": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@coolaj86/urequest/-/urequest-1.3.7.tgz", + "integrity": "sha512-PPrVYra9aWvZjSCKl/x1pJ9ZpXda1652oJrPBYy5rQumJJMkmTBN3ux+sK2xAUwVvv2wnewDlaQaHLxLwSHnIA==" + }, "@develar/schema-utils": { "version": "2.6.5", "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", @@ -21164,6 +21183,7 @@ "node-machine-id": "^1.1.12", "qs": "^6.11.0", "socks-proxy-agent": "^8.0.2", + "ssl-root-cas": "^1.3.1", "uuid": "^9.0.0", "vm2": "^3.9.13", "yup": "^0.32.11" @@ -28406,6 +28426,14 @@ "tweetnacl": "~0.14.0" } }, + "ssl-root-cas": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/ssl-root-cas/-/ssl-root-cas-1.3.1.tgz", + "integrity": "sha512-KR8J210Wfvjh+iNE9jcQEgbG0VG2713PHreItx6aNCPnkFO8XChz1cJ4iuCGeBj0+8wukLmgHgJqX+O5kRjPkQ==", + "requires": { + "@coolaj86/urequest": "^1.3.6" + } + }, "stable": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", diff --git a/packages/bruno-electron/package.json b/packages/bruno-electron/package.json index fdbd339c90..9476c5a5b5 100644 --- a/packages/bruno-electron/package.json +++ b/packages/bruno-electron/package.json @@ -40,6 +40,7 @@ "node-machine-id": "^1.1.12", "qs": "^6.11.0", "socks-proxy-agent": "^8.0.2", + "ssl-root-cas": "^1.3.1", "uuid": "^9.0.0", "vm2": "^3.9.13", "yup": "^0.32.11" diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 6d35dd5d41..c2becdec7b 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -1,3 +1,4 @@ +const os = require('os'); const qs = require('qs'); const https = require('https'); const axios = require('axios'); @@ -201,22 +202,19 @@ const registerNetworkIpc = (mainWindow) => { cancelTokenUid }); - const preferences = getPreferences(); - const sslVerification = get(preferences, 'request.sslVerification', true); const httpsAgentRequestFields = {}; - if (!sslVerification) { + if (!preferences.isTlsVerification()) { httpsAgentRequestFields['rejectUnauthorized'] = false; - } else { - const cacertArray = [preferences['cacert'], process.env.SSL_CERT_FILE, process.env.NODE_EXTRA_CA_CERTS]; - cacertFile = cacertArray.find((el) => el); - if (cacertFile && cacertFile.length > 1) { - try { - const fs = require('fs'); - caCrt = fs.readFileSync(cacertFile); - httpsAgentRequestFields['ca'] = caCrt; - } catch (err) { - console.log('Error reading CA cert file:' + cacertFile, err); - } + } + + const cacertArray = [preferences.getCaCert(), process.env.SSL_CERT_FILE, process.env.NODE_EXTRA_CA_CERTS]; + let cacertFile = cacertArray.find((el) => el); + if (cacertFile && cacertFile.length > 1) { + try { + const sslRootCas = require('ssl-root-cas').inject(); + sslRootCas.addFile(cacertFile); + } catch (err) { + console.log('Error reading CA cert file:' + cacertFile, err); } } @@ -249,16 +247,13 @@ const registerNetworkIpc = (mainWindow) => { if (socksEnabled) { const socksProxyAgent = new SocksProxyAgent(proxyUri); - request.httpsAgent = socksProxyAgent; - request.httpAgent = socksProxyAgent; } else { request.httpsAgent = new HttpsProxyAgent( proxyUri, Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined ); - request.httpAgent = new HttpProxyAgent(proxyUri); } } else if (Object.keys(httpsAgentRequestFields).length > 0) { From d64f4d374026733140c23e24cd3c968d0a09f614 Mon Sep 17 00:00:00 2001 From: Mirko Golze Date: Tue, 10 Oct 2023 07:49:22 +0200 Subject: [PATCH 06/65] make code runnable --- .../components/CollectionSettings/ProxySettings/index.js | 9 ++++++++- packages/bruno-electron/src/ipc/network/index.js | 6 +++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js b/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js index 9080ad2537..8b1b59996c 100644 --- a/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js @@ -53,6 +53,13 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { return (

Proxy Settings

+
-
+
diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index c2becdec7b..ff0c67e391 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -15,7 +15,7 @@ const { uuid } = require('../../utils/common'); const interpolateVars = require('./interpolate-vars'); const { interpolateString } = require('./interpolate-string'); const { sortFolder, getAllRequestsInFolderRecursively } = require('./helper'); -const { preferences } = require('../../store/preferences'); +const preferences = require('../../store/preferences'); const { getProcessEnvVars } = require('../../store/process-env'); const { getBrunoConfig } = require('../../store/bruno-config'); const { HttpsProxyAgent } = require('https-proxy-agent'); @@ -220,8 +220,8 @@ const registerNetworkIpc = (mainWindow) => { // proxy configuration const brunoConfig = getBrunoConfig(collectionUid); - const proxyEnabled = get(brunoConfig, 'proxy.enabled', false); - if (proxyEnabled) { + const proxyEnabled = get(brunoConfig, 'proxy.enabled', 'disabled'); + if (proxyEnabled === 'enabled') { let proxyUri; const interpolationOptions = { From ff3c666f485b983efa52f4849757b2191d358a8a Mon Sep 17 00:00:00 2001 From: Jack Scotson Date: Tue, 10 Oct 2023 17:32:17 +0100 Subject: [PATCH 07/65] use isWindowsOS() --- .../src/providers/ReduxStore/slices/collections/index.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 c0e9043998..bd78d4c4fd 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -22,10 +22,9 @@ import { areItemsTheSameExceptSeqUpdate } from 'utils/collections'; import { parseQueryParams, stringifyQueryParams } from 'utils/url'; -import { getSubdirectoriesFromRoot, getDirectoryName } from 'utils/common/platform'; -import os from 'os'; +import { getSubdirectoriesFromRoot, getDirectoryName, isWindowsOS } from 'utils/common/platform'; -const PATH_SEPARATOR = /Windows/i.test(os.release()) ? '\\' : '/'; +const PATH_SEPARATOR = isWindowsOS() ? '\\' : '/'; const initialState = { collections: [], From 6d7f397d7bea88f31f58d9fd66bcdbd3935aab95 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Wed, 11 Oct 2023 12:57:12 +0200 Subject: [PATCH 08/65] Update GitHub workflows - jump to `actions/checkout` v4 (latest version) - retrieve node version from NVM instead of hard-coded - add a new job to run prettier (in case people skip pre-commit hook) --- .github/workflows/unit-tests.yml | 22 ++++++++++++++++------ package.json | 1 + packages/bruno-app/package.json | 1 + 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 86a9e0ebdb..a2c73beec1 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -5,18 +5,16 @@ on: pull_request: branches: [main] jobs: - test: + tests: timeout-minutes: 60 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: - node-version: 16 - - name: Check package-lock.json - run: npm ci + node-version-file: '.nvmrc' - name: Install dependencies - run: npm i --legacy-peer-deps + run: npm ci --legacy-peer-deps - name: Test Package bruno-query run: npm run test --workspace=packages/bruno-query - name: Build Package bruno-query @@ -33,3 +31,15 @@ jobs: run: npm run test --workspace=packages/bruno-cli - name: Test Package bruno-electron run: npm run test --workspace=packages/bruno-electron + + prettier: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v3 + with: + node-version-file: '.nvmrc' + - name: Install dependencies + run: npm ci --legacy-peer-deps + - name: Run Prettier + run: npm run test:prettier:web diff --git a/package.json b/package.json index 9aaaf23f26..dbe318946b 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "build:electron": "./scripts/build-electron.sh", "test:e2e": "npx playwright test", "test:report": "npx playwright show-report", + "test:prettier:web": "npm run test:prettier --workspace=packages/bruno-app", "prepare": "husky install" }, "overrides": { diff --git a/packages/bruno-app/package.json b/packages/bruno-app/package.json index 625e9dc0da..13c0cd8822 100644 --- a/packages/bruno-app/package.json +++ b/packages/bruno-app/package.json @@ -8,6 +8,7 @@ "start": "next start", "lint": "next lint", "test": "jest", + "test:prettier": "prettier --check \"./src/**/*.{js,jsx,json,ts,tsx}\"", "prettier": "prettier --write \"./src/**/*.{js,jsx,json,ts,tsx}\"" }, "dependencies": { From 8a0f7c6b8fe53da6cd03ccbbf268b4404b2c9631 Mon Sep 17 00:00:00 2001 From: Jarne Date: Wed, 11 Oct 2023 19:22:50 +0200 Subject: [PATCH 09/65] Save window maximized state --- packages/bruno-electron/src/index.js | 15 +++++++++++---- packages/bruno-electron/src/store/window-state.js | 10 ++++++++++ packages/bruno-electron/src/utils/window.js | 11 +++++++++-- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/packages/bruno-electron/src/index.js b/packages/bruno-electron/src/index.js index 3a38e21aef..a57faddeac 100644 --- a/packages/bruno-electron/src/index.js +++ b/packages/bruno-electron/src/index.js @@ -9,7 +9,7 @@ const LastOpenedCollections = require('./store/last-opened-collections'); const registerNetworkIpc = require('./ipc/network'); const registerCollectionsIpc = require('./ipc/collection'); const Watcher = require('./app/watcher'); -const { loadWindowState, saveWindowState } = require('./utils/window'); +const { loadWindowState, saveBounds, saveMaximized } = require('./utils/window'); const lastOpenedCollections = new LastOpenedCollections(); @@ -32,7 +32,7 @@ let watcher; // Prepare the renderer once the app is ready app.on('ready', async () => { - const { x, y, width, height } = loadWindowState(); + const { maximized, x, y, width, height } = loadWindowState(); mainWindow = new BrowserWindow({ x, @@ -52,6 +52,10 @@ app.on('ready', async () => { // autoHideMenuBar: true }); + if (maximized) { + mainWindow.maximize(); + } + const url = isDev ? 'http://localhost:3000' : format({ @@ -63,8 +67,11 @@ app.on('ready', async () => { mainWindow.loadURL(url); watcher = new Watcher(); - mainWindow.on('resize', () => saveWindowState(mainWindow)); - mainWindow.on('move', () => saveWindowState(mainWindow)); + mainWindow.on('resize', () => saveBounds(mainWindow)); + mainWindow.on('move', () => saveBounds(mainWindow)); + + mainWindow.on('maximize', () => saveMaximized(true)); + mainWindow.on('unmaximize', () => saveMaximized(false)); mainWindow.webContents.on('new-window', function (e, url) { e.preventDefault(); diff --git a/packages/bruno-electron/src/store/window-state.js b/packages/bruno-electron/src/store/window-state.js index bb0a61b647..51c6060b7a 100644 --- a/packages/bruno-electron/src/store/window-state.js +++ b/packages/bruno-electron/src/store/window-state.js @@ -4,6 +4,8 @@ const Store = require('electron-store'); const DEFAULT_WINDOW_WIDTH = 1280; const DEFAULT_WINDOW_HEIGHT = 768; +const DEFAULT_MAXIMIZED = false; + class WindowStateStore { constructor() { this.store = new Store({ @@ -26,6 +28,14 @@ class WindowStateStore { setBounds(bounds) { this.store.set('window-bounds', bounds); } + + getMaximized() { + return this.store.get('maximized') || DEFAULT_MAXIMIZED; + } + + setMaximized(isMaximized) { + this.store.set('maximized', isMaximized); + } } module.exports = WindowStateStore; diff --git a/packages/bruno-electron/src/utils/window.js b/packages/bruno-electron/src/utils/window.js index d824141d30..949a2aaa2c 100644 --- a/packages/bruno-electron/src/utils/window.js +++ b/packages/bruno-electron/src/utils/window.js @@ -7,12 +7,14 @@ const DEFAULT_WINDOW_WIDTH = 1280; const DEFAULT_WINDOW_HEIGHT = 768; const loadWindowState = () => { + const maximized = windowStateStore.getMaximized(); const bounds = windowStateStore.getBounds(); const positionValid = isPositionValid(bounds); const sizeValid = isSizeValid(bounds); return { + maximized, x: bounds.x && positionValid ? bounds.x : undefined, y: bounds.y && positionValid ? bounds.y : undefined, width: bounds.width && sizeValid ? bounds.width : DEFAULT_WINDOW_WIDTH, @@ -20,12 +22,16 @@ const loadWindowState = () => { }; }; -const saveWindowState = (window) => { +const saveBounds = (window) => { const bounds = window.getBounds(); windowStateStore.setBounds(bounds); }; +const saveMaximized = (isMaximized) => { + windowStateStore.setMaximized(isMaximized); +}; + const isPositionValid = (bounds) => { const area = getArea(bounds); @@ -49,5 +55,6 @@ const getArea = (bounds) => { module.exports = { loadWindowState, - saveWindowState + saveBounds, + saveMaximized }; From 72521a6007c742a0e3edc278e31b86915eecfdf0 Mon Sep 17 00:00:00 2001 From: Its-treason <39559178+Its-treason@users.noreply.github.com> Date: Wed, 11 Oct 2023 22:09:11 +0200 Subject: [PATCH 10/65] fix(#529): Fix Ctrl+W closes Bruno The default shortcut to close in the menu is Ctrl+W, I changed it to Ctrl+Shift+Q because firefox uses this shortcut for closing --- packages/bruno-electron/src/app/menu-template.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bruno-electron/src/app/menu-template.js b/packages/bruno-electron/src/app/menu-template.js index 6b47077292..cc0f1267e3 100644 --- a/packages/bruno-electron/src/app/menu-template.js +++ b/packages/bruno-electron/src/app/menu-template.js @@ -44,7 +44,7 @@ const template = [ }, { role: 'window', - submenu: [{ role: 'minimize' }, { role: 'close' }] + submenu: [{ role: 'minimize' }, { role: 'close', accelerator: 'CommandOrControl+Shift+Q' }] }, { role: 'help', From d6628d960e7679e7c5c2b4aea2447ad5d5589f02 Mon Sep 17 00:00:00 2001 From: nyyu Date: Fri, 13 Oct 2023 22:30:01 +0200 Subject: [PATCH 11/65] feat: support client certificates --- .../ClientCertSettings/StyledWrapper.js | 31 +++++ .../ClientCertSettings/index.js | 120 ++++++++++++++++++ .../components/CollectionSettings/index.js | 37 ++++++ .../bruno-electron/src/ipc/network/index.js | 39 ++++-- 4 files changed, 219 insertions(+), 8 deletions(-) create mode 100644 packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/index.js diff --git a/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/StyledWrapper.js new file mode 100644 index 0000000000..bc5ad1565c --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/StyledWrapper.js @@ -0,0 +1,31 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + .settings-label { + width: 80px; + } + + input { + width: 300px; + } + + .textbox { + border: 1px solid #ccc; + padding: 0.15rem 0.45rem; + box-shadow: none; + border-radius: 0px; + outline: none; + box-shadow: none; + transition: border-color ease-in-out 0.1s; + border-radius: 3px; + background-color: ${(props) => props.theme.modal.input.bg}; + border: 1px solid ${(props) => props.theme.modal.input.border}; + + &:focus { + border: solid 1px ${(props) => props.theme.modal.input.focusBorder} !important; + outline: none !important; + } + } +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/index.js b/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/index.js new file mode 100644 index 0000000000..280e0f4bb5 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/index.js @@ -0,0 +1,120 @@ +import React, { useEffect } from 'react'; +import { useFormik } from 'formik'; +import * as Yup from 'yup'; + +import StyledWrapper from './StyledWrapper'; + +const ClientCertSettings = ({ clientCertConfig, onUpdate, onRemove }) => { + const formik = useFormik({ + initialValues: { + domain: '', + certFilePath: '', + keyFilePath: '', + passphrase: '' + }, + validationSchema: Yup.object({ + domain: Yup.string().required(), + certFilePath: Yup.string().required(), + keyFilePath: Yup.string().required(), + passphrase: Yup.string() + }), + onSubmit: (values) => { + onUpdate(values); + } + }); + + const getFile = (e) => { + formik.values[e.name] = e.files[0].path; + }; + + return ( + +

Current client certificates

+
    + {!clientCertConfig.length + ? 'None' + : clientCertConfig.map((clientCert) => ( +
  • + Domain: {clientCert.domain} + +
  • + ))} +
+

New client certicate

+ +
+ + + {formik.touched.domain && formik.errors.domain ? ( +
{formik.errors.domain}
+ ) : null} +
+
+ + getFile(e.target)} + /> + {formik.touched.certFilePath && formik.errors.certFilePath ? ( +
{formik.errors.certFilePath}
+ ) : null} +
+
+ + getFile(e.target)} + /> + {formik.touched.keyFilePath && formik.errors.keyFilePath ? ( +
{formik.errors.keyFilePath}
+ ) : null} +
+
+ + + {formik.touched.passphrase && formik.errors.passphrase ? ( +
{formik.errors.passphrase}
+ ) : null} +
+
+ +
+ +
+ ); +}; + +export default ClientCertSettings; diff --git a/packages/bruno-app/src/components/CollectionSettings/index.js b/packages/bruno-app/src/components/CollectionSettings/index.js index b3a1ece81c..90b3af9a41 100644 --- a/packages/bruno-app/src/components/CollectionSettings/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/index.js @@ -7,6 +7,7 @@ import { updateBrunoConfig } from 'providers/ReduxStore/slices/collections/actio import { updateSettingsSelectedTab } from 'providers/ReduxStore/slices/collections'; import { useDispatch } from 'react-redux'; import ProxySettings from './ProxySettings'; +import ClientCertSettings from './ClientCertSettings'; import Headers from './Headers'; import Auth from './Auth'; import Script from './Script'; @@ -28,6 +29,8 @@ const CollectionSettings = ({ collection }) => { const proxyConfig = get(collection, 'brunoConfig.proxy', {}); + const clientCertConfig = get(collection, 'brunoConfig.clientCertificates', []); + const onProxySettingsUpdate = (config) => { const brunoConfig = cloneDeep(collection.brunoConfig); brunoConfig.proxy = config; @@ -38,6 +41,28 @@ const CollectionSettings = ({ collection }) => { .catch((err) => console.log(err) && toast.error('Failed to update collection settings')); }; + const onClientCertSettingsUpdate = (config) => { + const brunoConfig = cloneDeep(collection.brunoConfig); + brunoConfig.clientCertificates + ? brunoConfig.clientCertificates.push(config) + : (brunoConfig.clientCertificates = [config]); + dispatch(updateBrunoConfig(brunoConfig, collection.uid)) + .then(() => { + toast.success('Collection settings updated successfully'); + }) + .catch((err) => console.log(err) && toast.error('Failed to update collection settings')); + }; + + const onClientCertSettingsRemove = (config) => { + const brunoConfig = cloneDeep(collection.brunoConfig); + brunoConfig.clientCertificates = brunoConfig.clientCertificates.filter((item) => item.domain != config.domain); + dispatch(updateBrunoConfig(brunoConfig, collection.uid)) + .then(() => { + toast.success('Collection settings updated successfully'); + }) + .catch((err) => console.log(err) && toast.error('Failed to update collection settings')); + }; + const getTabPanel = (tab) => { switch (tab) { case 'headers': { @@ -55,6 +80,15 @@ const CollectionSettings = ({ collection }) => { case 'proxy': { return ; } + case 'clientCert': { + return ( + + ); + } case 'docs': { return ; } @@ -85,6 +119,9 @@ const CollectionSettings = ({ collection }) => {
setTab('proxy')}> Proxy
+
setTab('clientCert')}> + Client certificate +
setTab('docs')}> Docs
diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 8e8cb62472..9d308faf7f 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -1,4 +1,5 @@ const os = require('os'); +const fs = require('fs'); const qs = require('qs'); const https = require('https'); const axios = require('axios'); @@ -214,7 +215,6 @@ const registerNetworkIpc = (mainWindow) => { cacertFile = cacertArray.find((el) => el); if (cacertFile && cacertFile.length > 1) { try { - const fs = require('fs'); caCrt = fs.readFileSync(cacertFile); httpsAgentRequestFields['ca'] = caCrt; } catch (err) { @@ -223,18 +223,41 @@ const registerNetworkIpc = (mainWindow) => { } } - // proxy configuration const brunoConfig = getBrunoConfig(collectionUid); + const interpolationOptions = { + envVars, + collectionVariables, + processEnvVars + }; + + // client certificate config + const clientCertConfig = get(brunoConfig, 'clientCertificates', []); + + for (clientCert of clientCertConfig) { + const domain = interpolateString(clientCert.domain, interpolationOptions); + const certFilePath = interpolateString(clientCert.certFilePath, interpolationOptions); + const keyFilePath = interpolateString(clientCert.keyFilePath, interpolationOptions); + if (domain && certFilePath && keyFilePath) { + const hostRegex = '^https:\\/\\/' + domain.replaceAll('.', '\\.').replaceAll('*', '.*'); + + if (request.url.match(hostRegex)) { + try { + httpsAgentRequestFields['cert'] = fs.readFileSync(certFilePath); + httpsAgentRequestFields['key'] = fs.readFileSync(keyFilePath); + } catch (err) { + console.log('Error reading cert/key file', err); + } + httpsAgentRequestFields['passphrase'] = interpolateString(clientCert.passphrase, interpolationOptions); + break; + } + } + } + + // proxy configuration const proxyEnabled = get(brunoConfig, 'proxy.enabled', false); if (proxyEnabled) { let proxyUri; - const interpolationOptions = { - envVars, - collectionVariables, - processEnvVars - }; - const proxyProtocol = interpolateString(get(brunoConfig, 'proxy.protocol'), interpolationOptions); const proxyHostname = interpolateString(get(brunoConfig, 'proxy.hostname'), interpolationOptions); const proxyPort = interpolateString(get(brunoConfig, 'proxy.port'), interpolationOptions); From 78eec9ea5cc52ca4c594019dcfc0115be7e6bdcd Mon Sep 17 00:00:00 2001 From: rayoz12 Date: Sat, 14 Oct 2023 21:05:44 +1100 Subject: [PATCH 12/65] Fixes: #552 Runs Post response vars and scripts on error. This normalises behaviour between: - Assertions - Scripts - Tests --- .../bruno-electron/src/ipc/network/index.js | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 8e8cb62472..9a52518250 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -435,6 +435,56 @@ const registerNetworkIpc = (mainWindow) => { }); } + // run post-response vars + const postResponseVars = get(request, 'vars.res', []); + if (postResponseVars && postResponseVars.length) { + const varsRuntime = new VarsRuntime(); + const result = varsRuntime.runPostResponseVars( + postResponseVars, + request, + error.response, + envVars, + collectionVariables, + collectionPath, + processEnvVars + ); + + if (result) { + mainWindow.webContents.send('main:script-environment-update', { + envVariables: result.envVariables, + collectionVariables: result.collectionVariables, + requestUid, + collectionUid + }); + } + } + + // run post-response script + const responseScript = compact([get(collectionRoot, 'request.script.res'), get(request, 'script.res')]).join( + os.EOL + ); + if (responseScript && responseScript.length) { + const scriptRuntime = new ScriptRuntime(); + const result = await scriptRuntime.runResponseScript( + decomment(responseScript), + request, + error.response, + envVars, + collectionVariables, + collectionPath, + onConsoleLog, + processEnvVars, + scriptingConfig + ); + + mainWindow.webContents.send('main:script-environment-update', { + envVariables: result.envVariables, + collectionVariables: result.collectionVariables, + requestUid, + collectionUid + }); + } + // run tests const testFile = compact([ get(collectionRoot, 'request.tests'), From a0be0e10acc55d087e1b475ea5788f53d36a8c1b Mon Sep 17 00:00:00 2001 From: game5413 Date: Sat, 14 Oct 2023 18:10:49 +0700 Subject: [PATCH 13/65] fix event triggered when hold mouse click and release at sidebar --- .../Collection/CollectionItem/index.js | 71 +++++++++---------- .../Sidebar/Collections/Collection/index.js | 29 ++++---- 2 files changed, 50 insertions(+), 50 deletions(-) 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 0b33941f75..db5bfc02c3 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 @@ -88,44 +88,41 @@ const CollectionItem = ({ item, collection, searchText }) => { }); const handleClick = (event) => { - switch (event.button) { - case 0: // left click - if (isItemARequest(item)) { - dispatch(hideHomePage()); - if (itemIsOpenedInTabs(item, tabs)) { - dispatch( - focusTab({ - uid: item.uid - }) - ); - return; - } - dispatch( - addTab({ - uid: item.uid, - collectionUid: collection.uid, - requestPaneTab: getDefaultRequestPaneTab(item) - }) - ); - return; - } + if (isItemARequest(item)) { + dispatch(hideHomePage()); + if (itemIsOpenedInTabs(item, tabs)) { dispatch( - collectionFolderClicked({ - itemUid: item.uid, - collectionUid: collection.uid + focusTab({ + uid: item.uid }) ); return; - case 2: // right click - const _menuDropdown = dropdownTippyRef.current; - if (_menuDropdown) { - let menuDropdownBehavior = 'show'; - if (_menuDropdown.state.isShown) { - menuDropdownBehavior = 'hide'; - } - _menuDropdown[menuDropdownBehavior](); - } - return; + } + dispatch( + addTab({ + uid: item.uid, + collectionUid: collection.uid, + requestPaneTab: getDefaultRequestPaneTab(item) + }) + ); + return; + } + dispatch( + collectionFolderClicked({ + itemUid: item.uid, + collectionUid: collection.uid + }) + ); + }; + + const handleRightClick = (event) => { + const _menuDropdown = dropdownTippyRef.current; + if (_menuDropdown) { + let menuDropdownBehavior = 'show'; + if (_menuDropdown.state.isShown) { + menuDropdownBehavior = 'hide'; + } + _menuDropdown[menuDropdownBehavior](); } }; @@ -203,7 +200,8 @@ const CollectionItem = ({ item, collection, searchText }) => { ? indents.map((i) => { return (
{ }) : null}
{ }); const handleClick = (event) => { + dispatch(collectionClicked(collection.uid)); + }; + + const handleRightClick = (event) => { const _menuDropdown = menuDropdownTippyRef.current; - switch (event.button) { - case 0: // left click - dispatch(collectionClicked(collection.uid)); - return; - case 2: // right click - if (_menuDropdown) { - let menuDropdownBehavior = 'show'; - if (_menuDropdown.state.isShown) { - menuDropdownBehavior = 'hide'; - } - _menuDropdown[menuDropdownBehavior](); - } - return; + if (_menuDropdown) { + let menuDropdownBehavior = 'show'; + if (_menuDropdown.state.isShown) { + menuDropdownBehavior = 'hide'; + } + _menuDropdown[menuDropdownBehavior](); } }; @@ -138,7 +135,11 @@ const Collection = ({ collection, searchText }) => { setCollectionPropertiesModal(false)} /> )}
-
+
Date: Sat, 14 Oct 2023 12:29:51 +0530 Subject: [PATCH 14/65] Feature: Support Import postman environment - Fixes issue #193 --- .../EnvironmentList/index.js | 2 + .../ImportEnvironment/index.js | 33 +++++++++ .../Environments/EnvironmentSettings/index.js | 2 + .../ReduxStore/slices/collections/actions.js | 26 +++++++ .../utils/importers/postman-environment.js | 71 +++++++++++++++++++ packages/bruno-electron/src/ipc/collection.js | 22 ++++++ 6 files changed, 156 insertions(+) create mode 100644 packages/bruno-app/src/components/Environments/EnvironmentSettings/ImportEnvironment/index.js create mode 100644 packages/bruno-app/src/utils/importers/postman-environment.js diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js index b80cd92a50..e310eb0c1f 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js @@ -4,6 +4,7 @@ import usePrevious from 'hooks/usePrevious'; import EnvironmentDetails from './EnvironmentDetails'; import CreateEnvironment from '../CreateEnvironment/index'; import StyledWrapper from './StyledWrapper'; +import ImportEnvironment from "components/Environments/EnvironmentSettings/ImportEnvironment"; const EnvironmentList = ({ collection }) => { const { environments } = collection; @@ -65,6 +66,7 @@ const EnvironmentList = ({ collection }) => {
setOpenCreateModal(true)}> + Create
+
diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/ImportEnvironment/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/ImportEnvironment/index.js new file mode 100644 index 0000000000..1b0fd9dd99 --- /dev/null +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/ImportEnvironment/index.js @@ -0,0 +1,33 @@ +import toast from "react-hot-toast"; +import {toastError} from "utils/common/error"; +import {useDispatch} from "react-redux"; +import {importEnvironment} from "providers/ReduxStore/slices/collections/actions"; +import importPostmanEnvironment from "utils/importers/postman-environment"; +import React from "react"; + +const ImportEnvironment = ({title, collectionUid}) => { + const dispatch = useDispatch(); + + const handleImportPostmanEnvironment = () => { + importPostmanEnvironment() + .then((environment) => { + dispatch(importEnvironment(environment.name, environment.variables, collectionUid)) + .then(() => { + toast.success('Environment imported successfully'); + }) + .catch(() => toast.error('An error occurred while importing the environment')); + }) + .catch((err) => toastError(err, 'Postman Import environment failed')); + }; + + return( + + ); +}; + +export default ImportEnvironment; \ No newline at end of file diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js index 855e0fb31f..39c410eed7 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js @@ -3,6 +3,7 @@ import React, { useState } from 'react'; import CreateEnvironment from './CreateEnvironment'; import EnvironmentList from './EnvironmentList'; import StyledWrapper from './StyledWrapper'; +import ImportEnvironment from "components/Environments/EnvironmentSettings/ImportEnvironment"; const EnvironmentSettings = ({ collection, onClose }) => { const { environments } = collection; @@ -28,6 +29,7 @@ const EnvironmentSettings = ({ collection, onClose }) => { > + Create Environment +
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 80c823454b..1b36ed65ae 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -722,6 +722,32 @@ export const addEnvironment = (name, collectionUid) => (dispatch, getState) => { }); }; +export const importEnvironment = (name, variables, collectionUid) => (dispatch, getState) => { + return new Promise((resolve, reject) => { + const state = getState(); + const collection = findCollectionByUid(state.collections.collections, collectionUid); + if (!collection) { + return reject(new Error('Collection not found')); + } + + ipcRenderer + .invoke('renderer:import-environment', collection.pathname, name, variables) + .then( + dispatch( + updateLastAction({ + collectionUid, + lastAction: { + type: 'ADD_ENVIRONMENT', + payload: name + } + }) + ) + ) + .then(resolve) + .catch(reject); + }); +}; + export const copyEnvironment = (name, baseEnvUid, collectionUid) => (dispatch, getState) => { return new Promise((resolve, reject) => { const state = getState(); diff --git a/packages/bruno-app/src/utils/importers/postman-environment.js b/packages/bruno-app/src/utils/importers/postman-environment.js new file mode 100644 index 0000000000..61c62311c7 --- /dev/null +++ b/packages/bruno-app/src/utils/importers/postman-environment.js @@ -0,0 +1,71 @@ +import each from 'lodash/each'; +import fileDialog from 'file-dialog'; +import { BrunoError } from 'utils/common/error'; + +const readFile = (files) => { + return new Promise((resolve, reject) => { + const fileReader = new FileReader(); + fileReader.onload = (e) => resolve(e.target.result); + fileReader.onerror = (err) => reject(err); + fileReader.readAsText(files[0]); + }); +}; + +const isSecret = (type) => { + return type === 'secret'; +}; + +const importPostmanEnvironmentVariables = (brunoEnvironment, values) => { + brunoEnvironment.variables = brunoEnvironment.variables || []; + + each(values, (i) => { + const brunoEnvironmentVariable = { + name: i.key, + value: i.value, + enabled: i.enabled, + secret: isSecret(i.type) + }; + + brunoEnvironment.variables.push(brunoEnvironmentVariable); + }); +}; + +const importPostmanEnvironment = (environment) => { + const brunoEnvironment = { + name: environment.name, + variables: [] + }; + + importPostmanEnvironmentVariables(brunoEnvironment, environment.values); + return brunoEnvironment; +}; + +const parsePostmanEnvironment = (str) => { + return new Promise((resolve, reject) => { + try { + let environment = JSON.parse(str); + return resolve(importPostmanEnvironment(environment)); + } catch (err) { + console.log(err); + if (err instanceof BrunoError) { + return reject(err); + } + return reject(new BrunoError('Unable to parse the postman environment json file')); + } + }); +}; + +const importEnvironment = () => { + return new Promise((resolve, reject) => { + fileDialog({ accept: 'application/json' }) + .then(readFile) + .then(parsePostmanEnvironment) + .then((environment) => resolve(environment)) + .catch((err) => { + console.log(err); + reject(new BrunoError('Import Environment failed')); + }); + }); +}; + +export default importEnvironment; diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index 944a04f01d..b5d4f05417 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -184,6 +184,28 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection } }); + // copy environment + ipcMain.handle('renderer:import-environment', async (event, collectionPathname, name, variables) => { + try { + const envDirPath = path.join(collectionPathname, 'environments'); + if (!fs.existsSync(envDirPath)) { + await createDirectory(envDirPath); + } + + const envFilePath = path.join(envDirPath, `${name}.bru`); + if (fs.existsSync(envFilePath)) { + throw new Error(`environment: ${envFilePath} already exists`); + } + + const content = envJsonToBru({ + variables: variables + }); + await writeFile(envFilePath, content); + } catch (error) { + return Promise.reject(error); + } + }); + // save environment ipcMain.handle('renderer:save-environment', async (event, collectionPathname, environment) => { try { From a2b6bc5970356bcefb6d9f63c3bae36aeb946e07 Mon Sep 17 00:00:00 2001 From: Dipin Jagadish Date: Sat, 14 Oct 2023 15:50:01 +0100 Subject: [PATCH 15/65] feat: add textbox for request timeout --- .../components/Preferences/General/index.js | 43 ++++++++++++++++++- .../src/providers/Preferences/index.js | 6 ++- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/packages/bruno-app/src/components/Preferences/General/index.js b/packages/bruno-app/src/components/Preferences/General/index.js index 637c483e5f..4193996c3a 100644 --- a/packages/bruno-app/src/components/Preferences/General/index.js +++ b/packages/bruno-app/src/components/Preferences/General/index.js @@ -6,6 +6,7 @@ const General = () => { const { preferences, setPreferences } = usePreferences(); const [sslVerification, setSslVerification] = useState(preferences.request.sslVerification); + const [timeout, setTimeout] = useState(preferences.request.timeout); const handleCheckboxChange = () => { const updatedPreferences = { @@ -25,11 +26,49 @@ const General = () => { }); }; + const handleTimeoutChange = (value) => { + const timeout = value === '' ? 0 : value; + const updatedPreferences = { + ...preferences, + request: { + ...preferences.request, + timeout + } + }; + + setPreferences(updatedPreferences) + .then(() => { + setTimeout(timeout); + }) + .catch((err) => { + console.error(err); + }); + }; + return (
- - SSL Certificate Verification + + +
+
+ + handleTimeoutChange(e.target.value)} + type="text" + className="block textbox w-1/6" + />
); diff --git a/packages/bruno-app/src/providers/Preferences/index.js b/packages/bruno-app/src/providers/Preferences/index.js index 9b03450046..604c40e335 100644 --- a/packages/bruno-app/src/providers/Preferences/index.js +++ b/packages/bruno-app/src/providers/Preferences/index.js @@ -14,13 +14,15 @@ import toast from 'react-hot-toast'; const defaultPreferences = { request: { - sslVerification: true + sslVerification: true, + timeout: 0 } }; const preferencesSchema = Yup.object().shape({ request: Yup.object().shape({ - sslVerification: Yup.boolean() + sslVerification: Yup.boolean(), + timeout: Yup.number() }) }); From b07ccbdfab5d0560253235f2bb864fdc96aa2e11 Mon Sep 17 00:00:00 2001 From: Jack Scotson Date: Sat, 14 Oct 2023 16:32:29 +0100 Subject: [PATCH 16/65] adds PATH_SEPARATOR constant --- .../src/providers/ReduxStore/slices/collections/actions.js | 4 +--- .../src/providers/ReduxStore/slices/collections/index.js | 4 +--- packages/bruno-app/src/utils/common/platform.js | 2 ++ 3 files changed, 4 insertions(+), 6 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 111f26d3ed..f18c7edfac 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -21,7 +21,7 @@ import { } from 'utils/collections'; import { collectionSchema, itemSchema, environmentSchema, environmentsSchema } from '@usebruno/schema'; import { waitForNextTick } from 'utils/common'; -import { getDirectoryName, isWindowsOS } from 'utils/common/platform'; +import { getDirectoryName, isWindowsOS, PATH_SEPARATOR } from 'utils/common/platform'; import { sendNetworkRequest, cancelNetworkRequest } from 'utils/network'; import { @@ -46,8 +46,6 @@ import { import { closeAllCollectionTabs } from 'providers/ReduxStore/slices/tabs'; import { resolveRequestFilename } from 'utils/common/platform'; -const PATH_SEPARATOR = path.sep; - export const renameCollection = (newName, collectionUid) => (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 70e12a3f82..9e64ff0f4e 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -24,9 +24,7 @@ import { areItemsTheSameExceptSeqUpdate } from 'utils/collections'; import { parseQueryParams, stringifyQueryParams } from 'utils/url'; -import { getSubdirectoriesFromRoot, getDirectoryName, isWindowsOS } from 'utils/common/platform'; - -const PATH_SEPARATOR = isWindowsOS() ? '\\' : '/'; +import { getSubdirectoriesFromRoot, getDirectoryName, PATH_SEPARATOR } from 'utils/common/platform'; const initialState = { collections: [], diff --git a/packages/bruno-app/src/utils/common/platform.js b/packages/bruno-app/src/utils/common/platform.js index 771daaf141..03ff9539e9 100644 --- a/packages/bruno-app/src/utils/common/platform.js +++ b/packages/bruno-app/src/utils/common/platform.js @@ -48,3 +48,5 @@ export const isMacOS = () => { return osFamily.includes('os x'); }; + +export const PATH_SEPARATOR = isWindowsOS() ? '\\' : '/'; From 7808a6db05bdf123ab47237ef94a7b3172c5fbfb Mon Sep 17 00:00:00 2001 From: Jack Scotson Date: Sat, 14 Oct 2023 16:36:10 +0100 Subject: [PATCH 17/65] Revert "fix(#251, #265): phantoms folders fix on rename/delete needs to be run only on windows" This reverts commit fcc12fb089472716328054665d767d7b0ae48e04. # Conflicts: # packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js # packages/bruno-app/src/utils/common/platform.js --- .../ReduxStore/slices/collections/actions.js | 36 ++++++------------- .../bruno-app/src/utils/common/platform.js | 1 - 2 files changed, 10 insertions(+), 27 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 f18c7edfac..79f3384b81 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -171,11 +171,7 @@ 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) => { - dispatch(_sortCollections(order)); -}; + export const runCollectionFolder = (collectionUid, folderUid, recursive) => (dispatch, getState) => { const state = getState(); const collection = findCollectionByUid(state.collections.collections, collectionUid); @@ -293,19 +289,10 @@ export const renameItem = (newName, itemUid, collectionUid) => (dispatch, getSta } const { ipcRenderer } = window; - 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); + ipcRenderer.invoke('renderer:rename-item', item.pathname, newPathname, newName).then(() => { + dispatch(_renameItem({ newName, itemUid, collectionUid })) + resolve() + }).catch(reject); }); }; @@ -390,14 +377,8 @@ export const deleteItem = (itemUid, collectionUid) => (dispatch, getState) => { ipcRenderer .invoke('renderer:delete-item', item.pathname, item.type) .then(() => { - // 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(); + dispatch(_deleteItem({ itemUid, collectionUid })) + resolve() }) .catch((error) => reject(error)); } @@ -405,6 +386,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/utils/common/platform.js b/packages/bruno-app/src/utils/common/platform.js index 03ff9539e9..24b4a41ac4 100644 --- a/packages/bruno-app/src/utils/common/platform.js +++ b/packages/bruno-app/src/utils/common/platform.js @@ -1,7 +1,6 @@ import trim from 'lodash/trim'; import path from 'path'; import slash from './slash'; -import platform from 'platform'; export const isElectron = () => { if (!window) { From 0f1fb72e216c7e56e98bcc770bc724af76de4ae6 Mon Sep 17 00:00:00 2001 From: Jack Scotson Date: Sat, 14 Oct 2023 16:38:12 +0100 Subject: [PATCH 18/65] fix(#251): Re-add platform var --- packages/bruno-app/src/utils/common/platform.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/bruno-app/src/utils/common/platform.js b/packages/bruno-app/src/utils/common/platform.js index 24b4a41ac4..03ff9539e9 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) { From 61b705112e0801bbeaad9b091977d1d62d829ebd Mon Sep 17 00:00:00 2001 From: Sai <92389813+oohsai@users.noreply.github.com> Date: Sun, 15 Oct 2023 00:52:33 +0530 Subject: [PATCH 19/65] Create PULL_REQUEST_TEMPLATE.md --- .github/PULL_REQUEST_TEMPLATE.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..fddeae6e69 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,7 @@ +# Description + + +# Contribution Checklist: +- [ ] **The pull request does not introduce any breaking changes** +- [ ] **I have read the [contribution guidelines](https://github.com/usebruno/bruno/blob/main/contributing.md).** +- [ ] **Create an issue and link to the pull request.** From ead1225bc4d1d101e0a1751ddf99660aec5bfd3b Mon Sep 17 00:00:00 2001 From: Mykola Makhin Date: Sun, 15 Oct 2023 01:54:52 +0300 Subject: [PATCH 20/65] Ukrainian translation for contributing.md --- contributing.md | 2 +- contributing_ru.md | 2 +- contributing_ua.md | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 contributing_ua.md diff --git a/contributing.md b/contributing.md index abfcce4d33..966a6134b3 100644 --- a/contributing.md +++ b/contributing.md @@ -1,4 +1,4 @@ -**English** | [Русский](/contributing_ru.md) +**English** | [Українська](/contributing_ua.md) | [Русский](/contributing_ru.md) ## Lets make bruno better, together !! diff --git a/contributing_ru.md b/contributing_ru.md index 3164081629..061b605697 100644 --- a/contributing_ru.md +++ b/contributing_ru.md @@ -1,4 +1,4 @@ -[English](/contributing.md) | **Русский** +[English](/contributing.md) | [Українська](/contributing_ua.md) | **Русский** ## Давайте вместе сделаем Бруно лучше!!! diff --git a/contributing_ua.md b/contributing_ua.md new file mode 100644 index 0000000000..75760f5653 --- /dev/null +++ b/contributing_ua.md @@ -0,0 +1,37 @@ +[English](/contributing.md) | **Українська** | [Русский](/contributing_ru.md) + +## Давайте зробимо Bruno краще, разом !! + +Я дуже радий що Ви бажаєте покращити Bruno. Нижче наведені вказівки як розпочати розробку Bruno на Вашому комп'ютері. + +### Стек технологій + +Bruno побудований на NextJs та React. Також для десктопної версії (яка підтримує локальні колекції) використовується Electron + +Бібліотеки, які ми використовуємо + +- CSS - Tailwind +- Редактори коду - Codemirror +- Керування станом - Redux +- Іконки - Tabler Icons +- Форми - formik +- Валідація по схемі - Yup +- Клієнт запитів - axios +- Спостерігач за файловою системою - chokidar + +### Залежності + +Вам знадобиться [Node v18.x або остання LTS версія](https://nodejs.org/en/) та npm 8.x. Ми використовуєм npm workspaces в цьому проекті + +### Починаєм писати код + +Будь ласка, зверніться до [development_ua.md](docs/development_ua.md) за інструкціями щодо запуску локального середовища розробки. + +### Створення Pull Request-ів + +- Будь ласка, робіть PR-и маленькими і сфокусованими на одній речі +- Будь ласка, слідуйте формату назв гілок + - feature/[назва feature]: Така гілка має містити зміни лише щодо конкретної feature + - Приклад: feature/dark-mode + - bugfix/[назва баґу]: Така гілка має містити лише виправлення конкретного багу + - Приклад: bugfix/bug-1 From 211f941b9f9e56b2809890d295db953ceaf83be4 Mon Sep 17 00:00:00 2001 From: Jack Scotson Date: Sun, 15 Oct 2023 10:16:41 +0100 Subject: [PATCH 21/65] rm renameitem --- .../src/providers/ReduxStore/slices/collections/actions.js | 2 -- 1 file changed, 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 79f3384b81..3a5e17bceb 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -31,7 +31,6 @@ import { requestCancelled, responseReceived, newItem as _newItem, - renameItem as _renameItem, cloneItem as _cloneItem, deleteItem as _deleteItem, saveRequest as _saveRequest, @@ -290,7 +289,6 @@ 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); }); From 074cbf0c03056e8ad5e8db2b340eea6520ad391c Mon Sep 17 00:00:00 2001 From: Jack Scotson Date: Sun, 15 Oct 2023 10:25:35 +0100 Subject: [PATCH 22/65] cleanup --- .../src/providers/ReduxStore/slices/collections/actions.js | 4 +--- 1 file changed, 1 insertion(+), 3 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 3a5e17bceb..1c3295a192 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -288,9 +288,7 @@ 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(resolve).catch(reject); }); }; From 7bf049a1f0994546291b0972cdeb85ff077309f5 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Sun, 15 Oct 2023 15:59:57 +0530 Subject: [PATCH 23/65] feat: use electron-store for saving preferences --- .../Preferences/Font/StyledWrapper.js | 20 ---- .../src/components/Preferences/Font/index.js | 56 ++++++++---- .../components/Preferences/General/index.js | 51 +++++++---- .../src/components/Preferences/index.js | 6 +- .../RequestPane/GraphQLVariables/index.js | 8 +- .../RequestPane/RequestBody/index.js | 7 +- .../components/RequestPane/Script/index.js | 9 +- .../src/components/RequestPane/Tests/index.js | 7 +- .../ResponsePane/QueryResult/index.js | 18 +++- .../GenerateCodeItem/CodeView/index.js | 15 ++- packages/bruno-app/src/pages/_app.js | 9 +- packages/bruno-app/src/providers/App/index.js | 4 +- ...eCollectionTreeSync.js => useIpcEvents.js} | 91 ++++++++++--------- .../src/providers/Preferences/index.js | 77 ---------------- .../src/providers/ReduxStore/slices/app.js | 34 ++++++- .../ReduxStore/slices/collections/index.js | 1 - packages/bruno-electron/src/index.js | 6 +- packages/bruno-electron/src/ipc/collection.js | 22 +---- .../bruno-electron/src/ipc/preferences.js | 35 +++++++ .../bruno-electron/src/store/preferences.js | 73 +++++++++++---- .../bruno-electron/src/store/window-state.js | 1 - 21 files changed, 291 insertions(+), 259 deletions(-) rename packages/bruno-app/src/providers/App/{useCollectionTreeSync.js => useIpcEvents.js} (57%) delete mode 100644 packages/bruno-app/src/providers/Preferences/index.js create mode 100644 packages/bruno-electron/src/ipc/preferences.js diff --git a/packages/bruno-app/src/components/Preferences/Font/StyledWrapper.js b/packages/bruno-app/src/components/Preferences/Font/StyledWrapper.js index 5bd4e46932..d45eda5b68 100644 --- a/packages/bruno-app/src/components/Preferences/Font/StyledWrapper.js +++ b/packages/bruno-app/src/components/Preferences/Font/StyledWrapper.js @@ -2,26 +2,6 @@ import styled from 'styled-components'; const StyledWrapper = styled.div` color: ${(props) => props.theme.text}; - div.input-container { - background-color: ${(props) => props.theme.modal.input.bg}; - height: 2.3rem; - } - div.input-container { - border: solid 1px ${(props) => props.theme.modal.input.border}; - border-top-right-radius: 3px; - border-bottom-right-radius: 3px; - - input { - background-color: ${(props) => props.theme.modal.input.bg}; - outline: none; - box-shadow: none; - - &:focus { - outline: none !important; - box-shadow: none !important; - } - } - } `; export default StyledWrapper; diff --git a/packages/bruno-app/src/components/Preferences/Font/index.js b/packages/bruno-app/src/components/Preferences/Font/index.js index 4505b5958a..bae23e7232 100644 --- a/packages/bruno-app/src/components/Preferences/Font/index.js +++ b/packages/bruno-app/src/components/Preferences/Font/index.js @@ -1,32 +1,52 @@ import React, { useState } from 'react'; -import { usePreferences } from 'providers/Preferences'; +import get from 'lodash/get'; +import { useSelector, useDispatch } from 'react-redux'; +import { savePreferences } from 'providers/ReduxStore/slices/app'; import StyledWrapper from './StyledWrapper'; -const Font = () => { - const { preferences, setPreferences } = usePreferences(); +const Font = ({ close }) => { + const dispatch = useDispatch(); + const preferences = useSelector((state) => state.app.preferences); - const [codeFont, setCodeFont] = useState(preferences.codeFont); + const [codeFont, setCodeFont] = useState(get(preferences, 'font.codeFont', 'default')); const handleInputChange = (event) => { - const updatedPreferences = { - ...preferences, - codeFont: event.target.value - }; + setCodeFont(event.target.value); + }; - setPreferences(updatedPreferences) - .then(() => { - setCodeFont(event.target.value); + const handleSave = () => { + dispatch( + savePreferences({ + ...preferences, + font: { + codeFont + } }) - .catch((err) => { - console.error(err); - }); + ).then(() => { + close(); + }); }; - return ( + return ( -

Font in code area

-
- + +
+ +
+ +
+
); diff --git a/packages/bruno-app/src/components/Preferences/General/index.js b/packages/bruno-app/src/components/Preferences/General/index.js index 637c483e5f..a622dd6b6e 100644 --- a/packages/bruno-app/src/components/Preferences/General/index.js +++ b/packages/bruno-app/src/components/Preferences/General/index.js @@ -1,35 +1,46 @@ import React, { useState } from 'react'; -import { usePreferences } from 'providers/Preferences'; +import { useSelector, useDispatch } from 'react-redux'; +import { savePreferences } from 'providers/ReduxStore/slices/app'; import StyledWrapper from './StyledWrapper'; -const General = () => { - const { preferences, setPreferences } = usePreferences(); +const General = ({ close }) => { + const preferences = useSelector((state) => state.app.preferences); + const dispatch = useDispatch(); const [sslVerification, setSslVerification] = useState(preferences.request.sslVerification); - const handleCheckboxChange = () => { - const updatedPreferences = { - ...preferences, - request: { - ...preferences.request, - sslVerification: !sslVerification - } - }; - - setPreferences(updatedPreferences) - .then(() => { - setSslVerification(!sslVerification); + const handleSave = () => { + dispatch( + savePreferences({ + ...preferences, + request: { + sslVerification + } }) - .catch((err) => { - console.error(err); - }); + ).then(() => { + close(); + }); }; return (
- - SSL Certificate Verification + setSslVerification(!sslVerification)} + className="mr-3 mousetrap" + /> + +
+ +
+
); diff --git a/packages/bruno-app/src/components/Preferences/index.js b/packages/bruno-app/src/components/Preferences/index.js index a48f5e66e8..217b3c57a0 100644 --- a/packages/bruno-app/src/components/Preferences/index.js +++ b/packages/bruno-app/src/components/Preferences/index.js @@ -19,11 +19,11 @@ const Preferences = ({ onClose }) => { const getTabPanel = (tab) => { switch (tab) { case 'general': { - return ; + return ; } case 'theme': { - return ; + return ; } case 'support': { @@ -31,7 +31,7 @@ const Preferences = ({ onClose }) => { } case 'font': { - return ; + return ; } } }; diff --git a/packages/bruno-app/src/components/RequestPane/GraphQLVariables/index.js b/packages/bruno-app/src/components/RequestPane/GraphQLVariables/index.js index 0e213442bd..0d913d97f8 100644 --- a/packages/bruno-app/src/components/RequestPane/GraphQLVariables/index.js +++ b/packages/bruno-app/src/components/RequestPane/GraphQLVariables/index.js @@ -1,17 +1,17 @@ import React from 'react'; -import { useDispatch } from 'react-redux'; +import get from 'lodash/get'; +import { useDispatch, useSelector } from 'react-redux'; import CodeEditor from 'components/CodeEditor'; import { updateRequestGraphqlVariables } from 'providers/ReduxStore/slices/collections'; import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import { useTheme } from 'providers/Theme'; -import { usePreferences } from 'providers/Preferences'; import StyledWrapper from './StyledWrapper'; const GraphQLVariables = ({ variables, item, collection }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); - const { preferences } = usePreferences(); + const preferences = useSelector((state) => state.app.preferences); const onEdit = (value) => { dispatch( @@ -32,7 +32,7 @@ const GraphQLVariables = ({ variables, item, collection }) => { collection={collection} value={variables || ''} theme={storedTheme} - font={preferences.codeFont} + font={get(preferences, 'font.codeFont', 'default')} onEdit={onEdit} mode="javascript" onRun={onRun} diff --git a/packages/bruno-app/src/components/RequestPane/RequestBody/index.js b/packages/bruno-app/src/components/RequestPane/RequestBody/index.js index 70ab1e8404..9daaf37f14 100644 --- a/packages/bruno-app/src/components/RequestPane/RequestBody/index.js +++ b/packages/bruno-app/src/components/RequestPane/RequestBody/index.js @@ -3,9 +3,8 @@ import get from 'lodash/get'; import CodeEditor from 'components/CodeEditor'; import FormUrlEncodedParams from 'components/RequestPane/FormUrlEncodedParams'; import MultipartFormParams from 'components/RequestPane/MultipartFormParams'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { useTheme } from 'providers/Theme'; -import { usePreferences } from 'providers/Preferences'; import { updateRequestBody } from 'providers/ReduxStore/slices/collections'; import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import StyledWrapper from './StyledWrapper'; @@ -15,7 +14,7 @@ const RequestBody = ({ item, collection }) => { const body = item.draft ? get(item, 'draft.request.body') : get(item, 'request.body'); const bodyMode = item.draft ? get(item, 'draft.request.body.mode') : get(item, 'request.body.mode'); const { storedTheme } = useTheme(); - const { preferences } = usePreferences(); + const preferences = useSelector((state) => state.app.preferences); const onEdit = (value) => { dispatch( @@ -50,7 +49,7 @@ const RequestBody = ({ item, collection }) => { { @@ -14,7 +13,7 @@ const Script = ({ item, collection }) => { const responseScript = item.draft ? get(item, 'draft.request.script.res') : get(item, 'request.script.res'); const { storedTheme } = useTheme(); - const { preferences } = usePreferences(); + const preferences = useSelector((state) => state.app.preferences); const onRequestScriptEdit = (value) => { dispatch( @@ -47,7 +46,7 @@ const Script = ({ item, collection }) => { collection={collection} value={requestScript || ''} theme={storedTheme} - font={preferences.codeFont} + font={get(preferences, 'font.codeFont', 'default')} onEdit={onRequestScriptEdit} mode="javascript" onRun={onRun} @@ -60,7 +59,7 @@ const Script = ({ item, collection }) => { collection={collection} value={responseScript || ''} theme={storedTheme} - font={preferences.codeFont} + font={get(preferences, 'font.codeFont', 'default')} onEdit={onResponseScriptEdit} mode="javascript" onRun={onRun} diff --git a/packages/bruno-app/src/components/RequestPane/Tests/index.js b/packages/bruno-app/src/components/RequestPane/Tests/index.js index dce6547f68..66645509aa 100644 --- a/packages/bruno-app/src/components/RequestPane/Tests/index.js +++ b/packages/bruno-app/src/components/RequestPane/Tests/index.js @@ -1,11 +1,10 @@ import React from 'react'; import get from 'lodash/get'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import CodeEditor from 'components/CodeEditor'; import { updateRequestTests } from 'providers/ReduxStore/slices/collections'; import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import { useTheme } from 'providers/Theme'; -import { usePreferences } from 'providers/Preferences'; import StyledWrapper from './StyledWrapper'; const Tests = ({ item, collection }) => { @@ -13,7 +12,7 @@ const Tests = ({ item, collection }) => { const tests = item.draft ? get(item, 'draft.request.tests') : get(item, 'request.tests'); const { storedTheme } = useTheme(); - const { preferences } = usePreferences(); + const preferences = useSelector((state) => state.app.preferences); const onEdit = (value) => { dispatch( @@ -34,7 +33,7 @@ const Tests = ({ item, collection }) => { collection={collection} value={tests || ''} theme={storedTheme} - font={preferences.codeFont} + font={get(preferences, 'font.codeFont', 'default')} onEdit={onEdit} mode="javascript" onRun={onRun} diff --git a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js index fc271e7433..bb29abd3ab 100644 --- a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js +++ b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js @@ -1,8 +1,8 @@ import React from 'react'; +import get from 'lodash/get'; import CodeEditor from 'components/CodeEditor'; import { useTheme } from 'providers/Theme'; -import { usePreferences } from 'providers/Preferences'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { sendRequest } from 'providers/ReduxStore/slices/collections/actions'; import classnames from 'classnames'; import { getContentType, safeStringifyJSON, safeParseXML } from 'utils/common'; @@ -14,7 +14,7 @@ import { useMemo } from 'react'; const QueryResult = ({ item, collection, data, width, disableRunEventListener, headers, error }) => { const { storedTheme } = useTheme(); - const { preferences } = usePreferences(); + const preferences = useSelector((state) => state.app.preferences); const [tab, setTab] = useState('preview'); const dispatch = useDispatch(); const contentType = getContentType(headers); @@ -113,7 +113,17 @@ const QueryResult = ({ item, collection, data, width, disableRunEventListener, h return image; } - return ; + return ( + + ); }, [tab, collection, storedTheme, onRun, value, mode]); return ( 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 f3223bab4d..64c229ae41 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,12 +1,13 @@ import CodeEditor from 'components/CodeEditor/index'; +import get from 'lodash/get'; import { HTTPSnippet } from 'httpsnippet'; import { useTheme } from 'providers/Theme/index'; -import { usePreferences } from 'providers/Preferences/index'; import { buildHarRequest } from 'utils/codegenerator/har'; +import { useSelector } from 'react-redux'; const CodeView = ({ language, item }) => { const { storedTheme } = useTheme(); - const { preferences } = usePreferences(); + const preferences = useSelector((state) => state.app.preferences); const { target, client, language: lang } = language; let snippet = ''; @@ -17,7 +18,15 @@ const CodeView = ({ language, item }) => { snippet = 'Error generating code snippet'; } - return ; + return ( + + ); }; export default CodeView; diff --git a/packages/bruno-app/src/pages/_app.js b/packages/bruno-app/src/pages/_app.js index 6761290490..0b2f9c3a48 100644 --- a/packages/bruno-app/src/pages/_app.js +++ b/packages/bruno-app/src/pages/_app.js @@ -3,7 +3,6 @@ import { Provider } from 'react-redux'; import { AppProvider } from 'providers/App'; import { ToastProvider } from 'providers/Toaster'; import { HotkeysProvider } from 'providers/Hotkeys'; -import { PreferencesProvider } from 'providers/Preferences'; import ReduxStore from 'providers/ReduxStore'; import ThemeProvider from 'providers/Theme/index'; @@ -50,11 +49,9 @@ function MyApp({ Component, pageProps }) { - - - - - + + + diff --git a/packages/bruno-app/src/providers/App/index.js b/packages/bruno-app/src/providers/App/index.js index 041bf6e9db..2fbd17e759 100644 --- a/packages/bruno-app/src/providers/App/index.js +++ b/packages/bruno-app/src/providers/App/index.js @@ -1,6 +1,6 @@ import React, { useEffect } from 'react'; import useTelemetry from './useTelemetry'; -import useCollectionTreeSync from './useCollectionTreeSync'; +import useIpcEvents from './useIpcEvents'; import useCollectionNextAction from './useCollectionNextAction'; import { useDispatch } from 'react-redux'; import { refreshScreenWidth } from 'providers/ReduxStore/slices/app'; @@ -10,7 +10,7 @@ export const AppContext = React.createContext(); export const AppProvider = (props) => { useTelemetry(); - useCollectionTreeSync(); + useIpcEvents(); useCollectionNextAction(); const dispatch = useDispatch(); diff --git a/packages/bruno-app/src/providers/App/useCollectionTreeSync.js b/packages/bruno-app/src/providers/App/useIpcEvents.js similarity index 57% rename from packages/bruno-app/src/providers/App/useCollectionTreeSync.js rename to packages/bruno-app/src/providers/App/useIpcEvents.js index caf057d5b8..8e87b1cf9d 100644 --- a/packages/bruno-app/src/providers/App/useCollectionTreeSync.js +++ b/packages/bruno-app/src/providers/App/useIpcEvents.js @@ -14,11 +14,12 @@ import { runFolderEvent, brunoConfigUpdateEvent } from 'providers/ReduxStore/slices/collections'; +import { updatePreferences } from 'providers/ReduxStore/slices/app'; import toast from 'react-hot-toast'; import { openCollectionEvent, collectionAddEnvFileEvent } from 'providers/ReduxStore/slices/collections/actions'; import { isElectron } from 'utils/common/platform'; -const useCollectionTreeSync = () => { +const useIpcEvents = () => { const dispatch = useDispatch(); useEffect(() => { @@ -28,10 +29,6 @@ const useCollectionTreeSync = () => { const { ipcRenderer } = window; - const _openCollection = (pathname, uid, brunoConfig) => { - dispatch(openCollectionEvent(uid, pathname, brunoConfig)); - }; - const _collectionTreeUpdated = (type, val) => { if (window.__IS_DEV__) { console.log(type); @@ -82,69 +79,73 @@ const useCollectionTreeSync = () => { } }; - const _collectionAlreadyOpened = (pathname) => { + ipcRenderer.invoke('renderer:ready'); + const removeCollectionTreeUpdateListener = ipcRenderer.on('main:collection-tree-updated', _collectionTreeUpdated); + + const removeOpenCollectionListener = ipcRenderer.on('main:collection-opened', (pathname, uid, brunoConfig) => { + dispatch(openCollectionEvent(uid, pathname, brunoConfig)); + }); + + const removeCollectionAlreadyOpenedListener = ipcRenderer.on('main:collection-already-opened', (pathname) => { toast.success('Collection is already opened'); - }; + }); - const _displayError = (error) => { + const removeDisplayErrorListener = ipcRenderer.on('main:display-error', (error) => { if (typeof error === 'string') { return toast.error(error || 'Something went wrong!'); } if (typeof message === 'object') { return toast.error(error.message || 'Something went wrong!'); } - }; + }); - const _scriptEnvironmentUpdate = (val) => { + const removeScriptEnvUpdateListener = ipcRenderer.on('main:script-environment-update', (val) => { dispatch(scriptEnvironmentUpdateEvent(val)); - }; - - const _processEnvUpdate = (val) => { - dispatch(processEnvUpdateEvent(val)); - }; + }); - const _collectionRenamed = (val) => { + const removeCollectionRenamedListener = ipcRenderer.on('main:collection-renamed', (val) => { dispatch(collectionRenamedEvent(val)); - }; + }); - const _runFolderEvent = (val) => { + const removeRunFolderEventListener = ipcRenderer.on('main:run-folder-event', (val) => { dispatch(runFolderEvent(val)); - }; + }); - const _runRequestEvent = (val) => { + const removeRunRequestEventListener = ipcRenderer.on('main:run-request-event', (val) => { dispatch(runRequestEvent(val)); - }; + }); - ipcRenderer.invoke('renderer:ready'); + const removeProcessEnvUpdatesListener = ipcRenderer.on('main:process-env-update', (val) => { + dispatch(processEnvUpdateEvent(val)); + }); - const removeListener1 = ipcRenderer.on('main:collection-opened', _openCollection); - const removeListener2 = ipcRenderer.on('main:collection-tree-updated', _collectionTreeUpdated); - const removeListener3 = ipcRenderer.on('main:collection-already-opened', _collectionAlreadyOpened); - const removeListener4 = ipcRenderer.on('main:display-error', _displayError); - const removeListener5 = ipcRenderer.on('main:script-environment-update', _scriptEnvironmentUpdate); - const removeListener6 = ipcRenderer.on('main:collection-renamed', _collectionRenamed); - const removeListener7 = ipcRenderer.on('main:run-folder-event', _runFolderEvent); - const removeListener8 = ipcRenderer.on('main:run-request-event', _runRequestEvent); - const removeListener9 = ipcRenderer.on('main:process-env-update', _processEnvUpdate); - const removeListener10 = ipcRenderer.on('main:console-log', (val) => { + const removeConsoleLogListener = ipcRenderer.on('main:console-log', (val) => { console[val.type](...val.args); }); - const removeListener11 = ipcRenderer.on('main:bruno-config-update', (val) => dispatch(brunoConfigUpdateEvent(val))); + + const removeConfigUpdatesListener = ipcRenderer.on('main:bruno-config-update', (val) => + dispatch(brunoConfigUpdateEvent(val)) + ); + + const removePreferencesUpdatesListener = ipcRenderer.on('main:load-preferences', (val) => { + dispatch(updatePreferences(val)); + }); return () => { - removeListener1(); - removeListener2(); - removeListener3(); - removeListener4(); - removeListener5(); - removeListener6(); - removeListener7(); - removeListener8(); - removeListener9(); - removeListener10(); - removeListener11(); + removeCollectionTreeUpdateListener(); + removeOpenCollectionListener(); + removeCollectionAlreadyOpenedListener(); + removeDisplayErrorListener(); + removeScriptEnvUpdateListener(); + removeCollectionRenamedListener(); + removeRunFolderEventListener(); + removeRunRequestEventListener(); + removeProcessEnvUpdatesListener(); + removeConsoleLogListener(); + removeConfigUpdatesListener(); + removePreferencesUpdatesListener(); }; }, [isElectron]); }; -export default useCollectionTreeSync; +export default useIpcEvents; diff --git a/packages/bruno-app/src/providers/Preferences/index.js b/packages/bruno-app/src/providers/Preferences/index.js deleted file mode 100644 index 9b03450046..0000000000 --- a/packages/bruno-app/src/providers/Preferences/index.js +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Preferences Provider - * - * This provider is responsible for managing the user's preferences in the app. - * The preferences are stored in the browser local storage. - * - * On start, an IPC event is published to the main process to set the preferences in the electron process. - */ - -import { useEffect, createContext, useContext } from 'react'; -import * as Yup from 'yup'; -import useLocalStorage from 'hooks/useLocalStorage/index'; -import toast from 'react-hot-toast'; - -const defaultPreferences = { - request: { - sslVerification: true - } -}; - -const preferencesSchema = Yup.object().shape({ - request: Yup.object().shape({ - sslVerification: Yup.boolean() - }) -}); - -export const PreferencesContext = createContext(); -export const PreferencesProvider = (props) => { - const [preferences, setPreferences] = useLocalStorage('bruno.preferences', defaultPreferences); - const { ipcRenderer } = window; - - useEffect(() => { - ipcRenderer.invoke('renderer:set-preferences', preferences).catch((err) => { - toast.error(err.message || 'Preferences sync error'); - }); - }, [preferences, toast]); - - const validatedSetPreferences = (newPreferences) => { - return new Promise((resolve, reject) => { - preferencesSchema - .validate(newPreferences, { abortEarly: true }) - .then((validatedPreferences) => { - setPreferences(validatedPreferences); - resolve(validatedPreferences); - }) - .catch((error) => { - let errMsg = error.message || 'Preferences validation error'; - toast.error(errMsg); - reject(error); - }); - }); - }; - - // todo: setPreferences must validate the preferences object against a schema - const value = { - preferences, - setPreferences: validatedSetPreferences - }; - - return ( - - <>{props.children} - - ); -}; - -export const usePreferences = () => { - const context = useContext(PreferencesContext); - - if (context === undefined) { - throw new Error(`usePreferences must be used within a PreferencesProvider`); - } - - return context; -}; - -export default PreferencesProvider; diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/app.js b/packages/bruno-app/src/providers/ReduxStore/slices/app.js index f1e9b91177..c3a3aa5a87 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/app.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/app.js @@ -1,11 +1,20 @@ import { createSlice } from '@reduxjs/toolkit'; +import toast from 'react-hot-toast'; const initialState = { isDragging: false, idbConnectionReady: false, leftSidebarWidth: 222, screenWidth: 500, - showHomePage: false + showHomePage: false, + preferences: { + request: { + sslVerification: true + }, + font: { + codeFont: 'default' + } + } }; export const appSlice = createSlice({ @@ -29,6 +38,9 @@ export const appSlice = createSlice({ }, hideHomePage: (state) => { state.showHomePage = false; + }, + updatePreferences: (state, action) => { + state.preferences = action.payload; } } }); @@ -39,7 +51,25 @@ export const { updateLeftSidebarWidth, updateIsDragging, showHomePage, - hideHomePage + hideHomePage, + updatePreferences } = appSlice.actions; +export const savePreferences = (preferences) => (dispatch, getState) => { + return new Promise((resolve, reject) => { + const { ipcRenderer } = window; + + ipcRenderer + .invoke('renderer:save-preferences', preferences) + .then(() => toast.success('Preferences saved successfully')) + .then(() => dispatch(updatePreferences(preferences))) + .then(resolve) + .catch((err) => { + toast.error('An error occurred while saving preferences'); + console.error(err); + reject(err); + }); + }); +}; + export default appSlice.reducer; 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 c5cb71f837..c60b3e68c0 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -1057,7 +1057,6 @@ export const collectionsSlice = createSlice({ if (collection) { collection.root = file.data; } - console.log('collectionAddFileEvent', file); return; } diff --git a/packages/bruno-electron/src/index.js b/packages/bruno-electron/src/index.js index 1acaa7adc1..2199122dc5 100644 --- a/packages/bruno-electron/src/index.js +++ b/packages/bruno-electron/src/index.js @@ -8,6 +8,7 @@ const menuTemplate = require('./app/menu-template'); const LastOpenedCollections = require('./store/last-opened-collections'); const registerNetworkIpc = require('./ipc/network'); const registerCollectionsIpc = require('./ipc/collection'); +const registerPreferencesIpc = require('./ipc/preferences'); const Watcher = require('./app/watcher'); const { loadWindowState, saveWindowState } = require('./utils/window'); @@ -39,8 +40,8 @@ app.on('ready', async () => { y, width, height, - minWidth:1000, - minHeight:640, + minWidth: 1000, + minHeight: 640, webPreferences: { nodeIntegration: true, contextIsolation: true, @@ -76,6 +77,7 @@ app.on('ready', async () => { // register all ipc handlers registerNetworkIpc(mainWindow, watcher, lastOpenedCollections); registerCollectionsIpc(mainWindow, watcher, lastOpenedCollections); + registerPreferencesIpc(mainWindow, watcher, lastOpenedCollections); }); // Quit the app once all windows are closed diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index 944a04f01d..3b1212009a 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -15,10 +15,9 @@ const { sanitizeDirectoryName } = require('../utils/filesystem'); const { stringifyJson } = require('../utils/common'); -const { openCollectionDialog, openCollection } = require('../app/collections'); +const { openCollectionDialog } = require('../app/collections'); const { generateUidBasedOnHash } = require('../utils/common'); const { moveRequestUid, deleteRequestUid } = require('../cache/requestUids'); -const { setPreferences } = require('../store/preferences'); const EnvironmentSecretsStore = require('../store/env-secrets'); const environmentSecretsStore = new EnvironmentSecretsStore(); @@ -469,25 +468,6 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection } }); - ipcMain.handle('renderer:ready', async (event) => { - // reload last opened collections - const lastOpened = lastOpenedCollections.getAll(); - - if (lastOpened && lastOpened.length) { - for (let collectionPath of lastOpened) { - if (isDirectory(collectionPath)) { - openCollection(mainWindow, watcher, collectionPath, { - dontSendDisplayErrors: true - }); - } - } - } - }); - - ipcMain.handle('renderer:set-preferences', async (event, preferences) => { - setPreferences(preferences); - }); - ipcMain.handle('renderer:update-bruno-config', async (event, brunoConfig, collectionPath, collectionUid) => { try { const brunoConfigPath = path.join(collectionPath, 'bruno.json'); diff --git a/packages/bruno-electron/src/ipc/preferences.js b/packages/bruno-electron/src/ipc/preferences.js new file mode 100644 index 0000000000..f93ec5e6fc --- /dev/null +++ b/packages/bruno-electron/src/ipc/preferences.js @@ -0,0 +1,35 @@ +const { ipcMain } = require('electron'); +const { getPreferences, savePreferences } = require('../store/preferences'); +const { isDirectory } = require('../utils/filesystem'); +const { openCollection } = require('../app/collections'); + +const registerPreferencesIpc = (mainWindow, watcher, lastOpenedCollections) => { + ipcMain.handle('renderer:ready', async (event) => { + // load preferences + const preferences = getPreferences(); + mainWindow.webContents.send('main:load-preferences', preferences); + + // reload last opened collections + const lastOpened = lastOpenedCollections.getAll(); + + if (lastOpened && lastOpened.length) { + for (let collectionPath of lastOpened) { + if (isDirectory(collectionPath)) { + openCollection(mainWindow, watcher, collectionPath, { + dontSendDisplayErrors: true + }); + } + } + } + }); + + ipcMain.handle('renderer:save-preferences', async (event, preferences) => { + try { + await savePreferences(preferences); + } catch (error) { + return Promise.reject(error); + } + }); +}; + +module.exports = registerPreferencesIpc; diff --git a/packages/bruno-electron/src/store/preferences.js b/packages/bruno-electron/src/store/preferences.js index f1b86b0f36..869895cd83 100644 --- a/packages/bruno-electron/src/store/preferences.js +++ b/packages/bruno-electron/src/store/preferences.js @@ -1,26 +1,65 @@ -/** - * The preferences are stored in the browser local storage. - * When the app is started, an IPC message is published from the renderer process to set the preferences. - * The electron process uses this module to get the preferences. - * - * { - * request: { - * sslVerification: boolean - * } - * } - */ - -let preferences = {}; +const Yup = require('yup'); +const Store = require('electron-store'); + +const defaultPreferences = { + request: { + sslVerification: true + }, + font: { + codeFont: 'default' + } +}; + +const preferencesSchema = Yup.object().shape({ + request: Yup.object().shape({ + sslVerification: Yup.boolean() + }), + font: Yup.object().shape({ + codeFont: Yup.string().nullable() + }) +}); + +class PreferencesStore { + constructor() { + this.store = new Store({ + name: 'preferences', + clearInvalidConfig: true + }); + } + + getPreferences() { + return { + defaultPreferences, + ...this.store.get('preferences') + }; + } + + savePreferences(newPreferences) { + return this.store.set('preferences', newPreferences); + } +} + +const preferencesStore = new PreferencesStore(); const getPreferences = () => { - return preferences; + return preferencesStore.getPreferences(); }; -const setPreferences = (newPreferences) => { - preferences = newPreferences; +const savePreferences = async (newPreferences) => { + return new Promise((resolve, reject) => { + preferencesSchema + .validate(newPreferences, { abortEarly: true }) + .then((validatedPreferences) => { + preferencesStore.savePreferences(validatedPreferences); + resolve(); + }) + .catch((error) => { + reject(error); + }); + }); }; module.exports = { getPreferences, - setPreferences + savePreferences }; diff --git a/packages/bruno-electron/src/store/window-state.js b/packages/bruno-electron/src/store/window-state.js index bb0a61b647..90bf7b8cb2 100644 --- a/packages/bruno-electron/src/store/window-state.js +++ b/packages/bruno-electron/src/store/window-state.js @@ -1,4 +1,3 @@ -const _ = require('lodash'); const Store = require('electron-store'); const DEFAULT_WINDOW_WIDTH = 1280; From 4230bf8e41fc9badc35dfdfa9338376343355482 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Sun, 15 Oct 2023 16:03:13 +0530 Subject: [PATCH 24/65] chore: purge pageComponents --- .../{pageComponents/Index => pages/Bruno}/StyledWrapper.js | 0 .../src/{pageComponents/Index => pages/Bruno}/index.js | 1 - packages/bruno-app/src/pages/index.js | 4 ++-- 3 files changed, 2 insertions(+), 3 deletions(-) rename packages/bruno-app/src/{pageComponents/Index => pages/Bruno}/StyledWrapper.js (100%) rename packages/bruno-app/src/{pageComponents/Index => pages/Bruno}/index.js (97%) diff --git a/packages/bruno-app/src/pageComponents/Index/StyledWrapper.js b/packages/bruno-app/src/pages/Bruno/StyledWrapper.js similarity index 100% rename from packages/bruno-app/src/pageComponents/Index/StyledWrapper.js rename to packages/bruno-app/src/pages/Bruno/StyledWrapper.js diff --git a/packages/bruno-app/src/pageComponents/Index/index.js b/packages/bruno-app/src/pages/Bruno/index.js similarity index 97% rename from packages/bruno-app/src/pageComponents/Index/index.js rename to packages/bruno-app/src/pages/Bruno/index.js index 480ea08f33..9f4de24347 100644 --- a/packages/bruno-app/src/pageComponents/Index/index.js +++ b/packages/bruno-app/src/pages/Bruno/index.js @@ -9,7 +9,6 @@ import StyledWrapper from './StyledWrapper'; import 'codemirror/theme/material.css'; import 'codemirror/theme/monokai.css'; import 'codemirror/addon/scroll/simplescrollbars.css'; -import Documentation from 'components/Documentation'; const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true; if (!SERVER_RENDERED) { diff --git a/packages/bruno-app/src/pages/index.js b/packages/bruno-app/src/pages/index.js index 8c1a8ae5e7..1567ef2c4f 100644 --- a/packages/bruno-app/src/pages/index.js +++ b/packages/bruno-app/src/pages/index.js @@ -1,5 +1,5 @@ import Head from 'next/head'; -import IndexPage from 'pageComponents/Index'; +import Bruno from './Bruno'; import GlobalStyle from '../globalStyles'; export default function Home() { @@ -13,7 +13,7 @@ export default function Home() {
- +
); From 470e9d044289d24c1a31b6dc62a38d80bb1c7c89 Mon Sep 17 00:00:00 2001 From: Mirko Golze Date: Sun, 15 Oct 2023 16:40:50 +0200 Subject: [PATCH 25/65] proxy settings --- package-lock.json | 28 ----- .../CollectionSettings/Docs/index.js | 2 +- .../CollectionSettings/ProxySettings/index.js | 88 +++++++++++---- .../Preferences/ProxySettings/index.js | 13 ++- .../src/providers/Preferences/index.js | 63 ++++++++--- packages/bruno-cli/src/commands/run.js | 4 +- .../src/runner/run-single-request.js | 28 ++--- .../bruno-cli/src/utils/axios-instance.js | 8 +- packages/bruno-cli/src/utils/proxy-util.js | 65 +++++++++++ packages/bruno-electron/package.json | 1 - .../src/ipc/network/axios-instance.js | 4 +- .../bruno-electron/src/ipc/network/index.js | 103 ++++++++++-------- .../bruno-electron/src/store/preferences.js | 7 +- .../bruno-electron/src/utils/proxy-util.js | 64 +++++++++++ .../tests/utils/proxy-util.spec.js | 50 +++++++++ tests/home.spec.js | 2 +- 16 files changed, 390 insertions(+), 140 deletions(-) create mode 100644 packages/bruno-cli/src/utils/proxy-util.js create mode 100644 packages/bruno-electron/src/utils/proxy-util.js create mode 100644 packages/bruno-electron/tests/utils/proxy-util.spec.js diff --git a/package-lock.json b/package-lock.json index d04f7e2d27..e8f35901bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2418,11 +2418,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@coolaj86/urequest": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/@coolaj86/urequest/-/urequest-1.3.7.tgz", - "integrity": "sha512-PPrVYra9aWvZjSCKl/x1pJ9ZpXda1652oJrPBYy5rQumJJMkmTBN3ux+sK2xAUwVvv2wnewDlaQaHLxLwSHnIA==" - }, "node_modules/@develar/schema-utils": { "version": "2.6.5", "dev": true, @@ -14796,14 +14791,6 @@ "node": ">=0.10.0" } }, - "node_modules/ssl-root-cas": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/ssl-root-cas/-/ssl-root-cas-1.3.1.tgz", - "integrity": "sha512-KR8J210Wfvjh+iNE9jcQEgbG0VG2713PHreItx6aNCPnkFO8XChz1cJ4iuCGeBj0+8wukLmgHgJqX+O5kRjPkQ==", - "dependencies": { - "@coolaj86/urequest": "^1.3.6" - } - }, "node_modules/stable": { "version": "0.1.8", "dev": true, @@ -16761,7 +16748,6 @@ "node-machine-id": "^1.1.12", "qs": "^6.11.0", "socks-proxy-agent": "^8.0.2", - "ssl-root-cas": "^1.3.1", "uuid": "^9.0.0", "vm2": "^3.9.13", "yup": "^0.32.11" @@ -18765,11 +18751,6 @@ "version": "0.2.3", "dev": true }, - "@coolaj86/urequest": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/@coolaj86/urequest/-/urequest-1.3.7.tgz", - "integrity": "sha512-PPrVYra9aWvZjSCKl/x1pJ9ZpXda1652oJrPBYy5rQumJJMkmTBN3ux+sK2xAUwVvv2wnewDlaQaHLxLwSHnIA==" - }, "@develar/schema-utils": { "version": "2.6.5", "dev": true, @@ -21658,7 +21639,6 @@ "node-machine-id": "^1.1.12", "qs": "^6.11.0", "socks-proxy-agent": "^8.0.2", - "ssl-root-cas": "^1.3.1", "uuid": "^9.0.0", "vm2": "^3.9.13", "yup": "^0.32.11" @@ -27279,14 +27259,6 @@ "tweetnacl": "~0.14.0" } }, - "ssl-root-cas": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/ssl-root-cas/-/ssl-root-cas-1.3.1.tgz", - "integrity": "sha512-KR8J210Wfvjh+iNE9jcQEgbG0VG2713PHreItx6aNCPnkFO8XChz1cJ4iuCGeBj0+8wukLmgHgJqX+O5kRjPkQ==", - "requires": { - "@coolaj86/urequest": "^1.3.6" - } - }, "stable": { "version": "0.1.8", "dev": true diff --git a/packages/bruno-app/src/components/CollectionSettings/Docs/index.js b/packages/bruno-app/src/components/CollectionSettings/Docs/index.js index ca15cb3a58..f759af2e3e 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Docs/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Docs/index.js @@ -1,7 +1,7 @@ import 'github-markdown-css/github-markdown.css'; import get from 'lodash/get'; import { updateCollectionDocs } from 'providers/ReduxStore/slices/collections'; -import { useTheme } from 'providers/Theme/index'; +import { useTheme } from 'providers/Theme'; import { useState } from 'react'; import { useDispatch } from 'react-redux'; import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions'; diff --git a/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js b/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js index 8b1b59996c..df0d35df7a 100644 --- a/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js @@ -1,10 +1,52 @@ import React, { useEffect } from 'react'; import { useFormik } from 'formik'; -import * as Yup from 'yup'; import StyledWrapper from './StyledWrapper'; +import * as Yup from 'yup'; +import toast from 'react-hot-toast'; const ProxySettings = ({ proxyConfig, onUpdate }) => { + const proxySchema = Yup.object({ + enabled: Yup.string().oneOf(['global', 'enabled', 'disabled']), + protocol: Yup.string().oneOf(['http', 'https', 'socks4', 'socks5']), + hostname: Yup.string() + .when('enabled', { + is: 'enabled', + then: (hostname) => hostname.required('Specify the hostname for your proxy.'), + otherwise: (hostname) => hostname.nullable() + }) + .max(1024), + port: Yup.number() + .when('enabled', { + is: 'enabled', + then: (port) => port.typeError('Specify port between 1 and 65535'), + otherwise: (port) => port.nullable().transform((_, val) => (val ? Number(val) : null)) + }) + .min(1) + .max(65535), + auth: Yup.object() + .when('enabled', { + is: 'enabled', + then: Yup.object({ + enabled: Yup.boolean(), + username: Yup.string() + .when(['enabled'], { + is: true, + then: (username) => username.required('Specify username for proxy authentication.') + }) + .max(1024), + password: Yup.string() + .when('enabled', { + is: true, + then: (password) => password.required('Specify password for proxy authentication.') + }) + .max(1024) + }) + }) + .optional(), + noProxy: Yup.string().optional().max(1024) + }); + const formik = useFormik({ initialValues: { enabled: proxyConfig.enabled || 'global', @@ -18,20 +60,17 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { }, noProxy: proxyConfig.noProxy || '' }, - validationSchema: Yup.object({ - enabled: Yup.string().oneOf(['global', 'enabled', 'disabled']), - protocol: Yup.string().oneOf(['http', 'https', 'socks5']), - hostname: Yup.string().max(1024), - port: Yup.number().min(0).max(65535), - auth: Yup.object({ - enabled: Yup.boolean(), - username: Yup.string().max(1024), - password: Yup.string().max(1024) - }), - noProxy: Yup.string().max(1024) - }), + validationSchema: proxySchema, onSubmit: (values) => { - onUpdate(values); + proxySchema + .validate(values, { abortEarly: true }) + .then((validatedProxy) => { + onUpdate(validatedProxy); + }) + .catch((error) => { + let errMsg = error.message || 'Preferences validation error'; + toast.error(errMsg); + }); } }); @@ -55,15 +94,15 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {

Proxy Settings

+ +
@@ -227,7 +227,9 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { onChange={formik.handleChange} value={formik.values.port} /> - {formik.touched.port && formik.errors.port ?
{formik.errors.port}
: null} + {formik.touched.port && formik.errors.port ? ( +
{formik.errors.port}
+ ) : null}
@@ -278,7 +280,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { onChange={formik.handleChange} /> {formik.touched.auth?.password && formik.errors.auth?.password ? ( -
{formik.errors.auth.password}
+
{formik.errors.auth.password}
) : null}
@@ -299,7 +301,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { value={formik.values.noProxy || ''} /> {formik.touched.noProxy && formik.errors.noProxy ? ( -
{formik.errors.noProxy}
+
{formik.errors.noProxy}
) : null}
diff --git a/packages/bruno-app/src/components/Preferences/ProxySettings/index.js b/packages/bruno-app/src/components/Preferences/ProxySettings/index.js index 37be015545..d40f987922 100644 --- a/packages/bruno-app/src/components/Preferences/ProxySettings/index.js +++ b/packages/bruno-app/src/components/Preferences/ProxySettings/index.js @@ -2,12 +2,55 @@ import React, { useEffect } from 'react'; import { useFormik } from 'formik'; import * as Yup from 'yup'; import toast from 'react-hot-toast'; +import { savePreferences } from 'providers/ReduxStore/slices/app'; import StyledWrapper from './StyledWrapper'; -import { usePreferences } from 'providers/Preferences'; +import { useDispatch, useSelector } from 'react-redux'; -const ProxySettings = () => { - const { preferences, setPreferences } = usePreferences(); +const ProxySettings = ({ close }) => { + const preferences = useSelector((state) => state.app.preferences); + const dispatch = useDispatch(); + + const proxySchema = Yup.object({ + enabled: Yup.boolean(), + protocol: Yup.string().required().oneOf(['http', 'https', 'socks4', 'socks5']), + hostname: Yup.string() + .when('enabled', { + is: true, + then: (hostname) => hostname.required('Specify the hostname for your proxy.'), + otherwise: (hostname) => hostname.nullable() + }) + .max(1024), + port: Yup.number() + .when('enabled', { + is: true, + then: (port) => port.required('Specify port between 1 and 65535').typeError('Specify port between 1 and 65535'), + otherwise: (port) => port.nullable().transform((_, val) => (val ? Number(val) : null)) + }) + .min(1) + .max(65535), + auth: Yup.object() + .when('enabled', { + is: true, + then: Yup.object({ + enabled: Yup.boolean(), + username: Yup.string() + .when(['enabled'], { + is: true, + then: (username) => username.required('Specify username for proxy authentication.') + }) + .max(1024), + password: Yup.string() + .when('enabled', { + is: true, + then: (password) => password.required('Specify password for proxy authentication.') + }) + .max(1024) + }) + }) + .optional(), + noProxy: Yup.string().optional().max(1024) + }); const formik = useFormik({ initialValues: { @@ -22,35 +65,28 @@ const ProxySettings = () => { }, noProxy: preferences.proxy.noProxy || '' }, - validationSchema: Yup.object({ - enabled: Yup.boolean(), - protocol: Yup.string().oneOf(['http', 'https', 'socks4', 'socks5']), - hostname: Yup.string().max(1024), - port: Yup.number().min(0).max(65535), - auth: Yup.object({ - enabled: Yup.boolean(), - username: Yup.string().max(1024), - password: Yup.string().max(1024) - }), - noProxy: Yup.string().max(1024) - }), + validationSchema: proxySchema, onSubmit: (values) => { onUpdate(values); } }); const onUpdate = (values) => { - const updatedPreferences = { - ...preferences, - proxy: values - }; - - setPreferences(updatedPreferences) - .then(() => { - toast.success('Proxy settings updated successfully.'); + proxySchema + .validate(values, { abortEarly: true }) + .then((validatedProxy) => { + dispatch( + savePreferences({ + ...preferences, + proxy: validatedProxy + }) + ).then(() => { + close(); + }); }) - .catch((err) => { - console.error(err); + .catch((error) => { + let errMsg = error.message || 'Preferences validation error'; + toast.error(errMsg); }); }; @@ -147,7 +183,7 @@ const ProxySettings = () => { value={formik.values.hostname || ''} /> {formik.touched.hostname && formik.errors.hostname ? ( -
{formik.errors.hostname}
+
{formik.errors.hostname}
) : null}
@@ -166,7 +202,9 @@ const ProxySettings = () => { onChange={formik.handleChange} value={formik.values.port} /> - {formik.touched.port && formik.errors.port ?
{formik.errors.port}
: null} + {formik.touched.port && formik.errors.port ? ( +
{formik.errors.port}
+ ) : null}
@@ -217,7 +255,7 @@ const ProxySettings = () => { onChange={formik.handleChange} /> {formik.touched.auth?.password && formik.errors.auth?.password ? ( -
{formik.errors.auth.password}
+
{formik.errors.auth.password}
) : null}
@@ -238,7 +276,7 @@ const ProxySettings = () => { value={formik.values.noProxy || ''} /> {formik.touched.noProxy && formik.errors.noProxy ? ( -
{formik.errors.noProxy}
+
{formik.errors.noProxy}
) : null}
diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js index 8c98ff5e35..b0cffb9002 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -87,22 +87,22 @@ const runSingleRequest = async function ( const httpsAgentRequestFields = {}; if (insecure) { httpsAgentRequestFields['rejectUnauthorized'] = false; - } - - const caCertArray = [options['cacert'], process.env.SSL_CERT_FILE, process.env.NODE_EXTRA_CA_CERTS]; - const caCert = caCertArray.find((el) => el); - if (caCert && caCert.length > 1) { - try { - httpsAgentRequestFields['ca'] = fs.readFileSync(caCert); - } catch (err) { - console.log('Error reading CA cert file:' + caCert, err); + } else { + const caCertArray = [options['cacert'], process.env.SSL_CERT_FILE, process.env.NODE_EXTRA_CA_CERTS]; + const caCert = caCertArray.find((el) => el); + if (caCert && caCert.length > 1) { + try { + httpsAgentRequestFields['ca'] = fs.readFileSync(caCert); + } catch (err) { + console.log('Error reading CA cert file:' + caCert, err); + } } } // set proxy if enabled const proxyEnabled = get(brunoConfig, 'proxy.enabled', false); - const proxyByPass = shouldUseProxy(request.url, get(brunoConfig, 'proxy.noProxy', '')); - if (proxyEnabled && !proxyByPass) { + const shouldProxy = shouldUseProxy(request.url, get(brunoConfig, 'proxy.noProxy', '')); + if (proxyEnabled && shouldProxy) { let proxyUri; const interpolationOptions = { envVars: envVariables, diff --git a/packages/bruno-electron/src/ipc/application.js b/packages/bruno-electron/src/ipc/application.js deleted file mode 100644 index 396de498ab..0000000000 --- a/packages/bruno-electron/src/ipc/application.js +++ /dev/null @@ -1,72 +0,0 @@ -const { ipcMain } = require('electron'); -const chokidar = require('chokidar'); -const stores = require('../store'); - -const registerApplicationIpc = (mainWindow, preferences) => { - const change = async (pathname, store) => { - if (store === stores.PREFERENCES) { - mainWindow.webContents.send('main:preferences-read', preferences.getAll()); - } - }; - - class StoreWatcher { - constructor() { - this.watchers = {}; - } - - addWatcher(watchPath, store) { - console.log(`watcher add: ${watchPath} for store ${store}`); - - if (this.watchers[watchPath]) { - this.watchers[watchPath].close(); - } - - const self = this; - setTimeout(() => { - const watcher = chokidar.watch(watchPath, { - ignoreInitial: false, - usePolling: false, - persistent: true, - ignorePermissionErrors: true, - awaitWriteFinish: { - stabilityThreshold: 80, - pollInterval: 10 - }, - depth: 20 - }); - - watcher.on('change', (pathname) => change(pathname, store)); - - self.watchers[watchPath] = watcher; - }, 100); - } - - hasWatcher(watchPath) { - return this.watchers[watchPath]; - } - - removeWatcher(watchPath) { - if (this.watchers[watchPath]) { - this.watchers[watchPath].close(); - this.watchers[watchPath] = null; - } - } - } - - const storeWatcher = new StoreWatcher(); - storeWatcher.addWatcher(preferences.getPath(), stores.PREFERENCES); - - ipcMain.handle('renderer:ready-application', async () => { - mainWindow.webContents.send('main:preferences-read', preferences.getAll()); - }); - - ipcMain.handle('renderer:set-preferences', async (event, newPreferences) => { - preferences.setPreferences(newPreferences); - }); - - ipcMain.handle('renderer:migrate-preferences', async (event, sslVerification) => { - preferences.migrateSslVerification(sslVerification); - }); -}; - -module.exports = registerApplicationIpc; diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 1a6f609251..a63aba0dca 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -16,7 +16,7 @@ const { uuid } = require('../../utils/common'); const interpolateVars = require('./interpolate-vars'); const { interpolateString } = require('./interpolate-string'); const { sortFolder, getAllRequestsInFolderRecursively } = require('./helper'); -const preferences = require('../../store/preferences'); +const { preferences } = require('../../store/preferences'); const { getProcessEnvVars } = require('../../store/process-env'); const { getBrunoConfig } = require('../../store/bruno-config'); const { HttpsProxyAgent } = require('https-proxy-agent'); @@ -84,23 +84,81 @@ const getSize = (data) => { return 0; }; -function getHttpsAgentRequestFields() { +const configureRequest = async (collectionUid, request, envVars, collectionVariables, processEnvVars) => { const httpsAgentRequestFields = {}; if (!preferences.isTlsVerification()) { httpsAgentRequestFields['rejectUnauthorized'] = false; + } else { + const cacCrtArray = [preferences.getCaCert(), process.env.SSL_CERT_FILE, process.env.NODE_EXTRA_CA_CERTS]; + let caCertFile = cacCrtArray.find((el) => el); + if (caCertFile && caCertFile.length > 1) { + try { + httpsAgentRequestFields['ca'] = fs.readFileSync(caCertFile); + } catch (err) { + console.log('Error reading CA cert file:' + caCertFile, err); + } + } } - const cacCrtArray = [preferences.getCaCert(), process.env.SSL_CERT_FILE, process.env.NODE_EXTRA_CA_CERTS]; - let caCertFile = cacCrtArray.find((el) => el); - if (caCertFile && caCertFile.length > 1) { - try { - httpsAgentRequestFields['ca'] = fs.readFileSync(caCertFile); - } catch (err) { - console.log('Error reading CA cert file:' + caCertFile, err); + // proxy configuration + const brunoConfig = getBrunoConfig(collectionUid); + let proxyConfig = get(brunoConfig, 'proxy', {}); + let proxyEnabled = get(proxyConfig, 'enabled', 'disabled'); + if (proxyEnabled === 'global') { + proxyConfig = preferences.getProxyConfig(); + proxyEnabled = get(proxyConfig, 'enabled', false); + } + const shouldProxy = shouldUseProxy(request.url, get(proxyConfig, 'noProxy', '')); + if ((proxyEnabled === true || proxyEnabled === 'enabled') && shouldProxy) { + let proxyUri; + const interpolationOptions = { + envVars, + collectionVariables, + processEnvVars + }; + + const proxyProtocol = interpolateString(get(proxyConfig, 'protocol'), interpolationOptions); + const proxyHostname = interpolateString(get(proxyConfig, 'hostname'), interpolationOptions); + const proxyPort = interpolateString(get(proxyConfig, 'port'), interpolationOptions); + const proxyAuthEnabled = get(proxyConfig, 'auth.enabled', false); + const socksEnabled = proxyProtocol.includes('socks'); + + if (proxyAuthEnabled) { + const proxyAuthUsername = interpolateString(get(proxyConfig, 'auth.username'), interpolationOptions); + const proxyAuthPassword = interpolateString(get(proxyConfig, 'auth.password'), interpolationOptions); + + proxyUri = `${proxyProtocol}://${proxyAuthUsername}:${proxyAuthPassword}@${proxyHostname}:${proxyPort}`; + } else { + proxyUri = `${proxyProtocol}://${proxyHostname}:${proxyPort}`; + } + + if (socksEnabled) { + const socksProxyAgent = new SocksProxyAgent(proxyUri); + request.httpsAgent = socksProxyAgent; + request.httpAgent = socksProxyAgent; + } else { + request.httpsAgent = new HttpsProxyAgent( + proxyUri, + Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined + ); + request.httpAgent = new HttpProxyAgent(proxyUri); } + } else if (Object.keys(httpsAgentRequestFields).length > 0) { + request.httpsAgent = new https.Agent({ + ...httpsAgentRequestFields + }); } - return httpsAgentRequestFields; -} + + const axiosInstance = makeAxiosInstance(); + + if (request.awsv4config) { + request.awsv4config = await resolveCredentials(request); + addAwsV4Interceptor(axiosInstance, request); + delete request.awsv4config; + } + + return axiosInstance; +}; const registerNetworkIpc = (mainWindow) => { // handler for sending http request @@ -224,64 +282,13 @@ const registerNetworkIpc = (mainWindow) => { cancelTokenUid }); - const httpsAgentRequestFields = getHttpsAgentRequestFields(); - - // proxy configuration - const brunoConfig = getBrunoConfig(collectionUid); - let proxyConfig = get(brunoConfig, 'proxy', {}); - let proxyEnabled = get(proxyConfig, 'enabled', 'disabled'); - if (proxyEnabled === 'global') { - proxyConfig = preferences.getProxyConfig(); - proxyEnabled = get(proxyConfig, 'enabled', false); - } - const proxyByPass = shouldUseProxy(request.url, get(proxyConfig, 'noProxy', '')); - if ((proxyEnabled === true || proxyEnabled === 'enabled') && !proxyByPass) { - let proxyUri; - const interpolationOptions = { - envVars, - collectionVariables, - processEnvVars - }; - - const proxyProtocol = interpolateString(get(proxyConfig, 'protocol'), interpolationOptions); - const proxyHostname = interpolateString(get(proxyConfig, 'hostname'), interpolationOptions); - const proxyPort = interpolateString(get(proxyConfig, 'port'), interpolationOptions); - const proxyAuthEnabled = get(proxyConfig, 'auth.enabled', false); - const socksEnabled = proxyProtocol.includes('socks'); - - if (proxyAuthEnabled) { - const proxyAuthUsername = interpolateString(get(proxyConfig, 'auth.username'), interpolationOptions); - const proxyAuthPassword = interpolateString(get(proxyConfig, 'auth.password'), interpolationOptions); - - proxyUri = `${proxyProtocol}://${proxyAuthUsername}:${proxyAuthPassword}@${proxyHostname}:${proxyPort}`; - } else { - proxyUri = `${proxyProtocol}://${proxyHostname}:${proxyPort}`; - } - - if (socksEnabled) { - const socksProxyAgent = new SocksProxyAgent(proxyUri); - request.httpsAgent = socksProxyAgent; - request.httpAgent = socksProxyAgent; - } else { - request.httpsAgent = new HttpsProxyAgent( - proxyUri, - Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined - ); - request.httpAgent = new HttpProxyAgent(proxyUri); - } - } else if (Object.keys(httpsAgentRequestFields).length > 0) { - request.httpsAgent = new https.Agent({ - ...httpsAgentRequestFields - }); - } - - const axiosInstance = makeAxiosInstance(); - - if (request.awsv4config) { - request.awsv4config = await resolveCredentials(request); - addAwsV4Interceptor(axiosInstance, request); - delete request.awsv4config; - } + const axiosInstance = await configureRequest( + collectionUid, + request, + envVars, + collectionVariables, + processEnvVars + ); /** @type {import('axios').AxiosResponse} */ const response = await axiosInstance(request); @@ -684,60 +691,17 @@ const registerNetworkIpc = (mainWindow) => { ...eventData }); - const httpsAgentRequestFields = getHttpsAgentRequestFields(); - - // proxy configuration - const brunoConfig = getBrunoConfig(collectionUid); - let proxyConfig = get(brunoConfig, 'proxy', {}); - let proxyEnabled = get(proxyConfig, 'enabled', 'disabled'); - if (proxyEnabled === 'global') { - proxyConfig = preferences.getProxyConfig(); - proxyEnabled = get(proxyConfig, 'enabled', false); - } - const proxyByPass = shouldUseProxy(request.url, get(proxyConfig, 'noProxy', '')); - if ((proxyEnabled === true || proxyEnabled === 'enabled') && !proxyByPass) { - let proxyUri; - const interpolationOptions = { - envVars, - collectionVariables, - processEnvVars - }; - - const proxyProtocol = interpolateString(get(proxyConfig, 'protocol'), interpolationOptions); - const proxyHostname = interpolateString(get(proxyConfig, 'hostname'), interpolationOptions); - const proxyPort = interpolateString(get(proxyConfig, 'port'), interpolationOptions); - const proxyAuthEnabled = get(proxyConfig, 'auth.enabled', false); - const socksEnabled = proxyProtocol.includes('socks'); - - if (proxyAuthEnabled) { - const proxyAuthUsername = interpolateString(get(proxyConfig, 'auth.username'), interpolationOptions); - const proxyAuthPassword = interpolateString(get(proxyConfig, 'auth.password'), interpolationOptions); - - proxyUri = `${proxyProtocol}://${proxyAuthUsername}:${proxyAuthPassword}@${proxyHostname}:${proxyPort}`; - } else { - proxyUri = `${proxyProtocol}://${proxyHostname}:${proxyPort}`; - } - - if (socksEnabled) { - const socksProxyAgent = new SocksProxyAgent(proxyUri); - request.httpsAgent = socksProxyAgent; - request.httpAgent = socksProxyAgent; - } else { - request.httpsAgent = new HttpsProxyAgent( - proxyUri, - Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined - ); - request.httpAgent = new HttpProxyAgent(proxyUri); - } - } else if (Object.keys(httpsAgentRequestFields).length > 0) { - request.httpsAgent = new https.Agent({ - ...httpsAgentRequestFields - }); - } + const axiosInstance = await configureRequest( + collectionUid, + request, + envVars, + collectionVariables, + processEnvVars + ); - // send request timeStart = Date.now(); - const response = await axios(request); + /** @type {import('axios').AxiosResponse} */ + const response = await axiosInstance(request); timeEnd = Date.now(); // run post-response vars diff --git a/packages/bruno-electron/src/ipc/preferences.js b/packages/bruno-electron/src/ipc/preferences.js index f93ec5e6fc..602de92b9e 100644 --- a/packages/bruno-electron/src/ipc/preferences.js +++ b/packages/bruno-electron/src/ipc/preferences.js @@ -1,9 +1,64 @@ const { ipcMain } = require('electron'); -const { getPreferences, savePreferences } = require('../store/preferences'); +const { getPreferences, savePreferences, getPath } = require('../store/preferences'); const { isDirectory } = require('../utils/filesystem'); const { openCollection } = require('../app/collections'); +const stores = require('../store'); +const chokidar = require('chokidar'); const registerPreferencesIpc = (mainWindow, watcher, lastOpenedCollections) => { + const change = async (pathname, store) => { + if (store === stores.PREFERENCES) { + mainWindow.webContents.send('main:load-preferences', getPreferences()); + } + }; + + class StoreWatcher { + constructor() { + this.watchers = {}; + } + + addWatcher(watchPath, store) { + console.log(`watcher add: ${watchPath} for store ${store}`); + + if (this.watchers[watchPath]) { + this.watchers[watchPath].close(); + } + + const self = this; + setTimeout(() => { + const watcher = chokidar.watch(watchPath, { + ignoreInitial: false, + usePolling: false, + persistent: true, + ignorePermissionErrors: true, + awaitWriteFinish: { + stabilityThreshold: 80, + pollInterval: 10 + }, + depth: 20 + }); + + watcher.on('change', (pathname) => change(pathname, store)); + + self.watchers[watchPath] = watcher; + }, 100); + } + + hasWatcher(watchPath) { + return this.watchers[watchPath]; + } + + removeWatcher(watchPath) { + if (this.watchers[watchPath]) { + this.watchers[watchPath].close(); + this.watchers[watchPath] = null; + } + } + } + + const storeWatcher = new StoreWatcher(); + storeWatcher.addWatcher(getPath(), stores.PREFERENCES); + ipcMain.handle('renderer:ready', async (event) => { // load preferences const preferences = getPreferences(); @@ -15,7 +70,7 @@ const registerPreferencesIpc = (mainWindow, watcher, lastOpenedCollections) => { if (lastOpened && lastOpened.length) { for (let collectionPath of lastOpened) { if (isDirectory(collectionPath)) { - openCollection(mainWindow, watcher, collectionPath, { + await openCollection(mainWindow, watcher, collectionPath, { dontSendDisplayErrors: true }); } diff --git a/packages/bruno-electron/src/store/preferences.js b/packages/bruno-electron/src/store/preferences.js index 62e17a564d..93fa3b80e4 100644 --- a/packages/bruno-electron/src/store/preferences.js +++ b/packages/bruno-electron/src/store/preferences.js @@ -47,6 +47,18 @@ const preferencesSchema = Yup.object().shape({ }), font: Yup.object().shape({ codeFont: Yup.string().nullable() + }), + proxy: Yup.object({ + enabled: Yup.boolean(), + protocol: Yup.string().oneOf(['http', 'https', 'socks4', 'socks5']), + hostname: Yup.string().max(1024), + port: Yup.number().min(1).max(65535), + auth: Yup.object({ + enabled: Yup.boolean(), + username: Yup.string().max(1024), + password: Yup.string().max(1024) + }).optional(), + noProxy: Yup.string().optional().max(1024) }) }); @@ -58,9 +70,13 @@ class PreferencesStore { }); } + getPath() { + return this.store.path; + } + getPreferences() { return { - defaultPreferences, + ...defaultPreferences, ...this.store.get('preferences') }; } @@ -90,17 +106,13 @@ const savePreferences = async (newPreferences) => { }); }; -const preferences = { - getAll() { - return getPreferences(); - }, - - getPath() { - return preferencesStore.getPath(); - }, +const getPath = () => { + return preferencesStore.getPath(); +}; +const preferences = { isTlsVerification: () => { - return get(getPreferences(), 'request.tlsVerification', true); + return get(getPreferences(), 'request.sslVerification', true); }, getCaCert: () => { @@ -115,5 +127,6 @@ const preferences = { module.exports = { getPreferences, savePreferences, + getPath, preferences }; From 43c5b4cf53f2aac30b6d3a191aaa01780c56548c Mon Sep 17 00:00:00 2001 From: Mirko Golze Date: Sun, 15 Oct 2023 20:30:13 +0200 Subject: [PATCH 28/65] proxy settings global and collection level --- packages/bruno-electron/src/store/preferences.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/bruno-electron/src/store/preferences.js b/packages/bruno-electron/src/store/preferences.js index 93fa3b80e4..ae462f646f 100644 --- a/packages/bruno-electron/src/store/preferences.js +++ b/packages/bruno-electron/src/store/preferences.js @@ -6,17 +6,6 @@ const { get } = require('lodash'); * The preferences are stored in the electron store 'preferences.json'. * The electron process uses this module to get the preferences. * - * { - * preferences { - * request: { - * tlsVerification: boolean, - * cacert: String (yet not implemented in front end) - * } - * proxy: { (yet not implemented in front end) - * ... - * } - * } - * } */ const defaultPreferences = { From 353be75d9cf840cc7fb374e1b3dd1729ff7808ab Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 16 Oct 2023 00:45:54 +0530 Subject: [PATCH 29/65] fix: fixed bug in loading preferences --- packages/bruno-electron/src/store/preferences.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bruno-electron/src/store/preferences.js b/packages/bruno-electron/src/store/preferences.js index 869895cd83..20bc1d4d5e 100644 --- a/packages/bruno-electron/src/store/preferences.js +++ b/packages/bruno-electron/src/store/preferences.js @@ -29,7 +29,7 @@ class PreferencesStore { getPreferences() { return { - defaultPreferences, + ...defaultPreferences, ...this.store.get('preferences') }; } From 0e1869139ba9bafe1ed0c53a1fcf2009b1f03419 Mon Sep 17 00:00:00 2001 From: Dipin Jagadish Date: Sun, 15 Oct 2023 20:19:08 +0100 Subject: [PATCH 30/65] fix: fixing merge conflicts --- .../components/Preferences/General/index.js | 40 +++++++++---------- .../bruno-electron/src/store/preferences.js | 6 ++- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/bruno-app/src/components/Preferences/General/index.js b/packages/bruno-app/src/components/Preferences/General/index.js index 1c99e54568..b6719b69c2 100644 --- a/packages/bruno-app/src/components/Preferences/General/index.js +++ b/packages/bruno-app/src/components/Preferences/General/index.js @@ -15,7 +15,8 @@ const General = ({ close }) => { savePreferences({ ...preferences, request: { - sslVerification + sslVerification, + timeout } }) ).then(() => { @@ -24,37 +25,34 @@ const General = ({ close }) => { }; const handleTimeoutChange = (value) => { - const timeout = value === '' ? 0 : value; - const updatedPreferences = { - ...preferences, - request: { - ...preferences.request, - timeout - } - }; - - setPreferences(updatedPreferences) - .then(() => { - setTimeout(timeout); - }) - .catch((err) => { - console.error(err); - }); + const validTimeout = isNaN(Number(value)) ? timeout : Number(value); + setTimeout(validTimeout); }; return (
+ setSslVerification(!sslVerification)} - className="mr-3 mousetrap" + className="mousetrap h-4 w-4 mr-0" /> -
+
+ + handleTimeoutChange(e.target.value)} + type="text" + className="block textbox w-1/6" + />
diff --git a/packages/bruno-electron/src/store/preferences.js b/packages/bruno-electron/src/store/preferences.js index 869895cd83..7238ce7cc5 100644 --- a/packages/bruno-electron/src/store/preferences.js +++ b/packages/bruno-electron/src/store/preferences.js @@ -3,7 +3,8 @@ const Store = require('electron-store'); const defaultPreferences = { request: { - sslVerification: true + sslVerification: true, + timeout: 0 }, font: { codeFont: 'default' @@ -12,7 +13,8 @@ const defaultPreferences = { const preferencesSchema = Yup.object().shape({ request: Yup.object().shape({ - sslVerification: Yup.boolean() + sslVerification: Yup.boolean(), + timeout: Yup.number() }), font: Yup.object().shape({ codeFont: Yup.string().nullable() From 333564f687b2e6f05dff90f5abe3184d850cd091 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 16 Oct 2023 02:07:15 +0530 Subject: [PATCH 31/65] feat(#275): polish client certificate support --- .../ClientCertSettings/StyledWrapper.js | 14 +++++++++- .../ClientCertSettings/index.js | 28 +++++++++++++------ .../components/CollectionSettings/index.js | 19 +++++++++---- 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/StyledWrapper.js index bc5ad1565c..625bc98e6e 100644 --- a/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/StyledWrapper.js +++ b/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/StyledWrapper.js @@ -2,13 +2,25 @@ import styled from 'styled-components'; const StyledWrapper = styled.div` .settings-label { - width: 80px; + width: 90px; + } + + .certificate-icon { + color: ${(props) => props.theme.colors.text.yellow}; } input { width: 300px; } + .available-certificates { + background-color: ${(props) => props.theme.requestTabPanel.url.bg}; + + button.remove-certificate { + color: ${(props) => props.theme.colors.text.danger}; + } + } + .textbox { border: 1px solid #ccc; padding: 0.15rem 0.45rem; diff --git a/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/index.js b/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/index.js index 280e0f4bb5..235e274f5e 100644 --- a/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/index.js @@ -1,5 +1,7 @@ -import React, { useEffect } from 'react'; +import React from 'react'; +import { IconCertificate, IconTrash, IconWorld } from '@tabler/icons'; import { useFormik } from 'formik'; +import { uuid } from 'utils/common'; import * as Yup from 'yup'; import StyledWrapper from './StyledWrapper'; @@ -29,20 +31,28 @@ const ClientCertSettings = ({ clientCertConfig, onUpdate, onRemove }) => { return ( -

Current client certificates

-
    +
    + Client Certificates +
    +
      {!clientCertConfig.length ? 'None' : clientCertConfig.map((clientCert) => ( -
    • - Domain: {clientCert.domain} - +
    • +
      +
      + + {clientCert.domain} +
      + +
    • ))}
    -

    New client certicate

    + +

    Add Client Certicate

    setTab('clientCert')}> - Client certificate + Client Certificates
    setTab('docs')}> Docs
-
{getTabPanel(tab)}
+
+ {getTabPanel(tab)} +
); }; From 58fbe2e64b7d7f9f1b9e696f67bd4bb5a7dccf9a Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 16 Oct 2023 02:10:18 +0530 Subject: [PATCH 32/65] chore: cleanup --- .../CollectionSettings/Auth/AwsV4Auth/index.js | 1 - .../components/RequestPane/Auth/AwsV4Auth/index.js | 1 - packages/bruno-electron/src/ipc/network/index.js | 11 ----------- 3 files changed, 13 deletions(-) diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/AwsV4Auth/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/AwsV4Auth/index.js index 1fe35eea08..bc9cb67b58 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/AwsV4Auth/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/AwsV4Auth/index.js @@ -12,7 +12,6 @@ const AwsV4Auth = ({ collection }) => { const { storedTheme } = useTheme(); const awsv4Auth = get(collection, 'root.request.auth.awsv4', {}); - console.log('saved auth', awsv4Auth); const handleSave = () => dispatch(saveCollectionRoot(collection.uid)); diff --git a/packages/bruno-app/src/components/RequestPane/Auth/AwsV4Auth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/AwsV4Auth/index.js index 9ed29ac073..7c144fbf86 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/AwsV4Auth/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/AwsV4Auth/index.js @@ -13,7 +13,6 @@ const AwsV4Auth = ({ onTokenChange, item, collection }) => { const { storedTheme } = useTheme(); const awsv4Auth = item.draft ? get(item, 'draft.request.auth.awsv4', {}) : get(item, 'request.auth.awsv4', {}); - console.log('saved auth', awsv4Auth); const handleRun = () => dispatch(sendRequest(item, collection.uid)); const handleSave = () => dispatch(saveRequest(item.uid, collection.uid)); diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 9d308faf7f..405ca7b73f 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -210,17 +210,6 @@ const registerNetworkIpc = (mainWindow) => { const httpsAgentRequestFields = {}; if (!sslVerification) { httpsAgentRequestFields['rejectUnauthorized'] = false; - } else { - const cacertArray = [preferences['cacert'], process.env.SSL_CERT_FILE, process.env.NODE_EXTRA_CA_CERTS]; - cacertFile = cacertArray.find((el) => el); - if (cacertFile && cacertFile.length > 1) { - try { - caCrt = fs.readFileSync(cacertFile); - httpsAgentRequestFields['ca'] = caCrt; - } catch (err) { - console.log('Error reading CA cert file:' + cacertFile, err); - } - } } const brunoConfig = getBrunoConfig(collectionUid); From 1f8c4431e06eaccd1b85462995eb8869c790d0dc Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 16 Oct 2023 03:13:06 +0530 Subject: [PATCH 33/65] feat(#589): polish importing postman env --- .../CreateEnvironment/index.js | 4 +- .../EnvironmentList/StyledWrapper.js | 8 ++- .../EnvironmentList/index.js | 17 +++-- .../ImportEnvironment/index.js | 62 ++++++++++--------- .../Environments/EnvironmentSettings/index.js | 18 ++++-- .../GenerateCodeItem/StyledWrapper.js | 1 + .../ReduxStore/slices/collections/actions.js | 28 ++++----- packages/bruno-electron/src/ipc/collection.js | 52 +++------------- 8 files changed, 93 insertions(+), 97 deletions(-) diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/CreateEnvironment/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/CreateEnvironment/index.js index c0ca7f5ec6..e6947bd3ad 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/CreateEnvironment/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/CreateEnvironment/index.js @@ -1,6 +1,6 @@ import React, { useEffect, useRef } from 'react'; -import Portal from 'components/Portal/index'; -import Modal from 'components/Modal/index'; +import Portal from 'components/Portal'; +import Modal from 'components/Modal'; import toast from 'react-hot-toast'; import { useFormik } from 'formik'; import { addEnvironment } from 'providers/ReduxStore/slices/collections/actions'; diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js index 722a15db17..687cde46c9 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js @@ -10,6 +10,7 @@ const StyledWrapper = styled.div` 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; + height: 100%; } .environment-item { @@ -35,7 +36,8 @@ const StyledWrapper = styled.div` } } - .btn-create-environment { + .btn-create-environment, + .btn-import-environment { padding: 8px 10px; cursor: pointer; border-bottom: none; @@ -47,6 +49,10 @@ const StyledWrapper = styled.div` } } } + + .btn-import-environment { + color: ${(props) => props.theme.colors.text.muted}; + } `; export default StyledWrapper; diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js index e310eb0c1f..44e18455f6 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js @@ -1,15 +1,19 @@ import React, { useEffect, useState, forwardRef, useRef } from 'react'; import { findEnvironmentInCollection } from 'utils/collections'; +import toast from 'react-hot-toast'; +import { toastError } from 'utils/common/error'; import usePrevious from 'hooks/usePrevious'; import EnvironmentDetails from './EnvironmentDetails'; -import CreateEnvironment from '../CreateEnvironment/index'; +import CreateEnvironment from '../CreateEnvironment'; +import { IconUpload } from '@tabler/icons'; +import ImportEnvironment from '../ImportEnvironment'; import StyledWrapper from './StyledWrapper'; -import ImportEnvironment from "components/Environments/EnvironmentSettings/ImportEnvironment"; const EnvironmentList = ({ collection }) => { const { environments } = collection; const [selectedEnvironment, setSelectedEnvironment] = useState(null); const [openCreateModal, setOpenCreateModal] = useState(false); + const [openImportModal, setOpenImportModal] = useState(false); const envUids = environments ? environments.map((env) => env.uid) : []; const prevEnvUids = usePrevious(envUids); @@ -49,9 +53,10 @@ const EnvironmentList = ({ collection }) => { return ( {openCreateModal && setOpenCreateModal(false)} />} + {openImportModal && setOpenImportModal(false)} />}
-
+
{environments && environments.length && environments.map((env) => ( @@ -66,7 +71,11 @@ const EnvironmentList = ({ collection }) => {
setOpenCreateModal(true)}> + Create
- + +
setOpenImportModal(true)}> + + Import +
diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/ImportEnvironment/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/ImportEnvironment/index.js index 1b0fd9dd99..5caba79b21 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/ImportEnvironment/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/ImportEnvironment/index.js @@ -1,33 +1,39 @@ -import toast from "react-hot-toast"; -import {toastError} from "utils/common/error"; -import {useDispatch} from "react-redux"; -import {importEnvironment} from "providers/ReduxStore/slices/collections/actions"; -import importPostmanEnvironment from "utils/importers/postman-environment"; -import React from "react"; +import React from 'react'; +import Portal from 'components/Portal'; +import toast from 'react-hot-toast'; +import { useDispatch } from 'react-redux'; +import importPostmanEnvironment from 'utils/importers/postman-environment'; +import { importEnvironment } from 'providers/ReduxStore/slices/collections/actions'; +import { toastError } from 'utils/common/error'; +import Modal from 'components/Modal'; -const ImportEnvironment = ({title, collectionUid}) => { - const dispatch = useDispatch(); +const ImportEnvironment = ({ onClose, collection }) => { + const dispatch = useDispatch(); - const handleImportPostmanEnvironment = () => { - importPostmanEnvironment() - .then((environment) => { - dispatch(importEnvironment(environment.name, environment.variables, collectionUid)) - .then(() => { - toast.success('Environment imported successfully'); - }) - .catch(() => toast.error('An error occurred while importing the environment')); - }) - .catch((err) => toastError(err, 'Postman Import environment failed')); - }; + const handleImportPostmanEnvironment = () => { + importPostmanEnvironment() + .then((environment) => { + dispatch(importEnvironment(environment.name, environment.variables, collection.uid)) + .then(() => { + toast.success('Environment imported successfully'); + onClose(); + }) + .catch(() => toast.error('An error occurred while importing the environment')); + }) + .catch((err) => toastError(err, 'Postman Import environment failed')); + }; - return( - - ); + return ( + + +
+
+ Postman Environment +
+
+
+
+ ); }; -export default ImportEnvironment; \ No newline at end of file +export default ImportEnvironment; diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js index 39c410eed7..6daccc3746 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js @@ -3,11 +3,12 @@ import React, { useState } from 'react'; import CreateEnvironment from './CreateEnvironment'; import EnvironmentList from './EnvironmentList'; import StyledWrapper from './StyledWrapper'; -import ImportEnvironment from "components/Environments/EnvironmentSettings/ImportEnvironment"; +import ImportEnvironment from './ImportEnvironment'; const EnvironmentSettings = ({ collection, onClose }) => { const { environments } = collection; const [openCreateModal, setOpenCreateModal] = useState(false); + const [openImportModal, setOpenImportModal] = useState(false); if (!environments || !environments.length) { return ( @@ -21,15 +22,24 @@ const EnvironmentSettings = ({ collection, onClose }) => { hideCancel={true} > {openCreateModal && setOpenCreateModal(false)} />} -
+ {openImportModal && setOpenImportModal(false)} />} +

No environments found!

+ + Or + + -
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 f1c1c33e47..635c545e92 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 @@ -9,6 +9,7 @@ const StyledWrapper = styled.div` 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; + height: 100%; } .generate-code-item { 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 1b36ed65ae..38e3c30ed8 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -731,20 +731,20 @@ export const importEnvironment = (name, variables, collectionUid) => (dispatch, } ipcRenderer - .invoke('renderer:import-environment', collection.pathname, name, variables) - .then( - dispatch( - updateLastAction({ - collectionUid, - lastAction: { - type: 'ADD_ENVIRONMENT', - payload: name - } - }) - ) + .invoke('renderer:create-environment', collection.pathname, name, variables) + .then( + dispatch( + updateLastAction({ + collectionUid, + lastAction: { + type: 'ADD_ENVIRONMENT', + payload: name + } + }) ) - .then(resolve) - .catch(reject); + ) + .then(resolve) + .catch(reject); }); }; @@ -762,7 +762,7 @@ export const copyEnvironment = (name, baseEnvUid, collectionUid) => (dispatch, g } ipcRenderer - .invoke('renderer:copy-environment', collection.pathname, name, baseEnv.variables) + .invoke('renderer:create-environment', collection.pathname, name, baseEnv.variables) .then( dispatch( updateLastAction({ diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index a78294b119..b4acd5eb0c 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -140,7 +140,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection }); // create environment - ipcMain.handle('renderer:create-environment', async (event, collectionPathname, name) => { + ipcMain.handle('renderer:create-environment', async (event, collectionPathname, name, variables) => { try { const envDirPath = path.join(collectionPathname, 'environments'); if (!fs.existsSync(envDirPath)) { @@ -152,53 +152,17 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection throw new Error(`environment: ${envFilePath} already exists`); } - const content = envJsonToBru({ - variables: [] - }); - await writeFile(envFilePath, content); - } catch (error) { - return Promise.reject(error); - } - }); - - // copy environment - ipcMain.handle('renderer:copy-environment', async (event, collectionPathname, name, baseVariables) => { - try { - const envDirPath = path.join(collectionPathname, 'environments'); - if (!fs.existsSync(envDirPath)) { - await createDirectory(envDirPath); - } - - const envFilePath = path.join(envDirPath, `${name}.bru`); - if (fs.existsSync(envFilePath)) { - throw new Error(`environment: ${envFilePath} already exists`); - } - - const content = envJsonToBru({ - variables: baseVariables - }); - await writeFile(envFilePath, content); - } catch (error) { - return Promise.reject(error); - } - }); + const environment = { + name: name, + variables: variables || [] + }; - // copy environment - ipcMain.handle('renderer:import-environment', async (event, collectionPathname, name, variables) => { - try { - const envDirPath = path.join(collectionPathname, 'environments'); - if (!fs.existsSync(envDirPath)) { - await createDirectory(envDirPath); + if (envHasSecrets(environment)) { + environmentSecretsStore.storeEnvSecrets(collectionPathname, environment); } - const envFilePath = path.join(envDirPath, `${name}.bru`); - if (fs.existsSync(envFilePath)) { - throw new Error(`environment: ${envFilePath} already exists`); - } + const content = envJsonToBru(environment); - const content = envJsonToBru({ - variables: variables - }); await writeFile(envFilePath, content); } catch (error) { return Promise.reject(error); From cdfa839cf33edaf0d7d07c72ca162e6b57f1325a Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 16 Oct 2023 03:41:47 +0530 Subject: [PATCH 34/65] chore: polish request timeout --- .../src/components/Preferences/Font/index.js | 22 +++++++++---------- .../components/Preferences/General/index.js | 20 +++++++++-------- .../bruno-electron/src/ipc/network/index.js | 19 ++++++++-------- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/packages/bruno-app/src/components/Preferences/Font/index.js b/packages/bruno-app/src/components/Preferences/Font/index.js index bae23e7232..2f27fea8b7 100644 --- a/packages/bruno-app/src/components/Preferences/Font/index.js +++ b/packages/bruno-app/src/components/Preferences/Font/index.js @@ -30,18 +30,16 @@ const Font = ({ close }) => { return ( -
- -
+
diff --git a/packages/bruno-electron/package.json b/packages/bruno-electron/package.json index ffe266f3e4..70a74326be 100644 --- a/packages/bruno-electron/package.json +++ b/packages/bruno-electron/package.json @@ -1,5 +1,5 @@ { - "version": "v0.24.0", + "version": "v0.25.0", "name": "bruno", "description": "Opensource API Client for Exploring and Testing APIs", "homepage": "https://www.usebruno.com", From ddd479ed4509ff2fa271a2bb2e0561be822b7fb6 Mon Sep 17 00:00:00 2001 From: Ross Gargett <73141350+Ross-Gargett@users.noreply.github.com> Date: Sun, 15 Oct 2023 19:38:47 -0700 Subject: [PATCH 36/65] fix: prevent non-numerical or negative timeouts --- .../bruno-app/src/components/Preferences/General/index.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/bruno-app/src/components/Preferences/General/index.js b/packages/bruno-app/src/components/Preferences/General/index.js index 2dbcfab762..917b5765d8 100644 --- a/packages/bruno-app/src/components/Preferences/General/index.js +++ b/packages/bruno-app/src/components/Preferences/General/index.js @@ -25,8 +25,9 @@ const General = ({ close }) => { }; const handleTimeoutChange = (value) => { - const validTimeout = isNaN(Number(value)) ? timeout : Number(value); - setTimeout(validTimeout); + if (/^[0-9]\d*$/.test(value) || value === '') { + setTimeout(value); + } }; return ( @@ -47,13 +48,14 @@ const General = ({ close }) => { handleTimeoutChange(e.target.value)} defaultValue={timeout === 0 ? '' : timeout} + value={timeout} />
From 1244716b9b6c511e39d8a12a8ccbe9bd471d6f35 Mon Sep 17 00:00:00 2001 From: Prem Kumar Easwaran Date: Mon, 16 Oct 2023 11:19:55 +0530 Subject: [PATCH 37/65] Change import icon --- .../Environments/EnvironmentSettings/EnvironmentList/index.js | 4 ++-- packages/bruno-app/src/components/Welcome/index.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js index 44e18455f6..dd7ac4f798 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js @@ -5,7 +5,7 @@ import { toastError } from 'utils/common/error'; import usePrevious from 'hooks/usePrevious'; import EnvironmentDetails from './EnvironmentDetails'; import CreateEnvironment from '../CreateEnvironment'; -import { IconUpload } from '@tabler/icons'; +import { IconDownload } from '@tabler/icons'; import ImportEnvironment from '../ImportEnvironment'; import StyledWrapper from './StyledWrapper'; @@ -73,7 +73,7 @@ const EnvironmentList = ({ collection }) => {
setOpenImportModal(true)}> - + Import
diff --git a/packages/bruno-app/src/components/Welcome/index.js b/packages/bruno-app/src/components/Welcome/index.js index 2785165385..adfce3dd88 100644 --- a/packages/bruno-app/src/components/Welcome/index.js +++ b/packages/bruno-app/src/components/Welcome/index.js @@ -2,7 +2,7 @@ import { useState } from 'react'; import toast from 'react-hot-toast'; import { useDispatch } from 'react-redux'; import { openCollection, importCollection } from 'providers/ReduxStore/slices/collections/actions'; -import { IconBrandGithub, IconPlus, IconUpload, IconFolders, IconSpeakerphone, IconBook } from '@tabler/icons'; +import { IconBrandGithub, IconPlus, IconDownload, IconFolders, IconSpeakerphone, IconBook } from '@tabler/icons'; import Bruno from 'components/Bruno'; import CreateCollection from 'components/Sidebar/CreateCollection'; @@ -69,7 +69,7 @@ const Welcome = () => { Open Collection
setImportCollectionModalOpen(true)}> - + Import Collection From 6e7fc2a9aae6a61163bf9a6eb86af79c3d2c2a42 Mon Sep 17 00:00:00 2001 From: "Donus(ADA)" Date: Mon, 16 Oct 2023 12:48:37 +0700 Subject: [PATCH 38/65] feat: add json body prettify --- .../RequestBody/RequestBodyMode/index.js | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/bruno-app/src/components/RequestPane/RequestBody/RequestBodyMode/index.js b/packages/bruno-app/src/components/RequestPane/RequestBody/RequestBodyMode/index.js index 0d3b63df6d..407d42a0d9 100644 --- a/packages/bruno-app/src/components/RequestPane/RequestBody/RequestBodyMode/index.js +++ b/packages/bruno-app/src/components/RequestPane/RequestBody/RequestBodyMode/index.js @@ -6,12 +6,15 @@ import { useDispatch } from 'react-redux'; import { updateRequestBodyMode } from 'providers/ReduxStore/slices/collections'; import { humanizeRequestBodyMode } from 'utils/collections'; import StyledWrapper from './StyledWrapper'; +import { updateRequestBody } from 'providers/ReduxStore/slices/collections/index'; +import { toastError } from 'utils/common/error'; const RequestBodyMode = ({ item, collection }) => { const dispatch = useDispatch(); const dropdownTippyRef = useRef(); const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); - const bodyMode = item.draft ? get(item, 'draft.request.body.mode') : get(item, 'request.body.mode'); + const body = item.draft ? get(item, 'draft.request.body') : get(item, 'request.body'); + const bodyMode = body?.mode; const Icon = forwardRef((props, ref) => { return ( @@ -31,6 +34,24 @@ const RequestBodyMode = ({ item, collection }) => { ); }; + const onPrettify = () => { + if (body?.json && bodyMode === 'json') { + try { + const bodyJson = JSON.parse(body.json); + const prettyBodyJson = JSON.stringify(bodyJson, null, 2); + dispatch( + updateRequestBody({ + content: prettyBodyJson, + itemUid: item.uid, + collectionUid: collection.uid + }) + ); + } catch (e) { + toastError(new Error('Unable to prettify. Invalid JSON format.')); + } + } + }; + return (
@@ -103,6 +124,11 @@ const RequestBodyMode = ({ item, collection }) => {
+ {bodyMode === 'json' && ( + + )}
); }; From 3fa87fe99a95a12a645273d14e08b2d176ee41b3 Mon Sep 17 00:00:00 2001 From: Mirko Golze Date: Mon, 16 Oct 2023 11:37:35 +0200 Subject: [PATCH 39/65] merge main into proxy branch --- .../bruno-electron/src/ipc/network/index.js | 27 +++---------------- .../bruno-electron/src/store/preferences.js | 2 +- 2 files changed, 5 insertions(+), 24 deletions(-) diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 9562f2cdf8..33eb69da91 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -88,16 +88,6 @@ const configureRequest = async (collectionUid, request, envVars, collectionVaria const httpsAgentRequestFields = {}; if (!preferences.isTlsVerification()) { httpsAgentRequestFields['rejectUnauthorized'] = false; - } else { - const cacCrtArray = [preferences.getCaCert(), process.env.SSL_CERT_FILE, process.env.NODE_EXTRA_CA_CERTS]; - let caCertFile = cacCrtArray.find((el) => el); - if (caCertFile && caCertFile.length > 1) { - try { - httpsAgentRequestFields['ca'] = fs.readFileSync(caCertFile); - } catch (err) { - console.log('Error reading CA cert file:' + caCertFile, err); - } - } } const brunoConfig = getBrunoConfig(collectionUid); @@ -109,8 +99,7 @@ const configureRequest = async (collectionUid, request, envVars, collectionVaria // client certificate config const clientCertConfig = get(brunoConfig, 'clientCertificates.certs', []); - - for (clientCert of clientCertConfig) { + for (let clientCert of clientCertConfig) { const domain = interpolateString(clientCert.domain, interpolationOptions); const certFilePath = interpolateString(clientCert.certFilePath, interpolationOptions); const keyFilePath = interpolateString(clientCert.keyFilePath, interpolationOptions); @@ -139,19 +128,13 @@ const configureRequest = async (collectionUid, request, envVars, collectionVaria } const shouldProxy = shouldUseProxy(request.url, get(proxyConfig, 'noProxy', '')); if ((proxyEnabled === true || proxyEnabled === 'enabled') && shouldProxy) { - let proxyUri; - const interpolationOptions = { - envVars, - collectionVariables, - processEnvVars - }; - const proxyProtocol = interpolateString(get(proxyConfig, 'protocol'), interpolationOptions); const proxyHostname = interpolateString(get(proxyConfig, 'hostname'), interpolationOptions); const proxyPort = interpolateString(get(proxyConfig, 'port'), interpolationOptions); const proxyAuthEnabled = get(proxyConfig, 'auth.enabled', false); const socksEnabled = proxyProtocol.includes('socks'); + let proxyUri; if (proxyAuthEnabled) { const proxyAuthUsername = interpolateString(get(proxyConfig, 'auth.username'), interpolationOptions); const proxyAuthPassword = interpolateString(get(proxyConfig, 'auth.password'), interpolationOptions); @@ -186,9 +169,7 @@ const configureRequest = async (collectionUid, request, envVars, collectionVaria delete request.awsv4config; } - const preferences = getPreferences(); - const timeout = get(preferences, 'request.timeout', 0); - request.timeout = timeout; + request.timeout = preferences.getTimeout(); return axiosInstance; }; @@ -744,7 +725,7 @@ const registerNetworkIpc = (mainWindow) => { // run post-response vars const postResponseVars = get(request, 'vars.res', []); - if (postResponseVars && postResponseVars.length) { + if (postResponseVars?.length) { const varsRuntime = new VarsRuntime(); const result = varsRuntime.runPostResponseVars( postResponseVars, diff --git a/packages/bruno-electron/src/store/preferences.js b/packages/bruno-electron/src/store/preferences.js index 3606086b35..88d411e682 100644 --- a/packages/bruno-electron/src/store/preferences.js +++ b/packages/bruno-electron/src/store/preferences.js @@ -106,7 +106,7 @@ const preferences = { }, getTimeout: () => { - return get(getPreferences(), 'request.timeout'); + return get(getPreferences(), 'request.timeout', 0); }, getProxyConfig: () => { From 19463cd0cf59c20a6dd415eee924cd8b11e462f3 Mon Sep 17 00:00:00 2001 From: Mirko Golze Date: Mon, 16 Oct 2023 12:08:10 +0200 Subject: [PATCH 40/65] merge main into proxy branch --- .../src/runner/run-single-request.js | 48 ++++++++++++++----- .../bruno-electron/src/ipc/network/index.js | 21 ++++---- 2 files changed, 44 insertions(+), 25 deletions(-) diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js index b0cffb9002..59339c0cbb 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -48,7 +48,7 @@ const runSingleRequest = async function ( // run pre-request vars const preRequestVars = get(bruJson, 'request.vars.req'); - if (preRequestVars && preRequestVars.length) { + if (preRequestVars?.length) { const varsRuntime = new VarsRuntime(); varsRuntime.runPreRequestVars( preRequestVars, @@ -65,7 +65,7 @@ const runSingleRequest = async function ( get(collectionRoot, 'request.script.req'), get(bruJson, 'request.script.req') ]).join(os.EOL); - if (requestScriptFile && requestScriptFile.length) { + if (requestScriptFile?.length) { const scriptRuntime = new ScriptRuntime(); await scriptRuntime.runRequestScript( decomment(requestScriptFile), @@ -99,23 +99,45 @@ const runSingleRequest = async function ( } } + const interpolationOptions = { + envVars: envVariables, + collectionVariables, + processEnvVars + }; + + // client certificate config + const clientCertConfig = get(brunoConfig, 'clientCertificates.certs', []); + for (let clientCert of clientCertConfig) { + const domain = interpolateString(clientCert.domain, interpolationOptions); + const certFilePath = interpolateString(clientCert.certFilePath, interpolationOptions); + const keyFilePath = interpolateString(clientCert.keyFilePath, interpolationOptions); + if (domain && certFilePath && keyFilePath) { + const hostRegex = '^https:\\/\\/' + domain.replaceAll('.', '\\.').replaceAll('*', '.*'); + + if (request.url.match(hostRegex)) { + try { + httpsAgentRequestFields['cert'] = fs.readFileSync(certFilePath); + httpsAgentRequestFields['key'] = fs.readFileSync(keyFilePath); + } catch (err) { + console.log('Error reading cert/key file', err); + } + httpsAgentRequestFields['passphrase'] = interpolateString(clientCert.passphrase, interpolationOptions); + break; + } + } + } + // set proxy if enabled const proxyEnabled = get(brunoConfig, 'proxy.enabled', false); const shouldProxy = shouldUseProxy(request.url, get(brunoConfig, 'proxy.noProxy', '')); if (proxyEnabled && shouldProxy) { - let proxyUri; - const interpolationOptions = { - envVars: envVariables, - collectionVariables, - processEnvVars - }; - const proxyProtocol = interpolateString(get(brunoConfig, 'proxy.protocol'), interpolationOptions); const proxyHostname = interpolateString(get(brunoConfig, 'proxy.hostname'), interpolationOptions); const proxyPort = interpolateString(get(brunoConfig, 'proxy.port'), interpolationOptions); const proxyAuthEnabled = get(brunoConfig, 'proxy.auth.enabled', false); const socksEnabled = proxyProtocol.includes('socks'); + let proxyUri; if (proxyAuthEnabled) { const proxyAuthUsername = interpolateString(get(brunoConfig, 'proxy.auth.username'), interpolationOptions); const proxyAuthPassword = interpolateString(get(brunoConfig, 'proxy.auth.password'), interpolationOptions); @@ -159,7 +181,7 @@ const runSingleRequest = async function ( responseTime = response.headers.get('request-duration'); response.headers.delete('request-duration'); } catch (err) { - if (err && err.response) { + if (err?.response) { response = err.response; // Prevents the duration on leaking to the actual result @@ -195,7 +217,7 @@ const runSingleRequest = async function ( // run post-response vars const postResponseVars = get(bruJson, 'request.vars.res'); - if (postResponseVars && postResponseVars.length) { + if (postResponseVars?.length) { const varsRuntime = new VarsRuntime(); varsRuntime.runPostResponseVars( postResponseVars, @@ -213,7 +235,7 @@ const runSingleRequest = async function ( get(collectionRoot, 'request.script.res'), get(bruJson, 'request.script.res') ]).join(os.EOL); - if (responseScriptFile && responseScriptFile.length) { + if (responseScriptFile?.length) { const scriptRuntime = new ScriptRuntime(); await scriptRuntime.runResponseScript( decomment(responseScriptFile), @@ -271,7 +293,7 @@ const runSingleRequest = async function ( testResults = get(result, 'results', []); } - if (testResults && testResults.length) { + if (testResults?.length) { each(testResults, (testResult) => { if (testResult.status === 'pass') { console.log(chalk.green(` ✓ `) + chalk.dim(testResult.description)); diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 33eb69da91..28b344084f 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -225,7 +225,7 @@ const registerNetworkIpc = (mainWindow) => { // run pre-request vars const preRequestVars = get(request, 'vars.req', []); - if (preRequestVars && preRequestVars.length) { + if (preRequestVars?.length) { const varsRuntime = new VarsRuntime(); const result = varsRuntime.runPreRequestVars( preRequestVars, @@ -250,7 +250,7 @@ const registerNetworkIpc = (mainWindow) => { const requestScript = compact([get(collectionRoot, 'request.script.req'), get(request, 'script.req')]).join( os.EOL ); - if (requestScript && requestScript.length) { + if (requestScript?.length) { const scriptRuntime = new ScriptRuntime(); const result = await scriptRuntime.runRequestScript( decomment(requestScript), @@ -309,7 +309,7 @@ const registerNetworkIpc = (mainWindow) => { // run post-response vars const postResponseVars = get(request, 'vars.res', []); - if (postResponseVars && postResponseVars.length) { + if (postResponseVars?.length) { const varsRuntime = new VarsRuntime(); const result = varsRuntime.runPostResponseVars( postResponseVars, @@ -335,7 +335,7 @@ const registerNetworkIpc = (mainWindow) => { const responseScript = compact([get(collectionRoot, 'request.script.res'), get(request, 'script.res')]).join( os.EOL ); - if (responseScript && responseScript.length) { + if (responseScript?.length) { const scriptRuntime = new ScriptRuntime(); const result = await scriptRuntime.runResponseScript( decomment(responseScript), @@ -438,7 +438,7 @@ const registerNetworkIpc = (mainWindow) => { return Promise.reject(error); } - if (error && error.response) { + if (error?.response) { // run assertions const assertions = get(request, 'assertions'); if (assertions) { @@ -530,10 +530,7 @@ const registerNetworkIpc = (mainWindow) => { const collectionRoot = get(collection, 'root', {}); const preparedRequest = prepareGqlIntrospectionRequest(endpoint, envVars, request, collectionRoot); - const preferences = getPreferences(); - const timeout = get(preferences, 'request.timeout', 0); - request.timeout = timeout; - const sslVerification = get(preferences, 'request.sslVerification', true); + request.timeout = preferences.getTimeout(); if (!preferences.isTlsVerification()) { request.httpsAgent = new https.Agent({ @@ -673,7 +670,7 @@ const registerNetworkIpc = (mainWindow) => { const requestScript = compact([get(collectionRoot, 'request.script.req'), get(request, 'script.req')]).join( os.EOL ); - if (requestScript && requestScript.length) { + if (requestScript?.length) { const scriptRuntime = new ScriptRuntime(); const result = await scriptRuntime.runRequestScript( decomment(requestScript), @@ -751,7 +748,7 @@ const registerNetworkIpc = (mainWindow) => { get(collectionRoot, 'request.script.res'), get(request, 'script.res') ]).join(os.EOL); - if (responseScript && responseScript.length) { + if (responseScript?.length) { const scriptRuntime = new ScriptRuntime(); const result = await scriptRuntime.runResponseScript( decomment(responseScript), @@ -845,7 +842,7 @@ const registerNetworkIpc = (mainWindow) => { duration = timeEnd - timeStart; } - if (error && error.response) { + if (error?.response) { responseReceived = { status: error.response.status, statusText: error.response.statusText, From 1b9ad34f3f2eab5637165dd5119084baac47b243 Mon Sep 17 00:00:00 2001 From: Mirko Golze Date: Mon, 16 Oct 2023 12:12:09 +0200 Subject: [PATCH 41/65] merge main into proxy branch --- packages/bruno-app/src/utils/network/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/bruno-app/src/utils/network/index.js b/packages/bruno-app/src/utils/network/index.js index 8952e1986a..c54c3338ed 100644 --- a/packages/bruno-app/src/utils/network/index.js +++ b/packages/bruno-app/src/utils/network/index.js @@ -45,6 +45,8 @@ export const fetchGqlSchema = async (endpoint, environment, request, collection) export const cancelNetworkRequest = async (cancelTokenUid) => { return new Promise((resolve, reject) => { + const { ipcRenderer } = window; + ipcRenderer.invoke('cancel-http-request', cancelTokenUid).then(resolve).catch(reject); }); }; From 63e0df1640ba6dba180b42cf3aa1623862e93148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linhart=20Luk=C3=A1=C5=A1?= Date: Mon, 16 Oct 2023 14:11:01 +0200 Subject: [PATCH 42/65] fix collection client certs --- .../bruno-electron/src/ipc/network/index.js | 55 +++++++++++++++---- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index ebd780ece9..89be40f366 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -662,6 +662,10 @@ const registerNetworkIpc = (mainWindow) => { const timeout = get(preferences, 'request.timeout', 0); request.timeout = timeout; const sslVerification = get(preferences, 'request.sslVerification', true); + const httpsAgentRequestFields = {}; + if (!sslVerification) { + httpsAgentRequestFields['rejectUnauthorized'] = false; + } // run pre-request script const requestScript = compact([get(collectionRoot, 'request.script.req'), get(request, 'script.req')]).join( @@ -704,17 +708,43 @@ const registerNetworkIpc = (mainWindow) => { ...eventData }); - // proxy configuration + const interpolationOptions = { + envVars, + collectionVariables, + processEnvVars + }; const brunoConfig = getBrunoConfig(collectionUid); + + // client certificate config + const clientCertConfig = get(brunoConfig, 'clientCertificates.certs', []); + + for (clientCert of clientCertConfig) { + const domain = interpolateString(clientCert.domain, interpolationOptions); + const certFilePath = interpolateString(clientCert.certFilePath, interpolationOptions); + const keyFilePath = interpolateString(clientCert.keyFilePath, interpolationOptions); + if (domain && certFilePath && keyFilePath) { + const hostRegex = '^https:\\/\\/' + domain.replaceAll('.', '\\.').replaceAll('*', '.*'); + + if (request.url.match(hostRegex)) { + try { + httpsAgentRequestFields['cert'] = fs.readFileSync(certFilePath); + httpsAgentRequestFields['key'] = fs.readFileSync(keyFilePath); + } catch (err) { + console.log('Error reading cert/key file', err); + } + httpsAgentRequestFields['passphrase'] = interpolateString( + clientCert.passphrase, + interpolationOptions + ); + break; + } + } + } + + // proxy configuration const proxyEnabled = get(brunoConfig, 'proxy.enabled', false); if (proxyEnabled) { let proxyUri; - const interpolationOptions = { - envVars, - collectionVariables, - processEnvVars - }; - const proxyProtocol = interpolateString(get(brunoConfig, 'proxy.protocol'), interpolationOptions); const proxyHostname = interpolateString(get(brunoConfig, 'proxy.hostname'), interpolationOptions); const proxyPort = interpolateString(get(brunoConfig, 'proxy.port'), interpolationOptions); @@ -743,15 +773,16 @@ const registerNetworkIpc = (mainWindow) => { request.httpsAgent = socksProxyAgent; request.httpAgent = socksProxyAgent; } else { - request.httpsAgent = new HttpsProxyAgent(proxyUri, { - rejectUnauthorized: sslVerification - }); + request.httpsAgent = new HttpsProxyAgent( + proxyUri, + Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined + ); request.httpAgent = new HttpProxyAgent(proxyUri); } - } else if (!sslVerification) { + } else if (Object.keys(httpsAgentRequestFields).length > 0) { request.httpsAgent = new https.Agent({ - rejectUnauthorized: false + ...httpsAgentRequestFields }); } From b28f7625e42b14aa1982a4b9c18a37c3b17d382d Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Tue, 17 Oct 2023 06:52:52 +0530 Subject: [PATCH 43/65] chore: update body selector styles --- .../RequestBody/RequestBodyMode/StyledWrapper.js | 6 +++++- .../RequestPane/RequestBody/RequestBodyMode/index.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/bruno-app/src/components/RequestPane/RequestBody/RequestBodyMode/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/RequestBody/RequestBodyMode/StyledWrapper.js index e727745243..3d571b4bf1 100644 --- a/packages/bruno-app/src/components/RequestPane/RequestBody/RequestBodyMode/StyledWrapper.js +++ b/packages/bruno-app/src/components/RequestPane/RequestBody/RequestBodyMode/StyledWrapper.js @@ -4,7 +4,7 @@ const Wrapper = styled.div` font-size: 0.8125rem; .body-mode-selector { - background: ${(props) => props.theme.requestTabPanel.bodyModeSelect.color}; + background: transparent; border-radius: 3px; .dropdown-item { @@ -15,6 +15,10 @@ const Wrapper = styled.div` .label-item { padding: 0.2rem 0.6rem !important; } + + .selected-body-mode { + color: ${(props) => props.theme.colors.text.yellow}; + } } .caret { diff --git a/packages/bruno-app/src/components/RequestPane/RequestBody/RequestBodyMode/index.js b/packages/bruno-app/src/components/RequestPane/RequestBody/RequestBodyMode/index.js index 407d42a0d9..ef000431fe 100644 --- a/packages/bruno-app/src/components/RequestPane/RequestBody/RequestBodyMode/index.js +++ b/packages/bruno-app/src/components/RequestPane/RequestBody/RequestBodyMode/index.js @@ -18,7 +18,7 @@ const RequestBodyMode = ({ item, collection }) => { const Icon = forwardRef((props, ref) => { return ( -
+
{humanizeRequestBodyMode(bodyMode)}
); From 67218f5bb496ce7b5713e053d08d5cb8eb56763d Mon Sep 17 00:00:00 2001 From: Ross Gargett <73141350+Ross-Gargett@users.noreply.github.com> Date: Mon, 16 Oct 2023 21:08:22 -0700 Subject: [PATCH 44/65] feedback: use formik and Yup for preferences form --- .../components/Preferences/General/index.js | 121 +++++++++++------- .../src/providers/ReduxStore/slices/app.js | 3 +- 2 files changed, 76 insertions(+), 48 deletions(-) diff --git a/packages/bruno-app/src/components/Preferences/General/index.js b/packages/bruno-app/src/components/Preferences/General/index.js index 917b5765d8..b67932eb6f 100644 --- a/packages/bruno-app/src/components/Preferences/General/index.js +++ b/packages/bruno-app/src/components/Preferences/General/index.js @@ -1,69 +1,96 @@ -import React, { useState } from 'react'; +import React from 'react'; +import { useFormik } from 'formik'; import { useSelector, useDispatch } from 'react-redux'; import { savePreferences } from 'providers/ReduxStore/slices/app'; import StyledWrapper from './StyledWrapper'; +import * as Yup from 'yup'; +import toast from 'react-hot-toast'; const General = ({ close }) => { const preferences = useSelector((state) => state.app.preferences); const dispatch = useDispatch(); - const [sslVerification, setSslVerification] = useState(preferences.request.sslVerification); - const [timeout, setTimeout] = useState(preferences.request.timeout); + const preferencesSchema = Yup.object().shape({ + sslVerification: Yup.boolean(), + timeout: Yup.number('Request Timeout must be a number') + .positive('Request Timeout must be a positive number') + .typeError('Request Timeout must be a number') + .optional() + }); - const handleSave = () => { + const formik = useFormik({ + initialValues: { + sslVerification: preferences.request.sslVerification, + timeout: preferences.request.timeout + }, + validationSchema: preferencesSchema, + onSubmit: async (values) => { + try { + const newPreferences = await proxySchema.validate(values, { abortEarly: true }); + handleSave(newPreferences); + } catch (error) { + console.error('Preferences validation error:', error.message); + } + } + }); + + const handleSave = (newPreferences) => { dispatch( savePreferences({ ...preferences, request: { - sslVerification, - timeout + sslVerification: newPreferences.sslVerification, + timeout: newPreferences.timeout } }) - ).then(() => { - close(); - }); - }; - - const handleTimeoutChange = (value) => { - if (/^[0-9]\d*$/.test(value) || value === '') { - setTimeout(value); - } + ) + .then(() => { + close(); + }) + .catch((err) => console.log(err) && toast.error('Failed to update preferences')); }; return ( -
- - setSslVerification(!sslVerification)} - className="mousetrap mr-0" - /> -
-
- - handleTimeoutChange(e.target.value)} - defaultValue={timeout === 0 ? '' : timeout} - value={timeout} - /> -
- -
- -
+ +
+ + +
+
+ + +
+ {formik.touched.timeout && formik.errors.timeout ? ( +
{formik.errors.timeout}
+ ) : null} +
+ +
+
); }; diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/app.js b/packages/bruno-app/src/providers/ReduxStore/slices/app.js index c3a3aa5a87..6f5c849f94 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/app.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/app.js @@ -9,7 +9,8 @@ const initialState = { showHomePage: false, preferences: { request: { - sslVerification: true + sslVerification: true, + timeout: 0 }, font: { codeFont: 'default' From ba9766fbf02bbaf69993a1adb238a0e8c8e9f3a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linhart=20Luk=C3=A1=C5=A1?= Date: Tue, 17 Oct 2023 08:41:56 +0200 Subject: [PATCH 45/65] fix js build --- scripts/build-electron.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/build-electron.js b/scripts/build-electron.js index 363fdf4d22..47693b47f1 100644 --- a/scripts/build-electron.js +++ b/scripts/build-electron.js @@ -3,7 +3,6 @@ const fs = require('fs-extra'); const util = require('util'); const exec = util.promisify(require('child_process').exec); - async function deleteFileIfExists(filePath) { try { const exists = await fs.pathExists(filePath); @@ -73,7 +72,7 @@ async function main() { } // Remove sourcemaps - await removeSourceMapFiles('packages/bruno-electron/web') + await removeSourceMapFiles('packages/bruno-electron/web'); // Run npm dist command console.log('Building the Electron distribution'); @@ -88,8 +87,7 @@ async function main() { osArg = 'linux'; } - await exec(`npm run dist-${osArg} --workspace=packages/bruno-electron`); - + await exec(`npm run dist:${osArg} --workspace=packages/bruno-electron`); } catch (error) { console.error('An error occurred:', error); } From 544edfa7d770aa9a01373218a8eb670123e779f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Heikki=20P=C3=B6l=C3=B6nen?= Date: Tue, 17 Oct 2023 11:42:40 +0300 Subject: [PATCH 46/65] Adds variable interpolate to assert runtime --- packages/bruno-js/src/interpolate-string.js | 55 +++++++++++++++++++ .../bruno-js/src/runtime/assert-runtime.js | 13 +++-- 2 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 packages/bruno-js/src/interpolate-string.js diff --git a/packages/bruno-js/src/interpolate-string.js b/packages/bruno-js/src/interpolate-string.js new file mode 100644 index 0000000000..33701dd0b0 --- /dev/null +++ b/packages/bruno-js/src/interpolate-string.js @@ -0,0 +1,55 @@ +const Handlebars = require('handlebars'); +const { forOwn, cloneDeep } = require('lodash'); + +const interpolateEnvVars = (str, processEnvVars) => { + if (!str || !str.length || typeof str !== 'string') { + return str; + } + + const template = Handlebars.compile(str, { noEscape: true }); + + return template({ + process: { + env: { + ...processEnvVars + } + } + }); +}; + +const interpolateString = (str, { envVars, collectionVariables, processEnvVars }) => { + if (!str || !str.length || typeof str !== 'string') { + return str; + } + + processEnvVars = processEnvVars || {}; + collectionVariables = collectionVariables || {}; + + // we clone envVars because we don't want to modify the original object + envVars = envVars ? cloneDeep(envVars) : {}; + + // envVars can inturn have values as {{process.env.VAR_NAME}} + // so we need to interpolate envVars first with processEnvVars + forOwn(envVars, (value, key) => { + envVars[key] = interpolateEnvVars(value, processEnvVars); + }); + + const template = Handlebars.compile(str, { noEscape: true }); + + // collectionVariables take precedence over envVars + const combinedVars = { + ...envVars, + ...collectionVariables, + process: { + env: { + ...processEnvVars + } + } + }; + + return template(combinedVars); +}; + +module.exports = { + interpolateString +}; diff --git a/packages/bruno-js/src/runtime/assert-runtime.js b/packages/bruno-js/src/runtime/assert-runtime.js index 06f6adab41..fa38270979 100644 --- a/packages/bruno-js/src/runtime/assert-runtime.js +++ b/packages/bruno-js/src/runtime/assert-runtime.js @@ -4,6 +4,7 @@ const { nanoid } = require('nanoid'); const Bru = require('../bru'); const BrunoRequest = require('../bruno-request'); const { evaluateJsTemplateLiteral, evaluateJsExpression, createResponseParser } = require('../utils'); +const { interpolateString } = require('../interpolate-string'); const { expect } = chai; chai.use(require('chai-string')); @@ -167,11 +168,15 @@ const evaluateRhsOperand = (rhsOperand, operator, context) => { rhsOperand = rhsOperand.substring(1, rhsOperand.length - 1); } - return rhsOperand.split(',').map((v) => evaluateJsTemplateLiteral(v.trim(), context)); + return rhsOperand + .split(',') + .map((v) => evaluateJsTemplateLiteral(interpolateString(v.trim(), context.bru), context)); } if (operator === 'between') { - const [lhs, rhs] = rhsOperand.split(',').map((v) => evaluateJsTemplateLiteral(v.trim(), context)); + const [lhs, rhs] = rhsOperand + .split(',') + .map((v) => evaluateJsTemplateLiteral(interpolateString(v.trim(), context.bru), context)); return [lhs, rhs]; } @@ -181,10 +186,10 @@ const evaluateRhsOperand = (rhsOperand, operator, context) => { rhsOperand = rhsOperand.substring(1, rhsOperand.length - 1); } - return rhsOperand; + return interpolateString(rhsOperand, context.bru); } - return evaluateJsTemplateLiteral(rhsOperand, context); + return evaluateJsTemplateLiteral(interpolateString(rhsOperand, context.bru), context); }; class AssertRuntime { From 95190fa4243c3032db8d381a77d88ef20960f536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linhart=20Luk=C3=A1=C5=A1?= Date: Tue, 17 Oct 2023 10:45:40 +0200 Subject: [PATCH 47/65] Print log from inner npm run --- scripts/build-electron.js | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/scripts/build-electron.js b/scripts/build-electron.js index 47693b47f1..291491dde3 100644 --- a/scripts/build-electron.js +++ b/scripts/build-electron.js @@ -1,7 +1,7 @@ const os = require('os'); const fs = require('fs-extra'); const util = require('util'); -const exec = util.promisify(require('child_process').exec); +const spawn = util.promisify(require('child_process').spawn); async function deleteFileIfExists(filePath) { try { @@ -46,6 +46,25 @@ async function removeSourceMapFiles(directory) { } } +async function execCommandWithOutput(command) { + return new Promise(async (resolve, reject) => { + const childProcess = await spawn(command, { + stdio: 'inherit', + shell: true + }); + childProcess.on('error', (error) => { + reject(error); + }); + childProcess.on('exit', (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`Command exited with code ${code}.`)); + } + }); + }); +} + async function main() { try { // Remove out directory @@ -87,7 +106,7 @@ async function main() { osArg = 'linux'; } - await exec(`npm run dist:${osArg} --workspace=packages/bruno-electron`); + await execCommandWithOutput(`npm run dist:${osArg} --workspace=packages/bruno-electron`); } catch (error) { console.error('An error occurred:', error); } From 8c101a8684e0d0c3ab88bdc2c99690253c80e750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linhart=20Luk=C3=A1=C5=A1?= Date: Tue, 17 Oct 2023 10:46:00 +0200 Subject: [PATCH 48/65] Fix _next replacement --- scripts/build-electron.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build-electron.js b/scripts/build-electron.js index 291491dde3..9825c3a09b 100644 --- a/scripts/build-electron.js +++ b/scripts/build-electron.js @@ -85,7 +85,7 @@ async function main() { for (const file of files) { if (file.endsWith('.html')) { let content = await fs.readFile(`packages/bruno-electron/web/${file}`, 'utf8'); - content = content.replace(/\/_next\//g, '/_next/'); + content = content.replace(/\/_next\//g, '_next/'); await fs.writeFile(`packages/bruno-electron/web/${file}`, content); } } From 7e654a81bfe4db4b5fb52309329ca1dcfb5587d4 Mon Sep 17 00:00:00 2001 From: Mykola Makhin Date: Tue, 17 Oct 2023 12:06:45 +0300 Subject: [PATCH 49/65] Ukrainian translation for ReadMe page --- readme.md | 2 +- readme_ru.md | 2 +- readme_ua.md | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 readme_ua.md diff --git a/readme.md b/readme.md index 8021a5b7af..cf3d2dc61b 100644 --- a/readme.md +++ b/readme.md @@ -10,7 +10,7 @@ [![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) -**English** | [Русский](/readme_ru.md) +**English** | [Українська](/readme_ua.md) | [Русский](/readme_ru.md) Bruno is a new and innovative API client, aimed at revolutionizing the status quo represented by Postman and similar tools out there. diff --git a/readme_ru.md b/readme_ru.md index 8c25e5c574..d8b8255f73 100644 --- a/readme_ru.md +++ b/readme_ru.md @@ -10,7 +10,7 @@ [![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) -[English](/readme.md) | **Русский** +[English](/readme.md) | [Українська](/readme_ua.md) | **Русский** Bruno - новый и инновационный клиент API, направленный на революцию в установившейся ситуации, представленной Postman и подобными инструментами. diff --git a/readme_ua.md b/readme_ua.md new file mode 100644 index 0000000000..02df2bb49e --- /dev/null +++ b/readme_ua.md @@ -0,0 +1,80 @@ +
+ + +### Bruno - IDE із відкритим кодом для тестування та дослідження API + +[![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) +[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse) +[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno) +[![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) + +[English](/readme.md) | **Українська** | [Русский](/readme_ru.md) + +Bruno це новий та іноваційний API клієнт, націлений на революційну зміну статус кво, запровадженого інструментами на кшталт Postman. + +Bruno зберігає ваші колекції напряму у теці на вашому диску. Він використовує текстову мову розмітки Bru для збереження інформації про ваші API запити. + +Ви можете використовувати git або будь-яку іншу систему контролю версій щоб спільно працювати над вашими колекціями API запитів. + +Bruno є повністю автономним. Немає жодних планів додавати будь-які синхронізації через хмару, ніколи. Ми цінуємо приватність ваших даних, і вважаєм, що вони мають залишитись лише на вашому комп'ютері. Взнати більше про наше бачення у довготривалій перспективі можна [тут](https://github.com/usebruno/bruno/discussions/269) + +![bruno](assets/images/landing-2.png)

+ +### Кросплатформенність 🖥️ + +![bruno](assets/images/run-anywhere.png)

+ +### Спільна робота через Git 👩‍💻🧑‍💻 + +Або будь-яку іншу систему контролю версій на ваш вибір + +![bruno](assets/images/version-control.png)

+ +### Важливі посилання 📌 + +- [Наше бачення довготривалої перспективи проекту](https://github.com/usebruno/bruno/discussions/269) +- [Дорожня карта проекту](https://github.com/usebruno/bruno/discussions/384) +- [Документація](https://docs.usebruno.com) +- [Сайт](https://www.usebruno.com) +- [Завантаження](https://www.usebruno.com/downloads) + +### Вітрина 🎥 + +- [Відгуки](https://github.com/usebruno/bruno/discussions/343) +- [Хаб знань](https://github.com/usebruno/bruno/discussions/386) +- [Scriptmania](https://github.com/usebruno/bruno/discussions/385) + +### Підтримка ❤️ + +Гав! Якщо вам сподобався проект, тисніть на ⭐ !! + +### Поділитись відгуками 📣 + +Якщо Bruno допоміг вам у вашій роботі і вашим командам, будь ласка не забудьте поділитись вашими [відгуками у github дискусії](https://github.com/usebruno/bruno/discussions/343) + +### Зробити свій внесок 👩‍💻🧑‍💻 + +Я радий що ви бажаєте покращити Bruno. Будь ласка переглянте [інструкцію по контрибуції](contributing.md) + +Навіть якщо ви не можете зробити свій внесок пишучи програмний код, будь ласка не соромтесь рапортувати про помилки і писати запити на новий функціонал, який потрібен вам у вашій роботі. + +### Автори + + + +### Залишайтесь на зв'язку 🌐 + +[Twitter](https://twitter.com/use_bruno)
+[Сайт](https://www.usebruno.com)
+[Discord](https://discord.com/invite/KgcZUncpjq) +[LinkedIn](https://www.linkedin.com/company/usebruno) + +### Ліцензія 📄 + +[MIT](license.md) From 4ab4f09987be811fe227ce68e34e889113c08ddd Mon Sep 17 00:00:00 2001 From: Stefan Ollinger Date: Tue, 17 Oct 2023 11:23:11 +0200 Subject: [PATCH 50/65] Set body on SPARQL request --- packages/bruno-cli/src/runner/prepare-request.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/bruno-cli/src/runner/prepare-request.js b/packages/bruno-cli/src/runner/prepare-request.js index 5568ae311d..ace3b3101a 100644 --- a/packages/bruno-cli/src/runner/prepare-request.js +++ b/packages/bruno-cli/src/runner/prepare-request.js @@ -88,6 +88,13 @@ const prepareRequest = (request, collectionRoot) => { axiosRequest.data = request.body.xml; } + if (request.body.mode === 'sparql') { + if (!contentTypeDefined) { + axiosRequest.headers['content-type'] = 'application/sparql-query'; + } + axiosRequest.data = request.body.sparql; + } + if (request.body.mode === 'formUrlEncoded') { axiosRequest.headers['content-type'] = 'application/x-www-form-urlencoded'; const params = {}; From 5f94fa11744708ad99b6d746bfb28a0f9abec596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Gl=C3=BCpker?= Date: Tue, 17 Oct 2023 15:37:51 +0200 Subject: [PATCH 51/65] feat: add collection name to remove dialog --- .../Sidebar/Collections/Collection/RemoveCollection/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveCollection/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveCollection/index.js index c151de895f..cd8291af40 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveCollection/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveCollection/index.js @@ -18,7 +18,7 @@ const RemoveCollection = ({ onClose, collection }) => { return ( - Are you sure you want to remove this collection? + Are you sure you want to delete collection {collection.name} ? ); }; From 0cf4f09608d44eda618470382c1e4c3d4eeddb9e Mon Sep 17 00:00:00 2001 From: Jarne Date: Tue, 17 Oct 2023 17:25:20 +0200 Subject: [PATCH 52/65] Save window bounds only if not maximized --- packages/bruno-electron/src/index.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/bruno-electron/src/index.js b/packages/bruno-electron/src/index.js index a57faddeac..83e5078304 100644 --- a/packages/bruno-electron/src/index.js +++ b/packages/bruno-electron/src/index.js @@ -67,8 +67,14 @@ app.on('ready', async () => { mainWindow.loadURL(url); watcher = new Watcher(); - mainWindow.on('resize', () => saveBounds(mainWindow)); - mainWindow.on('move', () => saveBounds(mainWindow)); + const handleBoundsChange = () => { + if (!mainWindow.isMaximized()) { + saveBounds(mainWindow); + } + }; + + mainWindow.on('resize', handleBoundsChange); + mainWindow.on('move', handleBoundsChange); mainWindow.on('maximize', () => saveMaximized(true)); mainWindow.on('unmaximize', () => saveMaximized(false)); From ad8e9c25610f9196e607df5fb12e45aa2fb28cb6 Mon Sep 17 00:00:00 2001 From: Prem Kumar Easwaran Date: Tue, 17 Oct 2023 21:07:32 +0530 Subject: [PATCH 53/65] Autofill folder name as collection name --- .../src/components/Sidebar/CreateCollection/index.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js b/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js index 9f90976374..5c88178a35 100644 --- a/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js +++ b/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js @@ -76,7 +76,14 @@ const CreateCollection = ({ onClose }) => { name="collectionName" ref={inputRef} className="block textbox mt-2 w-full" - onChange={formik.handleChange} + onChange = { + (e) => { + formik.handleChange(e); + if (formik.values.collectionName === formik.values.collectionFolderName) { + formik.setFieldValue("collectionFolderName", e.target.value); + } + } + } autoComplete="off" autoCorrect="off" autoCapitalize="off" From d034d599c559933dd3bbe00c77197ddd7c695f66 Mon Sep 17 00:00:00 2001 From: Oleksii Slabchenko Date: Tue, 17 Oct 2023 20:45:58 +0200 Subject: [PATCH 54/65] Fix #646: show exact response size in bytes on mouse hover --- .../src/components/ResponsePane/ResponseSize/index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseSize/index.js b/packages/bruno-app/src/components/ResponsePane/ResponseSize/index.js index 2500474cba..b956b0813b 100644 --- a/packages/bruno-app/src/components/ResponsePane/ResponseSize/index.js +++ b/packages/bruno-app/src/components/ResponsePane/ResponseSize/index.js @@ -13,6 +13,10 @@ const ResponseSize = ({ size }) => { sizeToDisplay = size + 'B'; } - return {sizeToDisplay}; + return ( + + {sizeToDisplay} + + ); }; export default ResponseSize; From b150694a5cda9170ea8d8223bf20fa252e7cc338 Mon Sep 17 00:00:00 2001 From: Mykola Makhin Date: Wed, 18 Oct 2023 02:21:10 +0300 Subject: [PATCH 55/65] Ukrainian translation for the development doc --- docs/development.md | 2 +- docs/development_ru.md | 2 +- docs/development_ua.md | 55 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 docs/development_ua.md diff --git a/docs/development.md b/docs/development.md index c1c402e087..d56d3e6cf8 100644 --- a/docs/development.md +++ b/docs/development.md @@ -1,4 +1,4 @@ -**English** | [Русский](/docs/development_ru.md) +**English** | [Українська](/docs/development_ua.md) | [Русский](/docs/development_ru.md) ## Development diff --git a/docs/development_ru.md b/docs/development_ru.md index 4d4e3a80e1..3816066e0f 100644 --- a/docs/development_ru.md +++ b/docs/development_ru.md @@ -1,4 +1,4 @@ -[English](/docs/development.md) | **Русский** +[English](/docs/development.md) | [Українська](/docs/development_ua.md) | **Русский** ## Разработка diff --git a/docs/development_ua.md b/docs/development_ua.md new file mode 100644 index 0000000000..d6d5bcdf87 --- /dev/null +++ b/docs/development_ua.md @@ -0,0 +1,55 @@ +[English](/docs/development.md) | **Українська** | [Русский](/docs/development_ru.md) + +## Розробка + +Bruno розробляється як декстопний застосунок. Вам потрібно запустити nextjs в одній сесії терміналу, та запустити застосунок Electron в іншій сесії терміналу. + +### Залежності + +- NodeJS v18 + +### Локальна розробка + +```bash +# Використовуйте nodejs 18-ї версії +nvm use + +# встановіть залежності +npm i --legacy-peer-deps + +# зберіть документацію graphql +npm run build:graphql-docs + +# зберіть bruno query +npm run build:bruno-query + +# запустіть додаток next (термінал 1) +npm run dev:web + +# запустіть додаток електрон (термінал 2) +npm run dev:electron +``` + +### Усунення несправностей + +Ви можете зтикнутись із помилкою `Unsupported platform` коли запускаєте `npm install`. Щоб усунути цю проблему, вам потрібно видалити `node_modules` та `package-lock.json`, і тоді запустити `npm install`. Це має встановити всі потрібні для запуску додатку пекеджі. + +```shell +# Видаліть node_modules в піддиректоріях +find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do + rm -rf "$dir" +done + +# Видаліть package-lock в піддиректоріях +find . -type f -name "package-lock.json" -delete +``` + +### Тестування + +```bash +# bruno-schema +npm test --workspace=packages/bruno-schema + +# bruno-lang +npm test --workspace=packages/bruno-lang +``` From 924f415176fbd0c126aa45c44513a4e3ae5f9c7d Mon Sep 17 00:00:00 2001 From: Mykola Makhin Date: Wed, 18 Oct 2023 02:25:35 +0300 Subject: [PATCH 56/65] Corrected link to contributing page from UA readme --- readme_ua.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme_ua.md b/readme_ua.md index 02df2bb49e..793e11a8ed 100644 --- a/readme_ua.md +++ b/readme_ua.md @@ -56,7 +56,7 @@ Bruno є повністю автономним. Немає жодних план ### Зробити свій внесок 👩‍💻🧑‍💻 -Я радий що ви бажаєте покращити Bruno. Будь ласка переглянте [інструкцію по контрибуції](contributing.md) +Я радий що ви бажаєте покращити Bruno. Будь ласка переглянте [інструкцію по контрибуції](contributing_ua.md) Навіть якщо ви не можете зробити свій внесок пишучи програмний код, будь ласка не соромтесь рапортувати про помилки і писати запити на новий функціонал, який потрібен вам у вашій роботі. From d809a58deb6bc1d5ea7bf3dcd4d6bd55307f976a Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Wed, 18 Oct 2023 10:25:01 +0530 Subject: [PATCH 57/65] chore: pr polish (#596) --- .../CollectionSettings/ProxySettings/index.js | 82 +++++++++++-------- .../components/Preferences/General/index.js | 2 +- .../Preferences/ProxySettings/index.js | 36 ++++---- .../src/runner/run-single-request.js | 2 +- packages/bruno-cli/src/utils/proxy-util.js | 11 +-- packages/bruno-electron/src/ipc/collection.js | 1 - .../src/ipc/network/awsv4auth-helper.js | 4 +- .../bruno-electron/src/ipc/network/index.js | 18 ++-- .../bruno-electron/src/ipc/preferences.js | 59 +------------ packages/bruno-electron/src/store/index.js | 7 -- .../bruno-electron/src/store/preferences.js | 31 +++---- .../bruno-electron/src/utils/proxy-util.js | 12 +-- 12 files changed, 103 insertions(+), 162 deletions(-) delete mode 100644 packages/bruno-electron/src/store/index.js diff --git a/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js b/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js index 3f0981f8d0..fd3cc89865 100644 --- a/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js @@ -1,24 +1,24 @@ import React, { useEffect } from 'react'; import { useFormik } from 'formik'; - +import Tooltip from 'components/Tooltip'; import StyledWrapper from './StyledWrapper'; import * as Yup from 'yup'; import toast from 'react-hot-toast'; const ProxySettings = ({ proxyConfig, onUpdate }) => { const proxySchema = Yup.object({ - enabled: Yup.string().oneOf(['global', 'enabled', 'disabled']), + use: Yup.string().oneOf(['global', 'true', 'false']), protocol: Yup.string().oneOf(['http', 'https', 'socks4', 'socks5']), hostname: Yup.string() - .when('enabled', { - is: 'enabled', + .when('use', { + is: true, then: (hostname) => hostname.required('Specify the hostname for your proxy.'), otherwise: (hostname) => hostname.nullable() }) .max(1024), port: Yup.number() - .when('enabled', { - is: 'enabled', + .when('use', { + is: true, then: (port) => port.required('Specify port between 1 and 65535').typeError('Specify port between 1 and 65535'), otherwise: (port) => port.nullable().transform((_, val) => (val ? Number(val) : null)) }) @@ -26,11 +26,11 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { .max(65535), auth: Yup.object() .when('enabled', { - is: 'enabled', + is: true, then: Yup.object({ enabled: Yup.boolean(), username: Yup.string() - .when(['enabled'], { + .when('enabled', { is: true, then: (username) => username.required('Specify username for proxy authentication.') }) @@ -44,12 +44,12 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { }) }) .optional(), - noProxy: Yup.string().optional().max(1024) + bypassProxy: Yup.string().optional().max(1024) }); const formik = useFormik({ initialValues: { - enabled: proxyConfig.enabled || 'global', + use: proxyConfig.use || 'global', protocol: proxyConfig.protocol || 'http', hostname: proxyConfig.hostname || '', port: proxyConfig.port || '', @@ -58,13 +58,20 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { username: proxyConfig.auth ? proxyConfig.auth.username || '' : '', password: proxyConfig.auth ? proxyConfig.auth.password || '' : '' }, - noProxy: proxyConfig.noProxy || '' + bypassProxy: proxyConfig.bypassProxy || '' }, validationSchema: proxySchema, onSubmit: (values) => { proxySchema .validate(values, { abortEarly: true }) .then((validatedProxy) => { + // serialize 'use' to boolean + if (validatedProxy.use === 'true') { + validatedProxy.use = true; + } else if (validatedProxy.use === 'false') { + validatedProxy.use = false; + } + onUpdate(validatedProxy); }) .catch((error) => { @@ -76,7 +83,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { useEffect(() => { formik.setValues({ - enabled: proxyConfig.enabled || 'global', + use: proxyConfig.use === true ? 'true' : proxyConfig.use === false ? 'false' : 'global', protocol: proxyConfig.protocol || 'http', hostname: proxyConfig.hostname || '', port: proxyConfig.port || '', @@ -85,32 +92,37 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { username: proxyConfig.auth ? proxyConfig.auth.username || '' : '', password: proxyConfig.auth ? proxyConfig.auth.password || '' : '' }, - noProxy: proxyConfig.noProxy || '' + bypassProxy: proxyConfig.bypassProxy || '' }); }, [proxyConfig]); return (

Proxy Settings

-
-
+ `} + tooltipId="request-var" + />
-
diff --git a/packages/bruno-app/src/components/Preferences/General/index.js b/packages/bruno-app/src/components/Preferences/General/index.js index 2c7bf0228b..077ef39fae 100644 --- a/packages/bruno-app/src/components/Preferences/General/index.js +++ b/packages/bruno-app/src/components/Preferences/General/index.js @@ -33,7 +33,7 @@ const General = ({ close }) => {
{ }) }) .optional(), - noProxy: Yup.string().optional().max(1024) + bypassProxy: Yup.string().optional().max(1024) }); const formik = useFormik({ @@ -63,7 +63,7 @@ const ProxySettings = ({ close }) => { username: preferences.proxy.auth ? preferences.proxy.auth.username || '' : '', password: preferences.proxy.auth ? preferences.proxy.auth.password || '' : '' }, - noProxy: preferences.proxy.noProxy || '' + bypassProxy: preferences.proxy.bypassProxy || '' }, validationSchema: proxySchema, onSubmit: (values) => { @@ -101,21 +101,21 @@ const ProxySettings = ({ close }) => { username: preferences.proxy.auth ? preferences.proxy.auth.username || '' : '', password: preferences.proxy.auth ? preferences.proxy.auth.password || '' : '' }, - noProxy: preferences.proxy.noProxy || '' + bypassProxy: preferences.proxy.bypassProxy || '' }); }, [preferences]); return ( -

Proxy Settings

+

Global Proxy Settings

-
+
-
+
@@ -166,7 +166,7 @@ const ProxySettings = ({ close }) => {
-
+
@@ -186,7 +186,7 @@ const ProxySettings = ({ close }) => {
{formik.errors.hostname}
) : null}
-
+
@@ -206,7 +206,7 @@ const ProxySettings = ({ close }) => {
{formik.errors.port}
) : null}
-
+
@@ -218,7 +218,7 @@ const ProxySettings = ({ close }) => { />
-
+
@@ -238,7 +238,7 @@ const ProxySettings = ({ close }) => {
{formik.errors.auth.username}
) : null}
-
+
@@ -259,24 +259,24 @@ const ProxySettings = ({ close }) => { ) : null}
-
-