mirror of
https://github.com/usebruno/bruno.git
synced 2025-01-03 04:29:09 +01:00
Merge branch 'usebruno:main' into feature/add-raw-file-request-body-option
This commit is contained in:
commit
77879913ff
5
.github/workflows/npm-bru-cli.yml
vendored
5
.github/workflows/npm-bru-cli.yml
vendored
@ -2,11 +2,6 @@ name: Bru CLI Tests (npm)
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
|
||||||
build:
|
|
||||||
description: 'Test Bru CLI (npm)'
|
|
||||||
required: true
|
|
||||||
default: 'true'
|
|
||||||
|
|
||||||
# Assign permissions for unit tests to be reported.
|
# Assign permissions for unit tests to be reported.
|
||||||
# See https://github.com/dorny/test-reporter/issues/168
|
# See https://github.com/dorny/test-reporter/issues/168
|
||||||
|
3
.github/workflows/tests.yml
vendored
3
.github/workflows/tests.yml
vendored
@ -5,6 +5,9 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
unit-test:
|
unit-test:
|
||||||
name: Unit Tests
|
name: Unit Tests
|
||||||
|
38
package-lock.json
generated
38
package-lock.json
generated
@ -9515,7 +9515,6 @@
|
|||||||
"version": "4.3.1",
|
"version": "4.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||||
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@ -13912,6 +13911,12 @@
|
|||||||
"uc.micro": "^1.0.1"
|
"uc.micro": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/load-script": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-kPEjMFtZvwL9TaZo0uZ2ml+Ye9HUMmPwbYRJ324qF9tqMejwykJ5ggTyvzmrbBeapCAbk98BSbTeovHEEP1uCA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/loader-runner": {
|
"node_modules/loader-runner": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
|
||||||
@ -14426,6 +14431,12 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/memoize-one": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/merge-descriptors": {
|
"node_modules/merge-descriptors": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
||||||
@ -17154,6 +17165,28 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-player": {
|
||||||
|
"version": "2.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-player/-/react-player-2.16.0.tgz",
|
||||||
|
"integrity": "sha512-mAIPHfioD7yxO0GNYVFD1303QFtI3lyyQZLY229UEAp/a10cSW+hPcakg0Keq8uWJxT2OiT/4Gt+Lc9bD6bJmQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"deepmerge": "^4.0.0",
|
||||||
|
"load-script": "^1.0.0",
|
||||||
|
"memoize-one": "^5.1.1",
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
|
"react-fast-compare": "^3.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-player/node_modules/react-fast-compare": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/react-redux": {
|
"node_modules/react-redux": {
|
||||||
"version": "7.2.9",
|
"version": "7.2.9",
|
||||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz",
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz",
|
||||||
@ -20575,6 +20608,7 @@
|
|||||||
"react-i18next": "^15.0.1",
|
"react-i18next": "^15.0.1",
|
||||||
"react-inspector": "^6.0.2",
|
"react-inspector": "^6.0.2",
|
||||||
"react-pdf": "9.1.1",
|
"react-pdf": "9.1.1",
|
||||||
|
"react-player": "^2.16.0",
|
||||||
"react-redux": "^7.2.6",
|
"react-redux": "^7.2.6",
|
||||||
"react-tooltip": "^5.5.2",
|
"react-tooltip": "^5.5.2",
|
||||||
"sass": "^1.46.0",
|
"sass": "^1.46.0",
|
||||||
@ -20688,7 +20722,7 @@
|
|||||||
},
|
},
|
||||||
"packages/bruno-electron": {
|
"packages/bruno-electron": {
|
||||||
"name": "bruno",
|
"name": "bruno",
|
||||||
"version": "v1.34.0",
|
"version": "v1.34.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/credential-providers": "3.658.1",
|
"@aws-sdk/credential-providers": "3.658.1",
|
||||||
"@usebruno/common": "0.1.0",
|
"@usebruno/common": "0.1.0",
|
||||||
|
2
packages/bruno-app/.gitignore
vendored
2
packages/bruno-app/.gitignore
vendored
@ -32,3 +32,5 @@ yarn-error.log*
|
|||||||
# next.js
|
# next.js
|
||||||
.next/
|
.next/
|
||||||
out/
|
out/
|
||||||
|
|
||||||
|
.env
|
16
packages/bruno-app/jest.config.js
Normal file
16
packages/bruno-app/jest.config.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
module.exports = {
|
||||||
|
rootDir: '.',
|
||||||
|
moduleNameMapper: {
|
||||||
|
'^assets/(.*)$': '<rootDir>/src/assets/$1',
|
||||||
|
'^components/(.*)$': '<rootDir>/src/components/$1',
|
||||||
|
'^hooks/(.*)$': '<rootDir>/src/hooks/$1',
|
||||||
|
'^themes/(.*)$': '<rootDir>/src/themes/$1',
|
||||||
|
'^api/(.*)$': '<rootDir>/src/api/$1',
|
||||||
|
'^pageComponents/(.*)$': '<rootDir>/src/pageComponents/$1',
|
||||||
|
'^providers/(.*)$': '<rootDir>/src/providers/$1',
|
||||||
|
'^utils/(.*)$': '<rootDir>/src/utils/$1'
|
||||||
|
},
|
||||||
|
clearMocks: true,
|
||||||
|
moduleDirectories: ['node_modules', 'src'],
|
||||||
|
testEnvironment: 'node'
|
||||||
|
};
|
@ -66,6 +66,7 @@
|
|||||||
"react-i18next": "^15.0.1",
|
"react-i18next": "^15.0.1",
|
||||||
"react-inspector": "^6.0.2",
|
"react-inspector": "^6.0.2",
|
||||||
"react-pdf": "9.1.1",
|
"react-pdf": "9.1.1",
|
||||||
|
"react-player": "^2.16.0",
|
||||||
"react-redux": "^7.2.6",
|
"react-redux": "^7.2.6",
|
||||||
"react-tooltip": "^5.5.2",
|
"react-tooltip": "^5.5.2",
|
||||||
"sass": "^1.46.0",
|
"sass": "^1.46.0",
|
||||||
|
@ -26,6 +26,12 @@ const StyledWrapper = styled.div`
|
|||||||
|
|
||||||
.CodeMirror-dialog {
|
.CodeMirror-dialog {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
|
input {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #d3d6db;
|
||||||
|
outline: none;
|
||||||
|
border-radius: 0px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#search-results-count {
|
#search-results-count {
|
||||||
@ -82,6 +88,14 @@ const StyledWrapper = styled.div`
|
|||||||
.CodeMirror-search-hint {
|
.CodeMirror-search-hint {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cm-s-default span.cm-property {
|
||||||
|
color: #1f61a0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-s-default span.cm-variable {
|
||||||
|
color: #397d13 !important;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default StyledWrapper;
|
export default StyledWrapper;
|
||||||
|
@ -15,7 +15,7 @@ import { JSHINT } from 'jshint';
|
|||||||
import stripJsonComments from 'strip-json-comments';
|
import stripJsonComments from 'strip-json-comments';
|
||||||
|
|
||||||
let CodeMirror;
|
let CodeMirror;
|
||||||
const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
const SERVER_RENDERED = typeof window === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
||||||
const TAB_SIZE = 2;
|
const TAB_SIZE = 2;
|
||||||
|
|
||||||
if (!SERVER_RENDERED) {
|
if (!SERVER_RENDERED) {
|
||||||
@ -58,13 +58,14 @@ if (!SERVER_RENDERED) {
|
|||||||
'req.getExecutionMode()',
|
'req.getExecutionMode()',
|
||||||
'bru',
|
'bru',
|
||||||
'bru.cwd()',
|
'bru.cwd()',
|
||||||
'bru.getEnvName(key)',
|
'bru.getEnvName()',
|
||||||
'bru.getProcessEnv(key)',
|
'bru.getProcessEnv(key)',
|
||||||
'bru.hasEnvVar(key)',
|
'bru.hasEnvVar(key)',
|
||||||
'bru.getEnvVar(key)',
|
'bru.getEnvVar(key)',
|
||||||
'bru.getFolderVar(key)',
|
'bru.getFolderVar(key)',
|
||||||
'bru.getCollectionVar(key)',
|
'bru.getCollectionVar(key)',
|
||||||
'bru.setEnvVar(key,value)',
|
'bru.setEnvVar(key,value)',
|
||||||
|
'bru.deleteEnvVar(key)',
|
||||||
'bru.hasVar(key)',
|
'bru.hasVar(key)',
|
||||||
'bru.getVar(key)',
|
'bru.getVar(key)',
|
||||||
'bru.setVar(key,value)',
|
'bru.setVar(key,value)',
|
||||||
@ -189,32 +190,8 @@ export default class CodeEditor extends React.Component {
|
|||||||
'Cmd-Y': 'foldAll',
|
'Cmd-Y': 'foldAll',
|
||||||
'Ctrl-I': 'unfoldAll',
|
'Ctrl-I': 'unfoldAll',
|
||||||
'Cmd-I': 'unfoldAll',
|
'Cmd-I': 'unfoldAll',
|
||||||
'Cmd-/': (cm) => {
|
'Ctrl-/': 'toggleComment',
|
||||||
// comment/uncomment every selected line(s)
|
'Cmd-/': 'toggleComment'
|
||||||
const selections = cm.listSelections();
|
|
||||||
selections.forEach((range) => {
|
|
||||||
for (let i = range.from().line; i <= range.to().line; i++) {
|
|
||||||
const selectedLine = cm.getLine(i);
|
|
||||||
// if commented line, remove comment
|
|
||||||
if (selectedLine.trim().startsWith('//')) {
|
|
||||||
cm.replaceRange(
|
|
||||||
selectedLine.replace(/^(\s*)\/\/\s?/, '$1'),
|
|
||||||
{ line: i, ch: 0 },
|
|
||||||
{ line: i, ch: selectedLine.length }
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// otherwise add comment
|
|
||||||
cm.replaceRange(
|
|
||||||
selectedLine.search(/\S|$/) >= TAB_SIZE
|
|
||||||
? ' '.repeat(TAB_SIZE) + '// ' + selectedLine.trim()
|
|
||||||
: '// ' + selectedLine,
|
|
||||||
{ line: i, ch: 0 },
|
|
||||||
{ line: i, ch: selectedLine.length }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
foldOptions: {
|
foldOptions: {
|
||||||
widget: (from, to) => {
|
widget: (from, to) => {
|
||||||
@ -281,9 +258,9 @@ export default class CodeEditor extends React.Component {
|
|||||||
while (end < currentLine.length && /[^{}();\s\[\]\,]/.test(currentLine.charAt(end))) ++end;
|
while (end < currentLine.length && /[^{}();\s\[\]\,]/.test(currentLine.charAt(end))) ++end;
|
||||||
while (start && /[^{}();\s\[\]\,]/.test(currentLine.charAt(start - 1))) --start;
|
while (start && /[^{}();\s\[\]\,]/.test(currentLine.charAt(start - 1))) --start;
|
||||||
let curWord = start != end && currentLine.slice(start, end);
|
let curWord = start != end && currentLine.slice(start, end);
|
||||||
//Qualify if autocomplete will be shown
|
// Qualify if autocomplete will be shown
|
||||||
if (
|
if (
|
||||||
/^(?!Shift|Tab|Enter|Escape|ArrowUp|ArrowDown|ArrowLeft|ArrowRight|\s)\w*/.test(event.key) &&
|
/^(?!Shift|Tab|Enter|Escape|ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Meta|Alt|Home|End\s)\w*/.test(event.key) &&
|
||||||
curWord.length > 0 &&
|
curWord.length > 0 &&
|
||||||
!/\/\/|\/\*|.*{{|`[^$]*{|`[^{]*$/.test(currentLine.slice(0, end)) &&
|
!/\/\/|\/\*|.*{{|`[^$]*{|`[^{]*$/.test(currentLine.slice(0, end)) &&
|
||||||
/(?<!\d)[a-zA-Z\._]$/.test(curWord)
|
/(?<!\d)[a-zA-Z\._]$/.test(curWord)
|
||||||
|
@ -5,7 +5,7 @@ import { defineCodeMirrorBrunoVariablesMode } from 'utils/common/codemirror';
|
|||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
let CodeMirror;
|
let CodeMirror;
|
||||||
const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
const SERVER_RENDERED = typeof window === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
||||||
|
|
||||||
if (!SERVER_RENDERED) {
|
if (!SERVER_RENDERED) {
|
||||||
CodeMirror = require('codemirror');
|
CodeMirror = require('codemirror');
|
||||||
|
@ -54,6 +54,14 @@ const StyledWrapper = styled.div`
|
|||||||
.CodeMirror-search-hint {
|
.CodeMirror-search-hint {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cm-s-default span.cm-property {
|
||||||
|
color: #1f61a0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-s-default span.cm-variable {
|
||||||
|
color: #397d13 !important;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default StyledWrapper;
|
export default StyledWrapper;
|
||||||
|
@ -19,7 +19,7 @@ import { IconWand } from '@tabler/icons';
|
|||||||
import onHasCompletion from './onHasCompletion';
|
import onHasCompletion from './onHasCompletion';
|
||||||
|
|
||||||
let CodeMirror;
|
let CodeMirror;
|
||||||
const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
const SERVER_RENDERED = typeof window === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
||||||
|
|
||||||
if (!SERVER_RENDERED) {
|
if (!SERVER_RENDERED) {
|
||||||
CodeMirror = require('codemirror');
|
CodeMirror = require('codemirror');
|
||||||
|
@ -1,14 +1,41 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
import CodeEditor from 'components/CodeEditor/index';
|
import CodeEditor from 'components/CodeEditor/index';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { sendRequest } from 'providers/ReduxStore/slices/collections/actions';
|
import { sendRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import { Document, Page } from 'react-pdf';
|
import { Document, Page } from 'react-pdf';
|
||||||
import { useState } from 'react';
|
|
||||||
import 'pdfjs-dist/build/pdf.worker';
|
import 'pdfjs-dist/build/pdf.worker';
|
||||||
import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
|
import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
|
||||||
import 'react-pdf/dist/esm/Page/TextLayer.css';
|
import 'react-pdf/dist/esm/Page/TextLayer.css';
|
||||||
import { GlobalWorkerOptions } from 'pdfjs-dist/build/pdf';
|
import { GlobalWorkerOptions } from 'pdfjs-dist/build/pdf';
|
||||||
GlobalWorkerOptions.workerSrc = 'pdfjs-dist/legacy/build/pdf.worker.min.mjs';
|
GlobalWorkerOptions.workerSrc = 'pdfjs-dist/legacy/build/pdf.worker.min.mjs';
|
||||||
|
import ReactPlayer from 'react-player';
|
||||||
|
|
||||||
|
const VideoPreview = React.memo(({ contentType, dataBuffer }) => {
|
||||||
|
const [videoUrl, setVideoUrl] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const videoType = contentType.split(';')[0];
|
||||||
|
const byteArray = Buffer.from(dataBuffer, 'base64');
|
||||||
|
const blob = new Blob([byteArray], { type: videoType });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
setVideoUrl(url);
|
||||||
|
return () => URL.revokeObjectURL(url);
|
||||||
|
}, [contentType, dataBuffer]);
|
||||||
|
|
||||||
|
if (!videoUrl) return <div>Loading video...</div>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ReactPlayer
|
||||||
|
url={videoUrl}
|
||||||
|
controls
|
||||||
|
muted={true}
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
onError={(e) => console.error('Error loading video:', e)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const QueryResultPreview = ({
|
const QueryResultPreview = ({
|
||||||
previewTab,
|
previewTab,
|
||||||
@ -73,9 +100,7 @@ const QueryResultPreview = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
case 'preview-video': {
|
case 'preview-video': {
|
||||||
return (
|
return <VideoPreview contentType={contentType} dataBuffer={dataBuffer} />;
|
||||||
<video controls src={`data:${contentType.replace(/\;(.*)/, '')};base64,${dataBuffer}`} className="mx-auto" />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
case 'raw': {
|
case 'raw': {
|
||||||
|
@ -8,7 +8,7 @@ import StyledWrapper from './StyledWrapper';
|
|||||||
import { useTheme } from 'providers/Theme/index';
|
import { useTheme } from 'providers/Theme/index';
|
||||||
|
|
||||||
let posthogClient = null;
|
let posthogClient = null;
|
||||||
const posthogApiKey = 'phc_7gtqSrrdZRohiozPMLIacjzgHbUlhalW1Bu16uYijMR';
|
const posthogApiKey = process.env.NEXT_PUBLIC_POSTHOG_API_KEY;
|
||||||
const getPosthogClient = () => {
|
const getPosthogClient = () => {
|
||||||
if (posthogClient) {
|
if (posthogClient) {
|
||||||
return posthogClient;
|
return posthogClient;
|
||||||
|
@ -39,6 +39,14 @@ const StyledWrapper = styled.div`
|
|||||||
textarea.curl-command {
|
textarea.curl-command {
|
||||||
min-height: 150px;
|
min-height: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown {
|
||||||
|
width: fit-content;
|
||||||
|
|
||||||
|
.dropdown-item {
|
||||||
|
padding: 0.2rem 0.6rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default StyledWrapper;
|
export default StyledWrapper;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useRef, useEffect, useCallback } from 'react';
|
import React, { useRef, useEffect, useCallback, forwardRef, useState } from 'react';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
@ -12,6 +12,8 @@ import HttpMethodSelector from 'components/RequestPane/QueryUrl/HttpMethodSelect
|
|||||||
import { getDefaultRequestPaneTab } from 'utils/collections';
|
import { getDefaultRequestPaneTab } from 'utils/collections';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import { getRequestFromCurlCommand } from 'utils/curl';
|
import { getRequestFromCurlCommand } from 'utils/curl';
|
||||||
|
import Dropdown from 'components/Dropdown';
|
||||||
|
import { IconCaretDown } from '@tabler/icons';
|
||||||
|
|
||||||
const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -19,6 +21,39 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
|||||||
const {
|
const {
|
||||||
brunoConfig: { presets: collectionPresets = {} }
|
brunoConfig: { presets: collectionPresets = {} }
|
||||||
} = collection;
|
} = collection;
|
||||||
|
const [curlRequestTypeDetected, setCurlRequestTypeDetected] = useState(null);
|
||||||
|
|
||||||
|
const dropdownTippyRef = useRef();
|
||||||
|
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||||
|
|
||||||
|
const Icon = forwardRef((props, ref) => {
|
||||||
|
return (
|
||||||
|
<div ref={ref} className="flex items-center justify-end auth-type-label select-none">
|
||||||
|
{curlRequestTypeDetected === 'http-request' ? "HTTP" : "GraphQL"}
|
||||||
|
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// This function analyzes a given cURL command string and determines whether the request is a GraphQL or HTTP request.
|
||||||
|
const identifyCurlRequestType = (url, headers, body) => {
|
||||||
|
if (url.endsWith('/graphql')) {
|
||||||
|
setCurlRequestTypeDetected('graphql-request');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = headers?.find((h) => h.name.toLowerCase() === 'content-type')?.value;
|
||||||
|
if (contentType && contentType.includes('application/graphql')) {
|
||||||
|
setCurlRequestTypeDetected('graphql-request');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurlRequestTypeDetected('http-request');
|
||||||
|
};
|
||||||
|
|
||||||
|
const curlRequestTypeChange = (type) => {
|
||||||
|
setCurlRequestTypeDetected(type);
|
||||||
|
};
|
||||||
|
|
||||||
const getRequestType = (collectionPresets) => {
|
const getRequestType = (collectionPresets) => {
|
||||||
if (!collectionPresets || !collectionPresets.requestType) {
|
if (!collectionPresets || !collectionPresets.requestType) {
|
||||||
@ -99,11 +134,11 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
|||||||
})
|
})
|
||||||
.catch((err) => toast.error(err ? err.message : 'An error occurred while adding the request'));
|
.catch((err) => toast.error(err ? err.message : 'An error occurred while adding the request'));
|
||||||
} else if (values.requestType === 'from-curl') {
|
} else if (values.requestType === 'from-curl') {
|
||||||
const request = getRequestFromCurlCommand(values.curlCommand);
|
const request = getRequestFromCurlCommand(values.curlCommand, curlRequestTypeDetected);
|
||||||
dispatch(
|
dispatch(
|
||||||
newHttpRequest({
|
newHttpRequest({
|
||||||
requestName: values.requestName,
|
requestName: values.requestName,
|
||||||
requestType: 'http-request',
|
requestType: curlRequestTypeDetected,
|
||||||
requestUrl: request.url,
|
requestUrl: request.url,
|
||||||
requestMethod: request.method,
|
requestMethod: request.method,
|
||||||
collectionUid: collection.uid,
|
collectionUid: collection.uid,
|
||||||
@ -158,6 +193,12 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
|||||||
formik.setFieldValue('requestType', 'from-curl');
|
formik.setFieldValue('requestType', 'from-curl');
|
||||||
formik.setFieldValue('curlCommand', pastedData);
|
formik.setFieldValue('curlCommand', pastedData);
|
||||||
|
|
||||||
|
// Identify the request type
|
||||||
|
const request = getRequestFromCurlCommand(pastedData);
|
||||||
|
if (request) {
|
||||||
|
identifyCurlRequestType(request.url, request.headers, request.body);
|
||||||
|
}
|
||||||
|
|
||||||
// Prevent the default paste behavior to avoid pasting into the textarea
|
// Prevent the default paste behavior to avoid pasting into the textarea
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
@ -165,6 +206,18 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
|||||||
[formik]
|
[formik]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleCurlCommandChange = (event) => {
|
||||||
|
formik.handleChange(event);
|
||||||
|
|
||||||
|
if (event.target.name === 'curlCommand') {
|
||||||
|
const curlCommand = event.target.value;
|
||||||
|
const request = getRequestFromCurlCommand(curlCommand);
|
||||||
|
if (request) {
|
||||||
|
identifyCurlRequestType(request.url, request.headers, request.body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper>
|
<StyledWrapper>
|
||||||
<Modal size="md" title="New Request" confirmText="Create" handleConfirm={onSubmit} handleCancel={onClose}>
|
<Modal size="md" title="New Request" confirmText="Create" handleConfirm={onSubmit} handleCancel={onClose}>
|
||||||
@ -279,15 +332,37 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<label htmlFor="request-url" className="block font-semibold">
|
<div className="flex justify-between">
|
||||||
cURL Command
|
<label htmlFor="request-url" className="block font-semibold">
|
||||||
</label>
|
cURL Command
|
||||||
|
</label>
|
||||||
|
<Dropdown className="dropdown" onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
curlRequestTypeChange('http-request');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
HTTP
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
curlRequestTypeChange('graphql-request');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
GraphQL
|
||||||
|
</div>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
name="curlCommand"
|
name="curlCommand"
|
||||||
placeholder="Enter cURL request here.."
|
placeholder="Enter cURL request here.."
|
||||||
className="block textbox w-full mt-4 curl-command"
|
className="block textbox w-full mt-4 curl-command"
|
||||||
value={formik.values.curlCommand}
|
value={formik.values.curlCommand}
|
||||||
onChange={formik.handleChange}
|
onChange={handleCurlCommandChange}
|
||||||
></textarea>
|
></textarea>
|
||||||
{formik.touched.curlCommand && formik.errors.curlCommand ? (
|
{formik.touched.curlCommand && formik.errors.curlCommand ? (
|
||||||
<div className="text-red-500">{formik.errors.curlCommand}</div>
|
<div className="text-red-500">{formik.errors.curlCommand}</div>
|
||||||
|
@ -6,7 +6,7 @@ import StyledWrapper from './StyledWrapper';
|
|||||||
import { IconEye, IconEyeOff } from '@tabler/icons';
|
import { IconEye, IconEyeOff } from '@tabler/icons';
|
||||||
|
|
||||||
let CodeMirror;
|
let CodeMirror;
|
||||||
const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
const SERVER_RENDERED = typeof window === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
||||||
|
|
||||||
if (!SERVER_RENDERED) {
|
if (!SERVER_RENDERED) {
|
||||||
CodeMirror = require('codemirror');
|
CodeMirror = require('codemirror');
|
||||||
|
@ -233,7 +233,7 @@ const GlobalStyle = createGlobalStyle`
|
|||||||
}
|
}
|
||||||
|
|
||||||
.CodeMirror-hint-active {
|
.CodeMirror-hint-active {
|
||||||
background: #89f !important;
|
background: #08f !important;
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -10,7 +10,7 @@ import 'codemirror/theme/material.css';
|
|||||||
import 'codemirror/theme/monokai.css';
|
import 'codemirror/theme/monokai.css';
|
||||||
import 'codemirror/addon/scroll/simplescrollbars.css';
|
import 'codemirror/addon/scroll/simplescrollbars.css';
|
||||||
|
|
||||||
const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
const SERVER_RENDERED = typeof window === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
||||||
if (!SERVER_RENDERED) {
|
if (!SERVER_RENDERED) {
|
||||||
require('codemirror/mode/javascript/javascript');
|
require('codemirror/mode/javascript/javascript');
|
||||||
require('codemirror/mode/xml/xml');
|
require('codemirror/mode/xml/xml');
|
||||||
|
@ -8,7 +8,6 @@ import ReduxStore from 'providers/ReduxStore';
|
|||||||
import ThemeProvider from 'providers/Theme/index';
|
import ThemeProvider from 'providers/Theme/index';
|
||||||
import ErrorBoundary from './ErrorBoundary';
|
import ErrorBoundary from './ErrorBoundary';
|
||||||
|
|
||||||
import '../styles/app.scss';
|
|
||||||
import '../styles/globals.css';
|
import '../styles/globals.css';
|
||||||
import 'codemirror/lib/codemirror.css';
|
import 'codemirror/lib/codemirror.css';
|
||||||
import 'graphiql/graphiql.min.css';
|
import 'graphiql/graphiql.min.css';
|
||||||
@ -31,7 +30,7 @@ function SafeHydrate({ children }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function NoSsr({ children }) {
|
function NoSsr({ children }) {
|
||||||
const SERVER_RENDERED = typeof navigator === 'undefined';
|
const SERVER_RENDERED = typeof window === 'undefined';
|
||||||
|
|
||||||
if (SERVER_RENDERED) {
|
if (SERVER_RENDERED) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -13,7 +13,7 @@ import platformLib from 'platform';
|
|||||||
import { uuid } from 'utils/common';
|
import { uuid } from 'utils/common';
|
||||||
|
|
||||||
const { publicRuntimeConfig } = getConfig();
|
const { publicRuntimeConfig } = getConfig();
|
||||||
const posthogApiKey = 'phc_7gtqSrrdZRohiozPMLIacjzgHbUlhalW1Bu16uYijMR';
|
const posthogApiKey = process.env.NEXT_PUBLIC_POSTHOG_API_KEY;
|
||||||
let posthogClient = null;
|
let posthogClient = null;
|
||||||
|
|
||||||
const isPlaywrightTestRunning = () => {
|
const isPlaywrightTestRunning = () => {
|
||||||
|
@ -92,9 +92,7 @@ export const addGlobalEnvironment = ({ name, variables = [] }) => (dispatch, get
|
|||||||
const uid = uuid();
|
const uid = uuid();
|
||||||
ipcRenderer
|
ipcRenderer
|
||||||
.invoke('renderer:create-global-environment', { name, uid, variables })
|
.invoke('renderer:create-global-environment', { name, uid, variables })
|
||||||
.then(
|
.then(() => dispatch(_addGlobalEnvironment({ name, uid, variables })))
|
||||||
dispatch(_addGlobalEnvironment({ name, uid, variables }))
|
|
||||||
)
|
|
||||||
.then(resolve)
|
.then(resolve)
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
});
|
});
|
||||||
@ -108,9 +106,7 @@ export const copyGlobalEnvironment = ({ name, environmentUid: baseEnvUid }) => (
|
|||||||
const uid = uuid();
|
const uid = uuid();
|
||||||
ipcRenderer
|
ipcRenderer
|
||||||
.invoke('renderer:create-global-environment', { uid, name, variables: baseEnv.variables })
|
.invoke('renderer:create-global-environment', { uid, name, variables: baseEnv.variables })
|
||||||
.then(() => {
|
.then(() => dispatch(_copyGlobalEnvironment({ name, uid, variables: baseEnv.variables })))
|
||||||
dispatch(_copyGlobalEnvironment({ name, uid, variables: baseEnv.variables }))
|
|
||||||
})
|
|
||||||
.then(resolve)
|
.then(resolve)
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
});
|
});
|
||||||
@ -127,9 +123,7 @@ export const renameGlobalEnvironment = ({ name: newName, environmentUid }) => (d
|
|||||||
environmentSchema
|
environmentSchema
|
||||||
.validate(environment)
|
.validate(environment)
|
||||||
.then(() => ipcRenderer.invoke('renderer:rename-global-environment', { name: newName, environmentUid }))
|
.then(() => ipcRenderer.invoke('renderer:rename-global-environment', { name: newName, environmentUid }))
|
||||||
.then(
|
.then(() => dispatch(_renameGlobalEnvironment({ name: newName, environmentUid })))
|
||||||
dispatch(_renameGlobalEnvironment({ name: newName, environmentUid }))
|
|
||||||
)
|
|
||||||
.then(resolve)
|
.then(resolve)
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
});
|
});
|
||||||
@ -151,9 +145,7 @@ export const saveGlobalEnvironment = ({ variables, environmentUid }) => (dispatc
|
|||||||
environmentUid,
|
environmentUid,
|
||||||
variables
|
variables
|
||||||
}))
|
}))
|
||||||
.then(
|
.then(() => dispatch(_saveGlobalEnvironment({ environmentUid, variables })))
|
||||||
dispatch(_saveGlobalEnvironment({ environmentUid, variables }))
|
|
||||||
)
|
|
||||||
.then(resolve)
|
.then(resolve)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
reject(error);
|
reject(error);
|
||||||
@ -165,9 +157,7 @@ export const selectGlobalEnvironment = ({ environmentUid }) => (dispatch, getSta
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
ipcRenderer
|
ipcRenderer
|
||||||
.invoke('renderer:select-global-environment', { environmentUid })
|
.invoke('renderer:select-global-environment', { environmentUid })
|
||||||
.then(
|
.then(() => dispatch(_selectGlobalEnvironment({ environmentUid })))
|
||||||
dispatch(_selectGlobalEnvironment({ environmentUid }))
|
|
||||||
)
|
|
||||||
.then(resolve)
|
.then(resolve)
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
});
|
});
|
||||||
@ -177,9 +167,7 @@ export const deleteGlobalEnvironment = ({ environmentUid }) => (dispatch, getSta
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
ipcRenderer
|
ipcRenderer
|
||||||
.invoke('renderer:delete-global-environment', { environmentUid })
|
.invoke('renderer:delete-global-environment', { environmentUid })
|
||||||
.then(
|
.then(() => dispatch(_deleteGlobalEnvironment({ environmentUid })))
|
||||||
dispatch(_deleteGlobalEnvironment({ environmentUid }))
|
|
||||||
)
|
|
||||||
.then(resolve)
|
.then(resolve)
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
});
|
});
|
||||||
@ -195,7 +183,6 @@ export const globalEnvironmentsUpdateEvent = ({ globalEnvironmentVariables }) =>
|
|||||||
const environment = globalEnvironments?.find(env => env?.uid == environmentUid);
|
const environment = globalEnvironments?.find(env => env?.uid == environmentUid);
|
||||||
|
|
||||||
if (!environment || !environmentUid) {
|
if (!environment || !environmentUid) {
|
||||||
console.error('Global Environment not found');
|
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,9 +215,7 @@ export const globalEnvironmentsUpdateEvent = ({ globalEnvironmentVariables }) =>
|
|||||||
environmentUid,
|
environmentUid,
|
||||||
variables
|
variables
|
||||||
}))
|
}))
|
||||||
.then(
|
.then(() => dispatch(_saveGlobalEnvironment({ environmentUid, variables })))
|
||||||
dispatch(_saveGlobalEnvironment({ environmentUid, variables }))
|
|
||||||
)
|
|
||||||
.then(resolve)
|
.then(resolve)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
reject(error);
|
reject(error);
|
||||||
|
@ -23,18 +23,19 @@
|
|||||||
--color-method-options: rgb(52 52 52);
|
--color-method-options: rgb(52 52 52);
|
||||||
--color-method-head: rgb(52 52 52);
|
--color-method-head: rgb(52 52 52);
|
||||||
}
|
}
|
||||||
|
|
||||||
:root,.graphiql-container,.CodeMirror-info,.CodeMirror-lint-tooltip,reach-portal {
|
:root,.graphiql-container,.CodeMirror-info,.CodeMirror-lint-tooltip,reach-portal {
|
||||||
/* Required CSS variables after upgrading GraphiQL from v1.5.9 to v2.4.7 */
|
/* Required CSS variables after upgrading GraphiQL from v1.5.9 to v2.4.7 */
|
||||||
/* Colors */
|
/* Colors */
|
||||||
--color-primary: 320, 95%, 43% !important;
|
--color-primary: 0, 0%, 0% !important;
|
||||||
--color-secondary: 242, 51%, 61% !important;
|
--color-secondary: 0, 0%, 0% !important;
|
||||||
--color-tertiary: 188, 100%, 36% !important;
|
--color-tertiary: 0, 0%, 0% !important;
|
||||||
--color-info: 208, 100%, 46% !important;
|
--color-info: 0, 0%, 0% !important;
|
||||||
--color-success: 158, 60%, 42% !important;
|
--color-success: 0, 0%, 0% !important;
|
||||||
--color-warning: 36, 100%, 41% !important;
|
--color-warning: 0, 0%, 0% !important;
|
||||||
--color-error: 13, 93%, 58% !important;
|
--color-error: 0, 0%, 0% !important;
|
||||||
--color-neutral: 219, 28%, 32% !important;
|
--color-neutral: 0, 0%, 0% !important;
|
||||||
--color-base: 219, 28%, 100% !important;
|
--color-base: 0, 0%, 100% !important;
|
||||||
|
|
||||||
/* Color alpha values */
|
/* Color alpha values */
|
||||||
--alpha-secondary: 0.76 !important;
|
--alpha-secondary: 0.76 !important;
|
||||||
@ -43,6 +44,59 @@
|
|||||||
--alpha-background-medium: 0.1 !important;
|
--alpha-background-medium: 0.1 !important;
|
||||||
--alpha-background-light: 0.07 !important;
|
--alpha-background-light: 0.07 !important;
|
||||||
|
|
||||||
|
--font-family: Consolas,Inconsolata,Droid Sans Mono,Monaco,monospace;
|
||||||
|
--font-family-mono: 'Fira Code', monospace;
|
||||||
|
--font-size-hint: .75rem;
|
||||||
|
--font-size-inline-code: .8125rem;
|
||||||
|
--font-size-body: .8rem;
|
||||||
|
--font-size-h4: 1.125rem;
|
||||||
|
--font-size-h3: 1.375rem;
|
||||||
|
--font-size-h2: 1.8125rem;
|
||||||
|
--font-weight-regular: 400;
|
||||||
|
--font-weight-medium: 500;
|
||||||
|
--line-height: 1.5;
|
||||||
|
--px-2: 0px;
|
||||||
|
--px-4: 0px;
|
||||||
|
--px-6: 2px;
|
||||||
|
--px-8: 8px;
|
||||||
|
--px-10: 10px;
|
||||||
|
--px-12: 12px;
|
||||||
|
--px-16: 16px;
|
||||||
|
--px-20: 20px;
|
||||||
|
--px-24: 24px;
|
||||||
|
--border-radius-2: 0px !important;
|
||||||
|
--border-radius-4: 0px !important;
|
||||||
|
--border-radius-8: 0px !important;
|
||||||
|
--border-radius-12: 0px !important;
|
||||||
|
--popover-box-shadow: 0px 0px 1px #000 !important;
|
||||||
|
--popover-border: none;
|
||||||
|
--sidebar-width: 60px;
|
||||||
|
--toolbar-width: 40px;
|
||||||
|
--session-header-height: 51px
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Required CSS variables after upgrading GraphiQL from v1.5.9 to v2.4.7 */
|
||||||
|
.graphiql-container, .CodeMirror-info, .CodeMirror-lint-tooltip, reach-portal {
|
||||||
|
/* General Colors */
|
||||||
|
--color-primary: 0, 0%, 0% !important;
|
||||||
|
--color-secondary: 0, 0%, 0% !important;
|
||||||
|
--color-tertiary: 0, 0%, 0% !important;
|
||||||
|
--color-info: 0, 0%, 0% !important;
|
||||||
|
--color-success: 0, 0%, 0% !important;
|
||||||
|
--color-warning: 0, 0%, 0% !important;
|
||||||
|
--color-error: 0, 0%, 0% !important;
|
||||||
|
--color-base: 0, 0%, 100% !important;
|
||||||
|
--color-neutral: 0, 0%, 60% !important;
|
||||||
|
|
||||||
|
/* Color alpha values */
|
||||||
|
--alpha-secondary: 0.76 !important;
|
||||||
|
--alpha-tertiary: 0.5 !important;
|
||||||
|
--alpha-background-heavy: 0.15 !important;
|
||||||
|
--alpha-background-medium: 0.1 !important;
|
||||||
|
--alpha-background-light: 0.07 !important;
|
||||||
|
|
||||||
|
--font-family: Consolas,Inconsolata,Droid Sans Mono,Monaco,monospace;
|
||||||
|
--font-family-mono: 'Fira Code', monospace;
|
||||||
--font-size-hint: .75rem;
|
--font-size-hint: .75rem;
|
||||||
--font-size-inline-code: .8125rem;
|
--font-size-inline-code: .8125rem;
|
||||||
--font-size-body: .9375rem;
|
--font-size-body: .9375rem;
|
||||||
@ -52,15 +106,15 @@
|
|||||||
--font-weight-regular: 400;
|
--font-weight-regular: 400;
|
||||||
--font-weight-medium: 500;
|
--font-weight-medium: 500;
|
||||||
--line-height: 1.5;
|
--line-height: 1.5;
|
||||||
--px-2: 2px;
|
--px-2: 2px !important;
|
||||||
--px-4: 4px;
|
--px-4: 4px !important;
|
||||||
--px-6: 6px;
|
--px-6: 6px !important;
|
||||||
--px-8: 8px;
|
--px-8: 8px !important;
|
||||||
--px-10: 10px;
|
--px-10: 10px !important;
|
||||||
--px-12: 12px;
|
--px-12: 12px !important;
|
||||||
--px-16: 16px;
|
--px-16: 16px !important;
|
||||||
--px-20: 20px;
|
--px-20: 20px !important;
|
||||||
--px-24: 24px;
|
--px-24: 24px !important;
|
||||||
--border-radius-2: 2px !important;
|
--border-radius-2: 2px !important;
|
||||||
--border-radius-4: 2px !important;
|
--border-radius-4: 2px !important;
|
||||||
--border-radius-8: 2px !important;
|
--border-radius-8: 2px !important;
|
||||||
@ -72,6 +126,15 @@
|
|||||||
--session-header-height: 51px
|
--session-header-height: 51px
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.CodeMirror-dialog {
|
||||||
|
--px-4: 0px !important;
|
||||||
|
--px-12: 2px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.graphiql-container {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -19,16 +19,23 @@ const createContentType = (mode) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a list of enabled headers for the request, ensuring no duplicate content-type headers.
|
||||||
|
*
|
||||||
|
* @param {Object} request - The request object.
|
||||||
|
* @param {Object[]} headers - The array of header objects, each containing name, value, and enabled properties.
|
||||||
|
* @returns {Object[]} - An array of enabled headers with normalized names and values.
|
||||||
|
*/
|
||||||
const createHeaders = (request, headers) => {
|
const createHeaders = (request, headers) => {
|
||||||
const enabledHeaders = headers
|
const enabledHeaders = headers
|
||||||
.filter((header) => header.enabled)
|
.filter((header) => header.enabled)
|
||||||
.map((header) => ({
|
.map((header) => ({
|
||||||
name: header.name,
|
name: header.name.toLowerCase(),
|
||||||
value: header.value
|
value: header.value
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const contentType = createContentType(request.body?.mode);
|
const contentType = createContentType(request.body?.mode);
|
||||||
if (contentType !== '') {
|
if (contentType !== '' && !enabledHeaders.some((header) => header.name === 'content-type')) {
|
||||||
enabledHeaders.push({ name: 'content-type', value: contentType });
|
enabledHeaders.push({ name: 'content-type', value: contentType });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
let CodeMirror;
|
let CodeMirror;
|
||||||
const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
const SERVER_RENDERED = typeof window === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
||||||
|
|
||||||
if (!SERVER_RENDERED) {
|
if (!SERVER_RENDERED) {
|
||||||
CodeMirror = require('codemirror');
|
CodeMirror = require('codemirror');
|
||||||
|
@ -12,7 +12,7 @@ import brunoCommon from '@usebruno/common';
|
|||||||
const { interpolate } = brunoCommon;
|
const { interpolate } = brunoCommon;
|
||||||
|
|
||||||
let CodeMirror;
|
let CodeMirror;
|
||||||
const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
const SERVER_RENDERED = typeof window === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
||||||
const { get } = require('lodash');
|
const { get } = require('lodash');
|
||||||
|
|
||||||
if (!SERVER_RENDERED) {
|
if (!SERVER_RENDERED) {
|
||||||
@ -44,8 +44,11 @@ if (!SERVER_RENDERED) {
|
|||||||
const into = document.createElement('div');
|
const into = document.createElement('div');
|
||||||
const descriptionDiv = document.createElement('div');
|
const descriptionDiv = document.createElement('div');
|
||||||
descriptionDiv.className = 'info-description';
|
descriptionDiv.className = 'info-description';
|
||||||
|
if (options?.variables?.maskedEnvVariables?.includes(variableName)) {
|
||||||
descriptionDiv.appendChild(document.createTextNode(variableValue));
|
descriptionDiv.appendChild(document.createTextNode('*****'));
|
||||||
|
} else {
|
||||||
|
descriptionDiv.appendChild(document.createTextNode(variableValue));
|
||||||
|
}
|
||||||
into.appendChild(descriptionDiv);
|
into.appendChild(descriptionDiv);
|
||||||
|
|
||||||
return into;
|
return into;
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
let CodeMirror;
|
let CodeMirror;
|
||||||
const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
const SERVER_RENDERED = typeof window === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
||||||
|
|
||||||
if (!SERVER_RENDERED) {
|
if (!SERVER_RENDERED) {
|
||||||
CodeMirror = require('codemirror');
|
CodeMirror = require('codemirror');
|
||||||
|
@ -820,6 +820,23 @@ export const getEnvironmentVariables = (collection) => {
|
|||||||
return variables;
|
return variables;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getEnvironmentVariablesMasked = (collection) => {
|
||||||
|
// Return an empty array if the collection is invalid or not provided
|
||||||
|
if (!collection || !collection.activeEnvironmentUid) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the active environment in the collection
|
||||||
|
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
|
||||||
|
if (!environment || !environment.variables) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter the environment variables to get only the masked (secret) ones
|
||||||
|
return environment.variables
|
||||||
|
.filter((variable) => variable.name && variable.value && variable.enabled && variable.secret)
|
||||||
|
.map((variable) => variable.name);
|
||||||
|
};
|
||||||
|
|
||||||
const getPathParams = (item) => {
|
const getPathParams = (item) => {
|
||||||
let pathParams = {};
|
let pathParams = {};
|
||||||
@ -855,6 +872,13 @@ export const getAllVariables = (collection, item) => {
|
|||||||
const { globalEnvironmentVariables = {} } = collection;
|
const { globalEnvironmentVariables = {} } = collection;
|
||||||
|
|
||||||
const { processEnvVariables = {}, runtimeVariables = {} } = collection;
|
const { processEnvVariables = {}, runtimeVariables = {} } = collection;
|
||||||
|
const mergedVariables = {
|
||||||
|
...folderVariables,
|
||||||
|
...requestVariables,
|
||||||
|
...runtimeVariables
|
||||||
|
};
|
||||||
|
const maskedEnvVariables = getEnvironmentVariablesMasked(collection);
|
||||||
|
const filteredMaskedEnvVariables = maskedEnvVariables.filter((key) => !(key in mergedVariables));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...globalEnvironmentVariables,
|
...globalEnvironmentVariables,
|
||||||
@ -866,6 +890,7 @@ export const getAllVariables = (collection, item) => {
|
|||||||
pathParams: {
|
pathParams: {
|
||||||
...pathParams
|
...pathParams
|
||||||
},
|
},
|
||||||
|
maskedEnvVariables: filteredMaskedEnvVariables,
|
||||||
process: {
|
process: {
|
||||||
env: {
|
env: {
|
||||||
...processEnvVariables
|
...processEnvVariables
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
|
|
||||||
let CodeMirror;
|
let CodeMirror;
|
||||||
const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
const SERVER_RENDERED = typeof window === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
||||||
|
|
||||||
if (!SERVER_RENDERED) {
|
if (!SERVER_RENDERED) {
|
||||||
CodeMirror = require('codemirror');
|
CodeMirror = require('codemirror');
|
||||||
@ -52,17 +52,33 @@ export class MaskedEditor {
|
|||||||
/** Replaces all rendered characters, with the provided character. */
|
/** Replaces all rendered characters, with the provided character. */
|
||||||
maskContent = () => {
|
maskContent = () => {
|
||||||
const content = this.editor.getValue();
|
const content = this.editor.getValue();
|
||||||
|
const lineCount = this.editor.lineCount();
|
||||||
|
|
||||||
|
if (lineCount === 0) return;
|
||||||
this.editor.operation(() => {
|
this.editor.operation(() => {
|
||||||
// Clear previous masked text
|
// Clear previous masked text
|
||||||
this.editor.getAllMarks().forEach((mark) => mark.clear());
|
this.editor.getAllMarks().forEach((mark) => mark.clear());
|
||||||
// Apply new masked text
|
// Apply new masked text
|
||||||
for (let i = 0; i < content.length; i++) {
|
|
||||||
if (content[i] !== '\n') {
|
if (content.length <= 500) {
|
||||||
const maskedNode = document.createTextNode(this.maskChar);
|
for (let i = 0; i < content.length; i++) {
|
||||||
|
if (content[i] !== '\n') {
|
||||||
|
const maskedNode = document.createTextNode(this.maskChar);
|
||||||
|
this.editor.markText(
|
||||||
|
{ line: this.editor.posFromIndex(i).line, ch: this.editor.posFromIndex(i).ch },
|
||||||
|
{ line: this.editor.posFromIndex(i + 1).line, ch: this.editor.posFromIndex(i + 1).ch },
|
||||||
|
{ replacedWith: maskedNode, handleMouseEvents: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let line = 0; line < lineCount; line++) {
|
||||||
|
const lineLength = this.editor.getLine(line).length;
|
||||||
|
const maskedNode = document.createTextNode('*'.repeat(lineLength));
|
||||||
this.editor.markText(
|
this.editor.markText(
|
||||||
{ line: this.editor.posFromIndex(i).line, ch: this.editor.posFromIndex(i).ch },
|
{ line, ch: 0 },
|
||||||
{ line: this.editor.posFromIndex(i + 1).line, ch: this.editor.posFromIndex(i + 1).ch },
|
{ line, ch: lineLength },
|
||||||
{ replacedWith: maskedNode, handleMouseEvents: true }
|
{ replacedWith: maskedNode, handleMouseEvents: false }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,12 @@ function getQueries(request) {
|
|||||||
return queries;
|
return queries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts request data to a string based on its content type.
|
||||||
|
*
|
||||||
|
* @param {Object} request - The request object containing data and headers.
|
||||||
|
* @returns {Object} An object containing the data string.
|
||||||
|
*/
|
||||||
function getDataString(request) {
|
function getDataString(request) {
|
||||||
if (typeof request.data === 'number') {
|
if (typeof request.data === 'number') {
|
||||||
request.data = request.data.toString();
|
request.data = request.data.toString();
|
||||||
@ -44,7 +50,13 @@ function getDataString(request) {
|
|||||||
const contentType = getContentType(request.headers);
|
const contentType = getContentType(request.headers);
|
||||||
|
|
||||||
if (contentType && contentType.includes('application/json')) {
|
if (contentType && contentType.includes('application/json')) {
|
||||||
return { data: request.data.toString() };
|
try {
|
||||||
|
const parsedData = JSON.parse(request.data);
|
||||||
|
return { data: JSON.stringify(parsedData) };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to parse JSON data:', error);
|
||||||
|
return { data: request.data.toString() };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedQueryString = querystring.parse(request.data, { sort: false });
|
const parsedQueryString = querystring.parse(request.data, { sort: false });
|
||||||
|
@ -2,7 +2,7 @@ import { forOwn } from 'lodash';
|
|||||||
import { convertToCodeMirrorJson } from 'utils/common';
|
import { convertToCodeMirrorJson } from 'utils/common';
|
||||||
import curlToJson from './curl-to-json';
|
import curlToJson from './curl-to-json';
|
||||||
|
|
||||||
export const getRequestFromCurlCommand = (curlCommand) => {
|
export const getRequestFromCurlCommand = (curlCommand, requestType = 'http-request') => {
|
||||||
const parseFormData = (parsedBody) => {
|
const parseFormData = (parsedBody) => {
|
||||||
const formData = [];
|
const formData = [];
|
||||||
forOwn(parsedBody, (value, key) => {
|
forOwn(parsedBody, (value, key) => {
|
||||||
@ -12,6 +12,22 @@ export const getRequestFromCurlCommand = (curlCommand) => {
|
|||||||
return formData;
|
return formData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const parseGraphQL = (text) => {
|
||||||
|
try {
|
||||||
|
const graphql = JSON.parse(text);
|
||||||
|
|
||||||
|
return {
|
||||||
|
query: graphql.query,
|
||||||
|
variables: JSON.stringify(graphql.variables, null, 2)
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
query: '',
|
||||||
|
variables: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!curlCommand || typeof curlCommand !== 'string' || curlCommand.length === 0) {
|
if (!curlCommand || typeof curlCommand !== 'string' || curlCommand.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
@ -24,6 +40,8 @@ export const getRequestFromCurlCommand = (curlCommand) => {
|
|||||||
Object.keys(parsedHeaders).map((key) => ({ name: key, value: parsedHeaders[key], enabled: true }));
|
Object.keys(parsedHeaders).map((key) => ({ name: key, value: parsedHeaders[key], enabled: true }));
|
||||||
|
|
||||||
const contentType = headers?.find((h) => h.name.toLowerCase() === 'content-type')?.value;
|
const contentType = headers?.find((h) => h.name.toLowerCase() === 'content-type')?.value;
|
||||||
|
const parsedBody = request.data;
|
||||||
|
|
||||||
const body = {
|
const body = {
|
||||||
mode: 'none',
|
mode: 'none',
|
||||||
json: null,
|
json: null,
|
||||||
@ -31,11 +49,15 @@ export const getRequestFromCurlCommand = (curlCommand) => {
|
|||||||
xml: null,
|
xml: null,
|
||||||
sparql: null,
|
sparql: null,
|
||||||
multipartForm: null,
|
multipartForm: null,
|
||||||
formUrlEncoded: null
|
formUrlEncoded: null,
|
||||||
|
graphql: null
|
||||||
};
|
};
|
||||||
const parsedBody = request.data;
|
|
||||||
if (parsedBody && contentType && typeof contentType === 'string') {
|
if (parsedBody && contentType && typeof contentType === 'string') {
|
||||||
if (contentType.includes('application/json')) {
|
if (requestType === 'graphql-request' && (contentType.includes('application/json') || contentType.includes('application/graphql'))) {
|
||||||
|
body.mode = 'graphql';
|
||||||
|
body.graphql = parseGraphQL(parsedBody);
|
||||||
|
} else if (contentType.includes('application/json')) {
|
||||||
body.mode = 'json';
|
body.mode = 'json';
|
||||||
body.json = convertToCodeMirrorJson(parsedBody);
|
body.json = convertToCodeMirrorJson(parsedBody);
|
||||||
} else if (contentType.includes('text/xml')) {
|
} else if (contentType.includes('text/xml')) {
|
||||||
|
@ -271,7 +271,7 @@ const resolveRefs = (spec, components = spec?.components, visitedItems = new Set
|
|||||||
|
|
||||||
// Recursively resolve references in nested objects
|
// Recursively resolve references in nested objects
|
||||||
for (const prop in spec) {
|
for (const prop in spec) {
|
||||||
spec[prop] = resolveRefs(spec[prop], components, visitedItems);
|
spec[prop] = resolveRefs(spec[prop], components, new Set(visitedItems));
|
||||||
}
|
}
|
||||||
|
|
||||||
return spec;
|
return spec;
|
||||||
@ -316,7 +316,7 @@ const getDefaultUrl = (serverObject) => {
|
|||||||
url = url.replace(`{${variableName}}`, sub);
|
url = url.replace(`{${variableName}}`, sub);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return url.endsWith('/') ? url : `${url}/`;
|
return url.endsWith('/') ? url.slice(0, -1) : url;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSecurity = (apiSpec) => {
|
const getSecurity = (apiSpec) => {
|
||||||
@ -353,7 +353,7 @@ const openAPIRuntimeExpressionToScript = (expression) => {
|
|||||||
return expression;
|
return expression;
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseOpenApiCollection = (data) => {
|
export const parseOpenApiCollection = (data) => {
|
||||||
const brunoCollection = {
|
const brunoCollection = {
|
||||||
name: '',
|
name: '',
|
||||||
uid: uuid(),
|
uid: uuid(),
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
import { parseOpenApiCollection } from './openapi-collection';
|
||||||
|
import { uuid } from 'utils/common';
|
||||||
|
|
||||||
|
jest.mock('utils/common');
|
||||||
|
|
||||||
|
describe('openapi importer util functions', () => {
|
||||||
|
afterEach(jest.clearAllMocks);
|
||||||
|
|
||||||
|
it('should convert openapi object to bruno collection correctly', async () => {
|
||||||
|
const input = {
|
||||||
|
openapi: '3.0.3',
|
||||||
|
info: {
|
||||||
|
title: 'Sample API with Multiple Servers',
|
||||||
|
description: 'API spec with multiple servers.',
|
||||||
|
version: '1.0.0'
|
||||||
|
},
|
||||||
|
servers: [
|
||||||
|
{ url: 'https://api.example.com/v1', description: 'Production Server' },
|
||||||
|
{ url: 'https://staging-api.example.com/v1', description: 'Staging Server' },
|
||||||
|
{ url: 'http://localhost:3000/v1', description: 'Local Server' }
|
||||||
|
],
|
||||||
|
paths: {
|
||||||
|
'/users': {
|
||||||
|
get: {
|
||||||
|
summary: 'Get a list of users',
|
||||||
|
parameters: [
|
||||||
|
{ name: 'page', in: 'query', required: false, schema: { type: 'integer' } },
|
||||||
|
{ name: 'limit', in: 'query', required: false, schema: { type: 'integer' } }
|
||||||
|
],
|
||||||
|
responses: {
|
||||||
|
'200': { description: 'A list of users' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const expectedOutput = {
|
||||||
|
name: 'Sample API with Multiple Servers',
|
||||||
|
version: '1',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: 'Get a list of users',
|
||||||
|
type: 'http-request',
|
||||||
|
request: {
|
||||||
|
url: '{{baseUrl}}/users',
|
||||||
|
method: 'GET',
|
||||||
|
params: [
|
||||||
|
{ name: 'page', value: '', enabled: false, type: 'query' },
|
||||||
|
{ name: 'limit', value: '', enabled: false, type: 'query' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
environments: [
|
||||||
|
{ name: 'Production Server', variables: [{ name: 'baseUrl', value: 'https://api.example.com/v1' }] },
|
||||||
|
{ name: 'Staging Server', variables: [{ name: 'baseUrl', value: 'https://staging-api.example.com/v1' }] },
|
||||||
|
{ name: 'Local Server', variables: [{ name: 'baseUrl', value: 'http://localhost:3000/v1' }] }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await parseOpenApiCollection(input);
|
||||||
|
|
||||||
|
expect(result).toMatchObject(expectedOutput);
|
||||||
|
expect(uuid).toHaveBeenCalledTimes(10);
|
||||||
|
});
|
||||||
|
});
|
@ -11,6 +11,9 @@ describe('postmanTranslation function', () => {
|
|||||||
pm.collectionVariables.set('key', 'value');
|
pm.collectionVariables.set('key', 'value');
|
||||||
const data = pm.response.json();
|
const data = pm.response.json();
|
||||||
pm.expect(pm.environment.has('key')).to.be.true;
|
pm.expect(pm.environment.has('key')).to.be.true;
|
||||||
|
postman.setEnvironmentVariable('key', 'value');
|
||||||
|
postman.getEnvironmentVariable('key');
|
||||||
|
postman.clearEnvironmentVariable('key');
|
||||||
`;
|
`;
|
||||||
const expectedOutput = `
|
const expectedOutput = `
|
||||||
bru.getEnvVar('key');
|
bru.getEnvVar('key');
|
||||||
@ -21,6 +24,9 @@ describe('postmanTranslation function', () => {
|
|||||||
bru.setVar('key', 'value');
|
bru.setVar('key', 'value');
|
||||||
const data = res.getBody();
|
const data = res.getBody();
|
||||||
expect(bru.getEnvVar('key') !== undefined && bru.getEnvVar('key') !== null).to.be.true;
|
expect(bru.getEnvVar('key') !== undefined && bru.getEnvVar('key') !== null).to.be.true;
|
||||||
|
bru.setEnvVar('key', 'value');
|
||||||
|
bru.getEnvVar('key');
|
||||||
|
bru.deleteEnvVar('key');
|
||||||
`;
|
`;
|
||||||
expect(postmanTranslation(inputScript)).toBe(expectedOutput);
|
expect(postmanTranslation(inputScript)).toBe(expectedOutput);
|
||||||
});
|
});
|
||||||
@ -151,3 +157,13 @@ test('should handle response commands', () => {
|
|||||||
`;
|
`;
|
||||||
expect(postmanTranslation(inputScript)).toBe(expectedOutput);
|
expect(postmanTranslation(inputScript)).toBe(expectedOutput);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should handle tests object', () => {
|
||||||
|
const inputScript = `
|
||||||
|
tests['Status code is 200'] = responseCode.code === 200;
|
||||||
|
`;
|
||||||
|
const expectedOutput = `
|
||||||
|
test("Status code is 200", function() { expect(Boolean(responseCode.code === 200)).to.be.true; });
|
||||||
|
`;
|
||||||
|
expect(postmanTranslation(inputScript)).toBe(expectedOutput);
|
||||||
|
});
|
||||||
|
@ -17,7 +17,13 @@ const replacements = {
|
|||||||
'pm\\.response\\.code': 'res.getStatus()',
|
'pm\\.response\\.code': 'res.getStatus()',
|
||||||
'pm\\.response\\.text\\(': 'res.getBody()?.toString(',
|
'pm\\.response\\.text\\(': 'res.getBody()?.toString(',
|
||||||
'pm\\.expect\\.fail\\(': 'expect.fail(',
|
'pm\\.expect\\.fail\\(': 'expect.fail(',
|
||||||
'pm\\.response\\.responseTime': 'res.getResponseTime()'
|
'pm\\.response\\.responseTime': 'res.getResponseTime()',
|
||||||
|
'pm\\.environment\\.name': 'bru.getEnvName()',
|
||||||
|
"tests\\['([^']+)'\\]\\s*=\\s*([^;]+);": 'test("$1", function() { expect(Boolean($2)).to.be.true; });',
|
||||||
|
// deprecated translations
|
||||||
|
'postman\\.setEnvironmentVariable\\(': 'bru.setEnvVar(',
|
||||||
|
'postman\\.getEnvironmentVariable\\(': 'bru.getEnvVar(',
|
||||||
|
'postman\\.clearEnvironmentVariable\\(': 'bru.deleteEnvVar(',
|
||||||
};
|
};
|
||||||
|
|
||||||
const extendedReplacements = Object.keys(replacements).reduce((acc, key) => {
|
const extendedReplacements = Object.keys(replacements).reduce((acc, key) => {
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"qs": "^6.11.0",
|
"qs": "^6.11.0",
|
||||||
"socks-proxy-agent": "^8.0.2",
|
"socks-proxy-agent": "^8.0.2",
|
||||||
|
"tough-cookie": "^4.1.3",
|
||||||
"@usebruno/vm2": "^3.9.13",
|
"@usebruno/vm2": "^3.9.13",
|
||||||
"xmlbuilder": "^15.1.1",
|
"xmlbuilder": "^15.1.1",
|
||||||
"yargs": "^17.6.2"
|
"yargs": "^17.6.2"
|
||||||
|
@ -211,6 +211,11 @@ const builder = async (yargs) => {
|
|||||||
description:
|
description:
|
||||||
'The specified custom CA certificate (--cacert) will be used exclusively and the default truststore is ignored, if this option is specified. Evaluated in combination with "--cacert" only.'
|
'The specified custom CA certificate (--cacert) will be used exclusively and the default truststore is ignored, if this option is specified. Evaluated in combination with "--cacert" only.'
|
||||||
})
|
})
|
||||||
|
.option('disable-cookies', {
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: 'Automatically save and sent cookies with requests'
|
||||||
|
})
|
||||||
.option('env', {
|
.option('env', {
|
||||||
describe: 'Environment variables',
|
describe: 'Environment variables',
|
||||||
type: 'string'
|
type: 'string'
|
||||||
@ -259,10 +264,30 @@ const builder = async (yargs) => {
|
|||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description: 'Stop execution after a failure of a request, test, or assertion'
|
description: 'Stop execution after a failure of a request, test, or assertion'
|
||||||
})
|
})
|
||||||
|
.option('reporter-skip-all-headers', {
|
||||||
|
type: 'boolean',
|
||||||
|
description: 'Omit headers from the reporter output',
|
||||||
|
default: false
|
||||||
|
})
|
||||||
|
.option('reporter-skip-headers', {
|
||||||
|
type: 'array',
|
||||||
|
description: 'Skip specific headers from the reporter output',
|
||||||
|
default: []
|
||||||
|
})
|
||||||
|
.option('client-cert-config', {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Path to the Client certificate config file used for securing the connection in the request'
|
||||||
|
})
|
||||||
|
|
||||||
.example('$0 run request.bru', 'Run a request')
|
.example('$0 run request.bru', 'Run a request')
|
||||||
.example('$0 run request.bru --env local', 'Run a request with the environment set to local')
|
.example('$0 run request.bru --env local', 'Run a request with the environment set to local')
|
||||||
.example('$0 run folder', 'Run all requests in a folder')
|
.example('$0 run folder', 'Run all requests in a folder')
|
||||||
.example('$0 run folder -r', 'Run all requests in a folder recursively')
|
.example('$0 run folder -r', 'Run all requests in a folder recursively')
|
||||||
|
.example('$0 run --reporter-skip-all-headers', 'Run all requests in a folder recursively with omitted headers from the reporter output')
|
||||||
|
.example(
|
||||||
|
'$0 run --reporter-skip-headers "Authorization"',
|
||||||
|
'Run all requests in a folder recursively with skipped headers from the reporter output'
|
||||||
|
)
|
||||||
.example(
|
.example(
|
||||||
'$0 run request.bru --env local --env-var secret=xxx',
|
'$0 run request.bru --env local --env-var secret=xxx',
|
||||||
'Run a request with the environment set to local and overwrite the variable secret with value xxx'
|
'Run a request with the environment set to local and overwrite the variable secret with value xxx'
|
||||||
@ -292,7 +317,8 @@ const builder = async (yargs) => {
|
|||||||
.example(
|
.example(
|
||||||
'$0 run folder --cacert myCustomCA.pem --ignore-truststore',
|
'$0 run folder --cacert myCustomCA.pem --ignore-truststore',
|
||||||
'Use a custom CA certificate exclusively when validating the peers of the requests in the specified folder.'
|
'Use a custom CA certificate exclusively when validating the peers of the requests in the specified folder.'
|
||||||
);
|
)
|
||||||
|
.example('$0 run --client-cert-config client-cert-config.json', 'Run a request with Client certificate configurations');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handler = async function (argv) {
|
const handler = async function (argv) {
|
||||||
@ -301,6 +327,7 @@ const handler = async function (argv) {
|
|||||||
filename,
|
filename,
|
||||||
cacert,
|
cacert,
|
||||||
ignoreTruststore,
|
ignoreTruststore,
|
||||||
|
disableCookies,
|
||||||
env,
|
env,
|
||||||
envVar,
|
envVar,
|
||||||
insecure,
|
insecure,
|
||||||
@ -312,7 +339,10 @@ const handler = async function (argv) {
|
|||||||
reporterHtml,
|
reporterHtml,
|
||||||
sandbox,
|
sandbox,
|
||||||
testsOnly,
|
testsOnly,
|
||||||
bail
|
bail,
|
||||||
|
reporterSkipAllHeaders,
|
||||||
|
reporterSkipHeaders,
|
||||||
|
clientCertConfig
|
||||||
} = argv;
|
} = argv;
|
||||||
const collectionPath = process.cwd();
|
const collectionPath = process.cwd();
|
||||||
|
|
||||||
@ -330,6 +360,41 @@ const handler = async function (argv) {
|
|||||||
const brunoConfig = JSON.parse(brunoConfigFile);
|
const brunoConfig = JSON.parse(brunoConfigFile);
|
||||||
const collectionRoot = getCollectionRoot(collectionPath);
|
const collectionRoot = getCollectionRoot(collectionPath);
|
||||||
|
|
||||||
|
if (clientCertConfig) {
|
||||||
|
try {
|
||||||
|
const clientCertConfigExists = await exists(clientCertConfig);
|
||||||
|
if (!clientCertConfigExists) {
|
||||||
|
console.error(chalk.red(`Client Certificate Config file "${clientCertConfig}" does not exist.`));
|
||||||
|
process.exit(constants.EXIT_STATUS.ERROR_FILE_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientCertConfigFileContent = fs.readFileSync(clientCertConfig, 'utf8');
|
||||||
|
let clientCertConfigJson;
|
||||||
|
|
||||||
|
try {
|
||||||
|
clientCertConfigJson = JSON.parse(clientCertConfigFileContent);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(chalk.red(`Failed to parse Client Certificate Config JSON: ${err.message}`));
|
||||||
|
process.exit(constants.EXIT_STATUS.ERROR_INVALID_JSON);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clientCertConfigJson?.enabled && Array.isArray(clientCertConfigJson?.certs)) {
|
||||||
|
if (brunoConfig.clientCertificates) {
|
||||||
|
brunoConfig.clientCertificates.certs.push(...clientCertConfigJson.certs);
|
||||||
|
} else {
|
||||||
|
brunoConfig.clientCertificates = { certs: clientCertConfigJson.certs };
|
||||||
|
}
|
||||||
|
console.log(chalk.green(`Client certificates has been added`));
|
||||||
|
} else {
|
||||||
|
console.warn(chalk.yellow(`Client certificate configuration is enabled, but it either contains no valid "certs" array or the added configuration has been set to false`));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(chalk.red(`Unexpected error: ${err.message}`));
|
||||||
|
process.exit(constants.EXIT_STATUS.ERROR_UNKNOWN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (filename && filename.length) {
|
if (filename && filename.length) {
|
||||||
const pathExists = await exists(filename);
|
const pathExists = await exists(filename);
|
||||||
if (!pathExists) {
|
if (!pathExists) {
|
||||||
@ -392,6 +457,9 @@ const handler = async function (argv) {
|
|||||||
if (insecure) {
|
if (insecure) {
|
||||||
options['insecure'] = true;
|
options['insecure'] = true;
|
||||||
}
|
}
|
||||||
|
if (disableCookies) {
|
||||||
|
options['disableCookies'] = true;
|
||||||
|
}
|
||||||
if (cacert && cacert.length) {
|
if (cacert && cacert.length) {
|
||||||
if (insecure) {
|
if (insecure) {
|
||||||
console.error(chalk.red(`Ignoring the cacert option since insecure connections are enabled`));
|
console.error(chalk.red(`Ignoring the cacert option since insecure connections are enabled`));
|
||||||
@ -525,6 +593,35 @@ const handler = async function (argv) {
|
|||||||
suitename: bruFilepath.replace('.bru', '')
|
suitename: bruFilepath.replace('.bru', '')
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (reporterSkipAllHeaders) {
|
||||||
|
results.forEach((result) => {
|
||||||
|
result.request.headers = {};
|
||||||
|
result.response.headers = {};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteHeaderIfExists = (headers, header) => {
|
||||||
|
if (headers && headers[header]) {
|
||||||
|
delete headers[header];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (reporterSkipHeaders?.length) {
|
||||||
|
results.forEach((result) => {
|
||||||
|
if (result.request?.headers) {
|
||||||
|
reporterSkipHeaders.forEach((header) => {
|
||||||
|
deleteHeaderIfExists(result.request.headers, header);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (result.response?.headers) {
|
||||||
|
reporterSkipHeaders.forEach((header) => {
|
||||||
|
deleteHeaderIfExists(result.response.headers, header);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// bail if option is set and there is a failure
|
// bail if option is set and there is a failure
|
||||||
if (bail) {
|
if (bail) {
|
||||||
const requestFailure = result?.error;
|
const requestFailure = result?.error;
|
||||||
|
@ -20,6 +20,7 @@ const { addAwsV4Interceptor, resolveAwsV4Credentials } = require('./awsv4auth-he
|
|||||||
const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../utils/proxy-util');
|
const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../utils/proxy-util');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { createFormData } = require('../utils/common');
|
const { createFormData } = require('../utils/common');
|
||||||
|
const { getCookieStringForUrl, saveCookies, shouldUseCookies } = require('../utils/cookies');
|
||||||
const protocolRegex = /^([-+\w]{1,25})(:?\/\/|:)/;
|
const protocolRegex = /^([-+\w]{1,25})(:?\/\/|:)/;
|
||||||
|
|
||||||
const onConsoleLog = (type, args) => {
|
const onConsoleLog = (type, args) => {
|
||||||
@ -178,6 +179,14 @@ const runSingleRequest = async function (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//set cookies if enabled
|
||||||
|
if (!options.disableCookies) {
|
||||||
|
const cookieString = getCookieStringForUrl(request.url);
|
||||||
|
if (cookieString && typeof cookieString === 'string' && cookieString.length) {
|
||||||
|
request.headers['cookie'] = cookieString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// stringify the request url encoded params
|
// stringify the request url encoded params
|
||||||
if (request.headers['content-type'] === 'application/x-www-form-urlencoded') {
|
if (request.headers['content-type'] === 'application/x-www-form-urlencoded') {
|
||||||
request.data = qs.stringify(request.data);
|
request.data = qs.stringify(request.data);
|
||||||
@ -220,6 +229,11 @@ const runSingleRequest = async function (
|
|||||||
// Prevents the duration on leaking to the actual result
|
// Prevents the duration on leaking to the actual result
|
||||||
responseTime = response.headers.get('request-duration');
|
responseTime = response.headers.get('request-duration');
|
||||||
response.headers.delete('request-duration');
|
response.headers.delete('request-duration');
|
||||||
|
|
||||||
|
//save cookies if enabled
|
||||||
|
if (!options.disableCookies) {
|
||||||
|
saveCookies(request.url, response.headers);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err?.response) {
|
if (err?.response) {
|
||||||
response = err.response;
|
response = err.response;
|
||||||
|
100
packages/bruno-cli/src/utils/cookies.js
Normal file
100
packages/bruno-cli/src/utils/cookies.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
const { Cookie, CookieJar } = require('tough-cookie');
|
||||||
|
const each = require('lodash/each');
|
||||||
|
|
||||||
|
const cookieJar = new CookieJar();
|
||||||
|
|
||||||
|
const addCookieToJar = (setCookieHeader, requestUrl) => {
|
||||||
|
const cookie = Cookie.parse(setCookieHeader, { loose: true });
|
||||||
|
cookieJar.setCookieSync(cookie, requestUrl, {
|
||||||
|
ignoreError: true // silently ignore things like parse errors and invalid domains
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCookiesForUrl = (url) => {
|
||||||
|
return cookieJar.getCookiesSync(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCookieStringForUrl = (url) => {
|
||||||
|
const cookies = getCookiesForUrl(url);
|
||||||
|
|
||||||
|
if (!Array.isArray(cookies) || !cookies.length) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const validCookies = cookies.filter((cookie) => !cookie.expires || cookie.expires > Date.now());
|
||||||
|
|
||||||
|
return validCookies.map((cookie) => cookie.cookieString()).join('; ');
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDomainsWithCookies = () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const domainCookieMap = {};
|
||||||
|
|
||||||
|
cookieJar.store.getAllCookies((err, cookies) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
cookies.forEach((cookie) => {
|
||||||
|
if (!domainCookieMap[cookie.domain]) {
|
||||||
|
domainCookieMap[cookie.domain] = [cookie];
|
||||||
|
} else {
|
||||||
|
domainCookieMap[cookie.domain].push(cookie);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const domains = Object.keys(domainCookieMap);
|
||||||
|
const domainsWithCookies = [];
|
||||||
|
|
||||||
|
each(domains, (domain) => {
|
||||||
|
const cookies = domainCookieMap[domain];
|
||||||
|
const validCookies = cookies.filter((cookie) => !cookie.expires || cookie.expires > Date.now());
|
||||||
|
|
||||||
|
if (validCookies.length) {
|
||||||
|
domainsWithCookies.push({
|
||||||
|
domain,
|
||||||
|
cookies: validCookies,
|
||||||
|
cookieString: validCookies.map((cookie) => cookie.cookieString()).join('; ')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
resolve(domainsWithCookies);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteCookiesForDomain = (domain) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
cookieJar.store.removeCookies(domain, null, (err) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveCookies = (url, headers) => {
|
||||||
|
let setCookieHeaders = [];
|
||||||
|
if (headers['set-cookie']) {
|
||||||
|
setCookieHeaders = Array.isArray(headers['set-cookie'])
|
||||||
|
? headers['set-cookie']
|
||||||
|
: [headers['set-cookie']];
|
||||||
|
for (let setCookieHeader of setCookieHeaders) {
|
||||||
|
if (typeof setCookieHeader === 'string' && setCookieHeader.length) {
|
||||||
|
addCookieToJar(setCookieHeader, url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
addCookieToJar,
|
||||||
|
getCookiesForUrl,
|
||||||
|
getCookieStringForUrl,
|
||||||
|
getDomainsWithCookies,
|
||||||
|
deleteCookiesForDomain,
|
||||||
|
saveCookies
|
||||||
|
};
|
@ -2,7 +2,7 @@ const _ = require('lodash');
|
|||||||
const fs = require('fs');
|
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, isWSLPath, normalizeAndResolvePath, normalizeWslPath } = require('../utils/filesystem');
|
||||||
const { bruToEnvJson, bruToJson, collectionBruToJson } = require('../bru');
|
const { bruToEnvJson, bruToJson, collectionBruToJson } = require('../bru');
|
||||||
const { dotenvToJson } = require('@usebruno/lang');
|
const { dotenvToJson } = require('@usebruno/lang');
|
||||||
|
|
||||||
@ -445,11 +445,11 @@ class Watcher {
|
|||||||
ignoreInitial: false,
|
ignoreInitial: false,
|
||||||
usePolling: watchPath.startsWith('\\\\') || forcePolling ? true : false,
|
usePolling: watchPath.startsWith('\\\\') || forcePolling ? true : false,
|
||||||
ignored: (filepath) => {
|
ignored: (filepath) => {
|
||||||
const normalizedPath = filepath.replace(/\\/g, '/');
|
const normalizedPath = isWSLPath(filepath) ? normalizeWslPath(filepath) : normalizeAndResolvePath(filepath);
|
||||||
const relativePath = path.relative(watchPath, normalizedPath);
|
const relativePath = path.relative(watchPath, normalizedPath);
|
||||||
|
|
||||||
return ignores.some((ignorePattern) => {
|
return ignores.some((ignorePattern) => {
|
||||||
const normalizedIgnorePattern = ignorePattern.replace(/\\/g, '/');
|
const normalizedIgnorePattern = isWSLPath(ignorePattern) ? normalizeWslPath(ignorePattern) : ignorePattern.replace(/\\/g, '/');
|
||||||
return relativePath === normalizedIgnorePattern || relativePath.startsWith(normalizedIgnorePattern);
|
return relativePath === normalizedIgnorePattern || relativePath.startsWith(normalizedIgnorePattern);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -63,6 +63,10 @@ class GlobalEnvironmentsStore {
|
|||||||
|
|
||||||
addGlobalEnvironment({ uid, name, variables = [] }) {
|
addGlobalEnvironment({ uid, name, variables = [] }) {
|
||||||
let globalEnvironments = this.getGlobalEnvironments();
|
let globalEnvironments = this.getGlobalEnvironments();
|
||||||
|
const existingEnvironment = globalEnvironments.find(env => env?.name == name);
|
||||||
|
if (existingEnvironment) {
|
||||||
|
throw new Error('Environment with the same name already exists');
|
||||||
|
}
|
||||||
globalEnvironments.push({
|
globalEnvironments.push({
|
||||||
uid,
|
uid,
|
||||||
name,
|
name,
|
||||||
|
@ -6,10 +6,34 @@ const { safeStorage } = require('electron');
|
|||||||
const ELECTRONSAFESTORAGE_ALGO = '00';
|
const ELECTRONSAFESTORAGE_ALGO = '00';
|
||||||
const AES256_ALGO = '01';
|
const AES256_ALGO = '01';
|
||||||
|
|
||||||
// AES-256 encryption and decryption functions
|
function deriveKeyAndIv(password, keyLength, ivLength) {
|
||||||
|
const key = Buffer.alloc(keyLength);
|
||||||
|
const iv = Buffer.alloc(ivLength);
|
||||||
|
const derivedBytes = [];
|
||||||
|
let lastHash = null;
|
||||||
|
|
||||||
|
while (Buffer.concat(derivedBytes).length < keyLength + ivLength) {
|
||||||
|
const hash = crypto.createHash('md5');
|
||||||
|
if (lastHash) {
|
||||||
|
hash.update(lastHash);
|
||||||
|
}
|
||||||
|
hash.update(Buffer.from(password, 'utf8'));
|
||||||
|
lastHash = hash.digest();
|
||||||
|
derivedBytes.push(lastHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
const concatenatedBytes = Buffer.concat(derivedBytes);
|
||||||
|
concatenatedBytes.copy(key, 0, 0, keyLength);
|
||||||
|
concatenatedBytes.copy(iv, 0, keyLength, keyLength + ivLength);
|
||||||
|
|
||||||
|
return { key, iv };
|
||||||
|
}
|
||||||
|
|
||||||
function aes256Encrypt(data) {
|
function aes256Encrypt(data) {
|
||||||
const key = machineIdSync();
|
const rawKey = machineIdSync();
|
||||||
const cipher = crypto.createCipher('aes-256-cbc', key);
|
const iv = Buffer.alloc(16, 0); // Default IV for new encryption
|
||||||
|
const key = crypto.createHash('sha256').update(rawKey).digest(); // Derive a 32-byte key
|
||||||
|
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
|
||||||
let encrypted = cipher.update(data, 'utf8', 'hex');
|
let encrypted = cipher.update(data, 'utf8', 'hex');
|
||||||
encrypted += cipher.final('hex');
|
encrypted += cipher.final('hex');
|
||||||
|
|
||||||
@ -17,14 +41,28 @@ function aes256Encrypt(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function aes256Decrypt(data) {
|
function aes256Decrypt(data) {
|
||||||
const key = machineIdSync();
|
const rawKey = machineIdSync();
|
||||||
const decipher = crypto.createDecipher('aes-256-cbc', key);
|
|
||||||
let decrypted = decipher.update(data, 'hex', 'utf8');
|
|
||||||
decrypted += decipher.final('utf8');
|
|
||||||
|
|
||||||
return decrypted;
|
// Attempt to decrypt using new method first
|
||||||
|
const iv = Buffer.alloc(16, 0); // Default IV for new encryption
|
||||||
|
const key = crypto.createHash('sha256').update(rawKey).digest(); // Derive a 32-byte key
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
|
||||||
|
let decrypted = decipher.update(data, 'hex', 'utf8');
|
||||||
|
decrypted += decipher.final('utf8');
|
||||||
|
return decrypted;
|
||||||
|
} catch (err) {
|
||||||
|
// If decryption fails, fall back to old key derivation
|
||||||
|
const { key: oldKey, iv: oldIv } = deriveKeyAndIv(rawKey, 32, 16);
|
||||||
|
const decipher = crypto.createDecipheriv('aes-256-cbc', oldKey, oldIv);
|
||||||
|
let decrypted = decipher.update(data, 'hex', 'utf8');
|
||||||
|
decrypted += decipher.final('utf8');
|
||||||
|
return decrypted;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// electron safe storage encryption and decryption functions
|
// electron safe storage encryption and decryption functions
|
||||||
function safeStorageEncrypt(str) {
|
function safeStorageEncrypt(str) {
|
||||||
let encryptedStringBuffer = safeStorage.encryptString(str);
|
let encryptedStringBuffer = safeStorage.encryptString(str);
|
||||||
|
@ -22,6 +22,13 @@ describe('Encryption and Decryption Tests', () => {
|
|||||||
expect(() => decryptString('garbage')).toThrow('Decrypt failed: unrecognized string format');
|
expect(() => decryptString('garbage')).toThrow('Decrypt failed: unrecognized string format');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.skip('string encrypted using createCipher (< node 20) should be decrypted properly', () => {
|
||||||
|
const encryptedString = '$01:2738e0e6a38bcde5fd80141ceadc9b67bc7b1fca7e398c552c1ca2bace28eb57';
|
||||||
|
const decryptedValue = decryptString(encryptedString);
|
||||||
|
|
||||||
|
expect(decryptedValue).toBe('bruno is awesome');
|
||||||
|
});
|
||||||
|
|
||||||
it('decrypt should throw an error for invalid algorithm', () => {
|
it('decrypt should throw an error for invalid algorithm', () => {
|
||||||
const invalidAlgo = '$99:abcdefg';
|
const invalidAlgo = '$99:abcdefg';
|
||||||
|
|
||||||
|
@ -65,6 +65,10 @@ class Bru {
|
|||||||
this.envVariables[key] = value;
|
this.envVariables[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteEnvVar(key) {
|
||||||
|
delete this.envVariables[key];
|
||||||
|
}
|
||||||
|
|
||||||
getGlobalEnvVar(key) {
|
getGlobalEnvVar(key) {
|
||||||
return this._interpolate(this.globalEnvironmentVariables[key]);
|
return this._interpolate(this.globalEnvironmentVariables[key]);
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,12 @@ const addBruShimToContext = (vm, bru) => {
|
|||||||
vm.setProp(bruObject, 'setEnvVar', setEnvVar);
|
vm.setProp(bruObject, 'setEnvVar', setEnvVar);
|
||||||
setEnvVar.dispose();
|
setEnvVar.dispose();
|
||||||
|
|
||||||
|
let deleteEnvVar = vm.newFunction('deleteEnvVar', function (key) {
|
||||||
|
return marshallToVm(bru.deleteEnvVar(vm.dump(key)), vm);
|
||||||
|
});
|
||||||
|
vm.setProp(bruObject, 'deleteEnvVar', deleteEnvVar);
|
||||||
|
deleteEnvVar.dispose();
|
||||||
|
|
||||||
let getGlobalEnvVar = vm.newFunction('getGlobalEnvVar', function (key) {
|
let getGlobalEnvVar = vm.newFunction('getGlobalEnvVar', function (key) {
|
||||||
return marshallToVm(bru.getGlobalEnvVar(vm.dump(key)), vm);
|
return marshallToVm(bru.getGlobalEnvVar(vm.dump(key)), vm);
|
||||||
});
|
});
|
||||||
|
@ -5,7 +5,7 @@ meta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get {
|
get {
|
||||||
url: https://gloutnikov.com/post/2024/bruno.png
|
url: https://www.usebruno.com/favicon.ico
|
||||||
body: none
|
body: none
|
||||||
auth: none
|
auth: none
|
||||||
}
|
}
|
||||||
@ -13,7 +13,7 @@ get {
|
|||||||
tests {
|
tests {
|
||||||
test("should return parsed xml", function() {
|
test("should return parsed xml", function() {
|
||||||
const headers = res.getHeaders();
|
const headers = res.getHeaders();
|
||||||
expect(headers['content-type']).to.eql("image/png");
|
expect(headers['content-type']).to.eql("image/x-icon");
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ assert {
|
|||||||
script:pre-request {
|
script:pre-request {
|
||||||
bru.setVar("rUser", {
|
bru.setVar("rUser", {
|
||||||
full_name: 'Bruno',
|
full_name: 'Bruno',
|
||||||
age: 4,
|
age: 5,
|
||||||
'fav-food': ['egg', 'meat'],
|
'fav-food': ['egg', 'meat'],
|
||||||
'want.attention': true
|
'want.attention': true
|
||||||
});
|
});
|
||||||
@ -49,7 +49,7 @@ script:pre-request {
|
|||||||
tests {
|
tests {
|
||||||
test("should return json", function() {
|
test("should return json", function() {
|
||||||
const expectedResponse = `Hi, I am Bruno,
|
const expectedResponse = `Hi, I am Bruno,
|
||||||
I am 4 years old.
|
I am 5 years old.
|
||||||
My favorite food is egg and meat.
|
My favorite food is egg and meat.
|
||||||
I like attention: true`;
|
I like attention: true`;
|
||||||
expect(res.getBody()).to.equal(expectedResponse);
|
expect(res.getBody()).to.equal(expectedResponse);
|
||||||
|
@ -44,12 +44,12 @@ Bruno is offline-only. There are no plans to add cloud-sync to Bruno, ever. We v
|
|||||||
|
|
||||||
![bruno](assets/images/landing-2.png) <br /><br />
|
![bruno](assets/images/landing-2.png) <br /><br />
|
||||||
|
|
||||||
## Golden Edition ✨
|
## Commercial Versions ✨
|
||||||
|
|
||||||
Majority of our features are free and open source.
|
Majority of our features are free and open source.
|
||||||
We strive to strike a harmonious balance between [open-source principles and sustainability](https://github.com/usebruno/bruno/discussions/269)
|
We strive to strike a harmonious balance between [open-source principles and sustainability](https://github.com/usebruno/bruno/discussions/269)
|
||||||
|
|
||||||
You can buy the [Golden Edition](https://www.usebruno.com/pricing) for a one-time payment of **$19**! <br/>
|
You can explore our [paid versions](https://www.usebruno.com/pricing) to see if there are additional features that you or your team may find useful! <br/>
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
- [Installation](#installation)
|
- [Installation](#installation)
|
||||||
|
Loading…
Reference in New Issue
Block a user