From b854e66a24107561e59c9db1d85c7d01f2404d4f Mon Sep 17 00:00:00 2001 From: Mirko Golze Date: Sun, 8 Oct 2023 23:12:03 +0200 Subject: [PATCH 01/36] #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 332917b37..428b2b985 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 c3746f566..da9c0fa37 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 455a6748f..0f552edf0 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 2e7cd8621..edde26b39 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 caf057d5b..421702be9 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 9b0345004..14fd30aaf 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 5e9916ef0..be7a1c389 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 000000000..396de498a --- /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 03a15305b..04ef3a300 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 a0b66099c..e3843c258 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 000000000..40d62b0c5 --- /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 f1b86b0f3..cb22f3cd1 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'); + }, + + 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 = { - getPreferences, - setPreferences -}; +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 02/36] 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 162eaae6e..4aca0e993 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 fdbd339c9..9476c5a5b 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 6d35dd5d4..c2becdec7 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 03/36] 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 9080ad253..8b1b59996 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 c2becdec7..ff0c67e39 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 6d7f397d7bea88f31f58d9fd66bcdbd3935aab95 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Wed, 11 Oct 2023 12:57:12 +0200 Subject: [PATCH 04/36] 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 86a9e0ebd..a2c73beec 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 9aaaf23f2..dbe318946 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 625e9dc0d..13c0cd882 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 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 05/36] 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 6b4707729..cc0f1267e 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 78eec9ea5cc52ca4c594019dcfc0115be7e6bdcd Mon Sep 17 00:00:00 2001 From: rayoz12 Date: Sat, 14 Oct 2023 21:05:44 +1100 Subject: [PATCH 06/36] 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 8e8cb6247..9a5251825 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 07/36] 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 0b33941f7..db5bfc02c 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: Sun, 15 Oct 2023 01:54:52 +0300 Subject: [PATCH 08/36] 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 abfcce4d3..966a6134b 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 316408162..061b60569 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 000000000..75760f565 --- /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 470e9d044289d24c1a31b6dc62a38d80bb1c7c89 Mon Sep 17 00:00:00 2001 From: Mirko Golze Date: Sun, 15 Oct 2023 16:40:50 +0200 Subject: [PATCH 09/36] 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 d04f7e2d2..e8f35901b 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 ca15cb3a5..f759af2e3 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 8b1b59996..df0d35df7 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 37be01554..d40f98792 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 8c98ff5e3..b0cffb900 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 396de498a..000000000 --- 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 1a6f60925..a63aba0dc 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; - } - - 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); + } 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); + } } } - return httpsAgentRequestFields; -} + + // 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 + }); + } + + 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(); + const axiosInstance = await configureRequest( + collectionUid, + request, + envVars, + collectionVariables, + processEnvVars + ); - // 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 - }); - } - - // 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 f93ec5e6f..602de92b9 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 62e17a564..93fa3b80e 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 getPath = () => { + return preferencesStore.getPath(); +}; + const preferences = { - getAll() { - return getPreferences(); - }, - - getPath() { - return preferencesStore.getPath(); - }, - 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 11/36] 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 93fa3b80e..ae462f646 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 1244716b9b6c511e39d8a12a8ccbe9bd471d6f35 Mon Sep 17 00:00:00 2001 From: Prem Kumar Easwaran Date: Mon, 16 Oct 2023 11:19:55 +0530 Subject: [PATCH 12/36] 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 44e18455f..dd7ac4f79 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 278516538..adfce3dd8 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 13/36] 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 0d3b63df6..407d42a0d 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 14/36] 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 9562f2cdf..33eb69da9 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 3606086b3..88d411e68 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 15/36] 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 b0cffb900..59339c0cb 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 33eb69da9..28b344084 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 16/36] 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 8952e1986..c54c3338e 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 17/36] 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 ebd780ece..89be40f36 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 18/36] 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 e72774524..3d571b4bf 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 407d42a0d..ef000431f 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 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 19/36] 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 363fdf4d2..47693b47f 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 20/36] 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 000000000..33701dd0b --- /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 06f6adab4..fa3827097 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 21/36] 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 47693b47f..291491dde 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 22/36] 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 291491dde..9825c3a09 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 23/36] 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 8021a5b7a..cf3d2dc61 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 8c25e5c57..d8b8255f7 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 000000000..02df2bb49 --- /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 24/36] 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 5568ae311..ace3b3101 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 25/36] 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 c151de895..cd8291af4 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 ad8e9c25610f9196e607df5fb12e45aa2fb28cb6 Mon Sep 17 00:00:00 2001 From: Prem Kumar Easwaran Date: Tue, 17 Oct 2023 21:07:32 +0530 Subject: [PATCH 26/36] 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 9f9097637..5c88178a3 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 27/36] 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 2500474cb..b956b0813 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 28/36] 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 c1c402e08..d56d3e6cf 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 4d4e3a80e..3816066e0 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 000000000..d6d5bcdf8 --- /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 29/36] 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 02df2bb49..793e11a8e 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 30/36] 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 3f0981f8d..fd3cc8986 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 2c7bf0228..077ef39fa 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}
-
-