mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-07 16:44:27 +01:00
Merge pull request #766 from martinsefcik/feature/save-response-to-file
Added possibility to save response to file
This commit is contained in:
commit
36f19ec7bc
4
package-lock.json
generated
4
package-lock.json
generated
@ -16630,7 +16630,7 @@
|
||||
},
|
||||
"packages/bruno-electron": {
|
||||
"name": "bruno",
|
||||
"version": "v0.27.1",
|
||||
"version": "v0.27.2",
|
||||
"dependencies": {
|
||||
"@aws-sdk/credential-providers": "^3.425.0",
|
||||
"@usebruno/js": "0.9.1",
|
||||
@ -16641,6 +16641,7 @@
|
||||
"axios": "^1.5.1",
|
||||
"chai": "^4.3.7",
|
||||
"chokidar": "^3.5.3",
|
||||
"content-disposition": "^0.5.4",
|
||||
"decomment": "^0.9.5",
|
||||
"dotenv": "^16.0.3",
|
||||
"electron-is-dev": "^2.0.0",
|
||||
@ -21519,6 +21520,7 @@
|
||||
"axios": "^1.5.1",
|
||||
"chai": "^4.3.7",
|
||||
"chokidar": "^3.5.3",
|
||||
"content-disposition": "^0.5.4",
|
||||
"decomment": "^0.9.5",
|
||||
"dmg-license": "^1.0.11",
|
||||
"dotenv": "^16.0.3",
|
||||
|
@ -0,0 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
font-size: 0.8125rem;
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import toast from 'react-hot-toast';
|
||||
import get from 'lodash/get';
|
||||
|
||||
const ResponseSave = ({ item }) => {
|
||||
const { ipcRenderer } = window;
|
||||
const response = item.response || {};
|
||||
|
||||
const saveResponseToFile = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(item);
|
||||
ipcRenderer
|
||||
.invoke('renderer:save-response-to-file', response, item.requestSent.url)
|
||||
.then(resolve)
|
||||
.catch((err) => {
|
||||
toast.error(get(err, 'error.message') || 'Something went wrong!');
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="ml-4">
|
||||
<button onClick={saveResponseToFile} disabled={!response.dataBuffer}>
|
||||
Save Response
|
||||
</button>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
export default ResponseSave;
|
@ -14,6 +14,7 @@ import Timeline from './Timeline';
|
||||
import TestResults from './TestResults';
|
||||
import TestResultsLabel from './TestResultsLabel';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import ResponseSave from 'src/components/ResponsePane/ResponseSave';
|
||||
|
||||
const ResponsePane = ({ rightPaneWidth, item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
@ -115,6 +116,7 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
|
||||
<StatusCode status={response.status} />
|
||||
<ResponseTime duration={response.duration} />
|
||||
<ResponseSize size={response.size} />
|
||||
<ResponseSave item={item} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
@ -28,6 +28,7 @@
|
||||
"axios": "^1.5.1",
|
||||
"chai": "^4.3.7",
|
||||
"chokidar": "^3.5.3",
|
||||
"content-disposition": "^0.5.4",
|
||||
"decomment": "^0.9.5",
|
||||
"dotenv": "^16.0.3",
|
||||
"electron-is-dev": "^2.0.0",
|
||||
@ -43,6 +44,7 @@
|
||||
"is-valid-path": "^0.1.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"mime-types": "^2.1.35",
|
||||
"mustache": "^4.2.0",
|
||||
"nanoid": "3.3.4",
|
||||
"node-machine-id": "^1.1.12",
|
||||
|
@ -7,6 +7,8 @@ const path = require('path');
|
||||
const decomment = require('decomment');
|
||||
const Mustache = require('mustache');
|
||||
const FormData = require('form-data');
|
||||
const contentDispositionParser = require('content-disposition');
|
||||
const mime = require('mime-types');
|
||||
const { ipcMain } = require('electron');
|
||||
const { forOwn, extend, each, get, compact } = require('lodash');
|
||||
const { VarsRuntime, AssertRuntime, ScriptRuntime, TestRuntime } = require('@usebruno/js');
|
||||
@ -25,6 +27,7 @@ const { SocksProxyAgent } = require('socks-proxy-agent');
|
||||
const { makeAxiosInstance } = require('./axios-instance');
|
||||
const { addAwsV4Interceptor, resolveAwsV4Credentials } = require('./awsv4auth-helper');
|
||||
const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../../utils/proxy-util');
|
||||
const { chooseFileToSave, writeBinaryFile } = require('../../utils/filesystem');
|
||||
|
||||
// override the default escape function to prevent escaping
|
||||
Mustache.escape = function (value) {
|
||||
@ -860,6 +863,51 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// save response to file
|
||||
ipcMain.handle('renderer:save-response-to-file', async (event, response, url) => {
|
||||
try {
|
||||
const getHeaderValue = (headerName) => {
|
||||
if (response.headers) {
|
||||
const header = response.headers.find((header) => header[0] === headerName);
|
||||
if (header && header.length > 1) {
|
||||
return header[1];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getFileNameFromContentDispositionHeader = () => {
|
||||
const contentDisposition = getHeaderValue('content-disposition');
|
||||
try {
|
||||
const disposition = contentDispositionParser.parse(contentDisposition);
|
||||
return disposition && disposition.parameters['filename'];
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
const getFileNameFromUrlPath = () => {
|
||||
const lastPathLevel = new URL(url).pathname.split('/').pop();
|
||||
if (lastPathLevel && /\..+/.exec(lastPathLevel)) {
|
||||
return lastPathLevel;
|
||||
}
|
||||
};
|
||||
|
||||
const getFileNameBasedOnContentTypeHeader = () => {
|
||||
const contentType = getHeaderValue('content-type');
|
||||
const extension = (contentType && mime.extension(contentType)) || 'txt';
|
||||
return `response.${extension}`;
|
||||
};
|
||||
|
||||
const fileName =
|
||||
getFileNameFromContentDispositionHeader() || getFileNameFromUrlPath() || getFileNameBasedOnContentTypeHeader();
|
||||
|
||||
const filePath = await chooseFileToSave(mainWindow, fileName);
|
||||
if (filePath) {
|
||||
await writeBinaryFile(filePath, Buffer.from(response.dataBuffer, 'base64'));
|
||||
}
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = registerNetworkIpc;
|
||||
|
@ -60,6 +60,14 @@ const writeFile = async (pathname, content) => {
|
||||
}
|
||||
};
|
||||
|
||||
const writeBinaryFile = async (pathname, content) => {
|
||||
try {
|
||||
fs.writeFileSync(pathname, content);
|
||||
} catch (err) {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
};
|
||||
|
||||
const hasJsonExtension = (filename) => {
|
||||
if (!filename || typeof filename !== 'string') return false;
|
||||
return ['json'].some((ext) => filename.toLowerCase().endsWith(`.${ext}`));
|
||||
@ -95,6 +103,14 @@ const browseDirectory = async (win) => {
|
||||
return isDirectory(resolvedPath) ? resolvedPath : false;
|
||||
};
|
||||
|
||||
const chooseFileToSave = async (win, preferredFileName = '') => {
|
||||
const { filePath } = await dialog.showSaveDialog(win, {
|
||||
defaultPath: preferredFileName
|
||||
});
|
||||
|
||||
return filePath;
|
||||
};
|
||||
|
||||
const searchForFiles = (dir, extension) => {
|
||||
let results = [];
|
||||
const files = fs.readdirSync(dir);
|
||||
@ -126,10 +142,12 @@ module.exports = {
|
||||
isDirectory,
|
||||
normalizeAndResolvePath,
|
||||
writeFile,
|
||||
writeBinaryFile,
|
||||
hasJsonExtension,
|
||||
hasBruExtension,
|
||||
createDirectory,
|
||||
browseDirectory,
|
||||
chooseFileToSave,
|
||||
searchForFiles,
|
||||
searchForBruFiles,
|
||||
sanitizeDirectoryName
|
||||
|
Loading…
Reference in New Issue
Block a user