From 7b55d52c11e8a89bd53cf09deedfa05f66e78de2 Mon Sep 17 00:00:00 2001 From: bplatta Date: Thu, 12 Oct 2023 21:53:14 -0500 Subject: [PATCH 01/59] firstpass at adding openapi v3 import json --- .../Sidebar/ImportCollection/index.js | 12 + .../src/utils/importers/openapi-collection.js | 346 ++++++++++++++++++ 2 files changed, 358 insertions(+) create mode 100644 packages/bruno-app/src/utils/importers/openapi-collection.js diff --git a/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js b/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js index 185e3c5cb..c119963db 100644 --- a/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js +++ b/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js @@ -2,6 +2,7 @@ import React from 'react'; import importBrunoCollection from 'utils/importers/bruno-collection'; import importPostmanCollection from 'utils/importers/postman-collection'; import importInsomniaCollection from 'utils/importers/insomnia-collection'; +import importOpenapiCollection from 'utils/importers/openapi-collection'; import { toastError } from 'utils/common/error'; import Modal from 'components/Modal'; @@ -30,6 +31,14 @@ const ImportCollection = ({ onClose, handleSubmit }) => { .catch((err) => toastError(err, 'Insomnia Import collection failed')); }; + const handleImportOpenapiCollection = () => { + importOpenapiCollection() + .then((collection) => { + handleSubmit(collection); + }) + .catch((err) => toastError(err, 'OpenAPI v3 Import collection failed')); + }; + return (
@@ -42,6 +51,9 @@ const ImportCollection = ({ onClose, handleSubmit }) => {
Insomnia Collection
+
+ OpenAPI Collection +
); diff --git a/packages/bruno-app/src/utils/importers/openapi-collection.js b/packages/bruno-app/src/utils/importers/openapi-collection.js new file mode 100644 index 000000000..a43c01e3d --- /dev/null +++ b/packages/bruno-app/src/utils/importers/openapi-collection.js @@ -0,0 +1,346 @@ +import each from 'lodash/each'; +import get from 'lodash/get'; +import fileDialog from 'file-dialog'; +import { uuid } from 'utils/common'; +import { BrunoError } from 'utils/common/error'; +import { validateSchema, transformItemsInCollection, hydrateSeqInCollection } from './common'; + +const readFile = (files) => { + return new Promise((resolve, reject) => { + const fileReader = new FileReader(); + fileReader.onload = (e) => resolve(e.target.result); + fileReader.onerror = (err) => reject(err); + fileReader.readAsText(files[0]); + }); +}; + +const ensureUrl = (url) => { + let protUrl = url.startsWith('http') ? url : `http://${url}`; + // replace any double or triple slashes + return protUrl.replace(/([^:]\/)\/+/g, '$1'); +}; + +const transformOpenapiRequestItem = (request) => { + console.log(request); + let _operationObject = request.operationObject; + const brunoRequestItem = { + uid: uuid(), + name: _operationObject.operationId, + type: 'http-request', + request: { + url: ensureUrl(request.global.server + '/' + request.path), + method: request.method.toUpperCase(), + auth: { + mode: 'none', + basic: null, + bearer: null + }, + headers: [], + params: [], + body: { + mode: 'none', + json: null, + text: null, + xml: null, + formUrlEncoded: [], + multipartForm: [] + } + } + }; + + each(_operationObject.parameters || [], (param) => { + if (param.in === 'query') { + brunoRequestItem.request.params.push({ + uid: uuid(), + name: param.name, + value: '', + description: param.description || '', + enabled: param.required + }); + } else if (param.in === 'header') { + brunoRequestItem.request.headers.push({ + uid: uuid(), + name: param.name, + value: '', + description: param.description || '', + enabled: param.required + }); + } + }); + + let auth; + // allow operation override + if (_operationObject.security) { + let schemeName = Object.keys(_operationObject.security[0])[0]; + auth = request.global.security.getScheme(schemeName); + } else if (request.global.security.supported.length > 0) { + auth = request.global.security.supported[0]; + } + + if (auth) { + if (auth.type === 'http' && auth.scheme === 'basic') { + brunoRequestItem.request.auth.mode = 'basic'; + brunoRequestItem.request.auth.basic = { + username: '{{username}}', + password: '{{password}}' + }; + } else if (auth.type === 'http' && auth.scheme === 'bearer') { + brunoRequestItem.request.auth.mode = 'bearer'; + brunoRequestItem.request.auth.bearer = { + token: '{{token}}' + }; + } else if (auth.type === 'apiKey' && auth.in === 'header') { + brunoRequestItem.request.headers.push({ + uid: uuid(), + name: auth.name, + value: '{{apiKey}}', + description: 'Authentication header', + enabled: true + }); + } + } + + // TODO: handle allOf/anyOf/oneOf + if (_operationObject.requestBody) { + let content = get(_operationObject, 'requestBody.content', {}); + Object.entries(content).forEach(([mimeType, body]) => { + if (mimeType === 'application/json') { + brunoRequestItem.request.body.mode = 'json'; + if (body.schema && body.schema.type === 'object') { + let _jsonBody = {}; + each(body.schema.properties || {}, (prop, name) => { + _jsonBody[name] = ''; + }); + brunoRequestItem.request.body.json = JSON.stringify(_jsonBody, null, 2); + } + } else if (mimeType === 'application/x-www-form-urlencoded') { + brunoRequestItem.request.body.mode = 'formUrlEncoded'; + if (body.schema && body.schema.type === 'object') { + each(body.schema.properties || {}, (prop, name) => { + brunoRequestItem.request.body.formUrlEncoded.push({ + uid: uuid(), + name: name, + value: '', + description: prop.description || '', + enabled: true + }); + }); + } + } else if (mimeType === 'multipart/form-data') { + brunoRequestItem.request.body.mode = 'multipartForm'; + if (body.schema && body.schema.type === 'object') { + each(body.schema.properties || {}, (prop, name) => { + brunoRequestItem.request.body.multipartForm.push({ + uid: uuid(), + name: name, + value: '', + description: prop.description || '', + enabled: true + }); + }); + } + } else if (mimeType === 'text/plain') { + brunoRequestItem.request.body.mode = 'text'; + brunoRequestItem.request.body.text = ''; + } else if (mimeType === 'text/xml') { + brunoRequestItem.request.body.mode = 'xml'; + brunoRequestItem.request.body.xml = ''; + } + }); + } + + return brunoRequestItem; +}; + +const resolveRefs = (spec, components = spec.components) => { + if (!spec || typeof spec !== 'object') { + return spec; + } + + if (Array.isArray(spec)) { + return spec.map((item) => resolveRefs(item, components)); + } + + if ('$ref' in spec) { + const refPath = spec.$ref; + + if (refPath.startsWith('#/components/')) { + // Local reference within components + const refKeys = refPath.replace('#/components/', '').split('/'); + let ref = components; + + for (const key of refKeys) { + if (ref[key]) { + ref = ref[key]; + } else { + // Handle invalid references gracefully? + return spec; + } + } + + return resolveRefs(ref, components); + } else { + // Handle external references (not implemented here) + // You would need to fetch the external reference and resolve it. + // Example: Fetch and resolve an external reference from a URL. + } + } + + // Recursively resolve references in nested objects + for (const prop in spec) { + spec[prop] = resolveRefs(spec[prop], components); + } + + return spec; +}; + +const groupRequestsByTags = (requests) => { + let _groups = {}; + let ungrouped = []; + each(requests, (request) => { + let tags = request.operationObject.tags || []; + if (tags.length > 0) { + let tag = tags[0]; // take first tag + if (!_groups[tag]) { + _groups[tag] = []; + } + _groups[tag].push(request); + } else { + ungrouped.push(request); + } + }); + + let groups = Object.keys(_groups).map((groupName) => { + return { + name: groupName, + requests: _groups[groupName] + }; + }); + + return [groups, ungrouped]; +}; + +const getDefaultUrl = (serverObject) => { + let url = serverObject.url; + if (serverObject.variables) { + each(serverObject.variables, (variable, variableName) => { + let sub = variable.default || (variable.enum ? variable.enum[0] : `{{${variableName}}}`); + url = url.replace(`{${variableName}}`, sub); + }); + } + return url; +}; + +const getSecurity = (apiSpec) => { + let supportedSchemes = apiSpec.security || []; + if (supportedSchemes.length === 0) { + return { + supported: [] + }; + } + + let securitySchemes = get(apiSpec, 'components.securitySchemes', {}); + if (Object.keys(securitySchemes) === 0) { + return { + supported: [] + }; + } + + return { + supported: supportedSchemes.map((scheme) => { + var schemeName = Object.keys(scheme)[0]; + return securitySchemes[schemeName]; + }), + schemes: securitySchemes, + getScheme: (schemeName) => { + return securitySchemes[schemeName]; + } + }; +}; + +const parseOpenapiCollection = (data) => { + const brunoCollection = { + name: '', + uid: uuid(), + version: '1', + items: [], + environments: [] + }; + + return new Promise((resolve, reject) => { + try { + const collectionData = resolveRefs(JSON.parse(data)); + if (!collectionData) { + reject(new BrunoError('Invalid OpenAPI collection. Failed to resolve refs.')); + return; + } + + // Currently parsing of openapi spec is "do your best", that is + // allows "invalid" openapi spec + + // assumes v3 if not defined. v2 no supported yet + if (collectionData.openapi && !collectionData.openapi.startsWith('3')) { + reject(new BrunoError('Only OpenAPI v3 is supported currently.')); + return; + } + + // TODO what if info.title not defined? + brunoCollection.name = collectionData.info.title; + let servers = collectionData.servers || []; + let baseUrl = servers[0] ? getDefaultUrl(servers[0]) : ''; + let securityConfig = getSecurity(collectionData); + + let allRequests = Object.entries(collectionData.paths) + .map(([path, methods]) => { + return Object.entries(methods).map(([method, operationObject]) => { + return { + method: method, + path: path, + operationObject: operationObject, + global: { + server: baseUrl, + security: securityConfig + } + }; + }); + }) + .reduce((acc, val) => acc.concat(val), []); // flatten + + let [groups, ungroupedRequests] = groupRequestsByTags(allRequests); + let brunoFolders = groups.map((group) => { + return { + uid: uuid(), + name: group.name, + type: 'folder', + items: group.requests.map(transformOpenapiRequestItem) + }; + }); + + let ungroupedItems = ungroupedRequests.map(transformOpenapiRequestItem); + let brunoCollectionItems = brunoFolders.concat(ungroupedItems); + brunoCollection.items = brunoCollectionItems; + resolve(brunoCollection); + } catch (err) { + console.error(err); + reject(new BrunoError('An error occurred while parsing the OpenAPI collection')); + } + }); +}; + +const importCollection = () => { + return new Promise((resolve, reject) => { + fileDialog({ accept: 'application/json' }) + .then(readFile) + .then(parseOpenapiCollection) + .then(transformItemsInCollection) + .then(hydrateSeqInCollection) + .then(validateSchema) + .then((collection) => resolve(collection)) + .catch((err) => { + console.log(err); + reject(new BrunoError('Import collection failed: ' + err.message)); + }); + }); +}; + +export default importCollection; From 35db296c1f19564c3c0c67ca60cbb92d1b62fe65 Mon Sep 17 00:00:00 2001 From: bplatta Date: Fri, 13 Oct 2023 10:50:47 -0500 Subject: [PATCH 02/59] Update console logs --- packages/bruno-app/src/utils/importers/openapi-collection.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/bruno-app/src/utils/importers/openapi-collection.js b/packages/bruno-app/src/utils/importers/openapi-collection.js index a43c01e3d..3c9178219 100644 --- a/packages/bruno-app/src/utils/importers/openapi-collection.js +++ b/packages/bruno-app/src/utils/importers/openapi-collection.js @@ -21,7 +21,6 @@ const ensureUrl = (url) => { }; const transformOpenapiRequestItem = (request) => { - console.log(request); let _operationObject = request.operationObject; const brunoRequestItem = { uid: uuid(), @@ -337,7 +336,7 @@ const importCollection = () => { .then(validateSchema) .then((collection) => resolve(collection)) .catch((err) => { - console.log(err); + console.error(err); reject(new BrunoError('Import collection failed: ' + err.message)); }); }); From 36ee1f542a4a902355c55d9290f756c3304df1dc Mon Sep 17 00:00:00 2001 From: bplatta Date: Fri, 13 Oct 2023 11:30:27 -0500 Subject: [PATCH 03/59] build entire json object for request and only support first requestBody content type --- .../src/utils/importers/openapi-collection.js | 99 +++++++++++-------- 1 file changed, 56 insertions(+), 43 deletions(-) diff --git a/packages/bruno-app/src/utils/importers/openapi-collection.js b/packages/bruno-app/src/utils/importers/openapi-collection.js index 3c9178219..440f3d521 100644 --- a/packages/bruno-app/src/utils/importers/openapi-collection.js +++ b/packages/bruno-app/src/utils/importers/openapi-collection.js @@ -20,6 +20,21 @@ const ensureUrl = (url) => { return protUrl.replace(/([^:]\/)\/+/g, '$1'); }; +const buildEmptyJsonBody = (bodySchema) => { + let _jsonBody = {}; + each(bodySchema.properties || {}, (prop, name) => { + if (prop.type === 'object') { + _jsonBody[name] = buildEmptyJsonBody(prop); + // handle arrays + } else if (prop.type === 'array') { + _jsonBody[name] = []; + } else { + _jsonBody[name] = ''; + } + }); + return _jsonBody; +}; + const transformOpenapiRequestItem = (request) => { let _operationObject = request.operationObject; const brunoRequestItem = { @@ -102,50 +117,48 @@ const transformOpenapiRequestItem = (request) => { // TODO: handle allOf/anyOf/oneOf if (_operationObject.requestBody) { let content = get(_operationObject, 'requestBody.content', {}); - Object.entries(content).forEach(([mimeType, body]) => { - if (mimeType === 'application/json') { - brunoRequestItem.request.body.mode = 'json'; - if (body.schema && body.schema.type === 'object') { - let _jsonBody = {}; - each(body.schema.properties || {}, (prop, name) => { - _jsonBody[name] = ''; - }); - brunoRequestItem.request.body.json = JSON.stringify(_jsonBody, null, 2); - } - } else if (mimeType === 'application/x-www-form-urlencoded') { - brunoRequestItem.request.body.mode = 'formUrlEncoded'; - if (body.schema && body.schema.type === 'object') { - each(body.schema.properties || {}, (prop, name) => { - brunoRequestItem.request.body.formUrlEncoded.push({ - uid: uuid(), - name: name, - value: '', - description: prop.description || '', - enabled: true - }); - }); - } - } else if (mimeType === 'multipart/form-data') { - brunoRequestItem.request.body.mode = 'multipartForm'; - if (body.schema && body.schema.type === 'object') { - each(body.schema.properties || {}, (prop, name) => { - brunoRequestItem.request.body.multipartForm.push({ - uid: uuid(), - name: name, - value: '', - description: prop.description || '', - enabled: true - }); - }); - } - } else if (mimeType === 'text/plain') { - brunoRequestItem.request.body.mode = 'text'; - brunoRequestItem.request.body.text = ''; - } else if (mimeType === 'text/xml') { - brunoRequestItem.request.body.mode = 'xml'; - brunoRequestItem.request.body.xml = ''; + let mimeType = Object.keys(content)[0]; + let body = content[mimeType] || {}; + let bodySchema = body.schema; + if (mimeType === 'application/json') { + brunoRequestItem.request.body.mode = 'json'; + if (bodySchema && bodySchema.type === 'object') { + let _jsonBody = buildEmptyJsonBody(bodySchema); + brunoRequestItem.request.body.json = JSON.stringify(_jsonBody, null, 2); } - }); + } else if (mimeType === 'application/x-www-form-urlencoded') { + brunoRequestItem.request.body.mode = 'formUrlEncoded'; + if (bodySchema && bodySchema.type === 'object') { + each(bodySchema.properties || {}, (prop, name) => { + brunoRequestItem.request.body.formUrlEncoded.push({ + uid: uuid(), + name: name, + value: '', + description: prop.description || '', + enabled: true + }); + }); + } + } else if (mimeType === 'multipart/form-data') { + brunoRequestItem.request.body.mode = 'multipartForm'; + if (bodySchema && bodySchema.type === 'object') { + each(bodySchema.properties || {}, (prop, name) => { + brunoRequestItem.request.body.multipartForm.push({ + uid: uuid(), + name: name, + value: '', + description: prop.description || '', + enabled: true + }); + }); + } + } else if (mimeType === 'text/plain') { + brunoRequestItem.request.body.mode = 'text'; + brunoRequestItem.request.body.text = ''; + } else if (mimeType === 'text/xml') { + brunoRequestItem.request.body.mode = 'xml'; + brunoRequestItem.request.body.xml = ''; + } } return brunoRequestItem; From 232027ff4ed6a3a2536de25638eb8b50f7fcfadb Mon Sep 17 00:00:00 2001 From: cardonator Date: Wed, 18 Oct 2023 12:35:48 -0600 Subject: [PATCH 04/59] adjust styles on environment dropdown and modal --- packages/bruno-app/src/components/Dropdown/StyledWrapper.js | 2 ++ .../EnvironmentSettings/EnvironmentList/StyledWrapper.js | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/bruno-app/src/components/Dropdown/StyledWrapper.js b/packages/bruno-app/src/components/Dropdown/StyledWrapper.js index ceb2fb1d7..6ad94e289 100644 --- a/packages/bruno-app/src/components/Dropdown/StyledWrapper.js +++ b/packages/bruno-app/src/components/Dropdown/StyledWrapper.js @@ -14,6 +14,8 @@ const Wrapper = styled.div` background-color: ${(props) => props.theme.dropdown.bg}; box-shadow: ${(props) => props.theme.dropdown.shadow}; border-radius: 3px; + max-height: 90vh; + overflow-y: auto; .tippy-content { padding-left: 0; diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js index 687cde46c..d6639b7db 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js @@ -10,7 +10,8 @@ const StyledWrapper = styled.div` background-color: ${(props) => props.theme.collection.environment.settings.sidebar.bg}; border-right: solid 1px ${(props) => props.theme.collection.environment.settings.sidebar.borderRight}; min-height: 400px; - height: 100%; + max-height: 85vh; + overflow-y: auto; } .environment-item { From 21c49093a5caaa4d64a67c243fb45957cf53f2da Mon Sep 17 00:00:00 2001 From: jcari-dev <582119jc@gmail.com> Date: Wed, 18 Oct 2023 18:00:45 -0400 Subject: [PATCH 05/59] fixed typo in contributing.md --- contributing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contributing.md b/contributing.md index 966a6134b..23501f4ef 100644 --- a/contributing.md +++ b/contributing.md @@ -33,5 +33,5 @@ Please reference [development.md](docs/development.md) for instructions on runni - Please follow the format of creating branches - feature/[feature name]: This branch should contain changes for a specific feature - Example: feature/dark-mode - - bugfix/[bug name]: This branch should container only bug fixes for a specific bug + - bugfix/[bug name]: This branch should contain only bug fixes for a specific bug - Example bugfix/bug-1 From ddddab811290c82e15a7008c4027792fe7772bf7 Mon Sep 17 00:00:00 2001 From: Louis Wilke Date: Thu, 19 Oct 2023 21:28:21 +0200 Subject: [PATCH 06/59] 571 - allow and encode multiline uri form data --- .../components/RequestPane/FormUrlEncodedParams/index.js | 1 + packages/bruno-app/src/components/SingleLineEditor/index.js | 6 +++++- packages/bruno-lang/v2/src/bruToJson.js | 2 +- packages/bruno-lang/v2/src/jsonToBru.js | 4 ++-- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js b/packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js index 3cf5842e8..6d1d5be25 100644 --- a/packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js +++ b/packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js @@ -107,6 +107,7 @@ const FormUrlEncodedParams = ({ item, collection }) => { 'value' ) } + allowNewlines={true} onRun={handleRun} collection={collection} /> diff --git a/packages/bruno-app/src/components/SingleLineEditor/index.js b/packages/bruno-app/src/components/SingleLineEditor/index.js index bee59b2d9..fb3bff212 100644 --- a/packages/bruno-app/src/components/SingleLineEditor/index.js +++ b/packages/bruno-app/src/components/SingleLineEditor/index.js @@ -57,6 +57,7 @@ class SingleLineEditor extends Component { } componentDidMount() { // Initialize CodeMirror as a single line editor + /** @type {import("codemirror").Editor} */ this.editor = CodeMirror(this.editorRef.current, { lineWrapping: false, lineNumbers: false, @@ -84,7 +85,10 @@ class SingleLineEditor extends Component { } }, 'Alt-Enter': () => { - if (this.props.onRun) { + if (this.props.allowNewlines) { + this.editor.setValue(this.editor.getValue() + '\n'); + this.editor.setCursor({ line: this.editor.lineCount(), ch: 0 }); + } else if (this.props.onRun) { this.props.onRun(); } }, diff --git a/packages/bruno-lang/v2/src/bruToJson.js b/packages/bruno-lang/v2/src/bruToJson.js index 5ce65eba0..d8d51d3b1 100644 --- a/packages/bruno-lang/v2/src/bruToJson.js +++ b/packages/bruno-lang/v2/src/bruToJson.js @@ -104,7 +104,7 @@ const mapPairListToKeyValPairs = (pairList = [], parseEnabled = true) => { } return _.map(pairList[0], (pair) => { let name = _.keys(pair)[0]; - let value = pair[name]; + let value = decodeURIComponent(pair[name]); if (!parseEnabled) { return { diff --git a/packages/bruno-lang/v2/src/jsonToBru.js b/packages/bruno-lang/v2/src/jsonToBru.js index 757406b70..56b5ec478 100644 --- a/packages/bruno-lang/v2/src/jsonToBru.js +++ b/packages/bruno-lang/v2/src/jsonToBru.js @@ -154,7 +154,7 @@ ${indentString(body.sparql)} if (enabled(body.formUrlEncoded).length) { bru += `\n${indentString( enabled(body.formUrlEncoded) - .map((item) => `${item.name}: ${item.value}`) + .map((item) => `${item.name}: ${encodeURIComponent(item.value)}`) .join('\n') )}`; } @@ -162,7 +162,7 @@ ${indentString(body.sparql)} if (disabled(body.formUrlEncoded).length) { bru += `\n${indentString( disabled(body.formUrlEncoded) - .map((item) => `~${item.name}: ${item.value}`) + .map((item) => `~${item.name}: ${encodeURIComponent(item.value)}`) .join('\n') )}`; } From b00d35f007243be0f4da84e5b31b0708dadeebc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Do=C4=9Fukan=20=C3=9Crker?= Date: Fri, 20 Oct 2023 00:22:19 +0300 Subject: [PATCH 07/59] Turkish translation for ReadMe page. --- readme.md | 2 +- readme_ru.md | 2 +- readme_tr.md | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++ readme_ua.md | 2 +- 4 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 readme_tr.md diff --git a/readme.md b/readme.md index cf3d2dc61..33755c2c0 100644 --- a/readme.md +++ b/readme.md @@ -10,7 +10,7 @@ [![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com) [![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads) -**English** | [Українська](/readme_ua.md) | [Русский](/readme_ru.md) +**English** | [Українська](/readme_ua.md) | [Русский](/readme_ru.md) | [Türkçe](/readme_tr.md) Bruno is a new and innovative API client, aimed at revolutionizing the status quo represented by Postman and similar tools out there. diff --git a/readme_ru.md b/readme_ru.md index d8b8255f7..207c399bb 100644 --- a/readme_ru.md +++ b/readme_ru.md @@ -10,7 +10,7 @@ [![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com) [![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads) -[English](/readme.md) | [Українська](/readme_ua.md) | **Русский** +[English](/readme.md) | [Українська](/readme_ua.md) | **Русский** | [Türkçe](/readme_tr.md) Bruno - новый и инновационный клиент API, направленный на революцию в установившейся ситуации, представленной Postman и подобными инструментами. diff --git a/readme_tr.md b/readme_tr.md new file mode 100644 index 000000000..7e1a046fa --- /dev/null +++ b/readme_tr.md @@ -0,0 +1,80 @@ +
+ + +### Bruno - API'leri keşfetmek ve test etmek için açık kaynaklı IDE. + +[![GitHub sürümü](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno) +[![CI](https://github.com/usebruno/bruno/actions/workflows/unit-tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/workflows/unit-tests.yml) +[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse) +[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno) +[![Web Sitesi](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com) +[![İndir](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads) + +[English](/readme.md) | [Українська](/readme_ua.md) | [Русский](/readme_ru.md) | **Türkçe** + +Bruno, Postman ve benzeri araçlar tarafından temsil edilen statükoda devrim yaratmayı amaçlayan yeni ve yenilikçi bir API istemcisidir. + +Bruno koleksiyonlarınızı doğrudan dosya sisteminizdeki bir klasörde saklar. API istekleri hakkındaki bilgileri kaydetmek için düz bir metin biçimlendirme dili olan Bru kullanıyoruz. + +API koleksiyonlarınız üzerinde işbirliği yapmak için git veya seçtiğiniz herhangi bir sürüm kontrolünü kullanabilirsiniz. + +Bruno yalnızca çevrimdışıdır. Bruno'ya bulut senkronizasyonu eklemek gibi bir planımız yok. Veri gizliliğinize değer veriyoruz ve cihazınızda kalması gerektiğine inanıyoruz. Uzun vadeli vizyonumuzu okuyun [burada](https://github.com/usebruno/bruno/discussions/269) + +![bruno](assets/images/landing-2.png)

+ +### Birden fazla platformda çalıştırın 🖥️ + +![bruno](assets/images/run-anywhere.png)

+ +### Git üzerinden işbirliği yapın 👩‍💻🧑‍💻 + +Veya seçtiğiniz herhangi bir sürüm kontrol sistemi + +![bruno](assets/images/version-control.png)

+ +### Önemli Bağlantılar 📌 + +- [Uzun Vadeli Vizyonumuz](https://github.com/usebruno/bruno/discussions/269) +- [Yol Haritası](https://github.com/usebruno/bruno/discussions/384) +- [Dokümantasyon](https://docs.usebruno.com) +- [Web sitesi](https://www.usebruno.com) +- [İndir](https://www.usebruno.com/downloads) + +### Vitrin 🎥 + +- [Görüşler](https://github.com/usebruno/bruno/discussions/343) +- [Bilgi Merkezi](https://github.com/usebruno/bruno/discussions/386) +- [Scriptmania](https://github.com/usebruno/bruno/discussions/385) + +### Destek ❤️ + +Woof! Projeyi beğendiyseniz, şu ⭐ düğmesine basın! + +### Referansları Paylaşın 📣 + +Bruno işinizde ve ekiplerinizde size yardımcı olduysa, lütfen [github tartışmamızdaki referanslarınızı](https://github.com/usebruno/bruno/discussions/343) paylaşmayı unutmayın + +### Katkıda Bulunun 👩‍💻🧑‍💻 + +Bruno'yu geliştirmek istemenize sevindim. Lütfen [katkıda bulunma kılavuzu](contributing.md)'na göz atın + +Kod yoluyla katkıda bulunamasanız bile, lütfen kullanım durumunuzu çözmek için uygulanması gereken hataları ve özellik isteklerini bildirmekten çekinmeyin. + +### Katkıda Bulunanlar + + + +### İletişimde Kalın 🌐 + +[Twitter](https://twitter.com/use_bruno)
+[Website](https://www.usebruno.com)
+[Discord](https://discord.com/invite/KgcZUncpjq) +[LinkedIn](https://www.linkedin.com/company/usebruno) + +### Lisans 📄 + +[MIT](license.md) diff --git a/readme_ua.md b/readme_ua.md index 793e11a8e..77a280543 100644 --- a/readme_ua.md +++ b/readme_ua.md @@ -10,7 +10,7 @@ [![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com) [![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads) -[English](/readme.md) | **Українська** | [Русский](/readme_ru.md) +[English](/readme.md) | **Українська** | [Русский](/readme_ru.md) | [Türkçe](/readme_tr.md) Bruno це новий та іноваційний API клієнт, націлений на революційну зміну статус кво, запровадженого інструментами на кшталт Postman. From 89fbb42512bb5e0bdc64b4ffa152d2ba82b9a92d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Do=C4=9Fukan=20=C3=9Crker?= Date: Fri, 20 Oct 2023 00:23:13 +0300 Subject: [PATCH 08/59] Turkish translation for contributing.md. --- contributing.md | 2 +- contributing_ru.md | 2 +- contributing_tr.md | 37 +++++++++++++++++++++++++++++++++++++ contributing_ua.md | 2 +- 4 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 contributing_tr.md diff --git a/contributing.md b/contributing.md index 966a6134b..6020d4c32 100644 --- a/contributing.md +++ b/contributing.md @@ -1,4 +1,4 @@ -**English** | [Українська](/contributing_ua.md) | [Русский](/contributing_ru.md) +**English** | [Українська](/contributing_ua.md) | [Русский](/contributing_ru.md) | [Türkçe](/contributing_tr.md) ## Lets make bruno better, together !! diff --git a/contributing_ru.md b/contributing_ru.md index 6636004ba..fd310a244 100644 --- a/contributing_ru.md +++ b/contributing_ru.md @@ -1,4 +1,4 @@ -[English](/contributing.md) | [Українська](/contributing_ua.md) | **Русский** +[English](/contributing.md) | [Українська](/contributing_ua.md) | **Русский** | [Türkçe](/contributing_tr.md) ## Давайте вместе сделаем Бруно лучше!!! diff --git a/contributing_tr.md b/contributing_tr.md new file mode 100644 index 000000000..f67e1a27b --- /dev/null +++ b/contributing_tr.md @@ -0,0 +1,37 @@ +[English](/readme.md) | [Українська](/contributing_ua.md) | [Русский](/contributing_ru.md) | **Türkçe** + +## Bruno'yu birlikte daha iyi hale getirelim !! + +Bruno'yu geliştirmek istemenizden mutluluk duyuyorum. Aşağıda, bruno'yu bilgisayarınıza getirmeye başlamak için yönergeler bulunmaktadır. + +### Kullanılan Teknolojiler + +Bruno, NextJs ve React kullanılarak oluşturulmuştur. Ayrıca bir masaüstü sürümü (yerel koleksiyonları destekleyen) göndermek için electron kullanıyoruz + +Kullandığımız kütüphaneler + +- CSS - Tailwind +- Kod Düzenleyiciler - Codemirror +- Durum Yönetimi - Redux +- Iconlar - Tabler Simgeleri +- Formlar - formik +- Şema Doğrulama - Yup +- İstek İstemcisi - axios +- Dosya Sistemi İzleyicisi - chokidar + +### Bağımlılıklar + +[Node v18.x veya en son LTS sürümüne](https://nodejs.org/en/) ve npm 8.x'e ihtiyacınız olacaktır. Projede npm çalışma alanlarını kullanıyoruz + +### Kodlamaya başlayalım + +Yerel geliştirme ortamının çalıştırılmasına ilişkin talimatlar için lütfen [development.md](docs/development.md) adresine başvurun. + +### Pull Request Oluşturma + +- Lütfen PR'ları küçük tutun ve tek bir şeye odaklanın +- Lütfen şube oluşturma formatını takip edin + - feature/[özellik adı]: Bu dal belirli bir özellik için değişiklikler içermelidir + - Örnek: feature/dark-mode + - bugfix/[hata adı]: Bu dal yalnızca belirli bir hata için hata düzeltmelerini içermelidir + - Örnek bugfix/bug-1 diff --git a/contributing_ua.md b/contributing_ua.md index 75760f565..a7fff3cfe 100644 --- a/contributing_ua.md +++ b/contributing_ua.md @@ -1,4 +1,4 @@ -[English](/contributing.md) | **Українська** | [Русский](/contributing_ru.md) +[English](/contributing.md) | **Українська** | [Русский](/contributing_ru.md) | [Türkçe](/contributing_tr.md) ## Давайте зробимо Bruno краще, разом !! From 16cf654e57f17f17955c1bce49937006fc5cab59 Mon Sep 17 00:00:00 2001 From: Jan Monschke Date: Fri, 20 Oct 2023 10:59:00 +0200 Subject: [PATCH 09/59] fix: overflowing code editor --- packages/bruno-app/src/components/CodeEditor/StyledWrapper.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/bruno-app/src/components/CodeEditor/StyledWrapper.js b/packages/bruno-app/src/components/CodeEditor/StyledWrapper.js index fe0f4cc19..3623d406d 100644 --- a/packages/bruno-app/src/components/CodeEditor/StyledWrapper.js +++ b/packages/bruno-app/src/components/CodeEditor/StyledWrapper.js @@ -5,6 +5,7 @@ const StyledWrapper = styled.div` background: ${(props) => props.theme.codemirror.bg}; border: solid 1px ${(props) => props.theme.codemirror.border}; font-family: ${(props) => (props.font ? props.font : 'default')}; + line-break: anywhere; } .CodeMirror-overlayscroll-horizontal div, From 55c52971d6c34fc6abd7839ac4f5fc036916a04a Mon Sep 17 00:00:00 2001 From: Martin Sefcik Date: Fri, 20 Oct 2023 14:34:04 +0200 Subject: [PATCH 10/59] bug(#699): fixed crashing app on clicking on request result in runner --- .../src/components/ResponsePane/QueryResult/index.js | 4 ++-- packages/bruno-app/src/components/ResponsePane/index.js | 1 + .../src/components/RunnerResults/ResponsePane/index.js | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js index ba8696a14..18b9ec2c6 100644 --- a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js +++ b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js @@ -35,7 +35,7 @@ const formatResponse = (data, mode) => { return safeStringifyJSON(data); }; -const QueryResult = ({ item, collection, data, width, disableRunEventListener, headers, error }) => { +const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEventListener, headers, error }) => { const contentType = getContentType(headers); const mode = getCodeMirrorModeBasedOnContentType(contentType); const formattedData = formatResponse(data, mode); @@ -90,7 +90,7 @@ const QueryResult = ({ item, collection, data, width, disableRunEventListener, h { collection={collection} width={rightPaneWidth} data={response.data} + dataBuffer={response.dataBuffer} headers={response.headers} error={response.error} key={item.filename} diff --git a/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js b/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js index 6526c7454..aeba867f4 100644 --- a/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js +++ b/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js @@ -34,6 +34,7 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => { width={rightPaneWidth} disableRunEventListener={true} data={responseReceived.data} + dataBuffer={responseReceived.dataBuffer} headers={responseReceived.headers} key={item.filename} /> From c2bdddaa5934ba25a1cdf5d23293cdbe5194f325 Mon Sep 17 00:00:00 2001 From: Chris Wilper Date: Fri, 20 Oct 2023 09:51:40 -0400 Subject: [PATCH 11/59] chore: consolidate prerequest-related code --- .../bruno-electron/src/ipc/network/index.js | 235 +++++++----------- .../src/ipc/network/prepare-request.js | 9 + 2 files changed, 105 insertions(+), 139 deletions(-) diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index f092e9ee9..5b0ba6582 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -189,6 +189,81 @@ const parseDataFromResponse = (response) => { }; const registerNetworkIpc = (mainWindow) => { + const onConsoleLog = (type, args) => { + console[type](...args); + + mainWindow.webContents.send('main:console-log', { + type, + args + }); + }; + + const runPreRequest = async ( + request, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig + ) => { + // run pre-request vars + const preRequestVars = get(request, 'vars.req', []); + if (preRequestVars?.length) { + const varsRuntime = new VarsRuntime(); + const result = varsRuntime.runPreRequestVars( + preRequestVars, + request, + envVars, + collectionVariables, + collectionPath, + processEnvVars + ); + + if (result) { + mainWindow.webContents.send('main:script-environment-update', { + envVariables: result.envVariables, + collectionVariables: result.collectionVariables, + requestUid, + collectionUid + }); + } + } + + // run pre-request script + const requestScript = compact([get(collectionRoot, 'request.script.req'), get(request, 'script.req')]).join(os.EOL); + if (requestScript?.length) { + const scriptRuntime = new ScriptRuntime(); + const result = await scriptRuntime.runRequestScript( + decomment(requestScript), + request, + envVars, + collectionVariables, + collectionPath, + onConsoleLog, + processEnvVars, + scriptingConfig + ); + + mainWindow.webContents.send('main:script-environment-update', { + envVariables: result.envVariables, + collectionVariables: result.collectionVariables, + requestUid, + collectionUid + }); + } + + // interpolate variables inside request + interpolateVars(request, envVars, collectionVariables, processEnvVars); + + // stringify the request url encoded params + if (request.headers['content-type'] === 'application/x-www-form-urlencoded') { + request.data = qs.stringify(request.data); + } + }; + // handler for sending http request ipcMain.handle('send-http-request', async (event, item, collection, environment, collectionVariables) => { const collectionUid = collection.uid; @@ -196,15 +271,6 @@ const registerNetworkIpc = (mainWindow) => { const cancelTokenUid = uuid(); const requestUid = uuid(); - const onConsoleLog = (type, args) => { - console[type](...args); - - mainWindow.webContents.send('main:console-log', { - type, - args - }); - }; - mainWindow.webContents.send('main:run-request-event', { type: 'request-queued', requestUid, @@ -222,75 +288,21 @@ const registerNetworkIpc = (mainWindow) => { const scriptingConfig = get(brunoConfig, 'scripts', {}); try { - // make axios work in node using form data - // reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427 - if (request.headers && request.headers['content-type'] === 'multipart/form-data') { - const form = new FormData(); - forOwn(request.data, (value, key) => { - form.append(key, value); - }); - extend(request.headers, form.getHeaders()); - request.data = form; - } - const cancelToken = axios.CancelToken.source(); request.cancelToken = cancelToken.token; saveCancelToken(cancelTokenUid, cancelToken); - // run pre-request vars - const preRequestVars = get(request, 'vars.req', []); - if (preRequestVars?.length) { - const varsRuntime = new VarsRuntime(); - const result = varsRuntime.runPreRequestVars( - preRequestVars, - request, - envVars, - collectionVariables, - collectionPath, - processEnvVars - ); - - if (result) { - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - requestUid, - collectionUid - }); - } - } - - // run pre-request script - const requestScript = compact([get(collectionRoot, 'request.script.req'), get(request, 'script.req')]).join( - os.EOL + await runPreRequest( + request, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig ); - if (requestScript?.length) { - const scriptRuntime = new ScriptRuntime(); - const result = await scriptRuntime.runRequestScript( - decomment(requestScript), - request, - envVars, - collectionVariables, - collectionPath, - onConsoleLog, - processEnvVars, - scriptingConfig - ); - - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - requestUid, - collectionUid - }); - } - - interpolateVars(request, envVars, collectionVariables, processEnvVars); - - // stringify the request url encoded params - if (request.headers['content-type'] === 'application/x-www-form-urlencoded') { - request.data = qs.stringify(request.data); - } // todo: // i have no clue why electron can't send the request object @@ -544,15 +556,6 @@ const registerNetworkIpc = (mainWindow) => { const scriptingConfig = get(brunoConfig, 'scripts', {}); const collectionRoot = get(collection, 'root', {}); - const onConsoleLog = (type, args) => { - console[type](...args); - - mainWindow.webContents.send('main:console-log', { - type, - args - }); - }; - if (!folder) { folder = collection; } @@ -602,67 +605,21 @@ const registerNetworkIpc = (mainWindow) => { const _request = item.draft ? item.draft.request : item.request; const request = prepareRequest(_request, collectionRoot); + const requestUid = uuid(); const processEnvVars = getProcessEnvVars(collectionUid); try { - // make axios work in node using form data - // reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427 - if (request.headers && request.headers['content-type'] === 'multipart/form-data') { - const form = new FormData(); - forOwn(request.data, (value, key) => { - form.append(key, value); - }); - extend(request.headers, form.getHeaders()); - request.data = form; - } - - // run pre-request vars - const preRequestVars = get(request, 'vars.req', []); - if (preRequestVars && preRequestVars.length) { - const varsRuntime = new VarsRuntime(); - const result = varsRuntime.runPreRequestVars( - preRequestVars, - request, - envVars, - collectionVariables, - collectionPath - ); - - if (result) { - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - collectionUid - }); - } - } - - // run pre-request script - const requestScript = compact([get(collectionRoot, 'request.script.req'), get(request, 'script.req')]).join( - os.EOL + await runPreRequest( + request, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig ); - if (requestScript?.length) { - const scriptRuntime = new ScriptRuntime(); - const result = await scriptRuntime.runRequestScript( - decomment(requestScript), - request, - envVars, - collectionVariables, - collectionPath, - onConsoleLog, - processEnvVars, - scriptingConfig - ); - - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - collectionUid - }); - } - - // interpolate variables inside request - interpolateVars(request, envVars, collectionVariables, processEnvVars); // todo: // i have no clue why electron can't send the request object diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js index 6c2d7d4b3..6e3f123c1 100644 --- a/packages/bruno-electron/src/ipc/network/prepare-request.js +++ b/packages/bruno-electron/src/ipc/network/prepare-request.js @@ -137,6 +137,15 @@ const prepareRequest = (request, collectionRoot) => { each(enabledParams, (p) => (params[p.name] = p.value)); axiosRequest.headers['content-type'] = 'multipart/form-data'; axiosRequest.data = params; + + // make axios work in node using form data + // reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427 + const form = new FormData(); + forOwn(axiosRequest.data, (value, key) => { + form.append(key, value); + }); + extend(axiosRequest.headers, form.getHeaders()); + axiosRequest.data = form; } if (request.body.mode === 'graphql') { From 846ac66ca365b4d8a4b4ae4d7dcc9c3660872572 Mon Sep 17 00:00:00 2001 From: Chris Wilper Date: Fri, 20 Oct 2023 10:56:50 -0400 Subject: [PATCH 12/59] feat: run prerequest code before gql schema fetch --- .../bruno-electron/src/ipc/network/index.js | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 5b0ba6582..01d1658e4 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -514,8 +514,25 @@ const registerNetworkIpc = (mainWindow) => { }); } - const processEnvVars = getProcessEnvVars(collection.uid); - interpolateVars(preparedRequest, envVars, collection.collectionVariables, processEnvVars); + const requestUid = uuid(); + const collectionPath = collection.pathname; + const collectionUid = collection.uid; + const collectionVariables = collection.collectionVariables; + const processEnvVars = getProcessEnvVars(collectionUid); + const brunoConfig = getBrunoConfig(collection.uid); + const scriptingConfig = get(brunoConfig, 'scripts', {}); + + await runPreRequest( + request, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig + ); const axiosInstance = await configureRequest( collection.uid, From 7e8073cbdbf4ccdb806b5dedd55334103bde7a31 Mon Sep 17 00:00:00 2001 From: Chris Wilper Date: Fri, 20 Oct 2023 11:49:12 -0400 Subject: [PATCH 13/59] chore: consolidate postresponse-related code --- .../bruno-electron/src/ipc/network/index.js | 182 +++++++++--------- 1 file changed, 86 insertions(+), 96 deletions(-) diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 01d1658e4..4dca5ae01 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -264,6 +264,69 @@ const registerNetworkIpc = (mainWindow) => { } }; + const runPostResponse = async ( + request, + response, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig + ) => { + // run post-response vars + const postResponseVars = get(request, 'vars.res', []); + if (postResponseVars?.length) { + const varsRuntime = new VarsRuntime(); + const result = varsRuntime.runPostResponseVars( + postResponseVars, + request, + response, + envVars, + collectionVariables, + collectionPath, + processEnvVars + ); + + if (result) { + mainWindow.webContents.send('main:script-environment-update', { + envVariables: result.envVariables, + collectionVariables: result.collectionVariables, + requestUid, + collectionUid + }); + } + } + + // run post-response script + const responseScript = compact([get(collectionRoot, 'request.script.res'), get(request, 'script.res')]).join( + os.EOL + ); + if (responseScript?.length) { + const scriptRuntime = new ScriptRuntime(); + const result = await scriptRuntime.runResponseScript( + decomment(responseScript), + request, + response, + envVars, + collectionVariables, + collectionPath, + onConsoleLog, + processEnvVars, + scriptingConfig + ); + + mainWindow.webContents.send('main:script-environment-update', { + envVariables: result.envVariables, + collectionVariables: result.collectionVariables, + requestUid, + collectionUid + }); + } + }; + // handler for sending http request ipcMain.handle('send-http-request', async (event, item, collection, environment, collectionVariables) => { const collectionUid = collection.uid; @@ -365,55 +428,18 @@ const registerNetworkIpc = (mainWindow) => { const { data, dataBuffer } = parseDataFromResponse(response); response.data = data; - // run post-response vars - const postResponseVars = get(request, 'vars.res', []); - if (postResponseVars?.length) { - const varsRuntime = new VarsRuntime(); - const result = varsRuntime.runPostResponseVars( - postResponseVars, - request, - response, - envVars, - collectionVariables, - collectionPath, - processEnvVars - ); - - if (result) { - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - requestUid, - collectionUid - }); - } - } - - // run post-response script - const responseScript = compact([get(collectionRoot, 'request.script.res'), get(request, 'script.res')]).join( - os.EOL + await runPostResponse( + request, + response, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig ); - if (responseScript?.length) { - const scriptRuntime = new ScriptRuntime(); - const result = await scriptRuntime.runResponseScript( - decomment(responseScript), - request, - response, - envVars, - collectionVariables, - collectionPath, - onConsoleLog, - processEnvVars, - scriptingConfig - ); - - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - requestUid, - collectionUid - }); - } // run assertions const assertions = get(request, 'assertions'); @@ -712,54 +738,18 @@ const registerNetworkIpc = (mainWindow) => { } } - // run post-response vars - const postResponseVars = get(request, 'vars.res', []); - if (postResponseVars?.length) { - const varsRuntime = new VarsRuntime(); - const result = varsRuntime.runPostResponseVars( - postResponseVars, - request, - response, - envVars, - collectionVariables, - collectionPath, - processEnvVars - ); - - if (result) { - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - collectionUid - }); - } - } - - // run response script - const responseScript = compact([ - get(collectionRoot, 'request.script.res'), - get(request, 'script.res') - ]).join(os.EOL); - if (responseScript && responseScript.length) { - const scriptRuntime = new ScriptRuntime(); - const result = await scriptRuntime.runResponseScript( - decomment(responseScript), - request, - response, - envVars, - collectionVariables, - collectionPath, - onConsoleLog, - processEnvVars, - scriptingConfig - ); - - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - collectionUid - }); - } + await runPostResponse( + request, + response, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig + ); // run assertions const assertions = get(item, 'request.assertions'); From a6ddf12ee117376b7f6019951750a5ff648caef3 Mon Sep 17 00:00:00 2001 From: Chris Wilper Date: Fri, 20 Oct 2023 12:13:35 -0400 Subject: [PATCH 14/59] feat: run postrequest code after gql schema fetch --- packages/bruno-electron/src/ipc/network/index.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 4dca5ae01..52922516b 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -569,6 +569,19 @@ const registerNetworkIpc = (mainWindow) => { ); const response = await axiosInstance(preparedRequest); + await runPostResponse( + request, + response, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig + ); + return { status: response.status, statusText: response.statusText, From bbb904437fa74bf5b2cf6e9e78b6755705f0e0a6 Mon Sep 17 00:00:00 2001 From: Chris Wilper Date: Sat, 21 Oct 2023 14:32:41 -0400 Subject: [PATCH 15/59] chore: re-sync proxy-util.js copies --- packages/bruno-cli/src/utils/proxy-util.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bruno-cli/src/utils/proxy-util.js b/packages/bruno-cli/src/utils/proxy-util.js index dd526d0d7..a9fdc1b9a 100644 --- a/packages/bruno-cli/src/utils/proxy-util.js +++ b/packages/bruno-cli/src/utils/proxy-util.js @@ -1,4 +1,5 @@ const parseUrl = require('url').parse; +const { isEmpty } = require('lodash'); const DEFAULT_PORTS = { ftp: 21, @@ -9,7 +10,7 @@ const DEFAULT_PORTS = { wss: 443 }; /** - * check for proxy bypass, Copied form 'proxy-from-env' + * check for proxy bypass, copied form 'proxy-from-env' */ const shouldUseProxy = (url, proxyBypass) => { if (proxyBypass === '*') { @@ -39,7 +40,6 @@ const shouldUseProxy = (url, proxyBypass) => { if (!dontProxyFor) { return true; // Skip zero-length hosts. } - const parsedProxy = dontProxyFor.match(/^(.+):(\d+)$/); let parsedProxyHostname = parsedProxy ? parsedProxy[1] : dontProxyFor; const parsedProxyPort = parsedProxy ? parseInt(parsedProxy[2]) : 0; From 1d58bdab59148d24734f0e73d8849a159528b3ba Mon Sep 17 00:00:00 2001 From: Chris Wilper Date: Sun, 22 Oct 2023 11:02:35 -0400 Subject: [PATCH 16/59] fix: respect rejectUnauthorized and ca opts when proxying https --- .../src/runner/run-single-request.js | 5 ++--- packages/bruno-cli/src/utils/proxy-util.js | 21 ++++++++++++++++++- .../bruno-electron/src/ipc/network/index.js | 5 ++--- .../bruno-electron/src/utils/proxy-util.js | 21 ++++++++++++++++++- 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js index bd440d62e..e047969f6 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -12,11 +12,10 @@ const { ScriptRuntime, TestRuntime, VarsRuntime, AssertRuntime } = require('@use const { stripExtension } = require('../utils/filesystem'); const { getOptions } = require('../utils/bru'); const https = require('https'); -const { HttpsProxyAgent } = require('https-proxy-agent'); const { HttpProxyAgent } = require('http-proxy-agent'); const { SocksProxyAgent } = require('socks-proxy-agent'); const { makeAxiosInstance } = require('../utils/axios-instance'); -const { shouldUseProxy } = require('../utils/proxy-util'); +const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../utils/proxy-util'); const runSingleRequest = async function ( filename, @@ -152,7 +151,7 @@ const runSingleRequest = async function ( request.httpsAgent = socksProxyAgent; request.httpAgent = socksProxyAgent; } else { - request.httpsAgent = new HttpsProxyAgent( + request.httpsAgent = new PatchedHttpsProxyAgent( proxyUri, Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined ); diff --git a/packages/bruno-cli/src/utils/proxy-util.js b/packages/bruno-cli/src/utils/proxy-util.js index a9fdc1b9a..729e03356 100644 --- a/packages/bruno-cli/src/utils/proxy-util.js +++ b/packages/bruno-cli/src/utils/proxy-util.js @@ -1,5 +1,6 @@ const parseUrl = require('url').parse; const { isEmpty } = require('lodash'); +const { HttpsProxyAgent } = require('https-proxy-agent'); const DEFAULT_PORTS = { ftp: 21, @@ -61,6 +62,24 @@ const shouldUseProxy = (url, proxyBypass) => { }); }; +/** + * Patched version of HttpsProxyAgent to get around a bug that ignores + * options like ca and rejectUnauthorized when upgrading the socket to TLS: + * https://github.com/TooTallNate/proxy-agents/issues/194 + */ +class PatchedHttpsProxyAgent extends HttpsProxyAgent { + constructor(proxy, opts) { + super(proxy, opts); + this.constructorOpts = opts; + } + + async connect(req, opts) { + const combinedOpts = { ...this.constructorOpts, ...opts }; + return super.connect(req, combinedOpts); + } +} + module.exports = { - shouldUseProxy + shouldUseProxy, + PatchedHttpsProxyAgent }; diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index f092e9ee9..7e24887e6 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -19,12 +19,11 @@ const { sortFolder, getAllRequestsInFolderRecursively } = require('./helper'); const { preferencesUtil } = require('../../store/preferences'); const { getProcessEnvVars } = require('../../store/process-env'); const { getBrunoConfig } = require('../../store/bruno-config'); -const { HttpsProxyAgent } = require('https-proxy-agent'); const { HttpProxyAgent } = require('http-proxy-agent'); const { SocksProxyAgent } = require('socks-proxy-agent'); const { makeAxiosInstance } = require('./axios-instance'); const { addAwsV4Interceptor, resolveAwsV4Credentials } = require('./awsv4auth-helper'); -const { shouldUseProxy } = require('../../utils/proxy-util'); +const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../../utils/proxy-util'); // override the default escape function to prevent escaping Mustache.escape = function (value) { @@ -149,7 +148,7 @@ const configureRequest = async (collectionUid, request, envVars, collectionVaria request.httpsAgent = socksProxyAgent; request.httpAgent = socksProxyAgent; } else { - request.httpsAgent = new HttpsProxyAgent( + request.httpsAgent = new PatchedHttpsProxyAgent( proxyUri, Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined ); diff --git a/packages/bruno-electron/src/utils/proxy-util.js b/packages/bruno-electron/src/utils/proxy-util.js index a9fdc1b9a..ef64d37ad 100644 --- a/packages/bruno-electron/src/utils/proxy-util.js +++ b/packages/bruno-electron/src/utils/proxy-util.js @@ -1,5 +1,6 @@ const parseUrl = require('url').parse; const { isEmpty } = require('lodash'); +const { HttpsProxyAgent } = require('https-proxy-agent'); const DEFAULT_PORTS = { ftp: 21, @@ -61,6 +62,24 @@ const shouldUseProxy = (url, proxyBypass) => { }); }; +/** + * Patched version of HttpsProxyAgent to get around a bug that ignores options + * such as ca and rejectUnauthorized when upgrading the proxied socket to TLS: + * https://github.com/TooTallNate/proxy-agents/issues/194 + */ +class PatchedHttpsProxyAgent extends HttpsProxyAgent { + constructor(proxy, opts) { + super(proxy, opts); + this.constructorOpts = opts; + } + + async connect(req, opts) { + const combinedOpts = { ...this.constructorOpts, ...opts }; + return super.connect(req, combinedOpts); + } +} + module.exports = { - shouldUseProxy + shouldUseProxy, + PatchedHttpsProxyAgent }; From 296e067222bd7c5add5ba5b35a20cd2e983b9a07 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 23 Oct 2023 01:37:30 +0530 Subject: [PATCH 17/59] chore(#684): renamed use to enabled in collection proxy config --- .../CollectionSettings/ProxySettings/index.js | 32 +++++++++---------- .../Sidebar/ImportCollection/index.js | 2 +- .../bruno-electron/src/ipc/network/index.js | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js b/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js index fd3cc8986..2ed25b1e5 100644 --- a/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js @@ -7,17 +7,17 @@ import toast from 'react-hot-toast'; const ProxySettings = ({ proxyConfig, onUpdate }) => { const proxySchema = Yup.object({ - use: Yup.string().oneOf(['global', 'true', 'false']), + enabled: Yup.string().oneOf(['global', 'true', 'false']), protocol: Yup.string().oneOf(['http', 'https', 'socks4', 'socks5']), hostname: Yup.string() - .when('use', { + .when('enabled', { is: true, then: (hostname) => hostname.required('Specify the hostname for your proxy.'), otherwise: (hostname) => hostname.nullable() }) .max(1024), port: Yup.number() - .when('use', { + .when('enabled', { is: true, then: (port) => port.required('Specify port between 1 and 65535').typeError('Specify port between 1 and 65535'), otherwise: (port) => port.nullable().transform((_, val) => (val ? Number(val) : null)) @@ -49,7 +49,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { const formik = useFormik({ initialValues: { - use: proxyConfig.use || 'global', + enabled: proxyConfig.enabled || 'global', protocol: proxyConfig.protocol || 'http', hostname: proxyConfig.hostname || '', port: proxyConfig.port || '', @@ -65,11 +65,11 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { proxySchema .validate(values, { abortEarly: true }) .then((validatedProxy) => { - // serialize 'use' to boolean - if (validatedProxy.use === 'true') { - validatedProxy.use = true; - } else if (validatedProxy.use === 'false') { - validatedProxy.use = false; + // serialize 'enabled' to boolean + if (validatedProxy.enabled === 'true') { + validatedProxy.enabled = true; + } else if (validatedProxy.enabled === 'false') { + validatedProxy.enabled = false; } onUpdate(validatedProxy); @@ -83,7 +83,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { useEffect(() => { formik.setValues({ - use: proxyConfig.use === true ? 'true' : proxyConfig.use === false ? 'false' : 'global', + enabled: proxyConfig.enabled === true ? 'true' : proxyConfig.enabled === false ? 'false' : 'global', protocol: proxyConfig.protocol || 'http', hostname: proxyConfig.hostname || '', port: proxyConfig.port || '', @@ -120,9 +120,9 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {