Merge branch 'main' into feature/proxy-global-and-collection

# Conflicts:
#	packages/bruno-app/src/components/Preferences/General/index.js
#	packages/bruno-electron/src/ipc/network/index.js
#	packages/bruno-electron/src/store/preferences.js
This commit is contained in:
Mirko Golze 2023-10-16 11:24:16 +02:00
commit 658a47e03e
22 changed files with 501 additions and 59 deletions

9
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,9 @@
# Description
<!-- Explain here the changes your PR introduces and text to help us understand the context of this change. -->
# Contribution Checklist:
- [ ] **The pull request does not introduce any breaking changes**
- [ ] **I have read the [contribution guidelines](https://github.com/usebruno/bruno/blob/main/contributing.md).**
- [ ] **Create an issue and link to the pull request.**

View File

@ -12,7 +12,6 @@ const AwsV4Auth = ({ collection }) => {
const { storedTheme } = useTheme();
const awsv4Auth = get(collection, 'root.request.auth.awsv4', {});
console.log('saved auth', awsv4Auth);
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));

View File

@ -0,0 +1,43 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
.settings-label {
width: 90px;
}
.certificate-icon {
color: ${(props) => props.theme.colors.text.yellow};
}
input {
width: 300px;
}
.available-certificates {
background-color: ${(props) => props.theme.requestTabPanel.url.bg};
button.remove-certificate {
color: ${(props) => props.theme.colors.text.danger};
}
}
.textbox {
border: 1px solid #ccc;
padding: 0.15rem 0.45rem;
box-shadow: none;
border-radius: 0px;
outline: none;
box-shadow: none;
transition: border-color ease-in-out 0.1s;
border-radius: 3px;
background-color: ${(props) => props.theme.modal.input.bg};
border: 1px solid ${(props) => props.theme.modal.input.border};
&:focus {
border: solid 1px ${(props) => props.theme.modal.input.focusBorder} !important;
outline: none !important;
}
}
`;
export default StyledWrapper;

View File

@ -0,0 +1,130 @@
import React from 'react';
import { IconCertificate, IconTrash, IconWorld } from '@tabler/icons';
import { useFormik } from 'formik';
import { uuid } from 'utils/common';
import * as Yup from 'yup';
import StyledWrapper from './StyledWrapper';
const ClientCertSettings = ({ clientCertConfig, onUpdate, onRemove }) => {
const formik = useFormik({
initialValues: {
domain: '',
certFilePath: '',
keyFilePath: '',
passphrase: ''
},
validationSchema: Yup.object({
domain: Yup.string().required(),
certFilePath: Yup.string().required(),
keyFilePath: Yup.string().required(),
passphrase: Yup.string()
}),
onSubmit: (values) => {
onUpdate(values);
}
});
const getFile = (e) => {
formik.values[e.name] = e.files[0].path;
};
return (
<StyledWrapper>
<div className="flex items-center font-semibold mt-4 mb-2">
<IconCertificate className="mr-1 certificate-icon" size={24} strokeWidth={1.5} /> Client Certificates
</div>
<ul className="mt-4">
{!clientCertConfig.length
? 'None'
: clientCertConfig.map((clientCert) => (
<li key={uuid()} className="flex items-center available-certificates p-2 rounded-lg mb-2">
<div className="flex items-center w-full justify-between">
<div className="flex items-center">
<IconWorld className="mr-2" size={18} strokeWidth={1.5} />
{clientCert.domain}
</div>
<button onClick={() => onRemove(clientCert)} className="remove-certificate ml-2">
<IconTrash size={18} strokeWidth={1.5} />
</button>
</div>
</li>
))}
</ul>
<h1 className="font-semibold mt-8 mb-2">Add Client Certicate</h1>
<form className="bruno-form" onSubmit={formik.handleSubmit}>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="domain">
Domain
</label>
<input
id="domain"
type="text"
name="domain"
placeholder="*.example.org"
className="block textbox"
onChange={formik.handleChange}
value={formik.values.domain || ''}
/>
{formik.touched.domain && formik.errors.domain ? (
<div className="ml-1 text-red-500">{formik.errors.domain}</div>
) : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="certFilePath">
Cert file
</label>
<input
id="certFilePath"
type="file"
name="certFilePath"
className="block"
onChange={(e) => getFile(e.target)}
/>
{formik.touched.certFilePath && formik.errors.certFilePath ? (
<div className="ml-1 text-red-500">{formik.errors.certFilePath}</div>
) : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="keyFilePath">
Key file
</label>
<input
id="keyFilePath"
type="file"
name="keyFilePath"
className="block"
onChange={(e) => getFile(e.target)}
/>
{formik.touched.keyFilePath && formik.errors.keyFilePath ? (
<div className="ml-1 text-red-500">{formik.errors.keyFilePath}</div>
) : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="passphrase">
Passphrase
</label>
<input
id="passphrase"
type="text"
name="passphrase"
className="block textbox"
onChange={formik.handleChange}
value={formik.values.passphrase || ''}
/>
{formik.touched.passphrase && formik.errors.passphrase ? (
<div className="ml-1 text-red-500">{formik.errors.passphrase}</div>
) : null}
</div>
<div className="mt-6">
<button type="submit" className="submit btn btn-sm btn-secondary">
Add
</button>
</div>
</form>
</StyledWrapper>
);
};
export default ClientCertSettings;

View File

@ -7,6 +7,7 @@ import { updateBrunoConfig } from 'providers/ReduxStore/slices/collections/actio
import { updateSettingsSelectedTab } from 'providers/ReduxStore/slices/collections';
import { useDispatch } from 'react-redux';
import ProxySettings from './ProxySettings';
import ClientCertSettings from './ClientCertSettings';
import Headers from './Headers';
import Auth from './Auth';
import Script from './Script';
@ -28,6 +29,8 @@ const CollectionSettings = ({ collection }) => {
const proxyConfig = get(collection, 'brunoConfig.proxy', {});
const clientCertConfig = get(collection, 'brunoConfig.clientCertificates.certs', []);
const onProxySettingsUpdate = (config) => {
const brunoConfig = cloneDeep(collection.brunoConfig);
brunoConfig.proxy = config;
@ -38,6 +41,33 @@ const CollectionSettings = ({ collection }) => {
.catch((err) => console.log(err) && toast.error('Failed to update collection settings'));
};
const onClientCertSettingsUpdate = (config) => {
const brunoConfig = cloneDeep(collection.brunoConfig);
if (!brunoConfig.clientCertificates) {
brunoConfig.clientCertificates = {
enabled: true,
certs: [config]
};
} else {
brunoConfig.clientCertificates.certs.push(config);
}
dispatch(updateBrunoConfig(brunoConfig, collection.uid))
.then(() => {
toast.success('Collection settings updated successfully');
})
.catch((err) => console.log(err) && toast.error('Failed to update collection settings'));
};
const onClientCertSettingsRemove = (config) => {
const brunoConfig = cloneDeep(collection.brunoConfig);
brunoConfig.clientCertificates = brunoConfig.clientCertificates.filter((item) => item.domain != config.domain);
dispatch(updateBrunoConfig(brunoConfig, collection.uid))
.then(() => {
toast.success('Collection settings updated successfully');
})
.catch((err) => console.log(err) && toast.error('Failed to update collection settings'));
};
const getTabPanel = (tab) => {
switch (tab) {
case 'headers': {
@ -55,6 +85,15 @@ const CollectionSettings = ({ collection }) => {
case 'proxy': {
return <ProxySettings proxyConfig={proxyConfig} onUpdate={onProxySettingsUpdate} />;
}
case 'clientCert': {
return (
<ClientCertSettings
clientCertConfig={clientCertConfig}
onUpdate={onClientCertSettingsUpdate}
onRemove={onClientCertSettingsRemove}
/>
);
}
case 'docs': {
return <Docs collection={collection} />;
}
@ -85,11 +124,16 @@ const CollectionSettings = ({ collection }) => {
<div className={getTabClassname('proxy')} role="tab" onClick={() => setTab('proxy')}>
Proxy
</div>
<div className={getTabClassname('clientCert')} role="tab" onClick={() => setTab('clientCert')}>
Client Certificates
</div>
<div className={getTabClassname('docs')} role="tab" onClick={() => setTab('docs')}>
Docs
</div>
</div>
<section className={`flex ${['auth', 'script', 'docs'].includes(tab) ? '' : 'mt-4'}`}>{getTabPanel(tab)}</section>
<section className={`flex ${['auth', 'script', 'docs', 'clientCert'].includes(tab) ? '' : 'mt-4'}`}>
{getTabPanel(tab)}
</section>
</StyledWrapper>
);
};

View File

@ -1,6 +1,6 @@
import React, { useEffect, useRef } from 'react';
import Portal from 'components/Portal/index';
import Modal from 'components/Modal/index';
import Portal from 'components/Portal';
import Modal from 'components/Modal';
import toast from 'react-hot-toast';
import { useFormik } from 'formik';
import { addEnvironment } from 'providers/ReduxStore/slices/collections/actions';

View File

@ -10,6 +10,7 @@ const StyledWrapper = styled.div`
background-color: ${(props) => props.theme.collection.environment.settings.sidebar.bg};
border-right: solid 1px ${(props) => props.theme.collection.environment.settings.sidebar.borderRight};
min-height: 400px;
height: 100%;
}
.environment-item {
@ -35,7 +36,8 @@ const StyledWrapper = styled.div`
}
}
.btn-create-environment {
.btn-create-environment,
.btn-import-environment {
padding: 8px 10px;
cursor: pointer;
border-bottom: none;
@ -47,6 +49,10 @@ const StyledWrapper = styled.div`
}
}
}
.btn-import-environment {
color: ${(props) => props.theme.colors.text.muted};
}
`;
export default StyledWrapper;

View File

@ -1,14 +1,19 @@
import React, { useEffect, useState, forwardRef, useRef } from 'react';
import { findEnvironmentInCollection } from 'utils/collections';
import toast from 'react-hot-toast';
import { toastError } from 'utils/common/error';
import usePrevious from 'hooks/usePrevious';
import EnvironmentDetails from './EnvironmentDetails';
import CreateEnvironment from '../CreateEnvironment/index';
import CreateEnvironment from '../CreateEnvironment';
import { IconUpload } from '@tabler/icons';
import ImportEnvironment from '../ImportEnvironment';
import StyledWrapper from './StyledWrapper';
const EnvironmentList = ({ collection }) => {
const { environments } = collection;
const [selectedEnvironment, setSelectedEnvironment] = useState(null);
const [openCreateModal, setOpenCreateModal] = useState(false);
const [openImportModal, setOpenImportModal] = useState(false);
const envUids = environments ? environments.map((env) => env.uid) : [];
const prevEnvUids = usePrevious(envUids);
@ -48,9 +53,10 @@ const EnvironmentList = ({ collection }) => {
return (
<StyledWrapper>
{openCreateModal && <CreateEnvironment collection={collection} onClose={() => setOpenCreateModal(false)} />}
{openImportModal && <ImportEnvironment collection={collection} onClose={() => setOpenImportModal(false)} />}
<div className="flex">
<div>
<div className="environments-sidebar">
<div className="environments-sidebar flex flex-col">
{environments &&
environments.length &&
environments.map((env) => (
@ -65,6 +71,11 @@ const EnvironmentList = ({ collection }) => {
<div className="btn-create-environment" onClick={() => setOpenCreateModal(true)}>
+ <span>Create</span>
</div>
<div className="mt-auto flex items-center btn-import-environment" onClick={() => setOpenImportModal(true)}>
<IconUpload size={12} strokeWidth={2} />
<span className="label ml-1 text-xs">Import</span>
</div>
</div>
</div>
<EnvironmentDetails environment={selectedEnvironment} collection={collection} />

View File

@ -0,0 +1,39 @@
import React from 'react';
import Portal from 'components/Portal';
import toast from 'react-hot-toast';
import { useDispatch } from 'react-redux';
import importPostmanEnvironment from 'utils/importers/postman-environment';
import { importEnvironment } from 'providers/ReduxStore/slices/collections/actions';
import { toastError } from 'utils/common/error';
import Modal from 'components/Modal';
const ImportEnvironment = ({ onClose, collection }) => {
const dispatch = useDispatch();
const handleImportPostmanEnvironment = () => {
importPostmanEnvironment()
.then((environment) => {
dispatch(importEnvironment(environment.name, environment.variables, collection.uid))
.then(() => {
toast.success('Environment imported successfully');
onClose();
})
.catch(() => toast.error('An error occurred while importing the environment'));
})
.catch((err) => toastError(err, 'Postman Import environment failed'));
};
return (
<Portal>
<Modal size="sm" title="Import Environment" hideFooter={true} handleConfirm={onClose} handleCancel={onClose}>
<div>
<div className="text-link hover:underline cursor-pointer" onClick={handleImportPostmanEnvironment}>
Postman Environment
</div>
</div>
</Modal>
</Portal>
);
};
export default ImportEnvironment;

View File

@ -3,10 +3,12 @@ import React, { useState } from 'react';
import CreateEnvironment from './CreateEnvironment';
import EnvironmentList from './EnvironmentList';
import StyledWrapper from './StyledWrapper';
import ImportEnvironment from './ImportEnvironment';
const EnvironmentSettings = ({ collection, onClose }) => {
const { environments } = collection;
const [openCreateModal, setOpenCreateModal] = useState(false);
const [openImportModal, setOpenImportModal] = useState(false);
if (!environments || !environments.length) {
return (
@ -20,13 +22,23 @@ const EnvironmentSettings = ({ collection, onClose }) => {
hideCancel={true}
>
{openCreateModal && <CreateEnvironment collection={collection} onClose={() => setOpenCreateModal(false)} />}
<div className="text-center">
{openImportModal && <ImportEnvironment collection={collection} onClose={() => setOpenImportModal(false)} />}
<div className="text-center flex flex-col">
<p>No environments found!</p>
<button
className="btn-create-environment text-link pr-2 py-3 mt-2 select-none"
onClick={() => setOpenCreateModal(true)}
>
+ <span>Create Environment</span>
<span>Create Environment</span>
</button>
<span>Or</span>
<button
className="btn-import-environment text-link pl-2 pr-2 py-3 select-none"
onClick={() => setOpenImportModal(true)}
>
<span>Import Environment</span>
</button>
</div>
</Modal>

View File

@ -30,18 +30,16 @@ const Font = ({ close }) => {
return (
<StyledWrapper>
<label className="block font-medium">Code Editor Font</label>
<div className="input-container">
<input
type="text"
className="block textbox mt-2 w-full"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={handleInputChange}
defaultValue={codeFont}
/>
</div>
<input
type="text"
className="block textbox mt-2 w-full"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={handleInputChange}
defaultValue={codeFont}
/>
<div className="mt-10">
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>

View File

@ -8,13 +8,15 @@ const General = ({ close }) => {
const dispatch = useDispatch();
const [sslVerification, setSslVerification] = useState(preferences.request.sslVerification);
const [timeout, setTimeout] = useState(preferences.request.timeout);
const handleSave = () => {
dispatch(
savePreferences({
...preferences,
request: {
sslVerification
sslVerification,
timeout
}
})
).then(() => {
@ -22,19 +24,37 @@ const General = ({ close }) => {
});
};
const handleTimeoutChange = (value) => {
const validTimeout = isNaN(Number(value)) ? timeout : Number(value);
setTimeout(validTimeout);
};
return (
<StyledWrapper>
<div className="flex items-center mt-2">
<label className="mr-2 select-none" style={{ minWidth: 200 }} htmlFor="ssl-cert-verification">
TLS Certificate Verification
</label>
<input
id="ssl-verification"
id="ssl-cert-verification"
type="checkbox"
checked={sslVerification}
onChange={() => setSslVerification(!sslVerification)}
className="mr-3 mousetrap"
className="mousetrap mr-0"
/>
</div>
<div className="flex flex-col mt-6">
<label className="block font-medium select-none">Request Timeout (in ms)</label>
<input
type="text"
className="block textbox mt-2 w-1/4"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={(e) => handleTimeoutChange(e.target.value)}
defaultValue={timeout === 0 ? '' : timeout}
/>
<label htmlFor="ssl-verification" className="select-none">
TLS Certificate Verification
</label>
</div>
<div className="mt-10">

View File

@ -13,7 +13,6 @@ const AwsV4Auth = ({ onTokenChange, item, collection }) => {
const { storedTheme } = useTheme();
const awsv4Auth = item.draft ? get(item, 'draft.request.auth.awsv4', {}) : get(item, 'request.auth.awsv4', {});
console.log('saved auth', awsv4Auth);
const handleRun = () => dispatch(sendRequest(item, collection.uid));
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));

View File

@ -9,6 +9,7 @@ const StyledWrapper = styled.div`
background-color: ${(props) => props.theme.collection.environment.settings.sidebar.bg};
border-right: solid 1px ${(props) => props.theme.collection.environment.settings.sidebar.borderRight};
min-height: 400px;
height: 100%;
}
.generate-code-item {

View File

@ -105,7 +105,7 @@ const Sidebar = () => {
Star
</GitHubButton>
</div>
<div className="flex flex-grow items-center justify-end text-xs mr-2">v0.24.0</div>
<div className="flex flex-grow items-center justify-end text-xs mr-2">v0.25.0</div>
</div>
</div>
</div>

View File

@ -722,6 +722,32 @@ export const addEnvironment = (name, collectionUid) => (dispatch, getState) => {
});
};
export const importEnvironment = (name, variables, collectionUid) => (dispatch, getState) => {
return new Promise((resolve, reject) => {
const state = getState();
const collection = findCollectionByUid(state.collections.collections, collectionUid);
if (!collection) {
return reject(new Error('Collection not found'));
}
ipcRenderer
.invoke('renderer:create-environment', collection.pathname, name, variables)
.then(
dispatch(
updateLastAction({
collectionUid,
lastAction: {
type: 'ADD_ENVIRONMENT',
payload: name
}
})
)
)
.then(resolve)
.catch(reject);
});
};
export const copyEnvironment = (name, baseEnvUid, collectionUid) => (dispatch, getState) => {
return new Promise((resolve, reject) => {
const state = getState();
@ -736,7 +762,7 @@ export const copyEnvironment = (name, baseEnvUid, collectionUid) => (dispatch, g
}
ipcRenderer
.invoke('renderer:copy-environment', collection.pathname, name, baseEnv.variables)
.invoke('renderer:create-environment', collection.pathname, name, baseEnv.variables)
.then(
dispatch(
updateLastAction({

View File

@ -0,0 +1,71 @@
import each from 'lodash/each';
import fileDialog from 'file-dialog';
import { BrunoError } from 'utils/common/error';
const readFile = (files) => {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = (e) => resolve(e.target.result);
fileReader.onerror = (err) => reject(err);
fileReader.readAsText(files[0]);
});
};
const isSecret = (type) => {
return type === 'secret';
};
const importPostmanEnvironmentVariables = (brunoEnvironment, values) => {
brunoEnvironment.variables = brunoEnvironment.variables || [];
each(values, (i) => {
const brunoEnvironmentVariable = {
name: i.key,
value: i.value,
enabled: i.enabled,
secret: isSecret(i.type)
};
brunoEnvironment.variables.push(brunoEnvironmentVariable);
});
};
const importPostmanEnvironment = (environment) => {
const brunoEnvironment = {
name: environment.name,
variables: []
};
importPostmanEnvironmentVariables(brunoEnvironment, environment.values);
return brunoEnvironment;
};
const parsePostmanEnvironment = (str) => {
return new Promise((resolve, reject) => {
try {
let environment = JSON.parse(str);
return resolve(importPostmanEnvironment(environment));
} catch (err) {
console.log(err);
if (err instanceof BrunoError) {
return reject(err);
}
return reject(new BrunoError('Unable to parse the postman environment json file'));
}
});
};
const importEnvironment = () => {
return new Promise((resolve, reject) => {
fileDialog({ accept: 'application/json' })
.then(readFile)
.then(parsePostmanEnvironment)
.then((environment) => resolve(environment))
.catch((err) => {
console.log(err);
reject(new BrunoError('Import Environment failed'));
});
});
};
export default importEnvironment;

View File

@ -1,5 +1,5 @@
{
"version": "v0.24.0",
"version": "v0.25.0",
"name": "bruno",
"description": "Opensource API Client for Exploring and Testing APIs",
"homepage": "https://www.usebruno.com",

View File

@ -135,7 +135,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
});
// create environment
ipcMain.handle('renderer:create-environment', async (event, collectionPathname, name) => {
ipcMain.handle('renderer:create-environment', async (event, collectionPathname, name, variables) => {
try {
const envDirPath = path.join(collectionPathname, 'environments');
if (!fs.existsSync(envDirPath)) {
@ -147,31 +147,17 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
throw new Error(`environment: ${envFilePath} already exists`);
}
const content = envJsonToBru({
variables: []
});
await writeFile(envFilePath, content);
} catch (error) {
return Promise.reject(error);
}
});
const environment = {
name: name,
variables: variables || []
};
// copy environment
ipcMain.handle('renderer:copy-environment', async (event, collectionPathname, name, baseVariables) => {
try {
const envDirPath = path.join(collectionPathname, 'environments');
if (!fs.existsSync(envDirPath)) {
await createDirectory(envDirPath);
if (envHasSecrets(environment)) {
environmentSecretsStore.storeEnvSecrets(collectionPathname, environment);
}
const envFilePath = path.join(envDirPath, `${name}.bru`);
if (fs.existsSync(envFilePath)) {
throw new Error(`environment: ${envFilePath} already exists`);
}
const content = envJsonToBru(environment);
const content = envJsonToBru({
variables: baseVariables
});
await writeFile(envFilePath, content);
} catch (error) {
return Promise.reject(error);

View File

@ -1,8 +1,8 @@
const os = require('os');
const fs = require('fs');
const qs = require('qs');
const https = require('https');
const axios = require('axios');
const fs = require('fs');
const decomment = require('decomment');
const Mustache = require('mustache');
const FormData = require('form-data');
@ -100,8 +100,37 @@ const configureRequest = async (collectionUid, request, envVars, collectionVaria
}
}
// proxy configuration
const brunoConfig = getBrunoConfig(collectionUid);
const interpolationOptions = {
envVars,
collectionVariables,
processEnvVars
};
// client certificate config
const clientCertConfig = get(brunoConfig, 'clientCertificates.certs', []);
for (clientCert of clientCertConfig) {
const domain = interpolateString(clientCert.domain, interpolationOptions);
const certFilePath = interpolateString(clientCert.certFilePath, interpolationOptions);
const keyFilePath = interpolateString(clientCert.keyFilePath, interpolationOptions);
if (domain && certFilePath && keyFilePath) {
const hostRegex = '^https:\\/\\/' + domain.replaceAll('.', '\\.').replaceAll('*', '.*');
if (request.url.match(hostRegex)) {
try {
httpsAgentRequestFields['cert'] = fs.readFileSync(certFilePath);
httpsAgentRequestFields['key'] = fs.readFileSync(keyFilePath);
} catch (err) {
console.log('Error reading cert/key file', err);
}
httpsAgentRequestFields['passphrase'] = interpolateString(clientCert.passphrase, interpolationOptions);
break;
}
}
}
// proxy configuration
let proxyConfig = get(brunoConfig, 'proxy', {});
let proxyEnabled = get(proxyConfig, 'enabled', 'disabled');
if (proxyEnabled === 'global') {
@ -157,6 +186,10 @@ const configureRequest = async (collectionUid, request, envVars, collectionVaria
delete request.awsv4config;
}
const preferences = getPreferences();
const timeout = get(preferences, 'request.timeout', 0);
request.timeout = timeout;
return axiosInstance;
};
@ -516,6 +549,11 @@ 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);
if (!preferences.isTlsVerification()) {
request.httpsAgent = new https.Agent({
rejectUnauthorized: false

View File

@ -11,7 +11,7 @@ const { get } = require('lodash');
const defaultPreferences = {
request: {
sslVerification: true,
caCert: ''
timeout: 0
},
font: {
codeFont: 'default'
@ -32,7 +32,8 @@ const defaultPreferences = {
const preferencesSchema = Yup.object().shape({
request: Yup.object().shape({
sslVerification: Yup.boolean()
sslVerification: Yup.boolean(),
timeout: Yup.number()
}),
font: Yup.object().shape({
codeFont: Yup.string().nullable()
@ -104,8 +105,8 @@ const preferences = {
return get(getPreferences(), 'request.sslVerification', true);
},
getCaCert: () => {
return get(getPreferences(), 'request.cacert');
getTimeout: () => {
return get(getPreferences(), 'request.timeout');
},
getProxyConfig: () => {

View File

@ -5,6 +5,7 @@ class BrunoRequest {
this.method = req.method;
this.headers = req.headers;
this.body = req.data;
this.timeout = req.timeout;
}
getUrl() {
@ -50,6 +51,14 @@ class BrunoRequest {
setMaxRedirects(maxRedirects) {
this.req.maxRedirects = maxRedirects;
}
getTimeout() {
return this.req.timeout;
}
setTimeout(timeout) {
this.req.timeout = timeout;
}
}
module.exports = BrunoRequest;