Merge pull request #766 from martinsefcik/feature/save-response-to-file

Added possibility to save response to file
This commit is contained in:
Anoop M D 2023-10-25 23:12:38 +05:30 committed by GitHub
commit 36f19ec7bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 111 additions and 1 deletions

4
package-lock.json generated
View File

@ -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",

View File

@ -0,0 +1,7 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
font-size: 0.8125rem;
`;
export default StyledWrapper;

View File

@ -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;

View File

@ -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>

View File

@ -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",

View File

@ -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;

View File

@ -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