mirror of
https://github.com/usebruno/bruno.git
synced 2025-02-01 18:39:34 +01:00
--------- Co-authored-by: marcosadsj <marcosadsj@gmail.com> Co-authored-by: naman-bruno <naman@usebruno.com> Co-authored-by: sanish chirayath <sanish@usebruno.com>
This commit is contained in:
parent
ecf883ba0d
commit
ed56584ba6
@ -34,7 +34,7 @@
|
||||
"graphiql": "3.7.1",
|
||||
"graphql": "^16.6.0",
|
||||
"graphql-request": "^3.7.0",
|
||||
"httpsnippet": "^3.0.6",
|
||||
"httpsnippet": "^3.0.9",
|
||||
"i18next": "24.1.2",
|
||||
"idb": "^7.0.0",
|
||||
"immer": "^9.0.15",
|
||||
|
@ -78,7 +78,10 @@ const EnvironmentSelector = ({ collection }) => {
|
||||
<IconDatabaseOff size={18} strokeWidth={1.5} />
|
||||
<span className="ml-2">No Environment</span>
|
||||
</div>
|
||||
<div className="dropdown-item border-top" onClick={handleSettingsIconClick}>
|
||||
<div className="dropdown-item border-top" onClick={() => {
|
||||
handleSettingsIconClick();
|
||||
dropdownTippyRef.current.hide();
|
||||
}}>
|
||||
<div className="pr-2 text-gray-600">
|
||||
<IconSettings size={18} strokeWidth={1.5} />
|
||||
</div>
|
||||
|
@ -6,10 +6,9 @@ import { IconX } from '@tabler/icons';
|
||||
import { isWindowsOS } from 'utils/common/platform';
|
||||
import slash from 'utils/common/slash';
|
||||
|
||||
const FilePickerEditor = ({ value, onChange, collection, isSingleFilePicker = false}) => {
|
||||
value = value || [];
|
||||
const FilePickerEditor = ({ value, onChange, collection, isSingleFilePicker = false }) => {
|
||||
const dispatch = useDispatch();
|
||||
const filenames = value
|
||||
const filenames = (isSingleFilePicker ? [value] : value || [])
|
||||
.filter((v) => v != null && v != '')
|
||||
.map((v) => {
|
||||
const separator = isWindowsOS() ? '\\' : '/';
|
||||
@ -20,7 +19,7 @@ const FilePickerEditor = ({ value, onChange, collection, isSingleFilePicker = fa
|
||||
const title = filenames.map((v) => `- ${v}`).join('\n');
|
||||
|
||||
const browse = () => {
|
||||
dispatch(browseFiles([],['']))
|
||||
dispatch(browseFiles([], [!isSingleFilePicker ? "multiSelections": ""]))
|
||||
.then((filePaths) => {
|
||||
// If file is in the collection's directory, then we use relative path
|
||||
// Otherwise, we use the absolute path
|
||||
@ -34,7 +33,7 @@ const FilePickerEditor = ({ value, onChange, collection, isSingleFilePicker = fa
|
||||
return filePath;
|
||||
});
|
||||
|
||||
onChange(filePaths);
|
||||
onChange(isSingleFilePicker ? filePaths[0] : filePaths);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
@ -42,7 +41,7 @@ const FilePickerEditor = ({ value, onChange, collection, isSingleFilePicker = fa
|
||||
};
|
||||
|
||||
const clear = () => {
|
||||
onChange([]);
|
||||
onChange(isSingleFilePicker ? '' : []);
|
||||
};
|
||||
|
||||
const renderButtonText = (filenames) => {
|
||||
@ -66,9 +65,9 @@ const FilePickerEditor = ({ value, onChange, collection, isSingleFilePicker = fa
|
||||
</div>
|
||||
) : (
|
||||
<button className="btn btn-secondary px-1" style={{ width: '100%' }} onClick={browse}>
|
||||
{isSingleFilePicker? 'Select File' : 'Select Files'}
|
||||
{isSingleFilePicker ? 'Select File' : 'Select Files'}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilePickerEditor;
|
||||
export default FilePickerEditor;
|
@ -55,7 +55,7 @@ const StyledMarkdownBodyWrapper = styled.div`
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: 24px 0;
|
||||
background-color: var(--color-border-default);
|
||||
background-color: var(--color-sidebar-collection-item-active-indent-border);
|
||||
border: 0;
|
||||
}
|
||||
|
||||
|
@ -62,7 +62,7 @@ const Modal = ({
|
||||
confirmText,
|
||||
cancelText,
|
||||
handleCancel,
|
||||
handleConfirm,
|
||||
handleConfirm = () => {},
|
||||
children,
|
||||
confirmDisabled,
|
||||
hideCancel,
|
||||
@ -92,7 +92,7 @@ const Modal = ({
|
||||
};
|
||||
|
||||
useFocusTrap(modalRef);
|
||||
|
||||
|
||||
const closeModal = (args) => {
|
||||
setIsClosing(true);
|
||||
setTimeout(() => handleCancel(args), closeModalFadeTimeout);
|
||||
@ -103,7 +103,7 @@ const Modal = ({
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeydown);
|
||||
};
|
||||
}, [disableEscapeKey, document]);
|
||||
}, [disableEscapeKey, document, handleConfirm]);
|
||||
|
||||
let classes = 'bruno-modal';
|
||||
if (isClosing) {
|
||||
|
@ -1,21 +1,13 @@
|
||||
import React from 'react';
|
||||
import get from 'lodash/get';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { get, cloneDeep, isArray } from 'lodash';
|
||||
import { IconTrash } from '@tabler/icons';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import {
|
||||
addBinaryFile,
|
||||
updateBinaryFile,
|
||||
deleteBinaryFile
|
||||
} from 'providers/ReduxStore/slices/collections';
|
||||
import { addBinaryFile, updateBinaryFile, deleteBinaryFile } from 'providers/ReduxStore/slices/collections/index';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import FilePickerEditor from 'components/FilePickerEditor';
|
||||
import FilePickerEditor from 'components/FilePickerEditor/index';
|
||||
import SingleLineEditor from 'components/SingleLineEditor/index';
|
||||
import { isArray } from 'lodash';
|
||||
import path from 'node:path';
|
||||
import { useState } from 'react';
|
||||
|
||||
const Binary = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
@ -29,35 +21,28 @@ const Binary = ({ item, collection }) => {
|
||||
addBinaryFile({
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid,
|
||||
type: 'binaryFile',
|
||||
value: [''],
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
const handleRun = () => dispatch(sendRequest(item, collection.uid));
|
||||
|
||||
|
||||
const handleParamChange = (e, _param, type) => {
|
||||
|
||||
const param = cloneDeep(_param);
|
||||
|
||||
switch (type) {
|
||||
|
||||
case 'value': {
|
||||
param.value = isArray(e.target.value) && e.target.value.length > 0 ? e.target.value : [''];
|
||||
param.name = param.value.length === 0 ? '': path.basename(param.value[0], path.extname(param.value[0]));
|
||||
case 'filePath': {
|
||||
param.filePath = e.target.filePath;
|
||||
param.contentType = "";
|
||||
break;
|
||||
}
|
||||
case 'contentType': {
|
||||
param.contentType = e.target.value;
|
||||
param.contentType = e.target.contentType;
|
||||
break;
|
||||
}
|
||||
case 'enabled': {
|
||||
param.enabled = e.target.checked;
|
||||
|
||||
setEnableFileUid(param.uid);
|
||||
|
||||
case 'selected': {
|
||||
param.selected = e.target.selected;
|
||||
setEnableFileUid(param.uid)
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -85,9 +70,15 @@ const Binary = ({ item, collection }) => {
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td><div className="flex items-center justify-center">File</div></td>
|
||||
<td><div className="flex items-center justify-center">Content-Type</div></td>
|
||||
<td><div className="flex items-center justify-center">Enabled</div></td>
|
||||
<td>
|
||||
<div className="flex items-center justify-center">File</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center justify-center">Content-Type</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center justify-center">Selected</div>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -97,26 +88,26 @@ const Binary = ({ item, collection }) => {
|
||||
return (
|
||||
<tr key={param.uid}>
|
||||
<td>
|
||||
<FilePickerEditor
|
||||
isSingleFilePicker={true}
|
||||
value={param.value}
|
||||
onChange={(newValue) =>
|
||||
handleParamChange(
|
||||
{
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
},
|
||||
param,
|
||||
'value'
|
||||
)
|
||||
}
|
||||
collection={collection}
|
||||
/>
|
||||
<FilePickerEditor
|
||||
isSingleFilePicker={true}
|
||||
value={param.filePath}
|
||||
onChange={(path) =>
|
||||
handleParamChange(
|
||||
{
|
||||
target: {
|
||||
filePath: path
|
||||
}
|
||||
},
|
||||
param,
|
||||
'filePath'
|
||||
)
|
||||
}
|
||||
collection={collection}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<SingleLineEditor
|
||||
className='flex items-center justify-center'
|
||||
className="flex items-center justify-center"
|
||||
onSave={onSave}
|
||||
theme={storedTheme}
|
||||
placeholder="Auto"
|
||||
@ -125,7 +116,7 @@ const Binary = ({ item, collection }) => {
|
||||
handleParamChange(
|
||||
{
|
||||
target: {
|
||||
value: newValue
|
||||
contentType: newValue
|
||||
}
|
||||
},
|
||||
param,
|
||||
@ -141,19 +132,19 @@ const Binary = ({ item, collection }) => {
|
||||
<input
|
||||
key={param.uid}
|
||||
type="radio"
|
||||
name="enabled"
|
||||
checked={enabledFileUid === param.uid || param.enabled}
|
||||
name="selected"
|
||||
checked={enabledFileUid === param.uid || param.selected}
|
||||
tabIndex="-1"
|
||||
className="mr-1 mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'enabled')}
|
||||
onChange={(e) => handleParamChange(e, param, 'selected')}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center justify-center">
|
||||
<button tabIndex="-1" onClick={() => handleRemoveParams(param)}>
|
||||
<IconTrash strokeWidth={1.5} size={20} />
|
||||
</button>
|
||||
<div className="flex items-center justify-center">
|
||||
<button tabIndex="-1" onClick={() => handleRemoveParams(param)}>
|
||||
<IconTrash strokeWidth={1.5} size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@ -170,4 +161,4 @@ const Binary = ({ item, collection }) => {
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
export default Binary;
|
||||
export default Binary;
|
@ -135,7 +135,7 @@ const RequestBodyMode = ({ item, collection }) => {
|
||||
onModeChange('binaryFile');
|
||||
}}
|
||||
>
|
||||
Binary File
|
||||
File / Binary
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
|
@ -152,6 +152,8 @@ const Collection = ({ collection, searchText }) => {
|
||||
<div className="flex py-1 collection-name items-center" ref={drop}>
|
||||
<div
|
||||
className="flex flex-grow items-center overflow-hidden"
|
||||
onClick={handleCollapseCollection}
|
||||
onContextMenu={handleRightClick}
|
||||
>
|
||||
<IconChevronRight
|
||||
size={16}
|
||||
@ -160,9 +162,7 @@ const Collection = ({ collection, searchText }) => {
|
||||
style={{ width: 16, minWidth: 16, color: 'rgb(160 160 160)' }}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
<div className="ml-1" id="sidebar-collection-name"
|
||||
onClick={handleCollapseCollection}
|
||||
onContextMenu={handleRightClick}>
|
||||
<div className="ml-1" id="sidebar-collection-name">
|
||||
{collection.name}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -68,7 +68,7 @@ const ImportCollection = ({ onClose, handleSubmit }) => {
|
||||
);
|
||||
};
|
||||
return (
|
||||
<Modal size="sm" title="Import Collection" hideFooter={true} handleConfirm={onClose} handleCancel={onClose}>
|
||||
<Modal size="sm" title="Import Collection" hideFooter={true} handleCancel={onClose}>
|
||||
<div className="flex flex-col">
|
||||
<h3 className="text-sm">Select the type of your existing collection :</h3>
|
||||
<div className="mt-4 grid grid-rows-2 grid-flow-col gap-2">
|
||||
|
@ -1041,13 +1041,16 @@ export const browseDirectory = () => (dispatch, getState) => {
|
||||
|
||||
export const browseFiles =
|
||||
(filters = [], properties = ['multiSelections']) =>
|
||||
(dispatch, getState) => {
|
||||
(_dispatch, _getState) => {
|
||||
const { ipcRenderer } = window;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
ipcRenderer.invoke('renderer:browse-files', undefined, undefined, undefined, filters, properties).then(resolve).catch(reject);
|
||||
ipcRenderer
|
||||
.invoke('renderer:browse-files', filters, properties)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export const updateBrunoConfig = (brunoConfig, collectionUid) => (dispatch, getState) => {
|
||||
const state = getState();
|
||||
|
@ -865,80 +865,82 @@ export const collectionsSlice = createSlice({
|
||||
}
|
||||
}
|
||||
},
|
||||
moveMultipartFormParam: (state, action) => {
|
||||
// Ensure item.draft is a deep clone of item if not already present
|
||||
if (!item.draft) {
|
||||
item.draft = cloneDeep(item);
|
||||
}
|
||||
|
||||
// Extract payload data
|
||||
const { updateReorderedItem } = action.payload;
|
||||
const params = item.draft.request.body.multipartForm;
|
||||
|
||||
item.draft.request.body.multipartForm = updateReorderedItem.map((uid) => {
|
||||
return params.find((param) => param.uid === uid);
|
||||
});
|
||||
},
|
||||
addBinaryFile: (state, action) => {
|
||||
moveMultipartFormParam: (state, action) => {
|
||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||
|
||||
if (collection) {
|
||||
const item = findItemInCollection(collection, action.payload.itemUid);
|
||||
|
||||
if (item && isItemARequest(item)) {
|
||||
// Ensure item.draft is a deep clone of item if not already present
|
||||
if (!item.draft) {
|
||||
item.draft = cloneDeep(item);
|
||||
}
|
||||
|
||||
// Extract payload data
|
||||
const { updateReorderedItem } = action.payload;
|
||||
const params = item.draft.request.body.multipartForm;
|
||||
|
||||
item.draft.request.body.multipartForm = updateReorderedItem.map((uid) => {
|
||||
return params.find((param) => param.uid === uid);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
addBinaryFile: (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.binaryFile = item.draft.request.body.binaryFile || [];
|
||||
|
||||
|
||||
item.draft.request.body.binaryFile.push({
|
||||
uid: uuid(),
|
||||
type: action.payload.type,
|
||||
name: '',
|
||||
value: [''],
|
||||
filePath: '',
|
||||
contentType: '',
|
||||
enabled: false
|
||||
selected: false
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
updateBinaryFile: (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.binaryFile = item.draft.request.body.binaryFile.map((p) => {
|
||||
p.enabled = false;
|
||||
return p;
|
||||
});
|
||||
|
||||
|
||||
const param = find(item.draft.request.body.binaryFile, (p) => p.uid === action.payload.param.uid);
|
||||
|
||||
|
||||
if (param) {
|
||||
|
||||
const contentType = mime.contentType(path.extname(action.payload.param.value[0]));
|
||||
|
||||
param.type = action.payload.param.type;
|
||||
param.name = action.payload.param.name;
|
||||
param.value = action.payload.param.value;
|
||||
const contentType = mime.contentType(path.extname(action.payload.param.filePath));
|
||||
param.filePath = action.payload.param.filePath;
|
||||
param.contentType = action.payload.param.contentType || contentType || '';
|
||||
param.enabled = action.payload.param.enabled;
|
||||
param.selected = action.payload.param.selected;
|
||||
|
||||
item.draft.request.body.binaryFile = item.draft.request.body.binaryFile.map((p) => {
|
||||
p.selected = p.uid === param.uid;
|
||||
return p;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
deleteBinaryFile: (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);
|
||||
@ -948,6 +950,10 @@ export const collectionsSlice = createSlice({
|
||||
item.draft.request.body.binaryFile,
|
||||
(p) => p.uid !== action.payload.paramUid
|
||||
);
|
||||
|
||||
if (item.draft.request.body.binaryFile.length > 0) {
|
||||
item.draft.request.body.binaryFile[0].selected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -65,6 +65,23 @@ const createPostData = (body, type) => {
|
||||
|
||||
switch (body.mode) {
|
||||
case 'formUrlEncoded':
|
||||
return {
|
||||
mimeType: contentType,
|
||||
text: new URLSearchParams(
|
||||
body[body.mode]
|
||||
.filter((param) => param.enabled)
|
||||
.reduce((acc, param) => {
|
||||
acc[param.name] = param.value;
|
||||
return acc;
|
||||
}, {})
|
||||
).toString(),
|
||||
params: body[body.mode]
|
||||
.filter((param) => param.enabled)
|
||||
.map((param) => ({
|
||||
name: param.name,
|
||||
value: param.value
|
||||
}))
|
||||
};
|
||||
case 'multipartForm':
|
||||
return {
|
||||
mimeType: contentType,
|
||||
@ -78,18 +95,13 @@ const createPostData = (body, type) => {
|
||||
};
|
||||
case 'binaryFile':
|
||||
const binary = {
|
||||
mimeType: 'application/octet-stream',
|
||||
// mimeType: body[body.mode].filter((param) => param.enabled)[0].contentType,
|
||||
mimeType: body[body.mode].filter((param) => param.enabled)[0].contentType,
|
||||
params: body[body.mode]
|
||||
.filter((param) => param.enabled)
|
||||
.filter((param) => param.selected)
|
||||
.map((param) => ({
|
||||
name: param.name,
|
||||
value: param.value,
|
||||
fileName: param.value
|
||||
value: param.filePath,
|
||||
}))
|
||||
};
|
||||
|
||||
console.log('curl-binary', binary);
|
||||
return binary;
|
||||
default:
|
||||
return {
|
||||
@ -100,10 +112,6 @@ const createPostData = (body, type) => {
|
||||
};
|
||||
|
||||
export const buildHarRequest = ({ request, headers, type }) => {
|
||||
|
||||
console.log('buildHarRequest', request, headers, type);
|
||||
|
||||
console.log('buildHarRequest-postData', createPostData(request.body, type));
|
||||
return {
|
||||
method: request.method,
|
||||
url: encodeURI(request.url),
|
||||
|
@ -275,11 +275,9 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {}
|
||||
return map(params, (param) => {
|
||||
return {
|
||||
uid: param.uid,
|
||||
type: param.type,
|
||||
name: param.name,
|
||||
value: param.value,
|
||||
filePath: param.filePath,
|
||||
contentType: param.contentType,
|
||||
enabled: param.enabled
|
||||
selected: param.selected
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -666,7 +664,7 @@ export const humanizeRequestBodyMode = (mode) => {
|
||||
break;
|
||||
}
|
||||
case 'binaryFile': {
|
||||
label = 'Binary File';
|
||||
label = 'File / Binary';
|
||||
break;
|
||||
}
|
||||
case 'formUrlEncoded': {
|
||||
|
@ -102,30 +102,24 @@ function getFilesString(request) {
|
||||
|
||||
data.data = {};
|
||||
|
||||
if (request.isDataBinary){
|
||||
if (request.isDataBinary) {
|
||||
let filePath = '';
|
||||
|
||||
let filePath = ''
|
||||
|
||||
if(request.data.startsWith('@')){
|
||||
if (request.data.startsWith('@')) {
|
||||
filePath = request.data.slice(1);
|
||||
}else{
|
||||
} else {
|
||||
filePath = request.data;
|
||||
}
|
||||
|
||||
const fileName = path.basename(filePath);
|
||||
|
||||
data.data = [
|
||||
{
|
||||
name: repr(fileName),
|
||||
value: [repr(filePath)],
|
||||
enabled: true,
|
||||
filePath: repr(filePath),
|
||||
contentType: request.headers['Content-Type'],
|
||||
type: 'binaryFile'
|
||||
selected: true,
|
||||
}
|
||||
];
|
||||
|
||||
return data;
|
||||
|
||||
}
|
||||
|
||||
data.files = {};
|
||||
@ -190,13 +184,11 @@ const curlToJson = (curlCommand) => {
|
||||
|
||||
if (request.query) {
|
||||
requestJson.queries = getQueries(request);
|
||||
}
|
||||
|
||||
else if (request.multipartUploads || request.isDataBinary) {
|
||||
} else if (request.multipartUploads || request.isDataBinary) {
|
||||
Object.assign(requestJson, getFilesString(request));
|
||||
} else if (typeof request.data === 'string' || typeof request.data === 'number') {
|
||||
Object.assign(requestJson, getDataString(request));
|
||||
}
|
||||
}
|
||||
|
||||
if (request.insecure) {
|
||||
requestJson.insecure = false;
|
||||
|
@ -54,14 +54,11 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
||||
});
|
||||
|
||||
// browse directory for file
|
||||
ipcMain.handle('renderer:browse-files', async (event, pathname, request, filters, properties) => {
|
||||
ipcMain.handle('renderer:browse-files', async (_, filters, properties) => {
|
||||
try {
|
||||
|
||||
const filePaths = await browseFiles(mainWindow, filters, properties);
|
||||
|
||||
return filePaths;
|
||||
return await browseFiles(mainWindow, filters, properties);
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -583,7 +583,6 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
|
||||
try {
|
||||
request.signal = abortController.signal;
|
||||
|
||||
saveCancelToken(cancelTokenUid, abortController);
|
||||
|
||||
await runPreRequest(
|
||||
@ -614,7 +613,7 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
url: request.url,
|
||||
method: request.method,
|
||||
headers: request.headers,
|
||||
data: request.mode == 'binaryFile'? undefined: safeParseJSON(safeStringifyJSON(request.data)) ,
|
||||
data: request.mode == 'binaryFile'? "<request body redacted>": safeParseJSON(safeStringifyJSON(request.data)) ,
|
||||
timestamp: Date.now()
|
||||
},
|
||||
collectionUid,
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { get, each, filter } = require('lodash');
|
||||
const { get, each, filter, find } = require('lodash');
|
||||
const decomment = require('decomment');
|
||||
const crypto = require('node:crypto');
|
||||
const fs = require('node:fs/promises');
|
||||
@ -254,30 +254,25 @@ const prepareRequest = async (item, collection, abortController) => {
|
||||
}
|
||||
|
||||
if (request.body.mode === 'binaryFile') {
|
||||
|
||||
if (!contentTypeDefined) {
|
||||
axiosRequest.headers['content-type'] = 'application/octet-stream';
|
||||
axiosRequest.headers['content-type'] = 'application/octet-stream'; // Default headers for binary file uploads
|
||||
}
|
||||
|
||||
if (request.body.binaryFile && request.body.binaryFile.length > 0) {
|
||||
|
||||
axiosRequest.headers['content-type'] = request.body.binaryFile[0].contentType;
|
||||
|
||||
let filePath = request.body.binaryFile[0].value[0];
|
||||
|
||||
if (filePath && filePath !== '') {
|
||||
|
||||
|
||||
const binaryFile = find(request.body.binaryFile, (param) => param.selected);
|
||||
if (binaryFile) {
|
||||
let { filePath, contentType } = binaryFile;
|
||||
|
||||
axiosRequest.headers['content-type'] = contentType;
|
||||
if (filePath) {
|
||||
if (!path.isAbsolute(filePath)) {
|
||||
|
||||
filePath = path.join(collectionPath, filePath);
|
||||
}
|
||||
|
||||
const file = await fs.readFile(filePath, abortController)
|
||||
|
||||
axiosRequest.data = file
|
||||
|
||||
if(axiosRequest.headers['content-type'].includes('application/json')) {
|
||||
axiosRequest.data = JSON.parse(file)
|
||||
|
||||
try {
|
||||
const fileContent = await fs.readFile(filePath);
|
||||
axiosRequest.data = fileContent;
|
||||
} catch (error) {
|
||||
console.error('Error reading file:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -177,9 +177,9 @@ const multipartExtractContentType = (pair) => {
|
||||
const binaryFileExtractContentType = (pair) => {
|
||||
if (_.isString(pair.value)) {
|
||||
const match = pair.value.match(/^(.*?)\s*@contentType\((.*?)\)\s*$/);
|
||||
if (match != null && match.length > 2) {
|
||||
pair.value = match[1];
|
||||
pair.contentType = match[2];
|
||||
if (match && match.length > 2) {
|
||||
pair.value = match[1].trim();
|
||||
pair.contentType = match[2].trim();
|
||||
} else {
|
||||
pair.contentType = '';
|
||||
}
|
||||
@ -206,21 +206,25 @@ const mapPairListToKeyValPairsMultipart = (pairList = [], parseEnabled = true) =
|
||||
|
||||
const mapPairListToKeyValPairsBinaryFile = (pairList = [], parseEnabled = true) => {
|
||||
const pairs = mapPairListToKeyValPairs(pairList, parseEnabled);
|
||||
|
||||
return pairs.map((pair) => {
|
||||
binaryFileExtractContentType(pair);
|
||||
|
||||
if (pair.value.startsWith('@file(') && pair.value.endsWith(')')) {
|
||||
let filestr = pair.value.replace(/^@file\(/, '').replace(/\)$/, '');
|
||||
pair.type = 'binaryFile';
|
||||
pair.value = filestr != '' ? filestr.split('|') : [''];
|
||||
let filePath = pair.value.replace(/^@file\(/, '').replace(/\)$/, '');
|
||||
pair.filePath = filePath;
|
||||
pair.selected = pair.enabled
|
||||
|
||||
// Remove pair.value as it only contains the file path reference
|
||||
delete pair.value;
|
||||
// Remove pair.name as it is auto-generated (e.g., file1, file2, file3, etc.)
|
||||
delete pair.name;
|
||||
delete pair.enabled;
|
||||
}
|
||||
|
||||
return pair;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const concatArrays = (objValue, srcValue) => {
|
||||
if (_.isArray(objValue) && _.isArray(srcValue)) {
|
||||
return objValue.concat(srcValue);
|
||||
@ -746,3 +750,4 @@ const parser = (input) => {
|
||||
};
|
||||
|
||||
module.exports = parser;
|
||||
|
@ -2,8 +2,8 @@ const _ = require('lodash');
|
||||
|
||||
const { indentString } = require('../../v1/src/utils');
|
||||
|
||||
const enabled = (items = []) => items.filter((item) => item.enabled);
|
||||
const disabled = (items = []) => items.filter((item) => !item.enabled);
|
||||
const enabled = (items = [], key = "enabled") => items.filter((item) => item[key]);
|
||||
const disabled = (items = [], key = "enabled") => items.filter((item) => !item[key]);
|
||||
|
||||
// remove the last line if two new lines are found
|
||||
const stripLastLine = (text) => {
|
||||
@ -316,21 +316,19 @@ ${indentString(body.sparql)}
|
||||
|
||||
if (body && body.binaryFile && body.binaryFile.length) {
|
||||
bru += `body:binary-file {`;
|
||||
const binaryFiles = enabled(body.binaryFile).concat(disabled(body.binaryFile));
|
||||
const binaryFiles = enabled(body.binaryFile, "selected").concat(disabled(body.binaryFile, "selected"));
|
||||
|
||||
if (binaryFiles.length) {
|
||||
bru += `\n${indentString(
|
||||
binaryFiles
|
||||
.map((item) => {
|
||||
const enabled = item.enabled ? '' : '~';
|
||||
const selected = item.selected ? '' : '~';
|
||||
const contentType =
|
||||
item.contentType && item.contentType !== '' ? ' @contentType(' + item.contentType + ')' : '';
|
||||
|
||||
if (item.type === 'binaryFile') {
|
||||
let filestr = item.value[0] || '';
|
||||
const value = `@file(${filestr})`;
|
||||
return `${enabled}${item.name}: ${value}${contentType}`;
|
||||
}
|
||||
const filePath = item.filePath || '';
|
||||
const value = `@file(${filePath})`;
|
||||
const itemName = "file";
|
||||
return `${selected}${itemName}: ${value}${contentType}`;
|
||||
})
|
||||
.join('\n')
|
||||
)}`;
|
||||
|
@ -104,7 +104,8 @@ body:multipart-form {
|
||||
|
||||
body:binary-file {
|
||||
file: @file(path/to/file.json) @contentType(application/json)
|
||||
~file2: @file(path/to/file2.json) @contentType(application/json)
|
||||
file: @file(path/to/file.json) @contentType(application/json)
|
||||
~file: @file(path/to/file2.json) @contentType(application/json)
|
||||
}
|
||||
|
||||
body:graphql {
|
||||
|
@ -140,18 +140,19 @@
|
||||
],
|
||||
"binaryFile" : [
|
||||
{
|
||||
"name": "file",
|
||||
"value": ["path/to/file.json"],
|
||||
"enabled": true,
|
||||
"type": "binaryFile",
|
||||
"contentType": "application/json"
|
||||
"filePath": "path/to/file.json",
|
||||
"contentType": "application/json",
|
||||
"selected": true
|
||||
},
|
||||
{
|
||||
"name": "file2",
|
||||
"value": ["path/to/file2.json"],
|
||||
"enabled": false,
|
||||
"type": "binaryFile",
|
||||
"contentType": "application/json"
|
||||
"filePath": "path/to/file.json",
|
||||
"contentType": "application/json",
|
||||
"selected": true
|
||||
},
|
||||
{
|
||||
"filePath": "path/to/file2.json",
|
||||
"contentType": "application/json",
|
||||
"selected": false
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -77,11 +77,9 @@ const multipartFormSchema = Yup.object({
|
||||
|
||||
const binaryFileSchema = Yup.object({
|
||||
uid: uidSchema,
|
||||
type: Yup.string().oneOf(['binaryFile']).required('type is required'),
|
||||
name: Yup.string().nullable(),
|
||||
value: Yup.array().of(Yup.string().nullable()).nullable(),
|
||||
filePath: Yup.string().nullable(),
|
||||
contentType: Yup.string().nullable(),
|
||||
enabled: Yup.boolean()
|
||||
selected: Yup.boolean()
|
||||
})
|
||||
.noUnknown(true)
|
||||
.strict();
|
||||
|
@ -0,0 +1,15 @@
|
||||
meta {
|
||||
name: echo binary
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{echo-host}}
|
||||
body: binaryFile
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:binary-file {
|
||||
file: @file(bruno.png) @contentType(image/png)
|
||||
}
|
@ -19,6 +19,17 @@ router.post('/xml-raw', (req, res) => {
|
||||
return res.send(req.rawBody);
|
||||
});
|
||||
|
||||
router.post('/bin', (req, res) => {
|
||||
const rawBody = req.body;
|
||||
|
||||
if (!rawBody || rawBody.length === 0) {
|
||||
return res.status(400).send('No data received');
|
||||
}
|
||||
|
||||
res.set('Content-Type', req.headers['content-type'] || 'application/octet-stream');
|
||||
res.send(rawBody);
|
||||
});
|
||||
|
||||
router.get('/bom-json-test', (req, res) => {
|
||||
const jsonData = {
|
||||
message: 'Hello!',
|
||||
|
@ -10,6 +10,7 @@ const multipartRouter = require('./multipart');
|
||||
const app = new express();
|
||||
const port = process.env.PORT || 8080;
|
||||
|
||||
app.use(express.raw({type: '*/*', limit: '100mb'}));
|
||||
app.use(cors());
|
||||
app.use(xmlParser());
|
||||
app.use(bodyParser.text());
|
||||
|
Loading…
Reference in New Issue
Block a user