mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-21 23:43:15 +01:00
feat(#334): collection level headers, auth, scripts and tests
This commit is contained in:
parent
159dd90b03
commit
1ce8d707f1
@ -0,0 +1,28 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
|
||||||
|
.auth-mode-selector {
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
.auth-mode-label {
|
||||||
|
color: ${(props) => props.theme.colors.text.yellow};
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item {
|
||||||
|
padding: 0.2rem 0.6rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-item {
|
||||||
|
padding: 0.2rem 0.6rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.caret {
|
||||||
|
color: rgb(140, 140, 140);
|
||||||
|
fill: rgb(140 140 140);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Wrapper;
|
@ -0,0 +1,69 @@
|
|||||||
|
import React, { useRef, forwardRef } from 'react';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import { IconCaretDown } from '@tabler/icons';
|
||||||
|
import Dropdown from 'components/Dropdown';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { updateCollectionAuthMode } from 'providers/ReduxStore/slices/collections';
|
||||||
|
import { humanizeRequestAuthMode } from 'utils/collections';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const AuthMode = ({ collection }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const dropdownTippyRef = useRef();
|
||||||
|
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||||
|
const authMode = get(collection, 'root.request.auth.mode');
|
||||||
|
|
||||||
|
const Icon = forwardRef((props, ref) => {
|
||||||
|
return (
|
||||||
|
<div ref={ref} className="flex items-center justify-center auth-mode-label select-none">
|
||||||
|
{humanizeRequestAuthMode(authMode)} <IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const onModeChange = (value) => {
|
||||||
|
dispatch(
|
||||||
|
updateCollectionAuthMode({
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
mode: value
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper>
|
||||||
|
<div className="inline-flex items-center cursor-pointer auth-mode-selector">
|
||||||
|
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
onModeChange('basic');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Basic Auth
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
onModeChange('bearer');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Bearer Token
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
onModeChange('none');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
No Auth
|
||||||
|
</div>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default AuthMode;
|
@ -0,0 +1,16 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
label {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-line-editor-wrapper {
|
||||||
|
padding: 0.15rem 0.4rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: solid 1px ${(props) => props.theme.input.border};
|
||||||
|
background-color: ${(props) => props.theme.input.bg};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Wrapper;
|
@ -0,0 +1,71 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import { useTheme } from 'providers/Theme';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import SingleLineEditor from 'components/SingleLineEditor';
|
||||||
|
import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections';
|
||||||
|
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const BasicAuth = ({ collection }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { storedTheme } = useTheme();
|
||||||
|
|
||||||
|
const basicAuth = get(collection, 'root.request.auth.basic', {});
|
||||||
|
|
||||||
|
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
|
||||||
|
|
||||||
|
const handleUsernameChange = (username) => {
|
||||||
|
dispatch(
|
||||||
|
updateCollectionAuth({
|
||||||
|
mode: 'basic',
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
content: {
|
||||||
|
username: username,
|
||||||
|
password: basicAuth.password
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePasswordChange = (password) => {
|
||||||
|
dispatch(
|
||||||
|
updateCollectionAuth({
|
||||||
|
mode: 'basic',
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
content: {
|
||||||
|
username: basicAuth.username,
|
||||||
|
password: password
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="mt-2 w-full">
|
||||||
|
<label className="block font-medium mb-2">Username</label>
|
||||||
|
<div className="single-line-editor-wrapper mb-2">
|
||||||
|
<SingleLineEditor
|
||||||
|
value={basicAuth.username || ''}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(val) => handleUsernameChange(val)}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label className="block font-medium mb-2">Password</label>
|
||||||
|
<div className="single-line-editor-wrapper">
|
||||||
|
<SingleLineEditor
|
||||||
|
value={basicAuth.password || ''}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(val) => handlePasswordChange(val)}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BasicAuth;
|
@ -0,0 +1,16 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
label {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-line-editor-wrapper {
|
||||||
|
padding: 0.15rem 0.4rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: solid 1px ${(props) => props.theme.input.border};
|
||||||
|
background-color: ${(props) => props.theme.input.bg};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Wrapper;
|
@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import { useTheme } from 'providers/Theme';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import SingleLineEditor from 'components/SingleLineEditor';
|
||||||
|
import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections';
|
||||||
|
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const BearerAuth = ({ collection }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { storedTheme } = useTheme();
|
||||||
|
|
||||||
|
const bearerToken = get(collection, 'root.request.auth.bearer.token');
|
||||||
|
|
||||||
|
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
|
||||||
|
|
||||||
|
const handleTokenChange = (token) => {
|
||||||
|
dispatch(
|
||||||
|
updateCollectionAuth({
|
||||||
|
mode: 'bearer',
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
content: {
|
||||||
|
token: token
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="mt-2 w-full">
|
||||||
|
<label className="block font-medium mb-2">Token</label>
|
||||||
|
<div className="single-line-editor-wrapper">
|
||||||
|
<SingleLineEditor
|
||||||
|
value={bearerToken}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(val) => handleTokenChange(val)}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BearerAuth;
|
@ -0,0 +1,5 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Wrapper = styled.div``;
|
||||||
|
|
||||||
|
export default Wrapper;
|
@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import AuthMode from './AuthMode';
|
||||||
|
import BearerAuth from './BearerAuth';
|
||||||
|
import BasicAuth from './BasicAuth';
|
||||||
|
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const Auth = ({ collection }) => {
|
||||||
|
const authMode = get(collection, 'root.request.auth.mode');
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
|
||||||
|
|
||||||
|
const getAuthView = () => {
|
||||||
|
switch (authMode) {
|
||||||
|
case 'basic': {
|
||||||
|
return <BasicAuth collection={collection} />;
|
||||||
|
}
|
||||||
|
case 'bearer': {
|
||||||
|
return <BearerAuth collection={collection} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="w-full mt-2">
|
||||||
|
<div className="flex flex-grow justify-start items-center">
|
||||||
|
<AuthMode collection={collection} />
|
||||||
|
</div>
|
||||||
|
{getAuthView()}
|
||||||
|
|
||||||
|
<div className="mt-6">
|
||||||
|
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default Auth;
|
@ -0,0 +1,56 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-weight: 600;
|
||||||
|
table-layout: fixed;
|
||||||
|
|
||||||
|
thead,
|
||||||
|
td {
|
||||||
|
border: 1px solid ${(props) => props.theme.table.border};
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
color: ${(props) => props.theme.table.thead.color};
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
padding: 6px 10px;
|
||||||
|
|
||||||
|
&:nth-child(1) {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(3) {
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-header {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='text'] {
|
||||||
|
width: 100%;
|
||||||
|
border: solid 1px transparent;
|
||||||
|
outline: none !important;
|
||||||
|
background-color: inherit;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none !important;
|
||||||
|
border: solid 1px transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='checkbox'] {
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Wrapper;
|
@ -0,0 +1,151 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
|
import { IconTrash } from '@tabler/icons';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { useTheme } from 'providers/Theme';
|
||||||
|
import {
|
||||||
|
addCollectionHeader,
|
||||||
|
updateCollectionHeader,
|
||||||
|
deleteCollectionHeader
|
||||||
|
} from 'providers/ReduxStore/slices/collections';
|
||||||
|
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import SingleLineEditor from 'components/SingleLineEditor';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import { headers as StandardHTTPHeaders } from 'know-your-http-well';
|
||||||
|
const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header);
|
||||||
|
|
||||||
|
const Headers = ({ collection }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { storedTheme } = useTheme();
|
||||||
|
const headers = get(collection, 'root.request.headers', []);
|
||||||
|
|
||||||
|
const addHeader = () => {
|
||||||
|
dispatch(
|
||||||
|
addCollectionHeader({
|
||||||
|
collectionUid: collection.uid
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
|
||||||
|
const handleHeaderValueChange = (e, _header, type) => {
|
||||||
|
const header = cloneDeep(_header);
|
||||||
|
switch (type) {
|
||||||
|
case 'name': {
|
||||||
|
header.name = e.target.value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'value': {
|
||||||
|
header.value = e.target.value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'enabled': {
|
||||||
|
header.enabled = e.target.checked;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dispatch(
|
||||||
|
updateCollectionHeader({
|
||||||
|
header: header,
|
||||||
|
collectionUid: collection.uid
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveHeader = (header) => {
|
||||||
|
dispatch(
|
||||||
|
deleteCollectionHeader({
|
||||||
|
headerUid: header.uid,
|
||||||
|
collectionUid: collection.uid
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="w-full">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Name</td>
|
||||||
|
<td>Value</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{headers && headers.length
|
||||||
|
? headers.map((header) => {
|
||||||
|
return (
|
||||||
|
<tr key={header.uid}>
|
||||||
|
<td>
|
||||||
|
<SingleLineEditor
|
||||||
|
value={header.name}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(newValue) =>
|
||||||
|
handleHeaderValueChange(
|
||||||
|
{
|
||||||
|
target: {
|
||||||
|
value: newValue
|
||||||
|
}
|
||||||
|
},
|
||||||
|
header,
|
||||||
|
'name'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
autocomplete={headerAutoCompleteList}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<SingleLineEditor
|
||||||
|
value={header.value}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(newValue) =>
|
||||||
|
handleHeaderValueChange(
|
||||||
|
{
|
||||||
|
target: {
|
||||||
|
value: newValue
|
||||||
|
}
|
||||||
|
},
|
||||||
|
header,
|
||||||
|
'value'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={header.enabled}
|
||||||
|
tabIndex="-1"
|
||||||
|
className="mr-3 mousetrap"
|
||||||
|
onChange={(e) => handleHeaderValueChange(e, header, 'enabled')}
|
||||||
|
/>
|
||||||
|
<button tabIndex="-1" onClick={() => handleRemoveHeader(header)}>
|
||||||
|
<IconTrash strokeWidth={1.5} size={20} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
: null}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<button className="btn-add-header text-link pr-2 py-3 mt-2 select-none" onClick={addHeader}>
|
||||||
|
+ Add Header
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="mt-6">
|
||||||
|
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default Headers;
|
@ -49,15 +49,14 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper>
|
<StyledWrapper>
|
||||||
<h1 className="font-medium mb-3">Proxy Settings</h1>
|
|
||||||
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
||||||
<div className="ml-4 mb-3 flex items-center">
|
<div className="mb-3 flex items-center">
|
||||||
<label className="settings-label" htmlFor="enabled">
|
<label className="settings-label" htmlFor="enabled">
|
||||||
Enabled
|
Enabled
|
||||||
</label>
|
</label>
|
||||||
<input type="checkbox" name="enabled" checked={formik.values.enabled} onChange={formik.handleChange} />
|
<input type="checkbox" name="enabled" checked={formik.values.enabled} onChange={formik.handleChange} />
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4 mb-3 flex items-center">
|
<div className="mb-3 flex items-center">
|
||||||
<label className="settings-label" htmlFor="protocol">
|
<label className="settings-label" htmlFor="protocol">
|
||||||
Protocol
|
Protocol
|
||||||
</label>
|
</label>
|
||||||
@ -86,7 +85,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4 mb-3 flex items-center">
|
<div className="mb-3 flex items-center">
|
||||||
<label className="settings-label" htmlFor="hostname">
|
<label className="settings-label" htmlFor="hostname">
|
||||||
Hostname
|
Hostname
|
||||||
</label>
|
</label>
|
||||||
@ -106,7 +105,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
|||||||
<div className="text-red-500">{formik.errors.hostname}</div>
|
<div className="text-red-500">{formik.errors.hostname}</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4 mb-3 flex items-center">
|
<div className="mb-3 flex items-center">
|
||||||
<label className="settings-label" htmlFor="port">
|
<label className="settings-label" htmlFor="port">
|
||||||
Port
|
Port
|
||||||
</label>
|
</label>
|
||||||
@ -124,7 +123,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
|||||||
/>
|
/>
|
||||||
{formik.touched.port && formik.errors.port ? <div className="text-red-500">{formik.errors.port}</div> : null}
|
{formik.touched.port && formik.errors.port ? <div className="text-red-500">{formik.errors.port}</div> : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4 mb-3 flex items-center">
|
<div className="mb-3 flex items-center">
|
||||||
<label className="settings-label" htmlFor="auth.enabled">
|
<label className="settings-label" htmlFor="auth.enabled">
|
||||||
Auth
|
Auth
|
||||||
</label>
|
</label>
|
||||||
@ -136,7 +135,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="ml-4 mb-3 flex items-center">
|
<div className="mb-3 flex items-center">
|
||||||
<label className="settings-label" htmlFor="auth.username">
|
<label className="settings-label" htmlFor="auth.username">
|
||||||
Username
|
Username
|
||||||
</label>
|
</label>
|
||||||
@ -156,7 +155,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
|||||||
<div className="text-red-500">{formik.errors.auth.username}</div>
|
<div className="text-red-500">{formik.errors.auth.username}</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4 mb-3 flex items-center">
|
<div className="mb-3 flex items-center">
|
||||||
<label className="settings-label" htmlFor="auth.password">
|
<label className="settings-label" htmlFor="auth.password">
|
||||||
Password
|
Password
|
||||||
</label>
|
</label>
|
||||||
@ -178,7 +177,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<button type="submit" className="submit btn btn-md btn-secondary">
|
<button type="submit" className="submit btn btn-sm btn-secondary">
|
||||||
Save
|
Save
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
div.CodeMirror {
|
||||||
|
height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.title {
|
||||||
|
color: var(--color-tab-inactive);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
@ -0,0 +1,73 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import CodeEditor from 'components/CodeEditor';
|
||||||
|
import { updateCollectionRequestScript, updateCollectionResponseScript } from 'providers/ReduxStore/slices/collections';
|
||||||
|
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import { useTheme } from 'providers/Theme';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const Script = ({ collection }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const requestScript = get(collection, 'root.request.script.req', '');
|
||||||
|
const responseScript = get(collection, 'root.request.script.res', '');
|
||||||
|
|
||||||
|
const { storedTheme } = useTheme();
|
||||||
|
|
||||||
|
const onRequestScriptEdit = (value) => {
|
||||||
|
dispatch(
|
||||||
|
updateCollectionRequestScript({
|
||||||
|
script: value,
|
||||||
|
collectionUid: collection.uid
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onResponseScriptEdit = (value) => {
|
||||||
|
dispatch(
|
||||||
|
updateCollectionResponseScript({
|
||||||
|
script: value,
|
||||||
|
collectionUid: collection.uid
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
dispatch(saveCollectionRoot(collection.uid));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="w-full flex flex-col">
|
||||||
|
<div className="flex-1 mt-2">
|
||||||
|
<div className="mb-1 title text-xs">Pre Request</div>
|
||||||
|
<CodeEditor
|
||||||
|
collection={collection}
|
||||||
|
value={requestScript || ''}
|
||||||
|
theme={storedTheme}
|
||||||
|
onEdit={onRequestScriptEdit}
|
||||||
|
mode="javascript"
|
||||||
|
onSave={handleSave}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 mt-6">
|
||||||
|
<div className="mt-1 mb-1 title text-xs">Post Response</div>
|
||||||
|
<CodeEditor
|
||||||
|
collection={collection}
|
||||||
|
value={responseScript || ''}
|
||||||
|
theme={storedTheme}
|
||||||
|
onEdit={onResponseScriptEdit}
|
||||||
|
mode="javascript"
|
||||||
|
onSave={handleSave}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-12">
|
||||||
|
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Script;
|
@ -1,6 +1,32 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
const StyledWrapper = styled.div`
|
const StyledWrapper = styled.div`
|
||||||
|
max-width: 800px;
|
||||||
|
|
||||||
|
div.tabs {
|
||||||
|
div.tab {
|
||||||
|
padding: 6px 0px;
|
||||||
|
border: none;
|
||||||
|
border-bottom: solid 2px transparent;
|
||||||
|
margin-right: 1.25rem;
|
||||||
|
color: var(--color-tab-inactive);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:focus,
|
||||||
|
&:active,
|
||||||
|
&:focus-within,
|
||||||
|
&:focus-visible,
|
||||||
|
&:target {
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: ${(props) => props.theme.tabs.active.color} !important;
|
||||||
|
border-bottom: solid 2px ${(props) => props.theme.tabs.active.border} !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
table {
|
table {
|
||||||
thead,
|
thead,
|
||||||
td {
|
td {
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div``;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
@ -0,0 +1,47 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import CodeEditor from 'components/CodeEditor';
|
||||||
|
import { updateCollectionTests } from 'providers/ReduxStore/slices/collections';
|
||||||
|
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import { useTheme } from 'providers/Theme';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const Tests = ({ collection }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const tests = get(collection, 'root.request.tests', '');
|
||||||
|
|
||||||
|
const { storedTheme } = useTheme();
|
||||||
|
|
||||||
|
const onEdit = (value) => {
|
||||||
|
dispatch(
|
||||||
|
updateCollectionTests({
|
||||||
|
tests: value,
|
||||||
|
collectionUid: collection.uid
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="w-full flex flex-col h-full">
|
||||||
|
<CodeEditor
|
||||||
|
collection={collection}
|
||||||
|
value={tests || ''}
|
||||||
|
theme={storedTheme}
|
||||||
|
onEdit={onEdit}
|
||||||
|
mode="javascript"
|
||||||
|
onSave={handleSave}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mt-6">
|
||||||
|
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Tests;
|
@ -1,14 +1,29 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import cloneDeep from 'lodash/cloneDeep';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { updateBrunoConfig } from 'providers/ReduxStore/slices/collections/actions';
|
import { updateBrunoConfig } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import { updateSettingsSelectedTab } from 'providers/ReduxStore/slices/collections';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import ProxySettings from './ProxySettings';
|
import ProxySettings from './ProxySettings';
|
||||||
|
import Headers from './Headers';
|
||||||
|
import Auth from './Auth';
|
||||||
|
import Script from './Script';
|
||||||
|
import Test from './Tests';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
const CollectionSettings = ({ collection }) => {
|
const CollectionSettings = ({ collection }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const tab = collection.settingsSelectedTab;
|
||||||
|
const setTab = (tab) => {
|
||||||
|
dispatch(
|
||||||
|
updateSettingsSelectedTab({
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
tab
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const proxyConfig = get(collection, 'brunoConfig.proxy', {});
|
const proxyConfig = get(collection, 'brunoConfig.proxy', {});
|
||||||
|
|
||||||
@ -22,11 +37,52 @@ const CollectionSettings = ({ collection }) => {
|
|||||||
.catch((err) => console.log(err) && toast.error('Failed to update collection settings'));
|
.catch((err) => console.log(err) && toast.error('Failed to update collection settings'));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const getTabPanel = (tab) => {
|
||||||
<StyledWrapper className="px-4 py-4">
|
switch (tab) {
|
||||||
<h1 className="font-semibold mb-4">Collection Settings</h1>
|
case 'headers': {
|
||||||
|
return <Headers collection={collection} />;
|
||||||
|
}
|
||||||
|
case 'auth': {
|
||||||
|
return <Auth collection={collection} />;
|
||||||
|
}
|
||||||
|
case 'script': {
|
||||||
|
return <Script collection={collection} />;
|
||||||
|
}
|
||||||
|
case 'tests': {
|
||||||
|
return <Test collection={collection} />;
|
||||||
|
}
|
||||||
|
case 'proxy': {
|
||||||
|
return <ProxySettings proxyConfig={proxyConfig} onUpdate={onProxySettingsUpdate} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
<ProxySettings proxyConfig={proxyConfig} onUpdate={onProxySettingsUpdate} />
|
const getTabClassname = (tabName) => {
|
||||||
|
return classnames(`tab select-none ${tabName}`, {
|
||||||
|
active: tabName === tab
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="flex flex-col h-full relative px-4 py-4">
|
||||||
|
<div className="flex flex-wrap items-center tabs" role="tablist">
|
||||||
|
<div className={getTabClassname('headers')} role="tab" onClick={() => setTab('headers')}>
|
||||||
|
Headers
|
||||||
|
</div>
|
||||||
|
<div className={getTabClassname('auth')} role="tab" onClick={() => setTab('auth')}>
|
||||||
|
Auth
|
||||||
|
</div>
|
||||||
|
<div className={getTabClassname('script')} role="tab" onClick={() => setTab('script')}>
|
||||||
|
Script
|
||||||
|
</div>
|
||||||
|
<div className={getTabClassname('tests')} role="tab" onClick={() => setTab('tests')}>
|
||||||
|
Tests
|
||||||
|
</div>
|
||||||
|
<div className={getTabClassname('proxy')} role="tab" onClick={() => setTab('proxy')}>
|
||||||
|
Proxy
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<section className={`flex ${['auth', 'script'].includes(tab) ? '' : 'mt-4'}`}>{getTabPanel(tab)}</section>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,7 @@ const SpecialTab = ({ handleCloseClick, type }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IconSettings size={18} strokeWidth={1.5} className="text-yellow-600" />
|
<IconSettings size={18} strokeWidth={1.5} className="text-yellow-600" />
|
||||||
<span className="ml-1">Settings</span>
|
<span className="ml-1">Collection</span>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,8 @@ export const HotkeysProvider = (props) => {
|
|||||||
if (item && item.uid) {
|
if (item && item.uid) {
|
||||||
dispatch(saveRequest(activeTab.uid, activeTab.collectionUid));
|
dispatch(saveRequest(activeTab.uid, activeTab.collectionUid));
|
||||||
} else {
|
} else {
|
||||||
setShowSaveRequestModal(true);
|
// todo: when ephermal requests go live
|
||||||
|
// setShowSaveRequestModal(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,29 @@ export const saveRequest = (itemUid, collectionUid) => (dispatch, getState) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const saveCollectionRoot = (collectionUid) => (dispatch, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
||||||
|
console.log(collection.root);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!collection) {
|
||||||
|
return reject(new Error('Collection not found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const { ipcRenderer } = window;
|
||||||
|
|
||||||
|
ipcRenderer
|
||||||
|
.invoke('renderer:save-collection-root', collection.pathname, collection.root)
|
||||||
|
.then(() => toast.success('Collection Settings saved successfully'))
|
||||||
|
.then(resolve)
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error('Failed to save collection settings!');
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const sendRequest = (item, collectionUid) => (dispatch, getState) => {
|
export const sendRequest = (item, collectionUid) => (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
||||||
|
@ -7,6 +7,8 @@ import concat from 'lodash/concat';
|
|||||||
import filter from 'lodash/filter';
|
import filter from 'lodash/filter';
|
||||||
import each from 'lodash/each';
|
import each from 'lodash/each';
|
||||||
import cloneDeep from 'lodash/cloneDeep';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import set from 'lodash/set';
|
||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
import { splitOnFirst } from 'utils/url';
|
import { splitOnFirst } from 'utils/url';
|
||||||
import {
|
import {
|
||||||
@ -40,6 +42,8 @@ export const collectionsSlice = createSlice({
|
|||||||
const collectionUids = map(state.collections, (c) => c.uid);
|
const collectionUids = map(state.collections, (c) => c.uid);
|
||||||
const collection = action.payload;
|
const collection = action.payload;
|
||||||
|
|
||||||
|
collection.settingsSelectedTab = 'headers';
|
||||||
|
|
||||||
// TODO: move this to use the nextAction approach
|
// TODO: move this to use the nextAction approach
|
||||||
// last action is used to track the last action performed on the collection
|
// last action is used to track the last action performed on the collection
|
||||||
// this is optional
|
// this is optional
|
||||||
@ -107,6 +111,15 @@ export const collectionsSlice = createSlice({
|
|||||||
collection.nextAction = nextAction;
|
collection.nextAction = nextAction;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
updateSettingsSelectedTab: (state, action) => {
|
||||||
|
const { collectionUid, tab } = action.payload;
|
||||||
|
|
||||||
|
const collection = findCollectionByUid(state.collections, collectionUid);
|
||||||
|
|
||||||
|
if (collection) {
|
||||||
|
collection.settingsSelectedTab = tab;
|
||||||
|
}
|
||||||
|
},
|
||||||
collectionUnlinkEnvFileEvent: (state, action) => {
|
collectionUnlinkEnvFileEvent: (state, action) => {
|
||||||
const { data: environment, meta } = action.payload;
|
const { data: environment, meta } = action.payload;
|
||||||
const collection = findCollectionByUid(state.collections, meta.collectionUid);
|
const collection = findCollectionByUid(state.collections, meta.collectionUid);
|
||||||
@ -930,10 +943,100 @@ export const collectionsSlice = createSlice({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
updateCollectionAuthMode: (state, action) => {
|
||||||
|
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||||
|
|
||||||
|
if (collection) {
|
||||||
|
set(collection, 'root.request.auth.mode', action.payload.mode);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateCollectionAuth: (state, action) => {
|
||||||
|
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||||
|
|
||||||
|
if (collection) {
|
||||||
|
switch (action.payload.mode) {
|
||||||
|
case 'bearer':
|
||||||
|
set(collection, 'root.request.auth.bearer', action.payload.content);
|
||||||
|
break;
|
||||||
|
case 'basic':
|
||||||
|
set(collection, 'root.request.auth.basic', action.payload.content);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateCollectionRequestScript: (state, action) => {
|
||||||
|
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||||
|
|
||||||
|
if (collection) {
|
||||||
|
set(collection, 'root.request.script.req', action.payload.script);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateCollectionResponseScript: (state, action) => {
|
||||||
|
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||||
|
|
||||||
|
if (collection) {
|
||||||
|
set(collection, 'root.request.script.res', action.payload.script);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateCollectionTests: (state, action) => {
|
||||||
|
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||||
|
|
||||||
|
if (collection) {
|
||||||
|
set(collection, 'root.request.tests', action.payload.tests);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addCollectionHeader: (state, action) => {
|
||||||
|
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||||
|
|
||||||
|
if (collection) {
|
||||||
|
const headers = get(collection, 'root.request.headers', []);
|
||||||
|
headers.push({
|
||||||
|
uid: uuid(),
|
||||||
|
name: '',
|
||||||
|
value: '',
|
||||||
|
description: '',
|
||||||
|
enabled: true
|
||||||
|
});
|
||||||
|
set(collection, 'root.request.headers', headers);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateCollectionHeader: (state, action) => {
|
||||||
|
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||||
|
|
||||||
|
if (collection) {
|
||||||
|
const headers = get(collection, 'root.request.headers', []);
|
||||||
|
const header = find(headers, (h) => h.uid === action.payload.header.uid);
|
||||||
|
if (header) {
|
||||||
|
header.name = action.payload.header.name;
|
||||||
|
header.value = action.payload.header.value;
|
||||||
|
header.description = action.payload.header.description;
|
||||||
|
header.enabled = action.payload.header.enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deleteCollectionHeader: (state, action) => {
|
||||||
|
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||||
|
|
||||||
|
if (collection) {
|
||||||
|
let headers = get(collection, 'root.request.headers', []);
|
||||||
|
headers = filter(headers, (h) => h.uid !== action.payload.headerUid);
|
||||||
|
set(collection, 'root.request.headers', headers);
|
||||||
|
}
|
||||||
|
},
|
||||||
collectionAddFileEvent: (state, action) => {
|
collectionAddFileEvent: (state, action) => {
|
||||||
const file = action.payload.file;
|
const file = action.payload.file;
|
||||||
|
const isCollectionRoot = file.meta.collectionRoot ? true : false;
|
||||||
const collection = findCollectionByUid(state.collections, file.meta.collectionUid);
|
const collection = findCollectionByUid(state.collections, file.meta.collectionUid);
|
||||||
|
|
||||||
|
if (isCollectionRoot) {
|
||||||
|
if (collection) {
|
||||||
|
collection.root = file.data;
|
||||||
|
}
|
||||||
|
console.log('collectionAddFileEvent', file);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (collection) {
|
if (collection) {
|
||||||
const dirname = getDirectoryName(file.meta.pathname);
|
const dirname = getDirectoryName(file.meta.pathname);
|
||||||
const subDirectories = getSubdirectoriesFromRoot(collection.pathname, dirname);
|
const subDirectories = getSubdirectoriesFromRoot(collection.pathname, dirname);
|
||||||
@ -1018,6 +1121,12 @@ export const collectionsSlice = createSlice({
|
|||||||
const { file } = action.payload;
|
const { file } = action.payload;
|
||||||
const collection = findCollectionByUid(state.collections, file.meta.collectionUid);
|
const collection = findCollectionByUid(state.collections, file.meta.collectionUid);
|
||||||
|
|
||||||
|
// check and update collection root
|
||||||
|
if (collection && file.meta.collectionRoot) {
|
||||||
|
collection.root = file.data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (collection) {
|
if (collection) {
|
||||||
const item = findItemInCollection(collection, file.data.uid);
|
const item = findItemInCollection(collection, file.data.uid);
|
||||||
|
|
||||||
@ -1222,6 +1331,7 @@ export const {
|
|||||||
sortCollections,
|
sortCollections,
|
||||||
updateLastAction,
|
updateLastAction,
|
||||||
updateNextAction,
|
updateNextAction,
|
||||||
|
updateSettingsSelectedTab,
|
||||||
collectionUnlinkEnvFileEvent,
|
collectionUnlinkEnvFileEvent,
|
||||||
saveEnvironment,
|
saveEnvironment,
|
||||||
selectEnvironment,
|
selectEnvironment,
|
||||||
@ -1267,6 +1377,14 @@ export const {
|
|||||||
addVar,
|
addVar,
|
||||||
updateVar,
|
updateVar,
|
||||||
deleteVar,
|
deleteVar,
|
||||||
|
addCollectionHeader,
|
||||||
|
updateCollectionHeader,
|
||||||
|
deleteCollectionHeader,
|
||||||
|
updateCollectionAuthMode,
|
||||||
|
updateCollectionAuth,
|
||||||
|
updateCollectionRequestScript,
|
||||||
|
updateCollectionResponseScript,
|
||||||
|
updateCollectionTests,
|
||||||
collectionAddFileEvent,
|
collectionAddFileEvent,
|
||||||
collectionAddDirectoryEvent,
|
collectionAddDirectoryEvent,
|
||||||
collectionChangeFileEvent,
|
collectionChangeFileEvent,
|
||||||
|
@ -54,8 +54,11 @@ body::-webkit-scrollbar-thumb,
|
|||||||
border-radius: 5rem;
|
border-radius: 5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* making all the checkboxes and radios bigger */
|
/*
|
||||||
input[type='checkbox'],
|
* todo: this will be supported in the future to be changed via applying a theme
|
||||||
input[type='radio'] {
|
* making all the checkboxes and radios bigger
|
||||||
transform: scale(1.25);
|
* input[type='checkbox'],
|
||||||
}
|
* input[type='radio'] {
|
||||||
|
* transform: scale(1.1);
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
@ -23,7 +23,7 @@ const sendHttpRequest = async (item, collection, environment, collectionVariable
|
|||||||
const { ipcRenderer } = window;
|
const { ipcRenderer } = window;
|
||||||
|
|
||||||
ipcRenderer
|
ipcRenderer
|
||||||
.invoke('send-http-request', item, collection.uid, collection.pathname, environment, collectionVariables)
|
.invoke('send-http-request', item, collection, environment, collectionVariables)
|
||||||
.then(resolve)
|
.then(resolve)
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
});
|
});
|
||||||
|
@ -3,7 +3,7 @@ const fs = require('fs');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const chokidar = require('chokidar');
|
const chokidar = require('chokidar');
|
||||||
const { hasBruExtension } = require('../utils/filesystem');
|
const { hasBruExtension } = require('../utils/filesystem');
|
||||||
const { bruToEnvJson, bruToJson } = require('../bru');
|
const { bruToEnvJson, bruToJson, collectionBruToJson } = require('../bru');
|
||||||
const { dotenvToJson } = require('@usebruno/lang');
|
const { dotenvToJson } = require('@usebruno/lang');
|
||||||
|
|
||||||
const { uuid } = require('../utils/common');
|
const { uuid } = require('../utils/common');
|
||||||
@ -37,6 +37,13 @@ const isBruEnvironmentConfig = (pathname, collectionPath) => {
|
|||||||
return dirname === envDirectory && hasBruExtension(basename);
|
return dirname === envDirectory && hasBruExtension(basename);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isCollectionRootBruFile = (pathname, collectionPath) => {
|
||||||
|
const dirname = path.dirname(pathname);
|
||||||
|
const basename = path.basename(pathname);
|
||||||
|
|
||||||
|
return dirname === collectionPath && basename === 'collection.bru';
|
||||||
|
};
|
||||||
|
|
||||||
const hydrateRequestWithUuid = (request, pathname) => {
|
const hydrateRequestWithUuid = (request, pathname) => {
|
||||||
request.uid = getRequestUid(pathname);
|
request.uid = getRequestUid(pathname);
|
||||||
|
|
||||||
@ -59,6 +66,20 @@ const hydrateRequestWithUuid = (request, pathname) => {
|
|||||||
return request;
|
return request;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hydrateBruCollectionFileWithUuid = (collectionRoot) => {
|
||||||
|
const params = _.get(collectionRoot, 'request.params', []);
|
||||||
|
const headers = _.get(collectionRoot, 'request.headers', []);
|
||||||
|
const requestVars = _.get(collectionRoot, 'request.vars.req', []);
|
||||||
|
const responseVars = _.get(collectionRoot, 'request.vars.res', []);
|
||||||
|
|
||||||
|
params.forEach((param) => (param.uid = uuid()));
|
||||||
|
headers.forEach((header) => (header.uid = uuid()));
|
||||||
|
requestVars.forEach((variable) => (variable.uid = uuid()));
|
||||||
|
responseVars.forEach((variable) => (variable.uid = uuid()));
|
||||||
|
|
||||||
|
return collectionRoot;
|
||||||
|
};
|
||||||
|
|
||||||
const envHasSecrets = (environment = {}) => {
|
const envHasSecrets = (environment = {}) => {
|
||||||
const secrets = _.filter(environment.variables, (v) => v.secret);
|
const secrets = _.filter(environment.variables, (v) => v.secret);
|
||||||
|
|
||||||
@ -195,6 +216,30 @@ const add = async (win, pathname, collectionUid, collectionPath) => {
|
|||||||
return addEnvironmentFile(win, pathname, collectionUid, collectionPath);
|
return addEnvironmentFile(win, pathname, collectionUid, collectionPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isCollectionRootBruFile(pathname, collectionPath)) {
|
||||||
|
const file = {
|
||||||
|
meta: {
|
||||||
|
collectionUid,
|
||||||
|
pathname,
|
||||||
|
name: path.basename(pathname),
|
||||||
|
collectionRoot: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
let bruContent = fs.readFileSync(pathname, 'utf8');
|
||||||
|
|
||||||
|
file.data = collectionBruToJson(bruContent);
|
||||||
|
|
||||||
|
hydrateBruCollectionFileWithUuid(file.data);
|
||||||
|
win.webContents.send('main:collection-tree-updated', 'addFile', file);
|
||||||
|
return;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (hasBruExtension(pathname)) {
|
if (hasBruExtension(pathname)) {
|
||||||
const file = {
|
const file = {
|
||||||
meta: {
|
meta: {
|
||||||
@ -208,6 +253,7 @@ const add = async (win, pathname, collectionUid, collectionPath) => {
|
|||||||
let bruContent = fs.readFileSync(pathname, 'utf8');
|
let bruContent = fs.readFileSync(pathname, 'utf8');
|
||||||
|
|
||||||
file.data = bruToJson(bruContent);
|
file.data = bruToJson(bruContent);
|
||||||
|
|
||||||
hydrateRequestWithUuid(file.data, pathname);
|
hydrateRequestWithUuid(file.data, pathname);
|
||||||
win.webContents.send('main:collection-tree-updated', 'addFile', file);
|
win.webContents.send('main:collection-tree-updated', 'addFile', file);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -274,6 +320,30 @@ const change = async (win, pathname, collectionUid, collectionPath) => {
|
|||||||
return changeEnvironmentFile(win, pathname, collectionUid, collectionPath);
|
return changeEnvironmentFile(win, pathname, collectionUid, collectionPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isCollectionRootBruFile(pathname, collectionPath)) {
|
||||||
|
const file = {
|
||||||
|
meta: {
|
||||||
|
collectionUid,
|
||||||
|
pathname,
|
||||||
|
name: path.basename(pathname),
|
||||||
|
collectionRoot: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
let bruContent = fs.readFileSync(pathname, 'utf8');
|
||||||
|
|
||||||
|
file.data = collectionBruToJson(bruContent);
|
||||||
|
|
||||||
|
hydrateBruCollectionFileWithUuid(file.data);
|
||||||
|
win.webContents.send('main:collection-tree-updated', 'change', file);
|
||||||
|
return;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (hasBruExtension(pathname)) {
|
if (hasBruExtension(pathname)) {
|
||||||
try {
|
try {
|
||||||
const file = {
|
const file = {
|
||||||
|
@ -1,5 +1,56 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const { bruToJsonV2, jsonToBruV2, bruToEnvJsonV2, envJsonToBruV2 } = require('@usebruno/lang');
|
const {
|
||||||
|
bruToJsonV2,
|
||||||
|
jsonToBruV2,
|
||||||
|
bruToEnvJsonV2,
|
||||||
|
envJsonToBruV2,
|
||||||
|
collectionBruToJson: _collectionBruToJson,
|
||||||
|
jsonToCollectionBru: _jsonToCollectionBru
|
||||||
|
} = require('@usebruno/lang');
|
||||||
|
|
||||||
|
const collectionBruToJson = (bru) => {
|
||||||
|
try {
|
||||||
|
const json = _collectionBruToJson(bru);
|
||||||
|
|
||||||
|
const transformedJson = {
|
||||||
|
request: {
|
||||||
|
params: _.get(json, 'query', []),
|
||||||
|
headers: _.get(json, 'headers', []),
|
||||||
|
auth: _.get(json, 'auth', {}),
|
||||||
|
script: _.get(json, 'script', {}),
|
||||||
|
vars: _.get(json, 'vars', {}),
|
||||||
|
tests: _.get(json, 'tests', '')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return transformedJson;
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const jsonToCollectionBru = (json) => {
|
||||||
|
try {
|
||||||
|
const collectionBruJson = {
|
||||||
|
query: _.get(json, 'request.params', []),
|
||||||
|
headers: _.get(json, 'request.headers', []),
|
||||||
|
auth: _.get(json, 'request.auth', {}),
|
||||||
|
script: {
|
||||||
|
req: _.get(json, 'request.script.req', ''),
|
||||||
|
res: _.get(json, 'request.script.res', '')
|
||||||
|
},
|
||||||
|
vars: {
|
||||||
|
req: _.get(json, 'request.vars.req', []),
|
||||||
|
res: _.get(json, 'request.vars.req', [])
|
||||||
|
},
|
||||||
|
tests: _.get(json, 'request.tests', '')
|
||||||
|
};
|
||||||
|
|
||||||
|
return _jsonToCollectionBru(collectionBruJson);
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const bruToEnvJson = (bru) => {
|
const bruToEnvJson = (bru) => {
|
||||||
try {
|
try {
|
||||||
@ -128,5 +179,7 @@ module.exports = {
|
|||||||
bruToJson,
|
bruToJson,
|
||||||
jsonToBru,
|
jsonToBru,
|
||||||
bruToEnvJson,
|
bruToEnvJson,
|
||||||
envJsonToBru
|
envJsonToBru,
|
||||||
|
collectionBruToJson,
|
||||||
|
jsonToCollectionBru
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,7 @@ const _ = require('lodash');
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { ipcMain, shell } = require('electron');
|
const { ipcMain, shell } = require('electron');
|
||||||
const { envJsonToBru, bruToJson, jsonToBru } = require('../bru');
|
const { envJsonToBru, bruToJson, jsonToBru, jsonToCollectionBru } = require('../bru');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isValidPathname,
|
isValidPathname,
|
||||||
@ -101,6 +101,17 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('renderer:save-collection-root', async (event, collectionPathname, collectionRoot) => {
|
||||||
|
try {
|
||||||
|
const collectionBruFilePath = path.join(collectionPathname, 'collection.bru');
|
||||||
|
|
||||||
|
const content = jsonToCollectionBru(collectionRoot);
|
||||||
|
await writeFile(collectionBruFilePath, content);
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// new request
|
// new request
|
||||||
ipcMain.handle('renderer:new-request', async (event, pathname, request) => {
|
ipcMain.handle('renderer:new-request', async (event, pathname, request) => {
|
||||||
try {
|
try {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
const os = require('os');
|
||||||
const qs = require('qs');
|
const qs = require('qs');
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
@ -5,7 +6,7 @@ const decomment = require('decomment');
|
|||||||
const Mustache = require('mustache');
|
const Mustache = require('mustache');
|
||||||
const FormData = require('form-data');
|
const FormData = require('form-data');
|
||||||
const { ipcMain } = require('electron');
|
const { ipcMain } = require('electron');
|
||||||
const { forOwn, extend, each, get } = require('lodash');
|
const { forOwn, extend, each, get, compact } = require('lodash');
|
||||||
const { VarsRuntime, AssertRuntime, ScriptRuntime, TestRuntime } = require('@usebruno/js');
|
const { VarsRuntime, AssertRuntime, ScriptRuntime, TestRuntime } = require('@usebruno/js');
|
||||||
const prepareRequest = require('./prepare-request');
|
const prepareRequest = require('./prepare-request');
|
||||||
const prepareGqlIntrospectionRequest = require('./prepare-gql-introspection-request');
|
const prepareGqlIntrospectionRequest = require('./prepare-gql-introspection-request');
|
||||||
@ -81,90 +82,202 @@ const getSize = (data) => {
|
|||||||
|
|
||||||
const registerNetworkIpc = (mainWindow) => {
|
const registerNetworkIpc = (mainWindow) => {
|
||||||
// handler for sending http request
|
// handler for sending http request
|
||||||
ipcMain.handle(
|
ipcMain.handle('send-http-request', async (event, item, collection, environment, collectionVariables) => {
|
||||||
'send-http-request',
|
const collectionUid = collection.uid;
|
||||||
async (event, item, collectionUid, collectionPath, environment, collectionVariables) => {
|
const collectionPath = collection.pathname;
|
||||||
const cancelTokenUid = uuid();
|
const cancelTokenUid = uuid();
|
||||||
const requestUid = uuid();
|
const requestUid = uuid();
|
||||||
|
|
||||||
const onConsoleLog = (type, args) => {
|
const onConsoleLog = (type, args) => {
|
||||||
console[type](...args);
|
console[type](...args);
|
||||||
|
|
||||||
mainWindow.webContents.send('main:console-log', {
|
mainWindow.webContents.send('main:console-log', {
|
||||||
type,
|
type,
|
||||||
args
|
args
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
mainWindow.webContents.send('main:run-request-event', {
|
||||||
|
type: 'request-queued',
|
||||||
|
requestUid,
|
||||||
|
collectionUid,
|
||||||
|
itemUid: item.uid,
|
||||||
|
cancelTokenUid
|
||||||
|
});
|
||||||
|
|
||||||
|
const collectionRoot = get(collection, 'root', {});
|
||||||
|
const _request = item.draft ? item.draft.request : item.request;
|
||||||
|
const request = prepareRequest(_request, collectionRoot);
|
||||||
|
const envVars = getEnvVars(environment);
|
||||||
|
const processEnvVars = getProcessEnvVars(collectionUid);
|
||||||
|
const brunoConfig = getBrunoConfig(collectionUid);
|
||||||
|
const scriptingConfig = get(brunoConfig, 'scripts', {});
|
||||||
|
|
||||||
|
try {
|
||||||
|
// make axios work in node using form data
|
||||||
|
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
|
||||||
|
if (request.headers && request.headers['content-type'] === 'multipart/form-data') {
|
||||||
|
const form = new FormData();
|
||||||
|
forOwn(request.data, (value, key) => {
|
||||||
|
form.append(key, value);
|
||||||
});
|
});
|
||||||
};
|
extend(request.headers, form.getHeaders());
|
||||||
|
request.data = form;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelToken = axios.CancelToken.source();
|
||||||
|
request.cancelToken = cancelToken.token;
|
||||||
|
saveCancelToken(cancelTokenUid, cancelToken);
|
||||||
|
|
||||||
|
// run pre-request vars
|
||||||
|
const preRequestVars = get(request, 'vars.req', []);
|
||||||
|
if (preRequestVars && preRequestVars.length) {
|
||||||
|
const varsRuntime = new VarsRuntime();
|
||||||
|
const result = varsRuntime.runPreRequestVars(
|
||||||
|
preRequestVars,
|
||||||
|
request,
|
||||||
|
envVars,
|
||||||
|
collectionVariables,
|
||||||
|
collectionPath,
|
||||||
|
processEnvVars
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
mainWindow.webContents.send('main:script-environment-update', {
|
||||||
|
envVariables: result.envVariables,
|
||||||
|
collectionVariables: result.collectionVariables,
|
||||||
|
requestUid,
|
||||||
|
collectionUid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// run pre-request script
|
||||||
|
const requestScript = compact([get(collectionRoot, 'request.script.req'), get(request, 'script.req')]).join(
|
||||||
|
os.EOL
|
||||||
|
);
|
||||||
|
if (requestScript && requestScript.length) {
|
||||||
|
const scriptRuntime = new ScriptRuntime();
|
||||||
|
const result = await scriptRuntime.runRequestScript(
|
||||||
|
decomment(requestScript),
|
||||||
|
request,
|
||||||
|
envVars,
|
||||||
|
collectionVariables,
|
||||||
|
collectionPath,
|
||||||
|
onConsoleLog,
|
||||||
|
processEnvVars,
|
||||||
|
scriptingConfig
|
||||||
|
);
|
||||||
|
|
||||||
|
mainWindow.webContents.send('main:script-environment-update', {
|
||||||
|
envVariables: result.envVariables,
|
||||||
|
collectionVariables: result.collectionVariables,
|
||||||
|
requestUid,
|
||||||
|
collectionUid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
interpolateVars(request, envVars, collectionVariables, processEnvVars);
|
||||||
|
|
||||||
|
// stringify the request url encoded params
|
||||||
|
if (request.headers['content-type'] === 'application/x-www-form-urlencoded') {
|
||||||
|
request.data = qs.stringify(request.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo:
|
||||||
|
// i have no clue why electron can't send the request object
|
||||||
|
// without safeParseJSON(safeStringifyJSON(request.data))
|
||||||
mainWindow.webContents.send('main:run-request-event', {
|
mainWindow.webContents.send('main:run-request-event', {
|
||||||
type: 'request-queued',
|
type: 'request-sent',
|
||||||
requestUid,
|
requestSent: {
|
||||||
|
url: request.url,
|
||||||
|
method: request.method,
|
||||||
|
headers: request.headers,
|
||||||
|
data: safeParseJSON(safeStringifyJSON(request.data))
|
||||||
|
},
|
||||||
collectionUid,
|
collectionUid,
|
||||||
itemUid: item.uid,
|
itemUid: item.uid,
|
||||||
|
requestUid,
|
||||||
cancelTokenUid
|
cancelTokenUid
|
||||||
});
|
});
|
||||||
|
|
||||||
const _request = item.draft ? item.draft.request : item.request;
|
const preferences = getPreferences();
|
||||||
const request = prepareRequest(_request);
|
const sslVerification = get(preferences, 'request.sslVerification', true);
|
||||||
const envVars = getEnvVars(environment);
|
const httpsAgentRequestFields = {};
|
||||||
const processEnvVars = getProcessEnvVars(collectionUid);
|
if (!sslVerification) {
|
||||||
const brunoConfig = getBrunoConfig(collectionUid);
|
httpsAgentRequestFields['rejectUnauthorized'] = false;
|
||||||
const scriptingConfig = get(brunoConfig, 'scripts', {});
|
} else {
|
||||||
|
const cacertArray = [preferences['cacert'], process.env.SSL_CERT_FILE, process.env.NODE_EXTRA_CA_CERTS];
|
||||||
try {
|
cacertFile = cacertArray.find((el) => el);
|
||||||
// make axios work in node using form data
|
if (cacertFile && cacertFile.length > 1) {
|
||||||
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
|
try {
|
||||||
if (request.headers && request.headers['content-type'] === 'multipart/form-data') {
|
const fs = require('fs');
|
||||||
const form = new FormData();
|
caCrt = fs.readFileSync(cacertFile);
|
||||||
forOwn(request.data, (value, key) => {
|
httpsAgentRequestFields['ca'] = caCrt;
|
||||||
form.append(key, value);
|
} catch (err) {
|
||||||
});
|
console.log('Error reading CA cert file:' + cacertFile, err);
|
||||||
extend(request.headers, form.getHeaders());
|
|
||||||
request.data = form;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cancelToken = axios.CancelToken.source();
|
|
||||||
request.cancelToken = cancelToken.token;
|
|
||||||
saveCancelToken(cancelTokenUid, cancelToken);
|
|
||||||
|
|
||||||
// run pre-request vars
|
|
||||||
const preRequestVars = get(request, 'vars.req', []);
|
|
||||||
if (preRequestVars && preRequestVars.length) {
|
|
||||||
const varsRuntime = new VarsRuntime();
|
|
||||||
const result = varsRuntime.runPreRequestVars(
|
|
||||||
preRequestVars,
|
|
||||||
request,
|
|
||||||
envVars,
|
|
||||||
collectionVariables,
|
|
||||||
collectionPath,
|
|
||||||
processEnvVars
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
mainWindow.webContents.send('main:script-environment-update', {
|
|
||||||
envVariables: result.envVariables,
|
|
||||||
collectionVariables: result.collectionVariables,
|
|
||||||
requestUid,
|
|
||||||
collectionUid
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// run pre-request script
|
// proxy configuration
|
||||||
const requestScript = get(request, 'script.req');
|
const brunoConfig = getBrunoConfig(collectionUid);
|
||||||
if (requestScript && requestScript.length) {
|
const proxyEnabled = get(brunoConfig, 'proxy.enabled', false);
|
||||||
const scriptRuntime = new ScriptRuntime();
|
if (proxyEnabled) {
|
||||||
const result = await scriptRuntime.runRequestScript(
|
let proxy;
|
||||||
decomment(requestScript),
|
|
||||||
request,
|
|
||||||
envVars,
|
|
||||||
collectionVariables,
|
|
||||||
collectionPath,
|
|
||||||
onConsoleLog,
|
|
||||||
processEnvVars,
|
|
||||||
scriptingConfig
|
|
||||||
);
|
|
||||||
|
|
||||||
|
const interpolationOptions = {
|
||||||
|
envVars,
|
||||||
|
collectionVariables,
|
||||||
|
processEnvVars
|
||||||
|
};
|
||||||
|
|
||||||
|
const proxyProtocol = interpolateString(get(brunoConfig, 'proxy.protocol'), interpolationOptions);
|
||||||
|
const proxyHostname = interpolateString(get(brunoConfig, 'proxy.hostname'), interpolationOptions);
|
||||||
|
const proxyPort = interpolateString(get(brunoConfig, 'proxy.port'), interpolationOptions);
|
||||||
|
const proxyAuthEnabled = get(brunoConfig, 'proxy.auth.enabled', false);
|
||||||
|
|
||||||
|
if (proxyAuthEnabled) {
|
||||||
|
const proxyAuthUsername = interpolateString(get(brunoConfig, 'proxy.auth.username'), interpolationOptions);
|
||||||
|
const proxyAuthPassword = interpolateString(get(brunoConfig, 'proxy.auth.password'), interpolationOptions);
|
||||||
|
|
||||||
|
proxy = `${proxyProtocol}://${proxyAuthUsername}:${proxyAuthPassword}@${proxyHostname}:${proxyPort}`;
|
||||||
|
} else {
|
||||||
|
proxy = `${proxyProtocol}://${proxyHostname}:${proxyPort}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
request.httpsAgent = new HttpsProxyAgent(
|
||||||
|
proxy,
|
||||||
|
Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
request.httpAgent = new HttpProxyAgent(proxy);
|
||||||
|
} else if (Object.keys(httpsAgentRequestFields).length > 0) {
|
||||||
|
request.httpsAgent = new https.Agent({
|
||||||
|
...httpsAgentRequestFields
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const axiosInstance = makeAxiosInstance();
|
||||||
|
|
||||||
|
/** @type {import('axios').AxiosResponse} */
|
||||||
|
const response = await axiosInstance(request);
|
||||||
|
|
||||||
|
// run post-response vars
|
||||||
|
const postResponseVars = get(request, 'vars.res', []);
|
||||||
|
if (postResponseVars && postResponseVars.length) {
|
||||||
|
const varsRuntime = new VarsRuntime();
|
||||||
|
const result = varsRuntime.runPostResponseVars(
|
||||||
|
postResponseVars,
|
||||||
|
request,
|
||||||
|
response,
|
||||||
|
envVars,
|
||||||
|
collectionVariables,
|
||||||
|
collectionPath,
|
||||||
|
processEnvVars
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
mainWindow.webContents.send('main:script-environment-update', {
|
mainWindow.webContents.send('main:script-environment-update', {
|
||||||
envVariables: result.envVariables,
|
envVariables: result.envVariables,
|
||||||
collectionVariables: result.collectionVariables,
|
collectionVariables: result.collectionVariables,
|
||||||
@ -172,141 +285,116 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
collectionUid
|
collectionUid
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interpolateVars(request, envVars, collectionVariables, processEnvVars);
|
// run post-response script
|
||||||
|
const responseScript = compact([get(collectionRoot, 'request.script.res'), get(request, 'script.res')]).join(
|
||||||
|
os.EOL
|
||||||
|
);
|
||||||
|
if (responseScript && responseScript.length) {
|
||||||
|
const scriptRuntime = new ScriptRuntime();
|
||||||
|
const result = await scriptRuntime.runResponseScript(
|
||||||
|
decomment(responseScript),
|
||||||
|
request,
|
||||||
|
response,
|
||||||
|
envVars,
|
||||||
|
collectionVariables,
|
||||||
|
collectionPath,
|
||||||
|
onConsoleLog,
|
||||||
|
processEnvVars,
|
||||||
|
scriptingConfig
|
||||||
|
);
|
||||||
|
|
||||||
// stringify the request url encoded params
|
mainWindow.webContents.send('main:script-environment-update', {
|
||||||
if (request.headers['content-type'] === 'application/x-www-form-urlencoded') {
|
envVariables: result.envVariables,
|
||||||
request.data = qs.stringify(request.data);
|
collectionVariables: result.collectionVariables,
|
||||||
}
|
requestUid,
|
||||||
|
collectionUid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// run assertions
|
||||||
|
const assertions = get(request, 'assertions');
|
||||||
|
if (assertions) {
|
||||||
|
const assertRuntime = new AssertRuntime();
|
||||||
|
const results = assertRuntime.runAssertions(
|
||||||
|
assertions,
|
||||||
|
request,
|
||||||
|
response,
|
||||||
|
envVars,
|
||||||
|
collectionVariables,
|
||||||
|
collectionPath
|
||||||
|
);
|
||||||
|
|
||||||
// todo:
|
|
||||||
// i have no clue why electron can't send the request object
|
|
||||||
// without safeParseJSON(safeStringifyJSON(request.data))
|
|
||||||
mainWindow.webContents.send('main:run-request-event', {
|
mainWindow.webContents.send('main:run-request-event', {
|
||||||
type: 'request-sent',
|
type: 'assertion-results',
|
||||||
requestSent: {
|
results: results,
|
||||||
url: request.url,
|
|
||||||
method: request.method,
|
|
||||||
headers: request.headers,
|
|
||||||
data: safeParseJSON(safeStringifyJSON(request.data))
|
|
||||||
},
|
|
||||||
collectionUid,
|
|
||||||
itemUid: item.uid,
|
itemUid: item.uid,
|
||||||
requestUid,
|
requestUid,
|
||||||
cancelTokenUid
|
collectionUid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// run tests
|
||||||
|
const testFile = compact([
|
||||||
|
get(collectionRoot, 'request.tests'),
|
||||||
|
item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests')
|
||||||
|
]).join(os.EOL);
|
||||||
|
if (typeof testFile === 'string') {
|
||||||
|
const testRuntime = new TestRuntime();
|
||||||
|
const testResults = await testRuntime.runTests(
|
||||||
|
decomment(testFile),
|
||||||
|
request,
|
||||||
|
response,
|
||||||
|
envVars,
|
||||||
|
collectionVariables,
|
||||||
|
collectionPath,
|
||||||
|
onConsoleLog,
|
||||||
|
processEnvVars,
|
||||||
|
scriptingConfig
|
||||||
|
);
|
||||||
|
|
||||||
|
mainWindow.webContents.send('main:run-request-event', {
|
||||||
|
type: 'test-results',
|
||||||
|
results: testResults.results,
|
||||||
|
itemUid: item.uid,
|
||||||
|
requestUid,
|
||||||
|
collectionUid
|
||||||
});
|
});
|
||||||
|
|
||||||
const preferences = getPreferences();
|
mainWindow.webContents.send('main:script-environment-update', {
|
||||||
const sslVerification = get(preferences, 'request.sslVerification', true);
|
envVariables: testResults.envVariables,
|
||||||
const httpsAgentRequestFields = {};
|
collectionVariables: testResults.collectionVariables,
|
||||||
if (!sslVerification) {
|
requestUid,
|
||||||
httpsAgentRequestFields['rejectUnauthorized'] = false;
|
collectionUid
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// proxy configuration
|
deleteCancelToken(cancelTokenUid);
|
||||||
const brunoConfig = getBrunoConfig(collectionUid);
|
// Prevents the duration on leaking to the actual result
|
||||||
const proxyEnabled = get(brunoConfig, 'proxy.enabled', false);
|
const requestDuration = response.headers.get('request-duration');
|
||||||
if (proxyEnabled) {
|
response.headers.delete('request-duration');
|
||||||
let proxy;
|
|
||||||
|
|
||||||
const interpolationOptions = {
|
return {
|
||||||
envVars,
|
status: response.status,
|
||||||
collectionVariables,
|
statusText: response.statusText,
|
||||||
processEnvVars
|
headers: response.headers,
|
||||||
};
|
data: response.data,
|
||||||
|
duration: requestDuration
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
// todo: better error handling
|
||||||
|
// need to convey the error to the UI
|
||||||
|
// and need not be always a network error
|
||||||
|
deleteCancelToken(cancelTokenUid);
|
||||||
|
|
||||||
const proxyProtocol = interpolateString(get(brunoConfig, 'proxy.protocol'), interpolationOptions);
|
if (axios.isCancel(error)) {
|
||||||
const proxyHostname = interpolateString(get(brunoConfig, 'proxy.hostname'), interpolationOptions);
|
let error = new Error('Request cancelled');
|
||||||
const proxyPort = interpolateString(get(brunoConfig, 'proxy.port'), interpolationOptions);
|
error.isCancel = true;
|
||||||
const proxyAuthEnabled = get(brunoConfig, 'proxy.auth.enabled', false);
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
if (proxyAuthEnabled) {
|
|
||||||
const proxyAuthUsername = interpolateString(get(brunoConfig, 'proxy.auth.username'), interpolationOptions);
|
|
||||||
const proxyAuthPassword = interpolateString(get(brunoConfig, 'proxy.auth.password'), interpolationOptions);
|
|
||||||
|
|
||||||
proxy = `${proxyProtocol}://${proxyAuthUsername}:${proxyAuthPassword}@${proxyHostname}:${proxyPort}`;
|
|
||||||
} else {
|
|
||||||
proxy = `${proxyProtocol}://${proxyHostname}:${proxyPort}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
request.httpsAgent = new HttpsProxyAgent(
|
|
||||||
proxy,
|
|
||||||
Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined
|
|
||||||
);
|
|
||||||
|
|
||||||
request.httpAgent = new HttpProxyAgent(proxy);
|
|
||||||
} else if (Object.keys(httpsAgentRequestFields).length > 0) {
|
|
||||||
request.httpsAgent = new https.Agent({
|
|
||||||
...httpsAgentRequestFields
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const axiosInstance = makeAxiosInstance();
|
|
||||||
|
|
||||||
/** @type {import('axios').AxiosResponse} */
|
|
||||||
const response = await axiosInstance(request);
|
|
||||||
|
|
||||||
// run post-response vars
|
|
||||||
const postResponseVars = get(request, 'vars.res', []);
|
|
||||||
if (postResponseVars && postResponseVars.length) {
|
|
||||||
const varsRuntime = new VarsRuntime();
|
|
||||||
const result = varsRuntime.runPostResponseVars(
|
|
||||||
postResponseVars,
|
|
||||||
request,
|
|
||||||
response,
|
|
||||||
envVars,
|
|
||||||
collectionVariables,
|
|
||||||
collectionPath,
|
|
||||||
processEnvVars
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
mainWindow.webContents.send('main:script-environment-update', {
|
|
||||||
envVariables: result.envVariables,
|
|
||||||
collectionVariables: result.collectionVariables,
|
|
||||||
requestUid,
|
|
||||||
collectionUid
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// run post-response script
|
|
||||||
const responseScript = get(request, 'script.res');
|
|
||||||
if (responseScript && responseScript.length) {
|
|
||||||
const scriptRuntime = new ScriptRuntime();
|
|
||||||
const result = await scriptRuntime.runResponseScript(
|
|
||||||
decomment(responseScript),
|
|
||||||
request,
|
|
||||||
response,
|
|
||||||
envVars,
|
|
||||||
collectionVariables,
|
|
||||||
collectionPath,
|
|
||||||
onConsoleLog,
|
|
||||||
processEnvVars,
|
|
||||||
scriptingConfig
|
|
||||||
);
|
|
||||||
|
|
||||||
mainWindow.webContents.send('main:script-environment-update', {
|
|
||||||
envVariables: result.envVariables,
|
|
||||||
collectionVariables: result.collectionVariables,
|
|
||||||
requestUid,
|
|
||||||
collectionUid
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (error && error.response) {
|
||||||
// run assertions
|
// run assertions
|
||||||
const assertions = get(request, 'assertions');
|
const assertions = get(request, 'assertions');
|
||||||
if (assertions) {
|
if (assertions) {
|
||||||
@ -314,7 +402,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
const results = assertRuntime.runAssertions(
|
const results = assertRuntime.runAssertions(
|
||||||
assertions,
|
assertions,
|
||||||
request,
|
request,
|
||||||
response,
|
error.response,
|
||||||
envVars,
|
envVars,
|
||||||
collectionVariables,
|
collectionVariables,
|
||||||
collectionPath
|
collectionPath
|
||||||
@ -330,13 +418,16 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// run tests
|
// run tests
|
||||||
const testFile = item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests');
|
const testFile = compact([
|
||||||
|
get(collectionRoot, 'request.tests'),
|
||||||
|
item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests')
|
||||||
|
]).join(os.EOL);
|
||||||
if (typeof testFile === 'string') {
|
if (typeof testFile === 'string') {
|
||||||
const testRuntime = new TestRuntime();
|
const testRuntime = new TestRuntime();
|
||||||
const testResults = await testRuntime.runTests(
|
const testResults = await testRuntime.runTests(
|
||||||
decomment(testFile),
|
decomment(testFile),
|
||||||
request,
|
request,
|
||||||
response,
|
error.response,
|
||||||
envVars,
|
envVars,
|
||||||
collectionVariables,
|
collectionVariables,
|
||||||
collectionPath,
|
collectionPath,
|
||||||
@ -361,101 +452,21 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteCancelToken(cancelTokenUid);
|
// Prevents the duration from leaking to the actual result
|
||||||
// Prevents the duration on leaking to the actual result
|
const requestDuration = error.response.headers.get('request-duration');
|
||||||
const requestDuration = response.headers.get('request-duration');
|
error.response.headers.delete('request-duration');
|
||||||
response.headers.delete('request-duration');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: response.status,
|
status: error.response.status,
|
||||||
statusText: response.statusText,
|
statusText: error.response.statusText,
|
||||||
headers: response.headers,
|
headers: error.response.headers,
|
||||||
data: response.data,
|
data: error.response.data,
|
||||||
duration: requestDuration
|
duration: requestDuration ?? 0
|
||||||
};
|
};
|
||||||
} catch (error) {
|
|
||||||
// todo: better error handling
|
|
||||||
// need to convey the error to the UI
|
|
||||||
// and need not be always a network error
|
|
||||||
deleteCancelToken(cancelTokenUid);
|
|
||||||
|
|
||||||
if (axios.isCancel(error)) {
|
|
||||||
let error = new Error('Request cancelled');
|
|
||||||
error.isCancel = true;
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error && error.response) {
|
|
||||||
// run assertions
|
|
||||||
const assertions = get(request, 'assertions');
|
|
||||||
if (assertions) {
|
|
||||||
const assertRuntime = new AssertRuntime();
|
|
||||||
const results = assertRuntime.runAssertions(
|
|
||||||
assertions,
|
|
||||||
request,
|
|
||||||
error.response,
|
|
||||||
envVars,
|
|
||||||
collectionVariables,
|
|
||||||
collectionPath
|
|
||||||
);
|
|
||||||
|
|
||||||
mainWindow.webContents.send('main:run-request-event', {
|
|
||||||
type: 'assertion-results',
|
|
||||||
results: results,
|
|
||||||
itemUid: item.uid,
|
|
||||||
requestUid,
|
|
||||||
collectionUid
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// run tests
|
|
||||||
const testFile = item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests');
|
|
||||||
if (typeof testFile === 'string') {
|
|
||||||
const testRuntime = new TestRuntime();
|
|
||||||
const testResults = await testRuntime.runTests(
|
|
||||||
decomment(testFile),
|
|
||||||
request,
|
|
||||||
error.response,
|
|
||||||
envVars,
|
|
||||||
collectionVariables,
|
|
||||||
collectionPath,
|
|
||||||
onConsoleLog,
|
|
||||||
processEnvVars,
|
|
||||||
scriptingConfig
|
|
||||||
);
|
|
||||||
|
|
||||||
mainWindow.webContents.send('main:run-request-event', {
|
|
||||||
type: 'test-results',
|
|
||||||
results: testResults.results,
|
|
||||||
itemUid: item.uid,
|
|
||||||
requestUid,
|
|
||||||
collectionUid
|
|
||||||
});
|
|
||||||
|
|
||||||
mainWindow.webContents.send('main:script-environment-update', {
|
|
||||||
envVariables: testResults.envVariables,
|
|
||||||
collectionVariables: testResults.collectionVariables,
|
|
||||||
requestUid,
|
|
||||||
collectionUid
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevents the duration from leaking to the actual result
|
|
||||||
const requestDuration = error.response.headers.get('request-duration');
|
|
||||||
error.response.headers.delete('request-duration');
|
|
||||||
return {
|
|
||||||
status: error.response.status,
|
|
||||||
statusText: error.response.statusText,
|
|
||||||
headers: error.response.headers,
|
|
||||||
data: error.response.data,
|
|
||||||
duration: requestDuration ?? 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
ipcMain.handle('cancel-http-request', async (event, cancelTokenUid) => {
|
ipcMain.handle('cancel-http-request', async (event, cancelTokenUid) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -516,6 +527,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
const folderUid = folder ? folder.uid : null;
|
const folderUid = folder ? folder.uid : null;
|
||||||
const brunoConfig = getBrunoConfig(collectionUid);
|
const brunoConfig = getBrunoConfig(collectionUid);
|
||||||
const scriptingConfig = get(brunoConfig, 'scripts', {});
|
const scriptingConfig = get(brunoConfig, 'scripts', {});
|
||||||
|
const collectionRoot = get(collection, 'root', {});
|
||||||
|
|
||||||
const onConsoleLog = (type, args) => {
|
const onConsoleLog = (type, args) => {
|
||||||
console[type](...args);
|
console[type](...args);
|
||||||
@ -574,7 +586,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const _request = item.draft ? item.draft.request : item.request;
|
const _request = item.draft ? item.draft.request : item.request;
|
||||||
const request = prepareRequest(_request);
|
const request = prepareRequest(_request, collectionRoot);
|
||||||
const processEnvVars = getProcessEnvVars(collectionUid);
|
const processEnvVars = getProcessEnvVars(collectionUid);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -611,7 +623,9 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// run pre-request script
|
// run pre-request script
|
||||||
const requestScript = get(request, 'script.req');
|
const requestScript = compact([get(collectionRoot, 'request.script.req'), get(request, 'script.req')]).join(
|
||||||
|
os.EOL
|
||||||
|
);
|
||||||
if (requestScript && requestScript.length) {
|
if (requestScript && requestScript.length) {
|
||||||
const scriptRuntime = new ScriptRuntime();
|
const scriptRuntime = new ScriptRuntime();
|
||||||
const result = await scriptRuntime.runRequestScript(
|
const result = await scriptRuntime.runRequestScript(
|
||||||
@ -724,7 +738,10 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// run response script
|
// run response script
|
||||||
const responseScript = get(request, 'script.res');
|
const responseScript = compact([
|
||||||
|
get(collectionRoot, 'request.script.res'),
|
||||||
|
get(request, 'script.res')
|
||||||
|
]).join(os.EOL);
|
||||||
if (responseScript && responseScript.length) {
|
if (responseScript && responseScript.length) {
|
||||||
const scriptRuntime = new ScriptRuntime();
|
const scriptRuntime = new ScriptRuntime();
|
||||||
const result = await scriptRuntime.runResponseScript(
|
const result = await scriptRuntime.runResponseScript(
|
||||||
@ -768,7 +785,10 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// run tests
|
// run tests
|
||||||
const testFile = item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests');
|
const testFile = compact([
|
||||||
|
get(collectionRoot, 'request.tests'),
|
||||||
|
item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests')
|
||||||
|
]).join(os.EOL);
|
||||||
if (typeof testFile === 'string') {
|
if (typeof testFile === 'string') {
|
||||||
const testRuntime = new TestRuntime();
|
const testRuntime = new TestRuntime();
|
||||||
const testResults = await testRuntime.runTests(
|
const testResults = await testRuntime.runTests(
|
||||||
@ -848,7 +868,10 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// run tests
|
// run tests
|
||||||
const testFile = item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests');
|
const testFile = compact([
|
||||||
|
get(collectionRoot, 'request.tests'),
|
||||||
|
item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests')
|
||||||
|
]).join(os.EOL);
|
||||||
if (typeof testFile === 'string') {
|
if (typeof testFile === 'string') {
|
||||||
const testRuntime = new TestRuntime();
|
const testRuntime = new TestRuntime();
|
||||||
const testResults = await testRuntime.runTests(
|
const testResults = await testRuntime.runTests(
|
||||||
|
@ -1,9 +1,20 @@
|
|||||||
const { get, each, filter } = require('lodash');
|
const { get, each, filter } = require('lodash');
|
||||||
const decomment = require('decomment');
|
const decomment = require('decomment');
|
||||||
|
|
||||||
const prepareRequest = (request) => {
|
const prepareRequest = (request, collectionRoot) => {
|
||||||
const headers = {};
|
const headers = {};
|
||||||
let contentTypeDefined = false;
|
let contentTypeDefined = false;
|
||||||
|
|
||||||
|
// collection headers
|
||||||
|
each(get(collectionRoot, 'request.headers', []), (h) => {
|
||||||
|
if (h.enabled) {
|
||||||
|
headers[h.name] = h.value;
|
||||||
|
if (h.name.toLowerCase() === 'content-type') {
|
||||||
|
contentTypeDefined = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
each(request.headers, (h) => {
|
each(request.headers, (h) => {
|
||||||
if (h.enabled) {
|
if (h.enabled) {
|
||||||
headers[h.name] = h.value;
|
headers[h.name] = h.value;
|
||||||
@ -20,6 +31,23 @@ const prepareRequest = (request) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Authentication
|
// Authentication
|
||||||
|
// A request can override the collection auth with another auth
|
||||||
|
// But it cannot override the collection auth with no auth
|
||||||
|
// We will provide support for disabling the auth via scripting in the future
|
||||||
|
const collectionAuth = get(collectionRoot, 'request.auth');
|
||||||
|
if (collectionAuth) {
|
||||||
|
if (collectionAuth.mode === 'basic') {
|
||||||
|
axiosRequest.auth = {
|
||||||
|
username: get(collectionAuth, 'basic.username'),
|
||||||
|
password: get(collectionAuth, 'basic.password')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (collectionAuth.mode === 'bearer') {
|
||||||
|
axiosRequest.headers['authorization'] = `Bearer ${get(collectionAuth, 'bearer.token')}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (request.auth) {
|
if (request.auth) {
|
||||||
if (request.auth.mode === 'basic') {
|
if (request.auth.mode === 'basic') {
|
||||||
axiosRequest.auth = {
|
axiosRequest.auth = {
|
||||||
|
@ -1,21 +1,23 @@
|
|||||||
const { bruToJson, jsonToBru, bruToEnvJson, envJsonToBru } = require('../v1/src');
|
|
||||||
|
|
||||||
const bruToJsonV2 = require('../v2/src/bruToJson');
|
const bruToJsonV2 = require('../v2/src/bruToJson');
|
||||||
const jsonToBruV2 = require('../v2/src/jsonToBru');
|
const jsonToBruV2 = require('../v2/src/jsonToBru');
|
||||||
const bruToEnvJsonV2 = require('../v2/src/envToJson');
|
const bruToEnvJsonV2 = require('../v2/src/envToJson');
|
||||||
const envJsonToBruV2 = require('../v2/src/jsonToEnv');
|
const envJsonToBruV2 = require('../v2/src/jsonToEnv');
|
||||||
const dotenvToJson = require('../v2/src/dotenvToJson');
|
const dotenvToJson = require('../v2/src/dotenvToJson');
|
||||||
|
|
||||||
module.exports = {
|
const collectionBruToJson = require('../v2/src/collectionBruToJson');
|
||||||
bruToJson,
|
const jsonToCollectionBru = require('../v2/src/jsonToCollectionBru');
|
||||||
jsonToBru,
|
|
||||||
bruToEnvJson,
|
|
||||||
envJsonToBru,
|
|
||||||
|
|
||||||
|
// Todo: remove V2 suffixes
|
||||||
|
// Changes will have to be made to the CLI and GUI
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
bruToJsonV2,
|
bruToJsonV2,
|
||||||
jsonToBruV2,
|
jsonToBruV2,
|
||||||
bruToEnvJsonV2,
|
bruToEnvJsonV2,
|
||||||
envJsonToBruV2,
|
envJsonToBruV2,
|
||||||
|
|
||||||
|
collectionBruToJson,
|
||||||
|
jsonToCollectionBru,
|
||||||
|
|
||||||
dotenvToJson
|
dotenvToJson
|
||||||
};
|
};
|
||||||
|
@ -12,7 +12,7 @@ const stripLastLine = (text) => {
|
|||||||
return text.replace(/(\r?\n)$/, '');
|
return text.replace(/(\r?\n)$/, '');
|
||||||
};
|
};
|
||||||
|
|
||||||
const jsonToBru = (json) => {
|
const jsonToCollectionBru = (json) => {
|
||||||
const { meta, query, headers, auth, script, tests, vars, docs } = json;
|
const { meta, query, headers, auth, script, tests, vars, docs } = json;
|
||||||
|
|
||||||
let bru = '';
|
let bru = '';
|
||||||
@ -182,4 +182,4 @@ ${indentString(docs)}
|
|||||||
return stripLastLine(bru);
|
return stripLastLine(bru);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = jsonToBru;
|
module.exports = jsonToCollectionBru;
|
||||||
|
@ -73,6 +73,7 @@ Even if you are not able to make contributions via code, please don't hesitate t
|
|||||||
[Twitter](https://twitter.com/use_bruno) <br />
|
[Twitter](https://twitter.com/use_bruno) <br />
|
||||||
[Website](https://www.usebruno.com) <br />
|
[Website](https://www.usebruno.com) <br />
|
||||||
[Discord](https://discord.com/invite/KgcZUncpjq)
|
[Discord](https://discord.com/invite/KgcZUncpjq)
|
||||||
|
[LinkedIn](https://www.linkedin.com/company/usebruno)
|
||||||
|
|
||||||
### License 📄
|
### License 📄
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user