mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-25 01:14:23 +01:00
feat: support for sending multipart form data (resolves #12)
This commit is contained in:
parent
bd153bf849
commit
3bf18a1127
14
main/ipc.js
14
main/ipc.js
@ -1,10 +1,24 @@
|
||||
const axios = require('axios');
|
||||
const FormData = require('form-data');
|
||||
const { ipcMain } = require('electron');
|
||||
const { forOwn, extend } = require('lodash');
|
||||
|
||||
|
||||
const registerIpc = () => {
|
||||
// handler for sending http request
|
||||
ipcMain.handle('send-http-request', async (event, request) => {
|
||||
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 result = await axios(request);
|
||||
|
||||
return {
|
||||
|
12446
package-lock.json
generated
12446
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -32,6 +32,7 @@
|
||||
"electron-store": "^8.0.1",
|
||||
"electron-util": "^0.17.2",
|
||||
"escape-html": "^1.0.3",
|
||||
"form-data": "^4.0.0",
|
||||
"formik": "^2.2.9",
|
||||
"fs-extra": "^10.0.1",
|
||||
"graphiql": "^1.5.9",
|
||||
|
@ -0,0 +1,45 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-weight: 600;
|
||||
|
||||
thead, td {
|
||||
border: 1px solid #efefef;
|
||||
}
|
||||
|
||||
thead {
|
||||
color: #616161;
|
||||
font-size: 0.8125rem;
|
||||
user-select: none;
|
||||
}
|
||||
td {
|
||||
padding: 6px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-add-param {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
border: solid 1px transparent;
|
||||
outline: none !important;
|
||||
|
||||
&:focus{
|
||||
outline: none !important;
|
||||
border: solid 1px transparent;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
119
renderer/components/RequestPane/MultipartFormParams/index.js
Normal file
119
renderer/components/RequestPane/MultipartFormParams/index.js
Normal file
@ -0,0 +1,119 @@
|
||||
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 { addMultipartFormParam, updateMultipartFormParam, deleteMultipartFormParam } from 'providers/ReduxStore/slices/collections';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const MultipartFormParams = ({item, collection}) => {
|
||||
const dispatch = useDispatch();
|
||||
const params = item.draft ? get(item, 'draft.request.body.multipartForm') : get(item, 'request.body.multipartForm');
|
||||
|
||||
const addParam = () => {
|
||||
dispatch(addMultipartFormParam({
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleParamChange = (e, _param, type) => {
|
||||
const param = cloneDeep(_param);
|
||||
switch(type) {
|
||||
case 'name' : {
|
||||
param.name = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'value' : {
|
||||
param.value = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'description' : {
|
||||
param.description = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'enabled' : {
|
||||
param.enabled = e.target.checked;
|
||||
break;
|
||||
}
|
||||
}
|
||||
dispatch(updateMultipartFormParam({
|
||||
param: param,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
}));
|
||||
};
|
||||
|
||||
const handleRemoveParams = (param) => {
|
||||
dispatch(deleteMultipartFormParam({
|
||||
paramUid: param.uid,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Key</td>
|
||||
<td>Value</td>
|
||||
<td>Description</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{params && params.length ? params.map((param, index) => {
|
||||
return (
|
||||
<tr key={param.uid}>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false"
|
||||
value={param.name}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'name')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false"
|
||||
value={param.value}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'value')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false"
|
||||
value={param.description}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'description')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={param.enabled}
|
||||
className="mr-3 mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'enabled')}
|
||||
/>
|
||||
<button onClick={() => handleRemoveParams(param)}>
|
||||
<IconTrash strokeWidth={1.5} size={20}/>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}) : null}
|
||||
</tbody>
|
||||
</table>
|
||||
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={addParam}>+ Add Param</button>
|
||||
</StyledWrapper>
|
||||
)
|
||||
};
|
||||
export default MultipartFormParams;
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||
import get from 'lodash/get';
|
||||
import CodeEditor from 'components/CodeEditor';
|
||||
import FormUrlEncodedParams from 'components/RequestPane/FormUrlEncodedParams';
|
||||
import MultipartFormParams from 'components/RequestPane/MultipartFormParams';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { updateRequestBody, sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
@ -52,6 +53,10 @@ const RequestBody = ({item, collection}) => {
|
||||
return <FormUrlEncodedParams item={item} collection={collection}/>;
|
||||
}
|
||||
|
||||
if(bodyMode === 'multipartForm') {
|
||||
return <MultipartFormParams item={item} collection={collection}/>;
|
||||
}
|
||||
|
||||
return(
|
||||
<StyledWrapper className="w-full">
|
||||
No Body
|
||||
|
@ -398,6 +398,61 @@ export const collectionsSlice = createSlice({
|
||||
}
|
||||
}
|
||||
},
|
||||
addMultipartFormParam: (state, action) => {
|
||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||
|
||||
if(collection) {
|
||||
const item = findItemInCollection(collection, action.payload.itemUid);
|
||||
|
||||
if(item && isItemARequest(item)) {
|
||||
if(!item.draft) {
|
||||
item.draft = cloneDeep(item);
|
||||
}
|
||||
item.draft.request.body.multipartForm = item.draft.request.body.multipartForm || [];
|
||||
item.draft.request.body.multipartForm.push({
|
||||
uid: uuid(),
|
||||
name: '',
|
||||
value: '',
|
||||
description: '',
|
||||
enabled: true
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
updateMultipartFormParam: (state, action) => {
|
||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||
|
||||
if(collection) {
|
||||
const item = findItemInCollection(collection, action.payload.itemUid);
|
||||
|
||||
if(item && isItemARequest(item)) {
|
||||
if(!item.draft) {
|
||||
item.draft = cloneDeep(item);
|
||||
}
|
||||
const param = find(item.draft.request.body.multipartForm, (p) => p.uid === action.payload.param.uid);
|
||||
if(param) {
|
||||
param.name = action.payload.param.name;
|
||||
param.value = action.payload.param.value;
|
||||
param.description = action.payload.param.description;
|
||||
param.enabled = action.payload.param.enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
deleteMultipartFormParam: (state, action) => {
|
||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||
|
||||
if(collection) {
|
||||
const item = findItemInCollection(collection, action.payload.itemUid);
|
||||
|
||||
if(item && isItemARequest(item)) {
|
||||
if(!item.draft) {
|
||||
item.draft = cloneDeep(item);
|
||||
}
|
||||
item.draft.request.body.multipartForm = filter(item.draft.request.body.multipartForm, (p) => p.uid !== action.payload.paramUid);
|
||||
}
|
||||
}
|
||||
},
|
||||
updateRequestBodyMode: (state, action) => {
|
||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||
|
||||
@ -487,6 +542,9 @@ export const {
|
||||
addFormUrlEncodedParam,
|
||||
updateFormUrlEncodedParam,
|
||||
deleteFormUrlEncodedParam,
|
||||
addMultipartFormParam,
|
||||
updateMultipartFormParam,
|
||||
deleteMultipartFormParam,
|
||||
updateRequestBodyMode,
|
||||
updateRequestBody,
|
||||
updateRequestMethod
|
||||
|
@ -140,6 +140,18 @@ export const transformCollectionToSaveToIdb = (collection, options = {}) => {
|
||||
});
|
||||
};
|
||||
|
||||
const copyMultipartFormParams = (params = []) => {
|
||||
return map(params, (param) => {
|
||||
return {
|
||||
uid: param.uid,
|
||||
name: param.name,
|
||||
value: param.value,
|
||||
description: param.description,
|
||||
enabled: param.enabled
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const copyItems = (sourceItems, destItems) => {
|
||||
each(sourceItems, (si) => {
|
||||
const di = {
|
||||
@ -163,7 +175,8 @@ export const transformCollectionToSaveToIdb = (collection, options = {}) => {
|
||||
text: si.draft.request.body.text,
|
||||
xml: si.draft.request.body.xml,
|
||||
multipartForm: si.draft.request.body.multipartForm,
|
||||
formUrlEncoded: copyFormUrlEncodedParams(si.draft.request.body.formUrlEncoded)
|
||||
formUrlEncoded: copyFormUrlEncodedParams(si.draft.request.body.formUrlEncoded),
|
||||
multipartForm: copyMultipartFormParams(si.draft.request.body.multipartForm)
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -180,7 +193,8 @@ export const transformCollectionToSaveToIdb = (collection, options = {}) => {
|
||||
text: si.request.body.text,
|
||||
xml: si.request.body.xml,
|
||||
multipartForm: si.request.body.multipartForm,
|
||||
formUrlEncoded: copyFormUrlEncodedParams(si.request.body.formUrlEncoded)
|
||||
formUrlEncoded: copyFormUrlEncodedParams(si.request.body.formUrlEncoded),
|
||||
multipartForm: copyMultipartFormParams(si.request.body.multipartForm)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -42,7 +42,7 @@ const sendHttpRequest = async (request) => {
|
||||
};
|
||||
|
||||
if(request.body.mode === 'json') {
|
||||
options.headers['Content-Type'] = 'application/json';
|
||||
options.headers['content-type'] = 'application/json';
|
||||
try {
|
||||
options.data = JSON.parse(request.body.json);
|
||||
} catch (ex) {
|
||||
@ -51,23 +51,31 @@ const sendHttpRequest = async (request) => {
|
||||
}
|
||||
|
||||
if(request.body.mode === 'text') {
|
||||
options.headers['Content-Type'] = 'text/plain';
|
||||
options.headers['content-type'] = 'text/plain';
|
||||
options.data = request.body.text;
|
||||
}
|
||||
|
||||
if(request.body.mode === 'xml') {
|
||||
options.headers['Content-Type'] = 'text/xml';
|
||||
options.headers['content-type'] = 'text/xml';
|
||||
options.data = request.body.xml;
|
||||
}
|
||||
|
||||
if(request.body.mode === 'formUrlEncoded') {
|
||||
options.headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
options.headers['content-type'] = 'application/x-www-form-urlencoded';
|
||||
const params = {};
|
||||
const enabledParams = filter(request.body.formUrlEncoded, p => p.enabled);
|
||||
each(enabledParams, (p) => params[p.name] = p.value);
|
||||
options.data = qs.stringify(params);
|
||||
}
|
||||
|
||||
if(request.body.mode === 'multipartForm') {
|
||||
const params = {};
|
||||
const enabledParams = filter(request.body.multipartForm, p => p.enabled);
|
||||
each(enabledParams, (p) => params[p.name] = p.value);
|
||||
options.headers['content-type'] = 'multipart/form-data';
|
||||
options.data = params;
|
||||
}
|
||||
|
||||
console.log('>>> Sending Request');
|
||||
console.log(options);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user