Compare commits

...

45 Commits

Author SHA1 Message Date
Anoop M D
a2695ff2bf Revert "fix ignore folders (#1929)"
This reverts commit acca7984a4.
2024-04-10 02:33:54 +05:30
Lukáš Linhart
acca7984a4 fix ignore folders (#1929)
Co-authored-by: Linhart Lukáš <Lukas.Linhart@tescosw.cz>
2024-04-10 02:29:14 +05:30
Timon
0cce14b4d5 fix: Collection variable viewer (#1947)
Due to changes in https://github.com/usebruno/bruno/pull/650
collection variables would be passed as a object but was exptected
to be an array. Collection variables are now converted to an array.
2024-04-10 02:21:18 +05:30
Gustavo Kath
179c30b15b fix(#1920): Ignore redirect abortion emmited by loadUrl (#1959) 2024-04-10 02:19:37 +05:30
Jean-Baptiste
5a31daf84a Bump checkout to v4 (#1960) 2024-04-10 02:18:15 +05:30
David Saunders
920c490548 🐛 Replace jsonBigint with lossless-json (#2006) 2024-04-10 02:15:44 +05:30
Pushpender Saini
484f6ef0c1 Fix content-type in client_credentials oauth flow (#2039) 2024-04-10 02:07:57 +05:30
Lallu Anthoor
e7ff0ba5a8 fix: resolve build issues (#2041)
* fix: resolve prettier issues

* fix: allow checks write access for CLI test

* fix: use node 18 in workflows
2024-04-10 01:51:11 +05:30
Marty
615ed0c584 conditionally accesses properties (#2047) 2024-04-10 01:48:44 +05:30
Rinku Chaudhari
0173d8812d feat: save unsaved request changes before renaming (#2018)
* feat: show unsaved changes alert while renaming request

* fix: save unsaved request changes silently instead of showing dialog

* fix: save unsaved request changes before renaming
2024-04-10 01:43:30 +05:30
Joel Wetzell
65dbf9c8ba fix population of data when data-urlencode flag is used with G flag in curl import (#2025) 2024-04-09 15:54:44 +05:30
Lallu Anthoor
67df18e6e0 fix: resolve typos in issue templates and config (#2027) 2024-04-09 15:49:59 +05:30
Anoop M D
aa5fee01c3 feat(#2030): ux touches for collection settings descriptions 2024-04-09 15:47:12 +05:30
Liz MacLean
84128052c0 feat: Add descriptions for each panel in CollectionSettings (#2031)
* Add descriptions for each panel in CollectionSettings

* Revert index.js

Leftover change from when i was putzing around
2024-04-09 15:26:56 +05:30
Anoop M D
fbee23329d fix(#1810): handled error case responseTime 2024-04-09 15:05:08 +05:30
RJ17799
5b2ca0ea03 fix(#1145): fix res.responseTime in runner (#1810) 2024-04-09 02:15:57 +05:30
Yaten Dhingra
cf08118458 created yaml issue templates (#1426)
* created yaml issue templates
* fixed errors and typos in the issue templates
2024-04-06 06:11:37 +05:30
j-lebek
17dac6be67 Fix typo in synopsis (#1787) 2024-04-06 06:10:06 +05:30
ccoVeille
64b90b4cc3 fix typos and french documentation (#1965)
* chore: fix typos in code

* chore: GitHub is a trademark

Github => GitHub

* chore: fix documentation in French
2024-04-06 06:08:50 +05:30
sirwoongke
2c83e98502 docs: Update readme_kr.md (#1969) 2024-04-06 06:07:13 +05:30
Joel Wetzell
ce5dd41267 chore: fix typo (#1848) 2024-04-06 06:05:34 +05:30
Sanjai Kumar
1349a79750 fix environment toggle bug (#1932) 2024-03-26 18:57:02 +05:30
Sanjai Kumar
c20beab0a0 feat(#1624): Implement opening cloned request automatically (#1930) 2024-03-26 15:26:19 +05:30
Anoop M D
5539cc1a2d chore: removed golden edition pre-order offer 2024-03-26 03:09:37 +05:30
Anoop M D
d20de4da0a chore: bumped version to 1.12.3 2024-03-26 00:11:42 +05:30
Anoop M D
50e3943fb1 chore: zuplo bronze sponsorship 2024-03-23 04:10:29 +05:30
Anoop M D
c357d3da4b chore: zuplo bronze sponsorship 2024-03-23 04:09:10 +05:30
Anoop M D
9b89f33103 chore: zuplo bronze sponsorship 2024-03-23 04:05:34 +05:30
Mateusz Pietryga
0f69c30a86 Fix: OAuth2 Authorization Request OPTIONAL parameters are required by bruno (#1797) (#1807) 2024-03-22 18:44:19 +05:30
Stefan
753ca4341f check oauth2 authorization code redirect for exact 'code' query parameter (#1777)
Co-authored-by: Stefan Grüttner <stefan.gruettner@deutschebahn.com>
2024-03-22 18:43:12 +05:30
Mateusz Pietryga
e278116356 Fix: OAuth2 Access Token request is sent as GET (#1795) (#1808) 2024-03-22 18:42:00 +05:30
Mateusz Pietryga
ae3c76a6c1 Fix OAuth2 code verifier too short (#1793) (#1809) 2024-03-22 18:39:50 +05:30
Baptiste Poulain
0b2b16abcc Fix/postman translation collection variables (#1894)
* fix(postman-translation-collectionVariables): auto translate pm.collectionVariables.set and get

* fix(postman-translation-collectionVariables) : additional translations, simplify regex, add testing

* fix(postman-translation-collectionVariables): update lock file

---------

Co-authored-by: bpoulaindev <bpoulainpro@gmail.com>
2024-03-22 18:37:30 +05:30
slowjoe007
8503752e09 Allow to keep the default truststore, when using a custom CA (#1863) 2024-03-22 18:35:42 +05:30
George Hopkins
9b7cdb2d48 feat: Add tooltips to sidebar footer (#1888) 2024-03-22 18:31:18 +05:30
Bobby
7741a3e4ee feat(#1839): Add Audio and Video Preview (#1840)
Any audio and video response can be now be previewed.
2024-03-22 18:29:39 +05:30
dw-0
82c600a0e6 feat: toggle visibility of secret envVars (#650)
* feat: toggle visibility of secret envVars

Signed-off-by: Dominik Willner <th33xitus@gmail.com>

* feat: also hide secrets in environment settings

Signed-off-by: Dominik Willner <th33xitus@gmail.com>

* style: run prettier

Signed-off-by: Dominik Willner <th33xitus@gmail.com>

* refactor: resolve conflict

Signed-off-by: Dominik Willner <th33xitus@gmail.com>

---------

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-03-22 18:24:16 +05:30
Anoop M D
f8ba781340 chore: version bump 2024-03-21 00:50:03 +05:30
Baptiste Poulain
f96f763f14 fix(enableTranslation): remove unused enableTranslation and useTranslation tokens (#1867)
Co-authored-by: bpoulaindev <bpoulainpro@gmail.com>
2024-03-20 18:45:27 +05:30
Afrian Junior
a546457e0f fix(ux): better text selection implementation on dark-mode (#1861) 2024-03-20 15:52:44 +05:30
Feldrise
2b0ad29b93 fix: system theme in dark mode (#1823) 2024-03-19 13:11:09 +05:30
Anoop M D
cdbb15f33e chore: bumped version to v1.12.0 2024-03-19 06:36:38 +05:30
Anoop M D
14911b4def chore: removed dependency on tailwind forms 2024-03-19 06:34:54 +05:30
Baptiste Poulain
410eecc884 feature(postman_tests_scripts): automatic tests and scripts translation from postman import (#1151)
* feature(postman_tests_scripts): automatic tests and scripts translation from postman import
---------

Co-authored-by: Baptiste POULAIN <baptistepoulain@MAC882.local>
Co-authored-by: bpoulaindev <bpoulainpro@gmail.com>
2024-03-13 18:40:31 +05:30
Anoop M D
2cd0e065bd chore: updated lib versions 2024-03-13 03:05:29 +05:30
76 changed files with 1083 additions and 629 deletions

34
.github/ISSUE_TEMPLATE/BugReport.yaml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: Bug Report
description: File a bug report
labels: ['bug']
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: checkboxes
attributes:
label: 'I have checked the following:'
options:
- label: I use the newest version of bruno.
required: true
- label: I've searched existing issues and found nothing related to my issue.
required: true
- type: textarea
attributes:
label: Describe the bug
description: A clear and concise description of the bug.
validations:
required: true
- type: textarea
attributes:
label: .bru file to reproduce the bug
description: Attach your .bru file here that can reqroduce the problem.
validations:
required: false
- type: textarea
attributes:
label: Screenshots/Live demo link
description: Add some screenshots to help explain the problem.
validations:
required: true

View File

@@ -0,0 +1,26 @@
name: Feature Request
description: Suggest an idea for this project.
labels: ['enhancement']
body:
- type: checkboxes
attributes:
label: 'I have checked the following:'
options:
- label: I've searched existing issues and found nothing related to my issue.
required: true
- type: markdown
attributes:
value: |
Suggest an idea for this project.
- type: textarea
attributes:
label: Describe the feature you want to add
description: A clear and concise description of the feature you want to be added.
validations:
required: true
- type: textarea
attributes:
label: Mockups or Images of the feature
description: Add some images to support your feature.
validations:
required: true

8
.github/ISSUE_TEMPLATE/config.yaml vendored Normal file
View File

@@ -0,0 +1,8 @@
blank_issues_enabled: true
contact_links:
- name: Discussions
url: https://github.com/usebruno/bruno/discussions
about: You can ask general questions or give feedback here.
- name: Discord Server
url: https://discord.com/invite/KgcZUncpjq
about: Join our Discord community to chat about Bruno.

View File

@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v3

View File

@@ -5,15 +5,6 @@ on:
pull_request:
branches: [main]
# Assign permissions for unit tests to be reported.
# See https://github.com/dorny/test-reporter/issues/168
permissions:
statuses: write
checks: write
contents: write
pull-requests: write
actions: write
jobs:
unit-test:
name: Unit Tests
@@ -21,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- name: Install dependencies
@@ -56,7 +47,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -75,19 +66,17 @@ jobs:
node ../../bruno-cli/bin/bru.js run --env Prod --output junit.xml --format junit
- name: Publish Test Report
uses: dorny/test-reporter@v1
if: success() || failure()
uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
with:
name: Test Report
path: packages/bruno-tests/collection/junit.xml
reporter: java-junit
files: packages/bruno-tests/collection/junit.xml
comment_mode: always
prettier:
name: Prettier
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- name: Install dependencies

1
.gitignore vendored
View File

@@ -1,6 +1,7 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
bun.lockb
node_modules
yarn.lock
pnpm-lock.yaml

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View File

@@ -56,7 +56,7 @@ npm run dev:electron
### Dépannage
Vous pourriez rencontrer une erreur `Unsupported platform` durant le lancement de `npm install`. Pour résoudre cela, veuillez supprimer le répertoire `node_modules` ainsi que le fichier `package-lock.json` et lancez à nouveau `npm install`. Cela devrait isntaller tous les paquets nécessaires pour lancer l'application.
Vous pourriez rencontrer une erreur `Unsupported platform` durant le lancement de `npm install`. Pour résoudre cela, veuillez supprimer le répertoire `node_modules` ainsi que le fichier `package-lock.json` et lancez à nouveau `npm install`. Cela devrait installer tous les paquets nécessaires pour lancer l'application.
```shell
# Efface les répertoires node_modules dans les sous-répertoires
@@ -85,6 +85,6 @@ npm test --workspace=packages/bruno-lang
- Merci de conserver les PR minimes et focalisées sur un seul objectif
- Merci de suivre le format de nom des branches :
- feature/[feature name]: Cette branche doit contenir une fonctionnalité spécifique
- Exemple: feature/dark-mode
- Exemple : feature/dark-mode
- bugfix/[bug name]: Cette branche doit contenir seulement une solution pour un bug spécifique
- Exemple: bugfix/bug-1
- Exemple : bugfix/bug-1

View File

@@ -2,6 +2,6 @@
### Publier Bruno dans un nouveau gestionnaire de paquets
Bien que notre code soit open source et disponible pour tout le monde, nous vous remercions de nous contacter avant de considérer sa publication sur un nouveau gestionnaire de paquets. En tant que createur de Bruno, je détiens la marque `Bruno` pour ce projet et j'aimerais gérer moi-même sa distribution. Si vous voyez Bruno sur un nouveau gestionnaire de paquets, merci de créer une _issue_ Github.
Bien que notre code soit open source et disponible pour tout le monde, nous vous remercions de nous contacter avant de considérer sa publication sur un nouveau gestionnaire de paquets. En tant que créateur de Bruno, je détiens la marque `Bruno` pour ce projet et j'aimerais gérer moi-même sa distribution. Si vous voyez Bruno sur un nouveau gestionnaire de paquets, merci de créer une _issue_ GitHub.
Bien que la majorité de nos fonctionnalités soient gratuites et open source (ce qui couvre les apis REST et GraphQL), nous nous efforçons de trouver un équilibre harmonieux entre les principes de l'open source et la pérennité - https://github.com/usebruno/bruno/discussions/269
Bien que la majorité de nos fonctionnalités soient gratuites et open source (ce qui couvre les APIs REST et GraphQL), nous nous efforçons de trouver un équilibre harmonieux entre les principes de l'open source et la pérennité - https://github.com/usebruno/bruno/discussions/269

View File

@@ -75,7 +75,7 @@ sudo apt install bruno
- [网站](https://www.usebruno.com)
- [价格](https://www.usebruno.com/pricing)
- [下载](https://www.usebruno.com/downloads)
- [Github 赞助](https://github.com/sponsors/helloanoop).
- [GitHub 赞助](https://github.com/sponsors/helloanoop).
### 展示 🎥
@@ -85,7 +85,7 @@ sudo apt install bruno
### 支持 ❤️
如果您喜欢 Bruno 并想支持我们的开源工作,请考虑通过 [Github Sponsors](https://github.com/sponsors/helloanoop) 来赞助我们。
如果您喜欢 Bruno 并想支持我们的开源工作,请考虑通过 [GitHub Sponsors](https://github.com/sponsors/helloanoop) 来赞助我们。
### 分享评价 📣

View File

@@ -3,7 +3,7 @@
### Bruno - IDE de código abierto para explorar y probar APIs.
[![Versión en Github](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![Versión en GitHub](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)
[![Actividad de Commits](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)
@@ -85,7 +85,7 @@ O cualquier otro sistema de control de versiones que prefieras
- [Precios](https://www.usebruno.com/pricing)
- [Descargas](https://www.usebruno.com/downloads)
### Casos de uso 🎥
### Casos de uso 🎥
- [Testimonios](https://github.com/usebruno/bruno/discussions/343)
- [Centro de Conocimiento](https://github.com/usebruno/bruno/discussions/386)

View File

@@ -13,13 +13,13 @@
[English](/readme.md) | [Українська](docs/readme/readme_ua.md) | [Русский](docs/readme/readme_ru.md) | [Türkçe](docs/readme/readme_tr.md) | [Deutsch](docs/readme/readme_de.md) | **Français** | [Português (BR)](docs/readme/readme_pt_br.md) | [한국어](docs/readme/readme_kr.md) | [বাংলা](docs/readme/readme_bn.md) | [Español](docs/readme/readme_es.md) | [Italiano](docs/readme/readme_it.md) | [Română](docs/readme/readme_ro.md) | [Polski](docs/readme/readme_pl.md)
Bruno est un nouveau client API, innovant, qui a pour but de révolutionner le _status quo_ que représente Postman et les autres outils.
Bruno est un nouveau client API, innovant, qui a pour but de révolutionner le _statu quo_ que représente Postman et les autres outils.
Bruno sauvegarde vos collections directement sur votre système de fichiers. Nous utilisons un langage de balise de type texte pour décrire les requêtes API.
Vous pouvez utiliser git ou tout autre gestionnaire de version pour travailler de manière collaborative sur vos collections d'APIs.
Bruno ne fonctionne qu'en mode déconnecté. Il n'y a pas de d'abonnement ou de synchronisation avec le cloud Bruno, il n'y en aura jamais. Nous sommes conscients de la confidentialité de vos données et nous sommes convaincus qu'elles doivent rester sur vos appareils. Vous pouvez lire notre vision à long terme [ici (en anglais)](https://github.com/usebruno/bruno/discussions/269).
Bruno ne fonctionne qu'en mode déconnecté. Il n'y a pas d'abonnement ou de synchronisation avec le cloud Bruno, il n'y en aura jamais. Nous sommes conscients de la confidentialité de vos données et nous sommes convaincus qu'elles doivent rester sur vos appareils. Vous pouvez lire notre vision à long terme [ici (en anglais)](https://github.com/usebruno/bruno/discussions/269).
📢 Regarder notre présentation récente lors de la conférence India FOSS 3.0 (en anglais) [ici](https://www.youtube.com/watch?v=7bSMFpbcPiY)
@@ -75,7 +75,7 @@ Ou n'importe quel système de gestion de sources
- [Site web](https://www.usebruno.com)
- [Prix](https://www.usebruno.com/pricing)
- [Téléchargement](https://www.usebruno.com/downloads)
- [Sponsors Github](https://github.com/sponsors/helloanoop)
- [Sponsors GitHub](https://github.com/sponsors/helloanoop)
### Showcase 🎥
@@ -89,7 +89,7 @@ Ouaf! Si vous aimez le projet, cliquez sur le bouton ⭐ !!
### Partage de témoignages 📣
Si Bruno vous a aidé dans votre travail, au sein de votre équipe, merci de penser à partager votre témoignage sur la [page discussion Github dédiée](https://github.com/usebruno/bruno/discussions/343)
Si Bruno vous a aidé dans votre travail, au sein de votre équipe, merci de penser à partager votre témoignage sur la [page discussion GitHub dédiée](https://github.com/usebruno/bruno/discussions/343)
### Publier Bruno sur un nouveau gestionnaire de paquets
@@ -125,7 +125,7 @@ Même si vous n'êtes pas en mesure de contribuer directement via du code, n'hé
**Logo**
Le logo est issu de [OpenMoji](https://openmoji.org/library/emoji-1F436/).
Licence: CC [BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
Licence : CC [BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
### Licence 📄

View File

@@ -79,11 +79,11 @@ sudo apt install bruno
### 후기 공유 📣
Bruno가 여러분과 여러분의 팀에 도움이 되었다면, 잊지 말고 공유해 주세요. [Github discussion 공유 링크](https://github.com/usebruno/bruno/discussions/343)
Bruno가 여러분과 여러분의 팀에 도움이 되었다면, 잊지 말고 공유해 주세요. [GitHub discussion 공유 링크](https://github.com/usebruno/bruno/discussions/343)
### 새 패키지 관리자에게 게시
더 많은 정보를 확인하시려 링크를 클릭해 주세요.[배포 가이드](publishing.md)
더 많은 정보를 확인하시려 링크를 클릭해 주세요. [배포 가이드](../../publishing.md)
### 컨트리뷰트 👩‍💻🧑‍💻

View File

@@ -73,7 +73,7 @@ Lub dowolny inny system kontroli wersji, który wybierzesz
- [Strona Internetowa](https://www.usebruno.com)
- [Cennik](https://www.usebruno.com/pricing)
- [Pobieranie](https://www.usebruno.com/downloads)
- [Sponsorzy Github](https://github.com/sponsors/helloanoop).
- [Sponsorzy GitHub](https://github.com/sponsors/helloanoop).
### Zobacz 🎥
@@ -83,7 +83,7 @@ Lub dowolny inny system kontroli wersji, który wybierzesz
### Wsparcie ❤️
Jeśli podoba Ci się Bruno i chcesz wspierać naszą pracę opensource, rozważ sponsorowanie nas przez [Sponsorzy Github](https://github.com/sponsors/helloanoop).
Jeśli podoba Ci się Bruno i chcesz wspierać naszą pracę opensource, rozważ sponsorowanie nas przez [Sponsorzy GitHub](https://github.com/sponsors/helloanoop).
### Udostępnij Opinie 📣

View File

@@ -94,7 +94,7 @@ Ou qualquer sistema de controle de versão de sua escolha.
- [Website](https://www.usebruno.com)
- [Preços](https://www.usebruno.com/pricing)
- [Download](https://www.usebruno.com/downloads)
- [Github Sponsors](https://github.com/sponsors/helloanoop)
- [GitHub Sponsors](https://github.com/sponsors/helloanoop)
### Showcase 🎥
@@ -104,7 +104,7 @@ Ou qualquer sistema de controle de versão de sua escolha.
### Apoie ❤️
Au-au! Se você gosta do projeto e deseja apoiar nosso trabalho, considere nos ajudando via [Github Sponsors](https://github.com/sponsors/helloanoop).
Au-au! Se você gosta do projeto e deseja apoiar nosso trabalho, considere nos ajudando via [GitHub Sponsors](https://github.com/sponsors/helloanoop).
### Compartilhe sua experiência 📣

View File

@@ -73,7 +73,7 @@ Veya seçtiğiniz herhangi bir sürüm kontrol sistemi
- [Web sitesi](https://www.usebruno.com)
- [Fiyatlandırma](https://www.usebruno.com/pricing)
- [İndir](https://www.usebruno.com/downloads)
- [Github Sponsorları](https://github.com/sponsors/helloanoop).
- [GitHub Sponsorları](https://github.com/sponsors/helloanoop).
### Vitrin 🎥
@@ -83,7 +83,7 @@ Veya seçtiğiniz herhangi bir sürüm kontrol sistemi
### Destek ❤️
Bruno'yu seviyorsanız ve açık kaynak çalışmalarımızı desteklemek istiyorsanız, [Github Sponsorları](https://github.com/sponsors/helloanoop) aracılığıyla bize sponsor olmayı düşünün.
Bruno'yu seviyorsanız ve açık kaynak çalışmalarımızı desteklemek istiyorsanız, [GitHub Sponsorları](https://github.com/sponsors/helloanoop) aracılığıyla bize sponsor olmayı düşünün.
### Referansları Paylaşın 📣

View File

@@ -73,7 +73,7 @@ sudo apt install bruno
- [網站](https://www.usebruno.com)
- [定價](https://www.usebruno.com/pricing)
- [下載](https://www.usebruno.com/downloads)
- [Github 贊助](https://github.com/sponsors/helloanoop).
- [GitHub 贊助](https://github.com/sponsors/helloanoop).
### 展示 🎥
@@ -83,7 +83,7 @@ sudo apt install bruno
### 贊助支持 ❤️
如果您喜歡 Bruno 和希望支持我們在開源上的工作,請考慮使用 [Github Sponsors](https://github.com/sponsors/helloanoop) 來贊助我們。
如果您喜歡 Bruno 和希望支持我們在開源上的工作,請考慮使用 [GitHub Sponsors](https://github.com/sponsors/helloanoop) 來贊助我們。
### 分享感想 📣

885
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -47,10 +47,12 @@
"test:prettier:web": "npm run test:prettier --workspace=packages/bruno-app",
"prepare": "husky install"
},
"overrides": {
"rollup": "3.2.5"
},
"dependencies": {
"json-bigint": "^1.0.0"
"json-bigint": "^1.0.0",
"lossless-json": "^4.0.1"
}
}

View File

@@ -17,10 +17,11 @@
"@fortawesome/react-fontawesome": "^0.1.16",
"@reduxjs/toolkit": "^1.8.0",
"@tabler/icons": "^1.46.0",
"@tailwindcss/forms": "^0.5.7",
"@tippyjs/react": "^4.2.6",
"@usebruno/common": "0.1.0",
"@usebruno/graphql-docs": "0.1.0",
"@usebruno/schema": "0.6.0",
"@usebruno/schema": "0.7.0",
"axios": "^1.5.1",
"classnames": "^2.3.1",
"codemirror": "5.65.2",

View File

@@ -37,7 +37,7 @@ const GrantTypeSelector = ({ collection }) => {
};
useEffect(() => {
// initalize redux state with a default oauth2 grant type
// initialize redux state with a default oauth2 grant type
// authorization_code - default option
!oAuth?.grantType &&
dispatch(

View File

@@ -37,7 +37,11 @@ const Auth = ({ collection }) => {
};
return (
<StyledWrapper className="w-full mt-2">
<StyledWrapper className="w-full h-full">
<div className="text-xs mb-4 text-muted">
Configures authentication for the entire collection. This applies to all requests using the{' '}
<span className="font-medium">Inherit</span> option in the <span className="font-medium">Auth</span> tab.
</div>
<div className="flex flex-grow justify-start items-center">
<AuthMode collection={collection} />
</div>

View File

@@ -30,13 +30,13 @@ const ClientCertSettings = ({ clientCertConfig, onUpdate, onRemove }) => {
};
return (
<StyledWrapper>
<div className="flex items-center font-semibold mt-4 mb-2">
<IconCertificate className="mr-1 certificate-icon" size={24} strokeWidth={1.5} /> Client Certificates
</div>
<StyledWrapper className="w-full h-full">
<div className="text-xs mb-4 text-muted">Add client certificates to be used for specific domains.</div>
<h1 className="font-semibold">Client Certificates</h1>
<ul className="mt-4">
{!clientCertConfig.length
? 'None'
? 'No client certificates added'
: clientCertConfig.map((clientCert) => (
<li key={uuid()} className="flex items-center available-certificates p-2 rounded-lg mb-2">
<div className="flex items-center w-full justify-between">
@@ -52,7 +52,7 @@ const ClientCertSettings = ({ clientCertConfig, onUpdate, onRemove }) => {
))}
</ul>
<h1 className="font-semibold mt-8 mb-2">Add Client Certicate</h1>
<h1 className="font-semibold mt-8 mb-2">Add Client Certificate</h1>
<form className="bruno-form" onSubmit={formik.handleSubmit}>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="domain">

View File

@@ -63,7 +63,10 @@ const Headers = ({ collection }) => {
};
return (
<StyledWrapper className="w-full">
<StyledWrapper className="h-full w-full">
<div className="text-xs mb-4 text-muted">
Add request headers that will be sent with every request in this collection.
</div>
<table>
<thead>
<tr>

View File

@@ -23,6 +23,7 @@ function countRequests(items) {
const Info = ({ collection }) => {
return (
<StyledWrapper className="w-full flex flex-col h-full">
<div className="text-xs mb-4 text-muted">General information about the collection.</div>
<table className="w-full border-collapse">
<tbody>
<tr className="">

View File

@@ -27,8 +27,10 @@ const PresetsSettings = ({ collection }) => {
});
return (
<StyledWrapper>
<h1 className="font-medium mb-3">Collection Presets</h1>
<StyledWrapper className="h-full w-full">
<div className="text-xs mb-4 text-muted">
These presets will be used as the default values for new requests in this collection.
</div>
<form className="bruno-form" onSubmit={formik.handleSubmit}>
<div className="mb-3 flex items-center">
<label className="settings-label flex items-center" htmlFor="enabled">
@@ -66,7 +68,7 @@ const PresetsSettings = ({ collection }) => {
<label className="settings-label" htmlFor="requestUrl">
Base URL
</label>
<div className="flex items-center">
<div className="flex items-center w-full">
<div className="flex items-center flex-grow input-container h-full">
<input
id="request-url"
@@ -79,6 +81,7 @@ const PresetsSettings = ({ collection }) => {
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.requestUrl || ''}
style={{ width: '100%' }}
/>
</div>
</div>

View File

@@ -95,8 +95,8 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
}, [proxyConfig]);
return (
<StyledWrapper>
<h1 className="font-medium mb-3">Proxy Settings</h1>
<StyledWrapper className="h-full w-full">
<div className="text-xs mb-4 text-muted">Configure proxy settings for this collection.</div>
<form className="bruno-form" onSubmit={formik.handleSubmit}>
<div className="mb-3 flex items-center">
<label className="settings-label flex items-center" htmlFor="enabled">

View File

@@ -38,7 +38,10 @@ const Script = ({ collection }) => {
};
return (
<StyledWrapper className="w-full flex flex-col">
<StyledWrapper className="w-full flex flex-col h-full">
<div className="text-xs mb-4 text-muted">
Write pre and post-request scripts that will run before and after any request in this collection is sent.
</div>
<div className="flex-1 mt-2">
<div className="mb-1 title text-xs">Pre Request</div>
<CodeEditor

View File

@@ -27,6 +27,7 @@ const Tests = ({ collection }) => {
return (
<StyledWrapper className="w-full flex flex-col h-full">
<div className="text-xs mb-4 text-muted">These tests will run any time a request in this collection is sent.</div>
<CodeEditor
collection={collection}
value={tests || ''}

View File

@@ -147,9 +147,7 @@ const CollectionSettings = ({ collection }) => {
Info
</div>
</div>
<section className={`flex ${['auth', 'script', 'docs', 'clientCert'].includes(tab) ? '' : 'mt-4'}`}>
{getTabPanel(tab)}
</section>
<section className="mt-4 h-full">{getTabPanel(tab)}</section>
</StyledWrapper>
);
};

View File

@@ -1,7 +1,7 @@
import styled from 'styled-components';
const Wrapper = styled.div`
.current-enviroment {
.current-environment {
background-color: ${(props) => props.theme.sidebar.badge.bg};
border-radius: 15px;

View File

@@ -18,7 +18,7 @@ const EnvironmentSelector = ({ collection }) => {
const Icon = forwardRef((props, ref) => {
return (
<div ref={ref} className="current-enviroment flex items-center justify-center pl-3 pr-2 py-1 select-none">
<div ref={ref} className="current-environment flex items-center justify-center pl-3 pr-2 py-1 select-none">
{activeEnvironment ? activeEnvironment.name : 'No Environment'}
<IconCaretDown className="caret" size={14} strokeWidth={2} />
</div>

View File

@@ -11,6 +11,7 @@ import { useFormik } from 'formik';
import * as Yup from 'yup';
import { uuid } from 'utils/common';
import { variableNameRegex } from 'utils/common/regex';
import { maskInputValue } from 'utils/collections';
const EnvironmentVariables = ({ environment, collection }) => {
const dispatch = useDispatch();
@@ -120,13 +121,17 @@ const EnvironmentVariables = ({ environment, collection }) => {
<ErrorMessage name={`${index}.name`} />
</td>
<td>
<SingleLineEditor
theme={storedTheme}
collection={collection}
name={`${index}.value`}
value={variable.value}
onChange={(newValue) => formik.setFieldValue(`${index}.value`, newValue, true)}
/>
{variable.secret ? (
<div className="overflow-hidden text-ellipsis">{maskInputValue(variable.value)}</div>
) : (
<SingleLineEditor
theme={storedTheme}
collection={collection}
name={`${index}.value`}
value={variable.value}
onChange={(newValue) => formik.setFieldValue(`${index}.value`, newValue, true)}
/>
)}
</td>
<td>
<input

View File

@@ -84,8 +84,9 @@ const Notifications = () => {
return (
<StyledWrapper>
<div
className="relative"
<a
title="Notifications"
className="relative cursor-pointer"
onClick={() => {
dispatch(fetchNotifications());
setShowNotificationsModal(true);
@@ -97,9 +98,9 @@ const Notifications = () => {
className={`mr-2 hover:text-gray-700 ${unreadNotifications?.length > 0 ? 'bell' : ''}`}
/>
{unreadNotifications.length > 0 && (
<div className="notification-count text-xs">{unreadNotifications.length}</div>
<span className="notification-count text-xs">{unreadNotifications.length}</span>
)}
</div>
</a>
{showNotificationsModal && (
<Modal

View File

@@ -21,6 +21,9 @@ const General = ({ close }) => {
enabled: Yup.boolean(),
filePath: Yup.string().nullable()
}),
keepDefaultCaCertificates: Yup.object({
enabled: Yup.boolean()
}),
storeCookies: Yup.boolean(),
sendCookies: Yup.boolean(),
timeout: Yup.mixed()
@@ -43,6 +46,9 @@ const General = ({ close }) => {
enabled: get(preferences, 'request.customCaCertificate.enabled', false),
filePath: get(preferences, 'request.customCaCertificate.filePath', null)
},
keepDefaultCaCertificates: {
enabled: get(preferences, 'request.keepDefaultCaCertificates.enabled', false)
},
timeout: preferences.request.timeout,
storeCookies: get(preferences, 'request.storeCookies', true),
sendCookies: get(preferences, 'request.sendCookies', true)
@@ -68,6 +74,9 @@ const General = ({ close }) => {
enabled: newPreferences.customCaCertificate.enabled,
filePath: newPreferences.customCaCertificate.filePath
},
keepDefaultCaCertificates: {
enabled: newPreferences.keepDefaultCaCertificates.enabled
},
timeout: newPreferences.timeout,
storeCookies: newPreferences.storeCookies,
sendCookies: newPreferences.sendCookies
@@ -158,6 +167,23 @@ const General = ({ close }) => {
</button>
</div>
)}
<div className="flex items-center mt-2">
<input
id="keepDefaultCaCertificatesEnabled"
type="checkbox"
name="keepDefaultCaCertificates.enabled"
checked={formik.values.keepDefaultCaCertificates.enabled}
onChange={formik.handleChange}
className={`mousetrap mr-0 ${formik.values.customCaCertificate.enabled ? '' : 'opacity-25'}`}
disabled={formik.values.customCaCertificate.enabled ? false : true}
/>
<label
className={`block ml-2 select-none ${formik.values.customCaCertificate.enabled ? '' : 'opacity-25'}`}
htmlFor="keepDefaultCaCertificatesEnabled"
>
Keep default CA Certificates
</label>
</div>
<div className="flex items-center mt-2">
<input
id="storeCookies"

View File

@@ -37,7 +37,7 @@ const GrantTypeSelector = ({ item, collection }) => {
};
useEffect(() => {
// initalize redux state with a default oauth2 grant type
// initialize redux state with a default oauth2 grant type
// authorization_code - default option
!oAuth?.grantType &&
dispatch(

View File

@@ -27,7 +27,7 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
const variables = item.draft
? get(item, 'draft.request.body.graphql.variables')
: get(item, 'request.body.graphql.variables');
const { storedTheme } = useTheme();
const { displayedTheme } = useTheme();
const [schema, setSchema] = useState(null);
useEffect(() => {
@@ -61,7 +61,7 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
return (
<QueryEditor
collection={collection}
theme={storedTheme}
theme={displayedTheme}
schema={schema}
width={leftPaneWidth}
onSave={onSave}

View File

@@ -8,7 +8,7 @@ import { humanizeRequestBodyMode } from 'utils/collections';
import StyledWrapper from './StyledWrapper';
import { updateRequestBody } from 'providers/ReduxStore/slices/collections/index';
import { toastError } from 'utils/common/error';
import jsonBigint from 'json-bigint';
import { parse, stringify } from 'lossless-json';
const RequestBodyMode = ({ item, collection }) => {
const dispatch = useDispatch();
@@ -38,8 +38,8 @@ const RequestBodyMode = ({ item, collection }) => {
const onPrettify = () => {
if (body?.json && bodyMode === 'json') {
try {
const bodyJson = jsonBigint.parse(body.json);
const prettyBodyJson = jsonBigint.stringify(bodyJson, null, 2);
const bodyJson = parse(body.json);
const prettyBodyJson = stringify(bodyJson, null, 2);
dispatch(
updateRequestBody({
content: prettyBodyJson,

View File

@@ -65,6 +65,16 @@ const QueryResultPreview = ({
</div>
);
}
case 'preview-audio': {
return (
<audio controls src={`data:${contentType.replace(/\;(.*)/, '')};base64,${dataBuffer}`} className="mx-auto" />
);
}
case 'preview-video': {
return (
<video controls src={`data:${contentType.replace(/\;(.*)/, '')};base64,${dataBuffer}`} className="mx-auto" />
);
}
default:
case 'raw': {
return (

View File

@@ -67,6 +67,10 @@ const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEven
allowedPreviewModes.unshift('preview-image');
} else if (contentType.includes('pdf')) {
allowedPreviewModes.unshift('preview-pdf');
} else if (contentType.includes('audio')) {
allowedPreviewModes.unshift('preview-audio');
} else if (contentType.includes('video')) {
allowedPreviewModes.unshift('preview-video');
}
return allowedPreviewModes;

View File

@@ -29,7 +29,7 @@ export default function RunnerResults({ collection }) {
const autoScrollRunnerBody = () => {
if (runnerBodyRef?.current) {
// mimicks the native terminal scroll style
// mimics the native terminal scroll style
runnerBodyRef.current.scrollTo(0, 100000);
}
};

View File

@@ -4,7 +4,7 @@ import * as Yup from 'yup';
import Modal from 'components/Modal';
import { useDispatch } from 'react-redux';
import { isItemAFolder } from 'utils/tabs';
import { renameItem } from 'providers/ReduxStore/slices/collections/actions';
import { renameItem, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
const RenameCollectionItem = ({ collection, item, onClose }) => {
const dispatch = useDispatch();
@@ -21,7 +21,12 @@ const RenameCollectionItem = ({ collection, item, onClose }) => {
.max(50, 'must be 50 characters or less')
.required('name is required')
}),
onSubmit: (values) => {
onSubmit: async (values) => {
// if there is unsaved changes in the request,
// save them before renaming the request
if (!isFolder && item.draft) {
await dispatch(saveRequest(item.uid, collection.uid, true));
}
dispatch(renameItem(values.name, item.uid, collection.uid));
onClose();
}

View File

@@ -6,9 +6,8 @@ import { useDrag, useDrop } from 'react-dnd';
import { IconChevronRight, IconDots } from '@tabler/icons';
import { useSelector, useDispatch } from 'react-redux';
import { addTab, focusTab } from 'providers/ReduxStore/slices/tabs';
import { moveItem, sendRequest } from 'providers/ReduxStore/slices/collections/actions';
import { collectionFolderClicked } from 'providers/ReduxStore/slices/collections';
import { moveItem } from 'providers/ReduxStore/slices/collections/actions';
import { sendRequest } from 'providers/ReduxStore/slices/collections/actions';
import Dropdown from 'components/Dropdown';
import NewRequest from 'components/Sidebar/NewRequest';
import NewFolder from 'components/Sidebar/NewFolder';

View File

@@ -99,7 +99,7 @@ const GoldenEdition = ({ onClose }) => {
const goldenEditonOrganizations = [
'Centralized License Management',
'Intergration with Secret Managers',
'Integration with Secret Managers',
'Private Collection Registry',
'Request Forms',
'Priority Support'

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import importBrunoCollection from 'utils/importers/bruno-collection';
import importPostmanCollection from 'utils/importers/postman-collection';
import importInsomniaCollection from 'utils/importers/insomnia-collection';
@@ -7,6 +7,14 @@ import { toastError } from 'utils/common/error';
import Modal from 'components/Modal';
const ImportCollection = ({ onClose, handleSubmit }) => {
const [options, setOptions] = useState({
enablePostmanTranslations: {
enabled: true,
label: 'Auto translate postman scripts',
subLabel:
"When enabled, Bruno will try as best to translate the scripts from the imported collection to Bruno's format."
}
});
const handleImportBrunoCollection = () => {
importBrunoCollection()
.then((collection) => {
@@ -16,7 +24,7 @@ const ImportCollection = ({ onClose, handleSubmit }) => {
};
const handleImportPostmanCollection = () => {
importPostmanCollection()
importPostmanCollection(options)
.then((collection) => {
handleSubmit(collection);
})
@@ -38,21 +46,61 @@ const ImportCollection = ({ onClose, handleSubmit }) => {
})
.catch((err) => toastError(err, 'OpenAPI v3 Import collection failed'));
};
const toggleOptions = (event, optionKey) => {
setOptions({
...options,
[optionKey]: {
...options[optionKey],
enabled: !options[optionKey].enabled
}
});
};
const CollectionButton = ({ children, className, onClick }) => {
return (
<button
type="button"
onClick={onClick}
className={`rounded bg-transparent px-2.5 py-1 text-xs font-semibold text-slate-900 dark:text-slate-50 shadow-sm ring-1 ring-inset ring-zinc-300 dark:ring-zinc-500 hover:bg-gray-50 dark:hover:bg-zinc-700
${className}`}
>
{children}
</button>
);
};
return (
<Modal size="sm" title="Import Collection" hideFooter={true} handleConfirm={onClose} handleCancel={onClose}>
<div>
<div className="text-link hover:underline cursor-pointer" onClick={handleImportBrunoCollection}>
Bruno Collection
<div className="flex flex-col">
<h3 className="text-sm">Select the type of your existing collection :</h3>
<div className="mt-4 grid grid-rows-2 grid-flow-col gap-2">
<CollectionButton onClick={handleImportBrunoCollection}>Bruno Collection</CollectionButton>
<CollectionButton onClick={handleImportPostmanCollection}>Postman Collection</CollectionButton>
<CollectionButton onClick={handleImportInsomniaCollection}>Insomnia Collection</CollectionButton>
<CollectionButton onClick={handleImportOpenapiCollection}>OpenAPI V3 Spec</CollectionButton>
</div>
<div className="text-link hover:underline cursor-pointer mt-2" onClick={handleImportPostmanCollection}>
Postman Collection
</div>
<div className="text-link hover:underline cursor-pointer mt-2" onClick={handleImportInsomniaCollection}>
Insomnia Collection
</div>
<div className="text-link hover:underline cursor-pointer mt-2" onClick={handleImportOpenapiCollection}>
OpenAPI V3 Spec
<div className="flex justify-start w-full mt-4 max-w-[450px]">
{Object.entries(options || {}).map(([key, option]) => (
<div className="relative flex items-start">
<div className="flex h-6 items-center">
<input
id="comments"
aria-describedby="comments-description"
name="comments"
type="checkbox"
checked={option.enabled}
onChange={(e) => toggleOptions(e, key)}
className="h-3.5 w-3.5 rounded border-zinc-300 dark:ring-offset-zinc-800 bg-transparent text-indigo-600 dark:text-indigo-500 focus:ring-indigo-600 dark:focus:ring-indigo-500"
/>
</div>
<div className="ml-2 text-sm leading-6">
<label htmlFor="comments" className="font-medium text-gray-900 dark:text-zinc-50">
{option.label}
</label>
<p id="comments-description" className="text-zinc-500 text-xs dark:text-zinc-400">
{option.subLabel}
</p>
</div>
</div>
))}
</div>
</div>
</Modal>

View File

@@ -93,26 +93,29 @@ const Sidebar = () => {
<Collections />
</div>
<div className="footer flex px-1 py-2 absolute bottom-0 left-0 right-0 items-center cursor-pointer select-none">
<div className="footer flex px-1 py-2 absolute bottom-0 left-0 right-0 items-center select-none">
<div className="flex items-center ml-1 text-xs ">
<IconSettings
size={18}
strokeWidth={1.5}
className="mr-2 hover:text-gray-700"
<a
title="Preferences"
className="mr-2 cursor-pointer hover:text-gray-700"
onClick={() => dispatch(showPreferences(true))}
/>
<IconCookie
size={18}
strokeWidth={1.5}
className="mr-2 hover:text-gray-700"
>
<IconSettings size={18} strokeWidth={1.5} />
</a>
<a
title="Cookies"
className="mr-2 cursor-pointer hover:text-gray-700"
onClick={() => setCookiesOpen(true)}
/>
<IconHeart
size={18}
strokeWidth={1.5}
className="mr-2 hover:text-gray-700"
>
<IconCookie size={18} strokeWidth={1.5} />
</a>
<a
title="Golden Edition"
className="mr-2 cursor-pointer hover:text-gray-700"
onClick={() => setGoldenEditonOpen(true)}
/>
>
<IconHeart size={18} strokeWidth={1.5} />
</a>
<Notifications />
</div>
<div className="pl-1" style={{ position: 'relative', top: '3px' }}>
@@ -126,7 +129,7 @@ const Sidebar = () => {
Star
</GitHubButton> */}
</div>
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.11.0</div>
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.12.3</div>
</div>
</div>
</div>

View File

@@ -49,6 +49,10 @@ const StyledWrapper = styled.div`
padding-left: 0;
padding-right: 0;
}
.CodeMirror-selected {
background-color: rgba(212, 125, 59, 0.3);
}
}
`;

View File

@@ -1,23 +1,28 @@
import React from 'react';
import React, { useState } from 'react';
import get from 'lodash/get';
import filter from 'lodash/filter';
import { Inspector } from 'react-inspector';
import { useTheme } from 'providers/Theme';
import { findEnvironmentInCollection } from 'utils/collections';
import { findEnvironmentInCollection, maskInputValue } from 'utils/collections';
import StyledWrapper from './StyledWrapper';
import { IconEye, IconEyeOff } from '@tabler/icons';
const KeyValueExplorer = ({ data, theme }) => {
data = data || {};
const KeyValueExplorer = ({ data = [], theme }) => {
const [showSecret, setShowSecret] = useState(false);
return (
<div>
<SecretToggle showSecret={showSecret} onClick={() => setShowSecret(!showSecret)} />
<table className="border-collapse">
<tbody>
{Object.entries(data).map(([key, value]) => (
<tr key={key}>
<td className="px-2 py-1">{key}</td>
{data.map((envVar) => (
<tr key={envVar.name}>
<td className="px-2 py-1">{envVar.name}</td>
<td className="px-2 py-1">
<Inspector data={value} theme={theme} />
<Inspector
data={!showSecret && envVar.secret ? maskInputValue(envVar.value) : envVar.value}
theme={theme}
/>
</td>
</tr>
))}
@@ -41,10 +46,6 @@ const EnvVariables = ({ collection, theme }) => {
const envVars = get(environment, 'variables', []);
const enabledEnvVars = filter(envVars, (variable) => variable.enabled);
const envVarsObj = enabledEnvVars.reduce((acc, curr) => {
acc[curr.name] = curr.value;
return acc;
}, {});
return (
<>
@@ -53,7 +54,7 @@ const EnvVariables = ({ collection, theme }) => {
<span className="muted ml-2">({environment.name})</span>
</div>
{enabledEnvVars.length > 0 ? (
<KeyValueExplorer data={envVarsObj} theme={theme} />
<KeyValueExplorer data={enabledEnvVars} theme={theme} />
) : (
<div className="muted text-xs">No environment variables found</div>
)}
@@ -64,11 +65,17 @@ const EnvVariables = ({ collection, theme }) => {
const CollectionVariables = ({ collection, theme }) => {
const collectionVariablesFound = Object.keys(collection.collectionVariables).length > 0;
const collectionVariableArray = Object.entries(collection.collectionVariables).map(([name, value]) => ({
name,
value,
secret: false
}));
return (
<>
<h1 className="font-semibold mb-2">Collection Variables</h1>
{collectionVariablesFound ? (
<KeyValueExplorer data={collection.collectionVariables} theme={theme} />
<KeyValueExplorer data={collectionVariableArray} theme={theme} />
) : (
<div className="muted text-xs">No collection variables found</div>
)}
@@ -96,3 +103,12 @@ const VariablesEditor = ({ collection }) => {
};
export default VariablesEditor;
const SecretToggle = ({ showSecret, onClick }) => (
<div className="cursor-pointer mb-2 text-xs" onClick={onClick}>
<div className="flex items-center">
{showSecret ? <IconEyeOff size={16} strokeWidth={1.5} /> : <IconEye size={16} strokeWidth={1.5} />}
<span className="pl-1">{showSecret ? 'Hide secret variable values' : 'Show secret variable values'}</span>
</div>
</div>
);

View File

@@ -9,6 +9,9 @@ const GlobalStyle = createGlobalStyle`
.text-link {
color: ${(props) => props.theme.textLink};
}
.text-muted {
color: ${(props) => props.theme.colors.text.muted};
}
.btn {
text-align: center;

View File

@@ -60,7 +60,7 @@ const trackStart = () => {
event: 'start',
properties: {
os: platformLib.os.family,
version: '1.11.0'
version: '1.12.3'
}
});
};

View File

@@ -17,6 +17,9 @@ const initialState = {
enabled: false,
filePath: null
},
keepDefaultCaCertificates: {
enabled: false
},
timeout: 0
},
font: {

View File

@@ -55,7 +55,7 @@ export const renameCollection = (newName, collectionUid) => (dispatch, getState)
});
};
export const saveRequest = (itemUid, collectionUid) => (dispatch, getState) => {
export const saveRequest = (itemUid, collectionUid, saveSilently) => (dispatch, getState) => {
const state = getState();
const collection = findCollectionByUid(state.collections.collections, collectionUid);
@@ -76,7 +76,11 @@ export const saveRequest = (itemUid, collectionUid) => (dispatch, getState) => {
itemSchema
.validate(itemToSave)
.then(() => ipcRenderer.invoke('renderer:save-request', item.pathname, itemToSave))
.then(() => toast.success('Request saved successfully'))
.then(() => {
if (!saveSilently) {
toast.success('Request saved successfully');
}
})
.then(resolve)
.catch((err) => {
toast.error('Failed to save request!');
@@ -414,6 +418,15 @@ export const cloneItem = (newName, itemUid, collectionUid) => (dispatch, getStat
.then(() => ipcRenderer.invoke('renderer:new-request', fullName, itemToSave))
.then(resolve)
.catch(reject);
dispatch(
insertTaskIntoQueue({
uid: uuid(),
type: 'OPEN_REQUEST',
collectionUid,
itemPathname: fullName
})
);
} else {
return reject(new Error('Duplicate request names are not allowed under the same folder'));
}
@@ -434,6 +447,15 @@ export const cloneItem = (newName, itemUid, collectionUid) => (dispatch, getStat
.then(() => ipcRenderer.invoke('renderer:new-request', fullName, itemToSave))
.then(resolve)
.catch(reject);
dispatch(
insertTaskIntoQueue({
uid: uuid(),
type: 'OPEN_REQUEST',
collectionUid,
itemPathname: fullName
})
);
} else {
return reject(new Error('Duplicate request names are not allowed under the same folder'));
}

View File

@@ -360,7 +360,7 @@ export const collectionsSlice = createSlice({
urlParam.uid = existingParam ? existingParam.uid : uuid();
urlParam.enabled = true;
// once found, remove it - trying our best here to accomodate duplicate query params
// once found, remove it - trying our best here to accommodate duplicate query params
if (existingParam) {
enabledParams = filter(enabledParams, (p) => p.uid !== existingParam.uid);
}

View File

@@ -9,20 +9,31 @@ export const ThemeProvider = (props) => {
const isBrowserThemeLight = window.matchMedia('(prefers-color-scheme: light)').matches;
const [displayedTheme, setDisplayedTheme] = useState(isBrowserThemeLight ? 'light' : 'dark');
const [storedTheme, setStoredTheme] = useLocalStorage('bruno.theme', 'system');
const toggleHtml = () => {
const html = document.querySelector('html');
if (html) {
html.classList.toggle('dark');
}
};
useEffect(() => {
window.matchMedia('(prefers-color-scheme: light)').addEventListener('change', (e) => {
if (storedTheme !== 'system') return;
setDisplayedTheme(e.matches ? 'light' : 'dark');
toggleHtml();
});
}, []);
useEffect(() => {
const root = window.document.documentElement;
root.classList.remove('light', 'dark');
if (storedTheme === 'system') {
const isBrowserThemeLight = window.matchMedia('(prefers-color-scheme: light)').matches;
setDisplayedTheme(isBrowserThemeLight ? 'light' : 'dark');
root.classList.add(isBrowserThemeLight ? 'light' : 'dark');
} else {
setDisplayedTheme(storedTheme);
root.classList.add(storedTheme);
}
}, [storedTheme, setDisplayedTheme, window.matchMedia]);

View File

@@ -58,7 +58,7 @@ body::-webkit-scrollbar-thumb,
border-radius: 5rem;
}
/*
/*
* todo: this will be supported in the future to be changed via applying a theme
* making all the checkboxes and radios bigger
* input[type='checkbox'],

View File

@@ -8,7 +8,7 @@ const lightTheme = {
text: {
green: '#047857',
danger: 'rgb(185, 28, 28)',
muted: '#4b5563',
muted: '#838383',
purple: '#8e44ad',
yellow: '#d97706'
},

View File

@@ -639,3 +639,14 @@ export const getAllVariables = (collection) => {
}
};
};
export const maskInputValue = (value) => {
if (!value || typeof value !== 'string') {
return '';
}
return value
.split('')
.map(() => '*')
.join('');
};

View File

@@ -33,7 +33,7 @@ const parseCurlCommand = (curlCommand) => {
curlCommand = curlCommand.trim();
const parsedArguments = yargs(curlCommand, {
boolean: ['I', 'head', 'compressed', 'L', 'k', 'silent', 's'],
boolean: ['I', 'head', 'compressed', 'L', 'k', 'silent', 's', 'G', 'get'],
alias: {
H: 'header',
A: 'user-agent'
@@ -153,7 +153,10 @@ const parseCurlCommand = (curlCommand) => {
// NB: the -G flag does not change the http verb. It just moves the data into the url.
if (parsedArguments.G || parsedArguments.get) {
urlObject.query = urlObject.query ? urlObject.query : '';
const option = 'd' in parsedArguments ? 'd' : 'data' in parsedArguments ? 'data' : null;
let option = null;
if ('d' in parsedArguments) option = 'd';
if ('data' in parsedArguments) option = 'data';
if ('data-urlencode' in parsedArguments) option = 'data-urlencode';
if (option) {
let urlQueryString = '';
@@ -219,6 +222,8 @@ const parseCurlCommand = (curlCommand) => {
} else if (parsedArguments['data-raw']) {
request.data = parsedArguments['data-raw'];
request.isDataRaw = true;
} else if (parsedArguments['data-urlencode']) {
request.data = parsedArguments['data-urlencode'];
}
if (parsedArguments.u) {

View File

@@ -49,7 +49,7 @@ export const exportCollection = (collection) => {
const generateEventSection = (item) => {
const eventArray = [];
if (item.request.tests.length) {
if (item?.request?.tests?.length) {
eventArray.push({
listen: 'test',
script: {
@@ -58,7 +58,7 @@ export const exportCollection = (collection) => {
}
});
}
if (item.request.script.req) {
if (item?.request?.script?.req) {
eventArray.push({
listen: 'prerequest',
script: {

View File

@@ -4,6 +4,7 @@ import fileDialog from 'file-dialog';
import { uuid } from 'utils/common';
import { BrunoError } from 'utils/common/error';
import { validateSchema, transformItemsInCollection, hydrateSeqInCollection } from './common';
import { postmanTranslation } from 'utils/importers/translators/postman_translation';
const readFile = (files) => {
return new Promise((resolve, reject) => {
@@ -53,7 +54,7 @@ const convertV21Auth = (array) => {
}, {});
};
const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) => {
brunoParent.items = brunoParent.items || [];
const folderMap = {};
@@ -77,7 +78,7 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
brunoParent.items.push(brunoFolderItem);
folderMap[folderName] = brunoFolderItem;
if (i.item && i.item.length) {
importPostmanV2CollectionItem(brunoFolderItem, i.item, i.auth ?? parentAuth);
importPostmanV2CollectionItem(brunoFolderItem, i.item, i.auth ?? parentAuth, options);
}
} else {
if (i.request) {
@@ -121,9 +122,13 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
brunoRequestItem.request.script = {};
}
if (Array.isArray(event.script.exec)) {
brunoRequestItem.request.script.req = event.script.exec.map((line) => `// ${line}`).join('\n');
brunoRequestItem.request.script.req = event.script.exec
.map((line) => (options.enablePostmanTranslations.enabled ? postmanTranslation(line) : `// ${line}`))
.join('\n');
} else {
brunoRequestItem.request.script.req = `// ${event.script.exec[0]} `;
brunoRequestItem.request.script.req = options.enablePostmanTranslations.enabled
? postmanTranslation(event.script.exec[0])
: `// ${event.script.exec[0]} `;
}
}
if (event.listen === 'test' && event.script && event.script.exec) {
@@ -131,9 +136,13 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
brunoRequestItem.request.tests = {};
}
if (Array.isArray(event.script.exec)) {
brunoRequestItem.request.tests = event.script.exec.map((line) => `// ${line}`).join('\n');
brunoRequestItem.request.tests = event.script.exec
.map((line) => (options.enablePostmanTranslations.enabled ? postmanTranslation(line) : `// ${line}`))
.join('\n');
} else {
brunoRequestItem.request.tests = `// ${event.script.exec[0]} `;
brunoRequestItem.request.tests = options.enablePostmanTranslations.enabled
? postmanTranslation(event.script.exec[0])
: `// ${event.script.exec[0]} `;
}
}
});
@@ -263,7 +272,7 @@ const searchLanguageByHeader = (headers) => {
return contentType;
};
const importPostmanV2Collection = (collection) => {
const importPostmanV2Collection = (collection, options) => {
const brunoCollection = {
name: collection.info.name,
uid: uuid(),
@@ -272,12 +281,12 @@ const importPostmanV2Collection = (collection) => {
environments: []
};
importPostmanV2CollectionItem(brunoCollection, collection.item, collection.auth);
importPostmanV2CollectionItem(brunoCollection, collection.item, collection.auth, options);
return brunoCollection;
};
const parsePostmanCollection = (str) => {
const parsePostmanCollection = (str, options) => {
return new Promise((resolve, reject) => {
try {
let collection = JSON.parse(str);
@@ -289,7 +298,7 @@ const parsePostmanCollection = (str) => {
];
if (v2Schemas.includes(schema)) {
return resolve(importPostmanV2Collection(collection));
return resolve(importPostmanV2Collection(collection, options));
}
throw new BrunoError('Unknown postman schema');
@@ -304,11 +313,11 @@ const parsePostmanCollection = (str) => {
});
};
const importCollection = () => {
const importCollection = (options) => {
return new Promise((resolve, reject) => {
fileDialog({ accept: 'application/json' })
.then(readFile)
.then(parsePostmanCollection)
.then((str) => parsePostmanCollection(str, options))
.then(transformItemsInCollection)
.then(hydrateSeqInCollection)
.then(validateSchema)

View File

@@ -0,0 +1,134 @@
const { postmanTranslation } = require('./postman_translation'); // Adjust path as needed
describe('postmanTranslation function', () => {
test('should translate pm commands correctly', () => {
const inputScript = `
pm.environment.get('key');
pm.environment.set('key', 'value');
pm.variables.get('key');
pm.variables.set('key', 'value');
pm.collectionVariables.get('key');
pm.collectionVariables.set('key', 'value');
`;
const expectedOutput = `
bru.getEnvVar('key');
bru.setEnvVar('key', 'value');
bru.getVar('key');
bru.setVar('key', 'value');
bru.getVar('key');
bru.setVar('key', 'value');
`;
expect(postmanTranslation(inputScript)).toBe(expectedOutput);
});
test('should not translate non-pm commands', () => {
const inputScript = `
console.log('This script does not contain pm commands.');
const data = pm.environment.get('key');
pm.collectionVariables.set('key', data);
`;
const expectedOutput = `
console.log('This script does not contain pm commands.');
const data = bru.getEnvVar('key');
bru.setVar('key', data);
`;
expect(postmanTranslation(inputScript)).toBe(expectedOutput);
});
test('should comment non-translated pm commands', () => {
const inputScript = "pm.test('random test', () => pm.response.json());";
const expectedOutput = "// test('random test', () => pm.response.json());";
expect(postmanTranslation(inputScript)).toBe(expectedOutput);
});
test('should handle multiple pm commands on the same line', () => {
const inputScript = "pm.environment.get('key'); pm.environment.set('key', 'value');";
const expectedOutput = "bru.getEnv(var); bru.setEnvVar(var, 'value');";
});
test('should handle comments and other JavaScript code', () => {
const inputScript = `
// This is a comment
const value = 'test';
pm.environment.set('key', value);
/*
Multi-line comment
*/
const result = pm.environment.get('key');
console.log('Result:', result);
`;
const expectedOutput = `
// This is a comment
const value = 'test';
bru.setEnvVar('key', value);
/*
Multi-line comment
*/
const result = bru.getEnvVar('key');
console.log('Result:', result);
`;
expect(postmanTranslation(inputScript)).toBe(expectedOutput);
});
test('should handle nested commands and edge cases', () => {
const inputScript = `
const sampleObjects = [
{
key: pm.environment.get('key'),
value: pm.variables.get('value')
},
{
key: pm.collectionVariables.get('key'),
value: pm.collectionVariables.get('value')
}
];
const dataTesting = Object.entries(sampleObjects || {}).reduce((acc, [key, value]) => {
// this is a comment
acc[key] = pm.collectionVariables.get(pm.environment.get(value));
return acc; // Return the accumulator
}, {});
Object.values(dataTesting).forEach((data) => {
pm.environment.set(data.key, pm.variables.get(data.value));
});
`;
const expectedOutput = `
const sampleObjects = [
{
key: bru.getEnvVar('key'),
value: bru.getVar('value')
},
{
key: bru.getVar('key'),
value: bru.getVar('value')
}
];
const dataTesting = Object.entries(sampleObjects || {}).reduce((acc, [key, value]) => {
// this is a comment
acc[key] = bru.getVar(bru.getEnvVar(value));
return acc; // Return the accumulator
}, {});
Object.values(dataTesting).forEach((data) => {
bru.setEnvVar(data.key, bru.getVar(data.value));
});
`;
expect(postmanTranslation(inputScript)).toBe(expectedOutput);
});
test('should handle test commands', () => {
const inputScript = `
pm.test('Status code is 200', () => {
pm.response.to.have.status(200);
});
pm.test('this test will fail', () => {
return false
});
`;
const expectedOutput = `
test('Status code is 200', () => {
expect(res.getStatus()).to.equal(200);
});
test('this test will fail', () => {
return false
});
`;
expect(postmanTranslation(inputScript)).toBe(expectedOutput);
});
});

View File

@@ -0,0 +1,35 @@
const replacements = {
'pm\\.environment\\.get\\(': 'bru.getEnvVar(',
'pm\\.environment\\.set\\(': 'bru.setEnvVar(',
'pm\\.variables\\.get\\(': 'bru.getVar(',
'pm\\.variables\\.set\\(': 'bru.setVar(',
'pm\\.collectionVariables\\.get\\(': 'bru.getVar(',
'pm\\.collectionVariables\\.set\\(': 'bru.setVar(',
'pm\\.setNextRequest\\(': 'bru.setNextRequest(',
'pm\\.test\\(': 'test(',
'pm.response.to.have\\.status\\(': 'expect(res.getStatus()).to.equal('
};
const compiledReplacements = Object.entries(replacements).map(([pattern, replacement]) => ({
regex: new RegExp(pattern, 'g'),
replacement
}));
export const postmanTranslation = (script) => {
try {
let modifiedScript = script;
let modified = false;
for (const { regex, replacement } of compiledReplacements) {
if (regex.test(modifiedScript)) {
modifiedScript = modifiedScript.replace(regex, replacement);
modified = true;
}
}
if (modified && modifiedScript.includes('pm.')) {
modifiedScript = modifiedScript.replace(/^(.*pm\..*)$/gm, '// $1');
}
return modifiedScript;
} catch (e) {
return script;
}
};

View File

@@ -1,6 +1,8 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
darkMode: ['class'],
content: ['./src/**/*.{js,jsx}'],
prefix: '',
theme: {
extend: {}
},

View File

@@ -1,6 +1,6 @@
{
"name": "@usebruno/cli",
"version": "1.9.2",
"version": "1.11.0",
"license": "MIT",
"main": "src/index.js",
"bin": {
@@ -26,8 +26,8 @@
"dependencies": {
"@aws-sdk/credential-providers": "3.525.0",
"@usebruno/common": "0.1.0",
"@usebruno/js": "0.10.1",
"@usebruno/lang": "0.10.0",
"@usebruno/js": "0.11.0",
"@usebruno/lang": "0.11.0",
"aws4-axios": "^3.3.0",
"axios": "^1.5.1",
"chai": "^4.3.7",

View File

@@ -240,7 +240,7 @@ const builder = async (yargs) => {
'$0 run request.bru --output results.html --format html',
'Run a request and write the results to results.html in html format in the current directory'
)
.example('$0 run request.bru --test-only', 'Run all requests that have a test');
.example('$0 run request.bru --tests-only', 'Run all requests that have a test');
};
const handler = async function (argv) {

View File

@@ -1,5 +1,5 @@
{
"version": "v1.11.0",
"version": "v1.12.3",
"name": "bruno",
"description": "Opensource API Client for Exploring and Testing APIs",
"homepage": "https://www.usebruno.com",
@@ -21,9 +21,9 @@
"dependencies": {
"@aws-sdk/credential-providers": "3.525.0",
"@usebruno/common": "0.1.0",
"@usebruno/js": "0.10.1",
"@usebruno/lang": "0.10.0",
"@usebruno/schema": "0.6.0",
"@usebruno/js": "0.11.0",
"@usebruno/lang": "0.11.0",
"@usebruno/schema": "0.7.0",
"about-window": "^1.15.2",
"aws4-axios": "^3.3.0",
"axios": "^1.5.1",

View File

@@ -25,6 +25,7 @@ const contentSecurityPolicy = [
// this has been commented out to make oauth2 work
// "form-action 'none'",
"img-src 'self' blob: data: https:",
"media-src 'self' blob: data: https:",
"style-src 'self' 'unsafe-inline' https:"
];

View File

@@ -24,7 +24,7 @@ const authorizeUserInWindow = ({ authorizeUrl, callbackUrl, session }) => {
function onWindowRedirect(url) {
// check if the url contains an authorization code
if (url.match(/(code=).*/)) {
if (new URL(url).searchParams.has('code')) {
finalUrl = url;
if (!url || !finalUrl.includes(callbackUrl)) {
reject(new Error('Invalid Callback Url'));
@@ -75,6 +75,11 @@ const authorizeUserInWindow = ({ authorizeUrl, callbackUrl, session }) => {
try {
await window.loadURL(authorizeUrl);
} catch (error) {
// If browser redirects before load finished, loadURL throws an error with code ERR_ABORTED. This should be ignored.
if (error.code === 'ERR_ABORTED') {
console.debug('Ignoring ERR_ABORTED during authorizeUserInWindow');
return;
}
reject(error);
window.close();
}

View File

@@ -2,6 +2,7 @@ const os = require('os');
const fs = require('fs');
const qs = require('qs');
const https = require('https');
const tls = require('tls');
const axios = require('axios');
const path = require('path');
const decomment = require('decomment');
@@ -105,7 +106,11 @@ const configureRequest = async (
if (preferencesUtil.shouldUseCustomCaCertificate()) {
const caCertFilePath = preferencesUtil.getCustomCaCertificateFilePath();
if (caCertFilePath) {
httpsAgentRequestFields['ca'] = fs.readFileSync(caCertFilePath);
let caCertBuffer = fs.readFileSync(caCertFilePath);
if (preferencesUtil.shouldKeepDefaultCaCertificates()) {
caCertBuffer += '\n' + tls.rootCertificates.join('\n'); // Augment default truststore with custom CA certificates
}
httpsAgentRequestFields['ca'] = caCertBuffer;
}
}
@@ -203,6 +208,7 @@ const configureRequest = async (
interpolateVars(requestCopy, envVars, collectionVariables, processEnvVars);
const { data: authorizationCodeData, url: authorizationCodeAccessTokenUrl } =
await resolveOAuth2AuthorizationCodeAccessToken(requestCopy, collectionUid);
request.method = 'POST';
request.headers['content-type'] = 'application/x-www-form-urlencoded';
request.data = authorizationCodeData;
request.url = authorizationCodeAccessTokenUrl;
@@ -211,6 +217,8 @@ const configureRequest = async (
interpolateVars(requestCopy, envVars, collectionVariables, processEnvVars);
const { data: clientCredentialsData, url: clientCredentialsAccessTokenUrl } =
await transformClientCredentialsRequest(requestCopy);
request.method = 'POST';
request.headers['content-type'] = 'application/x-www-form-urlencoded';
request.data = clientCredentialsData;
request.url = clientCredentialsAccessTokenUrl;
break;
@@ -219,6 +227,7 @@ const configureRequest = async (
const { data: passwordData, url: passwordAccessTokenUrl } = await transformPasswordCredentialsRequest(
requestCopy
);
request.method = 'POST';
request.data = passwordData;
request.url = passwordAccessTokenUrl;
break;
@@ -251,7 +260,7 @@ const parseDataFromResponse = (response) => {
const dataBuffer = Buffer.from(response.data);
// Parse the charset from content type: https://stackoverflow.com/a/33192813
const charset = /charset=([^()<>@,;:"/[\]?.=\s]*)/i.exec(response.headers['Content-Type'] || '');
// Overwrite the original data for backwards compatability
// Overwrite the original data for backwards compatibility
let data = dataBuffer.toString(charset || 'utf-8');
// Try to parse response to JSON, this can quietly fail
try {
@@ -913,7 +922,7 @@ const registerNetworkIpc = (mainWindow) => {
);
timeStart = Date.now();
let response;
let response, responseTime;
try {
/** @type {import('axios').AxiosResponse} */
response = await axiosInstance(request);
@@ -921,6 +930,7 @@ const registerNetworkIpc = (mainWindow) => {
const { data, dataBuffer } = parseDataFromResponse(response);
response.data = data;
response.responseTime = response.headers.get('request-duration');
mainWindow.webContents.send('main:run-folder-event', {
type: 'response-received',
@@ -931,7 +941,8 @@ const registerNetworkIpc = (mainWindow) => {
duration: timeEnd - timeStart,
dataBuffer: dataBuffer.toString('base64'),
size: Buffer.byteLength(dataBuffer),
data: response.data
data: response.data,
responseTime: response.headers.get('request-duration')
},
...eventData
});
@@ -948,7 +959,8 @@ const registerNetworkIpc = (mainWindow) => {
duration: timeEnd - timeStart,
dataBuffer: dataBuffer.toString('base64'),
size: Buffer.byteLength(dataBuffer),
data: error.response.data
data: error.response.data,
responseTime: error.response.headers.get('request-duration')
};
// if we get a response from the server, we consider it as a success

View File

@@ -4,7 +4,7 @@ const { authorizeUserInWindow } = require('./authorize-user-in-window');
const Oauth2Store = require('../../store/oauth2');
const generateCodeVerifier = () => {
return crypto.randomBytes(16).toString('hex');
return crypto.randomBytes(22).toString('hex');
};
const generateCodeChallenge = (codeVerifier) => {
@@ -49,8 +49,13 @@ const getOAuth2AuthorizationCode = (request, codeChallenge, collectionUid) => {
const { callbackUrl, clientId, authorizationUrl, scope, pkce } = oauth2;
let oauth2QueryParams =
(authorizationUrl.indexOf('?') > -1 ? '&' : '?') +
`client_id=${clientId}&redirect_uri=${callbackUrl}&response_type=code&scope=${scope}`;
(authorizationUrl.indexOf('?') > -1 ? '&' : '?') + `client_id=${clientId}&response_type=code`;
if (callbackUrl) {
oauth2QueryParams += `&redirect_uri=${callbackUrl}`;
}
if (scope) {
oauth2QueryParams += `&scope=${scope}`;
}
if (pkce) {
oauth2QueryParams += `&code_challenge=${codeChallenge}&code_challenge_method=S256`;
}

View File

@@ -15,6 +15,9 @@ const defaultPreferences = {
enabled: false,
filePath: null
},
keepDefaultCaCertificates: {
enabled: false
},
storeCookies: true,
sendCookies: true,
timeout: 0
@@ -43,6 +46,9 @@ const preferencesSchema = Yup.object().shape({
enabled: Yup.boolean(),
filePath: Yup.string().nullable()
}),
keepDefaultCaCertificates: Yup.object({
enabled: Yup.boolean()
}),
storeCookies: Yup.boolean(),
sendCookies: Yup.boolean(),
timeout: Yup.number()
@@ -111,6 +117,9 @@ const preferencesUtil = {
shouldUseCustomCaCertificate: () => {
return get(getPreferences(), 'request.customCaCertificate.enabled', false);
},
shouldKeepDefaultCaCertificates: () => {
return get(getPreferences(), 'request.keepDefaultCaCertificates.enabled', false);
},
getCustomCaCertificateFilePath: () => {
return get(getPreferences(), 'request.customCaCertificate.filePath', null);
},

View File

@@ -1,6 +1,6 @@
{
"name": "@usebruno/js",
"version": "0.10.1",
"version": "0.11.0",
"license": "MIT",
"main": "src/index.js",
"files": [

View File

@@ -1,6 +1,6 @@
{
"name": "@usebruno/lang",
"version": "0.10.0",
"version": "0.11.0",
"license": "MIT",
"main": "src/index.js",
"files": [

View File

@@ -1,6 +1,6 @@
{
"name": "@usebruno/schema",
"version": "0.6.0",
"version": "0.7.0",
"license": "MIT",
"main": "src/index.js",
"files": [

View File

@@ -26,6 +26,7 @@ vars:pre-request {
assert {
res.status: eq 200
res.responseTime: lte 2000
~res.body: eq {{pong}}
}

View File

@@ -31,7 +31,7 @@ Bruno is offline-only. There are no plans to add cloud-sync to Bruno, ever. We v
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)
You can Pre-Orders the [Golden Edition](https://www.usebruno.com/pricing) at ~~$19~~ **$9** ! <br/>
You can buy the [Golden Edition](https://www.usebruno.com/pricing) for a one-time payment of **$19** ! <br/>
### Installation
@@ -89,6 +89,12 @@ Or any version control system of your choice
<img src="assets/images/sponsors/commit-company.png" width="70"/>
#### Bronze Sponsors
<a href="https://zuplo.link/bruno">
<img src="assets/images/sponsors/zuplo.png" width="120"/>
</a>
### Important Links 📌
- [Our Long Term Vision](https://github.com/usebruno/bruno/discussions/269)
@@ -98,7 +104,7 @@ Or any version control system of your choice
- [Website](https://www.usebruno.com)
- [Pricing](https://www.usebruno.com/pricing)
- [Download](https://www.usebruno.com/downloads)
- [Github Sponsors](https://github.com/sponsors/helloanoop).
- [GitHub Sponsors](https://github.com/sponsors/helloanoop).
### Showcase 🎥
@@ -108,7 +114,7 @@ Or any version control system of your choice
### Support ❤️
If you like Bruno and want to support our opensource work, consider sponsoring us via [Github Sponsors](https://github.com/sponsors/helloanoop).
If you like Bruno and want to support our opensource work, consider sponsoring us via [GitHub Sponsors](https://github.com/sponsors/helloanoop).
### Share Testimonials 📣