From b854e66a24107561e59c9db1d85c7d01f2404d4f Mon Sep 17 00:00:00 2001 From: Mirko Golze Date: Sun, 8 Oct 2023 23:12:03 +0200 Subject: [PATCH 1/9] #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 332917b3..428b2b98 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 c3746f56..da9c0fa3 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 455a6748..0f552edf 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 2e7cd862..edde26b3 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 caf057d5..421702be 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 9b034500..14fd30aa 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 5e9916ef..be7a1c38 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 00000000..396de498 --- /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 03a15305..04ef3a30 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 a0b66099..e3843c25 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 00000000..40d62b0c --- /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 f1b86b0f..cb22f3cd 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 2/9] 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 162eaae6..4aca0e99 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 fdbd339c..9476c5a5 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 6d35dd5d..c2becdec 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 3/9] 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 9080ad25..8b1b5999 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 c2becdec..ff0c67e3 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 470e9d044289d24c1a31b6dc62a38d80bb1c7c89 Mon Sep 17 00:00:00 2001 From: Mirko Golze Date: Sun, 15 Oct 2023 16:40:50 +0200 Subject: [PATCH 4/9] 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 d04f7e2d..e8f35901 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 ca15cb3a..f759af2e 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 8b1b5999..df0d35df 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 37be0155..d40f9879 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 8c98ff5e..b0cffb90 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 396de498..00000000 --- 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 1a6f6092..a63aba0d 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 f93ec5e6..602de92b 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 62e17a56..93fa3b80 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 6/9] 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 93fa3b80..ae462f64 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 3fa87fe99a95a12a645273d14e08b2d176ee41b3 Mon Sep 17 00:00:00 2001 From: Mirko Golze Date: Mon, 16 Oct 2023 11:37:35 +0200 Subject: [PATCH 7/9] 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 9562f2cd..33eb69da 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 3606086b..88d411e6 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 8/9] 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 b0cffb90..59339c0c 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 33eb69da..28b34408 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 9/9] 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 8952e198..c54c3338 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); }); };