From b854e66a24107561e59c9db1d85c7d01f2404d4f Mon Sep 17 00:00:00 2001 From: Mirko Golze Date: Sun, 8 Oct 2023 23:12:03 +0200 Subject: [PATCH] #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;